AKTUALISIEREN
Dies wurde in der nächsten Version (5.0.0-Vorschau4) behoben .
Ursprüngliche Antwort
Ich habe getestet float
und double
, und interessanterweise in diesem speziellen Fall, nur double
das Problem gehabt, während float
es 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 Float32Double
dem es sich um eine 4-Byte / 32-Bit-Gleitkommazahl mit einfacher Genauigkeit nach IEEE 754 handelt, obwohl Number
es 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 forceFloat64
Option wird jedoch nicht von signalr-protocol-msgpack verwendet .
Das erklärt zwar, warum es float
auf 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
forceFloat64
Standardwert true ?? Ich weiß es nicht.
- Wechseln Sie
float
auf die Serverseite
- Verwenden Sie
string
auf beiden Seiten
- Wechseln Sie
decimal
auf die Serverseite und schreiben Sie benutzerdefiniert IFormatterProvider
. decimal
ist kein primitiver Typ und IFormatterProvider<decimal>
wird für komplexe Typeneigenschaften aufgerufen
- Geben Sie eine Methode zum Abrufen des
double
Eigenschaftswerts 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 double
in-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 decimal
dahin double
den 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 double
zurü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 float
Zahl 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 .