ОБНОВИТЬ
Это было исправлено в следующем выпуске (5.0.0-preview4) .
Оригинальный ответ
Я проверил, float
и double
, что интересно, в данном конкретном случае толькоdouble
имел проблему только тогда, когда, float
кажется, работает (т. Е. На сервере читается 0,005).
Изучение байтов сообщения показало, что 0,005 отправляется как тип Float32Double
который представляет собой 4-байтовое / 32-битное число с плавающей запятой одинарной точности IEEE 754, несмотря на то, что оно Number
равно 64-битной.
Запустите следующий код в консоли, подтвердив вышесказанное:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 предоставляет возможность принудительного использования 64-битной плавающей запятой:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Однако forceFloat64
опция не используется в signalr-protocol-msgpack .
Хотя это и объясняет, почему float
работает на стороне сервера, но на самом деле пока нет исправления . Давайте подождем, что скажет Microsoft .
Возможные обходные пути
- Взломать опции msgpack5? Форк и скомпилируйте свой собственный msgpack5 с
forceFloat64
значением по умолчанию true? Я не знаю.
- Переключить на
float
на стороне сервера
- использование
string
с обеих сторон
- Переключитесь
decimal
на серверную часть и напишите на заказ IFormatterProvider
. decimal
не примитивный тип, аIFormatterProvider<decimal>
вызывается для свойств сложного типа
- Укажите метод для получения
double
значения свойства и выполните double
-> float
->decimal
double
трюк ->
- Другие нереальные решения, о которых вы могли подумать
TL; DR
Проблема с отправкой JS-клиентом одного числа с плавающей запятой в бэкэнд C # вызывает известную проблему с плавающей запятой:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Для прямого использования double
методов in, проблема может быть решена с помощью обычая 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;
}
}
И используйте преобразователь:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Распознаватель не является совершенным, так как приведение к decimal
затем double
замедляет процесс вниз и это может быть опасно .
Однако
Как указано в комментариях к OP, это не может решить проблему, если использовать сложные типы, имеющие double
возвращаемые свойства.
Дальнейшее расследование выявило причину проблемы в 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;
}
}
}
Вышеуказанный декодер используется для преобразования одного float
числа в double
:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Эта проблема существует в версии v2 MessagePack-CSharp. Я подал проблему на GitHub , хотя проблема не будет решена .