ACTUALIZAR
Esto se ha solucionado en la próxima versión (5.0.0-preview4) .
Respuesta original
Lo probé floaty double, curiosamente en este caso particular, solo doubletuve el problema, mientras que floatparece 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, Float32Doubleque es un número de coma flotante de precisión simple IEEE 754 de 4 bytes / 32 bits a pesar de Numberser 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 forceFloat64opción no es utilizada por signalr-protocol-msgpack .
Aunque eso explica por qué floatfunciona 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
forceFloat64valor predeterminado verdadero? No lo sé.
- Cambiar al
floatlado del servidor
- Usar
stringen ambos lados
- Cambie al
decimallado del servidor y escriba personalizado IFormatterProvider. decimalno es de tipo primitivo y IFormatterProvider<decimal>se llama para propiedades de tipo complejo
- Proporcione un método para recuperar el
doublevalor 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 doublemé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 decimalcontinuación doubleralentiza 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 doublepropiedades 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 floatnú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á .