AKTUALIZACJA
Zostało to naprawione w następnej wersji (5.0.0-Preview4) .
Oryginalna odpowiedź
Testowałem floati double, co ciekawe, w tym konkretnym przypadku, doublemiałem tylko problem, podczas gdy floatwydaje się, że działa (tj. 0,005 jest odczytywany na serwerze).
Sprawdzanie bajtów wiadomości sugerowało, że 0,005 jest wysyłany jako typ, Float32Doublektóry jest 4-bajtową / 32-bitową liczbą zmiennoprzecinkową pojedynczej precyzji IEEE 754, pomimo że Numberjest liczbą zmiennoprzecinkową 64-bitową.
Uruchom następujący kod w konsoli potwierdził powyższe:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 zapewnia opcję wymuszenia 64-bitowego zmiennoprzecinkowego:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Jednak forceFloat64opcja ta nie jest używana przez msgpack signalr-protokół-protokół .
Chociaż to wyjaśnia, dlaczego floatdziała po stronie serwera, ale tak naprawdę nie ma na to obecnie żadnej poprawki . Poczekajmy, co mówi Microsoft .
Możliwe obejścia
- Włamać opcje msgpack5? Rozwidlaj i skompiluj własny msgpack5 z
forceFloat64domyślną wartością true? Nie wiem
- Przełącz na
floatpo stronie serwera
- Użyj
stringpo obu stronach
- Przełącz
decimalna stronę serwera i napisz niestandardowe IFormatterProvider. decimalnie jest typem pierwotnym i IFormatterProvider<decimal>jest wywoływany dla właściwości typu złożonego
- Podaj metodę pobierania
doublewartości właściwości i wykonaj sztuczkę double-> float-> decimal->double
- Inne nierealne rozwiązania, o których można pomyśleć
TL; DR
Problem z wysyłaniem przez klienta JS pojedynczej liczby zmiennoprzecinkowej do zaplecza C # powoduje znany problem zmiennoprzecinkowy:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
W przypadku bezpośredniego użycia doublemetod, problem można rozwiązać niestandardowo MessagePack.IFormatterResolver:
public class MyDoubleFormatterResolver : IFormatterResolver
{
public static MyDoubleFormatterResolver Instance = new MyDoubleFormatterResolver();
private MyDoubleFormatterResolver()
{ }
public IMessagePackFormatter<T> GetFormatter<T>()
{
return MyDoubleFormatter.Instance as IMessagePackFormatter<T>;
}
}
public sealed class MyDoubleFormatter : IMessagePackFormatter<double>, IMessagePackFormatter
{
public static readonly MyDoubleFormatter Instance = new MyDoubleFormatter();
private MyDoubleFormatter()
{
}
public int Serialize(
ref byte[] bytes,
int offset,
double value,
IFormatterResolver formatterResolver)
{
return MessagePackBinary.WriteDouble(ref bytes, offset, value);
}
public double Deserialize(
byte[] bytes,
int offset,
IFormatterResolver formatterResolver,
out int readSize)
{
double value;
if (bytes[offset] == 0xca)
{
// 4 bytes single
// cast to decimal then double will fix precision issue
value = (double)(decimal)MessagePackBinary.ReadSingle(bytes, offset, out readSize);
return value;
}
value = MessagePackBinary.ReadDouble(bytes, offset, out readSize);
return value;
}
}
I użyj resolvera:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Resolver nie jest doskonały, jak do odlewania decimalnastępnie doublespowalnia proces w dół, a to może być niebezpieczne .
jednak
Jak wskazał PO w komentarzach, nie można rozwiązać tego problemu, jeśli używa się złożonych typów o doublezwracanych właściwościach.
Dalsze dochodzenie ujawniło przyczynę problemu w MessagePack-CSharp:
// Type: MessagePack.MessagePackBinary
// Assembly: MessagePack, Version=1.9.0.0, Culture=neutral, PublicKeyToken=b4a0369545f0a1be
// MVID: B72E7BA0-FA95-4EB9-9083-858959938BCE
// Assembly location: ...\.nuget\packages\messagepack\1.9.11\lib\netstandard2.0\MessagePack.dll
namespace MessagePack.Decoders
{
internal sealed class Float32Double : IDoubleDecoder
{
internal static readonly IDoubleDecoder Instance = (IDoubleDecoder) new Float32Double();
private Float32Double()
{
}
public double Read(byte[] bytes, int offset, out int readSize)
{
readSize = 5;
// The problem is here
// Cast a float value to double like this causes precision loss
return (double) new Float32Bits(bytes, checked (offset + 1)).Value;
}
}
}
Powyższy dekoder jest używany, gdy trzeba przekonwertować pojedynczy floatnumer na double:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Ten problem występuje w wersjach MessagePack-CSharp v2. Złożyłem problem na githubie , ale problem nie zostanie naprawiony .