ATUALIZAR
Isso foi corrigido na próxima versão (5.0.0-preview4) .
Resposta original
Eu testei float
e double
, e , curiosamente, neste caso em particular, só double
tive o problema, enquanto float
parece estar funcionando (ou seja, 0,005 é lido no servidor).
A inspeção nos bytes da mensagem sugeriu que 0,005 é enviado como um tipo de Float32Double
ponto flutuante de precisão única de 4 bytes / 32 bits IEEE 754, apesar de Number
ser um ponto flutuante de 64 bits.
Execute o seguinte código no console confirmou o acima:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
O mspack5 fornece uma opção para forçar o ponto flutuante de 64 bits:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
No entanto, a forceFloat64
opção não é usada pelo signalr-protocol-msgpack .
Embora isso explique por que float
funciona no lado do servidor, mas não há realmente uma correção para isso a partir de agora . Vamos esperar o que a Microsoft diz .
Soluções possíveis
- Hack opções de msgpack5? Bifurque e compile seu próprio msgpack5 com o
forceFloat64
padrão true? Eu não sei.
- Mudar para o
float
lado do servidor
- Use
string
nos dois lados
- Alterne para o
decimal
lado do servidor e escreva personalizado IFormatterProvider
. decimal
não é do tipo primitivo e IFormatterProvider<decimal>
é chamado para propriedades de tipo complexas
- Forneça um método para recuperar o
double
valor da propriedade e faça o truque double
-> float
-> decimal
->double
- Outras soluções irrealistas em que você poderia pensar
TL; DR
O problema com o cliente JS enviando um número de ponto flutuante único para o back-end em C # causa um problema conhecido de ponto flutuante:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Para usos diretos de double
métodos, o problema pode ser resolvido por um costume 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 use o resolvedor:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
O resolvedor não é perfeito, pois a conversão para decimal
então double
atrasa o processo e pode ser perigoso .
Contudo
Conforme o OP apontado nos comentários, isso não pode resolver o problema se estiver usando tipos complexos com double
propriedades retornadas.
Uma investigação mais aprofundada revelou a causa do problema no 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;
}
}
}
O decodificador acima é usado quando é necessário converter um único float
número para double
:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Esse problema existe nas versões v2 do MessagePack-CSharp. Eu arquivei um problema no github , embora o problema não seja corrigido .