ОБНОВИТЬ
Это было исправлено в следующем выпуске (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->decimaldouble трюк ->
- Другие нереальные решения, о которых вы могли подумать
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 , хотя проблема не будет решена .