ACTUALIZAR
Esto se ha solucionado en la próxima versión (5.0.0-preview4) .
Respuesta original
Lo probé float
y double
, curiosamente en este caso particular, solo double
tuve el problema, mientras que float
parece estar funcionando (es decir, 0.005 se lee en el servidor).
La inspección en los bytes del mensaje sugirió que 0.005 se envía como tipo, Float32Double
que es un número de coma flotante de precisión simple IEEE 754 de 4 bytes / 32 bits a pesar de Number
ser un punto flotante de 64 bits.
Ejecute el siguiente código en la consola confirmó lo anterior:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 proporciona una opción para forzar el punto flotante de 64 bits:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Sin embargo, la forceFloat64
opción no es utilizada por signalr-protocol-msgpack .
Aunque eso explica por qué float
funciona en el lado del servidor, pero no hay realmente una solución para eso a partir de ahora . Esperemos lo que dice Microsoft .
Posibles soluciones
- Hackear las opciones de msgpack5? Bifurca y compila tu propio msgpack5 con el
forceFloat64
valor predeterminado verdadero? No lo sé.
- Cambiar al
float
lado del servidor
- Usar
string
en ambos lados
- Cambie al
decimal
lado del servidor y escriba personalizado IFormatterProvider
. decimal
no es de tipo primitivo y IFormatterProvider<decimal>
se llama para propiedades de tipo complejo
- Proporcione un método para recuperar el
double
valor de la propiedad y haga el truco double
-> float
-> decimal
->double
- Otras soluciones poco realistas que se te ocurran
TL; DR
El problema con el cliente JS que envía un único número de coma flotante al backend de C # causa un problema conocido de coma flotante:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Para usos directos de los double
métodos in, el problema podría resolverse mediante una costumbre 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;
}
}
Y usa el resolutor:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
El resolutor no es perfecto, ya que lanzarlo a decimal
continuación double
ralentiza el proceso y podría ser peligroso .
sin embargo
Según el OP señalado en los comentarios, esto no puede resolver el problema si se utilizan tipos complejos con double
propiedades de retorno.
La investigación adicional reveló la causa del problema en 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;
}
}
}
El decodificador anterior se usa cuando se necesita convertir un solo float
número a double
:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Este problema existe en las versiones v2 de MessagePack-CSharp. He presentado un problema en github , aunque el problema no se solucionará .