AGGIORNARE
Questo problema è stato risolto nella prossima versione (5.0.0-preview4) .
Risposta originale
Ho provato float
e double
, cosa interessante, in questo caso particolare, ho double
avuto solo il problema, mentre float
sembra funzionare (ovvero 0,005 viene letto sul server).
L'ispezione dei byte di messaggio ha suggerito che 0,005 viene inviato come tipo Float32Double
che è un numero in virgola mobile IEEE 754 a 4 byte / 32 bit a precisione singola nonostante Number
sia 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' forceFloat64
opzione non è utilizzata da signalr-protocol-msgpack .
Anche se questo spiega perché float
funziona 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
forceFloat64
default su true ?? Non lo so.
- Passa al
float
lato server
- Utilizzare
string
su entrambi i lati
- Passa a
decimal
lato server e scrivi personalizzato IFormatterProvider
. decimal
non è un tipo primitivo ed IFormatterProvider<decimal>
è chiamato per proprietà di tipo complesso
- Fornisci il metodo per recuperare il
double
valore 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 double
in 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 decimal
poi double
rallentare 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 double
proprietà 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 float
numero 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 .