AKTUALIZACJA
Zostało to naprawione w następnej wersji (5.0.0-Preview4) .
Oryginalna odpowiedź
Testowałem float
i double
, co ciekawe, w tym konkretnym przypadku, double
miałem tylko problem, podczas gdy float
wydaje 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, Float32Double
który jest 4-bajtową / 32-bitową liczbą zmiennoprzecinkową pojedynczej precyzji IEEE 754, pomimo że Number
jest 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 forceFloat64
opcja ta nie jest używana przez msgpack signalr-protokół-protokół .
Chociaż to wyjaśnia, dlaczego float
dział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
forceFloat64
domyślną wartością true? Nie wiem
- Przełącz na
float
po stronie serwera
- Użyj
string
po obu stronach
- Przełącz
decimal
na stronę serwera i napisz niestandardowe IFormatterProvider
. decimal
nie jest typem pierwotnym i IFormatterProvider<decimal>
jest wywoływany dla właściwości typu złożonego
- Podaj metodę pobierania
double
wartoś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 double
metod, 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 decimal
następnie double
spowalnia 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 double
zwracanych 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 float
numer 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 .