CẬP NHẬT
Điều này đã được sửa trong phiên bản tiếp theo (5.0.0-preview4) .
Câu trả lời gốc
Tôi đã thử nghiệm float
và double
, và thú vị trong trường hợp cụ thể này, chỉ double
có vấn đề, trong khi float
dường như đang hoạt động (tức là 0,005 được đọc trên máy chủ).
Việc kiểm tra các byte thông báo cho thấy 0,005 được gửi dưới dạng loại Float32Double
là số dấu phẩy động chính xác đơn 4 byte / 32 bit của IEEE 754 mặc dù Number
là dấu phẩy động 64 bit.
Chạy đoạn mã sau trong bảng điều khiển đã xác nhận ở trên:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 không cung cấp tùy chọn bắt buộc dấu phẩy động 64 bit:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Tuy nhiên, forceFloat64
tùy chọn này không được sử dụng bởi signalr-Protocol-dirpack .
Mặc dù điều đó giải thích tại sao float
hoạt động ở phía máy chủ, nhưng thực sự không có cách khắc phục nào cho đến bây giờ . Chúng ta hãy chờ những gì Microsoft nói .
Cách giải quyết có thể
- Hack tùy chọn hackpack5? Ngã ba và biên dịch trình tin riêng của bạn với
forceFloat64
mặc định là đúng ?? Tôi không biết.
- Chuyển sang
float
phía máy chủ
- Sử dụng
string
cho cả hai bên
- Chuyển sang
decimal
phía máy chủ và viết tùy chỉnh IFormatterProvider
. decimal
không phải là kiểu nguyên thủy và IFormatterProvider<decimal>
được gọi cho các thuộc tính kiểu phức tạp
- Cung cấp phương thức để lấy
double
giá trị tài sản và thực hiện double
-> float
-> decimal
-> double
lừa
- Các giải pháp phi thực tế khác mà bạn có thể nghĩ đến
TL; DR
Sự cố với máy khách JS gửi số dấu phẩy động đơn đến phụ trợ C # gây ra sự cố điểm nổi đã biết:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Để sử dụng trực tiếp các double
phương thức, vấn đề có thể được giải quyết bằng một tùy chỉnh 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;
}
}
Và sử dụng trình giải quyết:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Bộ giải quyết không hoàn hảo, vì khi sử dụng decimal
để double
làm chậm quá trình và nó có thể nguy hiểm .
Tuy nhiên
Theo OP chỉ ra trong các bình luận, điều này không thể giải quyết vấn đề nếu sử dụng các loại phức tạp có double
thuộc tính trả về.
Điều tra sâu hơn cho thấy nguyên nhân của vấn đề trong 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;
}
}
}
Bộ giải mã trên được sử dụng khi cần chuyển đổi một float
số thành double
:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Vấn đề này tồn tại trong các phiên bản v2 của MessagePack-CSharp. Tôi đã gửi một vấn đề trên github , mặc dù vấn đề này sẽ không được khắc phục .