AKTUALISIEREN
Dies wurde in der nächsten Version (5.0.0-Vorschau4) behoben .
Ursprüngliche Antwort
Ich habe getestet floatund double, und interessanterweise in diesem speziellen Fall, nur doubledas Problem gehabt, während floates zu funktionieren scheint (dh 0,005 wird auf dem Server gelesen).
Die Überprüfung der Nachrichtenbytes ergab, dass 0,005 als Typ gesendet wird, bei Float32Doubledem es sich um eine 4-Byte / 32-Bit-Gleitkommazahl mit einfacher Genauigkeit nach IEEE 754 handelt, obwohl Numberes sich um 64-Bit-Gleitkommazahlen handelt.
Führen Sie den folgenden Code in der Konsole aus, um Folgendes zu bestätigen:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 bietet eine Option zum Erzwingen eines 64-Bit-Gleitkommas:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Die forceFloat64Option wird jedoch nicht von signalr-protocol-msgpack verwendet .
Das erklärt zwar, warum es floatauf der Serverseite funktioniert, aber es gibt derzeit keine wirkliche Lösung dafür . Warten wir, was Microsoft sagt .
Mögliche Problemumgehungen
- Msgpack5-Optionen hacken? Fork und kompiliere dein eigenes msgpack5 mit dem
forceFloat64Standardwert true ?? Ich weiß es nicht.
- Wechseln Sie
floatauf die Serverseite
- Verwenden Sie
stringauf beiden Seiten
- Wechseln Sie
decimalauf die Serverseite und schreiben Sie benutzerdefiniert IFormatterProvider. decimalist kein primitiver Typ und IFormatterProvider<decimal>wird für komplexe Typeneigenschaften aufgerufen
- Geben Sie eine Methode zum Abrufen des
doubleEigenschaftswerts an und führen Sie den Trick double-> float-> decimal-> ausdouble
- Andere unrealistische Lösungen, die Sie sich vorstellen können
TL; DR
Das Problem, dass der JS-Client eine einzelne Gleitkommazahl an das C # -Backend sendet, verursacht ein bekanntes Gleitkommaproblem:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Für die direkte Verwendung von doublein-Methoden könnte das Problem durch eine benutzerdefinierte Lösung gelöst werden 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;
}
}
Und benutze den Resolver:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Der Resolver ist nicht perfekt, da das Gießen bis decimaldahin doubleden Prozess verlangsamt und gefährlich sein kann .
jedoch
Gemäß dem in den Kommentaren genannten OP kann dies das Problem nicht lösen, wenn komplexe Typen mit doublezurückgegebenen Eigenschaften verwendet werden.
Weitere Untersuchungen ergaben die Ursache des Problems in 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;
}
}
}
Der obige Decoder wird verwendet, wenn eine einzelne floatZahl konvertiert werden muss in double:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Dieses Problem tritt in Version 2 von MessagePack-CSharp auf. Ich habe ein Problem bei Github eingereicht , das Problem wird jedoch nicht behoben .