ОБНОВЛЕНИЕ
Это было исправлено в следующем выпуске (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
трюк - Другие нереалистичные c решения, о которых вы могли подумать
TL; DR
Проблема с JS клиентом, отправляющим одно число с плавающей запятой на C# бэкэнд, вызывает известную проблему с плавающей запятой:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Для прямого использования double
в методах, проблема может быть решена с помощью пользовательский 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
замедляет процесс и это может быть опасно .
Однако
Согласно ОП, указанному в комментариях, этот не может решить проблема при использовании сложных типов, имеющих 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
Эта проблема существует в версиях MessagePack-CSharp версии 2. Я подал проблему на github , , хотя проблема не будет решена .