MEMPERBARUI
Ini telah diperbaiki pada rilis berikutnya (5.0.0-preview4) .
Jawaban Asli
Saya menguji float
dan double
, dan yang menarik dalam kasus khusus ini, hanya double
punya masalah, sedangkan float
tampaknya berfungsi (yaitu 0,005 dibaca di server).
Memeriksa byte pesan menyarankan bahwa 0,005 dikirim sebagai tipe Float32Double
yang merupakan nomor 4-byte / 32-bit floating point presisi tunggal IEEE 754 meskipun Number
64 bit floating point.
Jalankan kode berikut di konsol yang mengkonfirmasi hal di atas:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 memang menyediakan opsi untuk memaksa floating point 64 bit:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Namun, forceFloat64
opsi ini tidak digunakan oleh signalr-protokol-msgpack .
Meskipun itu menjelaskan mengapa float
bekerja di sisi server, tetapi sebenarnya tidak ada perbaikan untuk itu sampai sekarang . Mari kita tunggu apa yang dikatakan Microsoft .
Kemungkinan solusi
- Opsi hack msgpack5? Fork dan kompilasi msgpack5 Anda sendiri dengan
forceFloat64
default to true ?? Saya tidak tahu
- Beralih ke
float
di sisi server
- Gunakan
string
di kedua sisi
- Beralihlah ke
decimal
sisi server dan tulis kustom IFormatterProvider
. decimal
bukan tipe primitif, dan IFormatterProvider<decimal>
dipanggil untuk properti tipe kompleks
- Berikan metode untuk mengambil
double
nilai properti dan lakukan trik double
-> float
-> decimal
->double
- Solusi tidak realistis lainnya yang dapat Anda pikirkan
TL; DR
Masalah dengan klien JS yang mengirim nomor floating point tunggal ke C # backend menyebabkan masalah floating point yang diketahui:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Untuk penggunaan langsung double
metode, masalah dapat diselesaikan dengan kebiasaan 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;
}
}
Dan gunakan resolver:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Penyelesai tidak sempurna, karena casting decimal
untuk double
memperlambat proses dan itu bisa berbahaya .
Namun
Seperti OP yang ditunjukkan dalam komentar, ini tidak dapat menyelesaikan masalah jika menggunakan tipe kompleks yang memiliki double
properti yang dikembalikan.
Investigasi lebih lanjut mengungkapkan penyebab masalah di 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;
}
}
}
Dekoder di atas digunakan ketika perlu mengkonversi satu float
nomor ke double
:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Masalah ini ada di versi v2 MessagePack-CSharp. Saya telah mengajukan masalah pada github , meskipun masalah ini tidak akan diperbaiki .