AGGIORNARE
Questo problema è stato risolto nella prossima versione (5.0.0-preview4) .
Risposta originale
Ho provato floate double, cosa interessante, in questo caso particolare, ho doubleavuto solo il problema, mentre floatsembra funzionare (ovvero 0,005 viene letto sul server).
L'ispezione dei byte di messaggio ha suggerito che 0,005 viene inviato come tipo Float32Doubleche è un numero in virgola mobile IEEE 754 a 4 byte / 32 bit a precisione singola nonostante Numbersia un virgola mobile a 64 bit.
Esegui il seguente codice nella console confermato quanto sopra:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 fornisce un'opzione per forzare il virgola mobile a 64 bit:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Tuttavia, l' forceFloat64opzione non è utilizzata da signalr-protocol-msgpack .
Anche se questo spiega perché floatfunziona sul lato server, ma al momento non esiste una soluzione per questo . Aspettiamo cosa dice Microsoft .
Possibili soluzioni alternative
- Hack msgpack5 opzioni? Effettua il fork e compila il tuo msgpack5 di
forceFloat64default su true ?? Non lo so.
- Passa al
floatlato server
- Utilizzare
stringsu entrambi i lati
- Passa a
decimallato server e scrivi personalizzato IFormatterProvider. decimalnon è un tipo primitivo ed IFormatterProvider<decimal>è chiamato per proprietà di tipo complesso
- Fornisci il metodo per recuperare il
doublevalore della proprietà ed eseguire il trucco double-> float-> decimal->double
- Altre soluzioni non realistiche a cui potresti pensare
TL; DR
Il problema con il client JS che invia un singolo numero in virgola mobile al back-end C # provoca un problema noto in virgola mobile:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Per usi diretti di doublein metodi, il problema potrebbe essere risolto da un'abitudine 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;
}
}
E usa il resolver:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Il resolver non è perfetto, dato che il casting per decimalpoi doublerallentare il processo e potrebbe essere pericoloso .
però
Come indicato dall'OP nei commenti, questo non può risolvere il problema se si utilizzano tipi complessi con doubleproprietà di ritorno.
Ulteriori indagini hanno rivelato la causa del problema 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;
}
}
}
Il decodificatore sopra è utilizzato quando è necessario convertire un singolo floatnumero in double:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Questo problema esiste nelle versioni v2 di MessagePack-CSharp. Ho presentato un problema su github , anche se il problema non verrà risolto .