更新
これは次のリリース(5.0.0-preview4)で修正されました。
元の回答
私はをテストfloat
しdouble
、そして興味深いことに、この特定のケースでdouble
は、問題があるだけでしたが、機能しているfloat
ようです(つまり、サーバーで0.005が読み取られます)。
メッセージバイトを調べると、64ビットの浮動小数点であるFloat32Double
にもかかわらず、0.005が4バイト/ 32ビットのIEEE 754単精度浮動小数点数であるタイプとして送信されることが示唆されましたNumber
。
上記で確認されたコンソールで次のコードを実行します。
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
サーバー側で機能する理由を説明していますが、現時点では実際には修正されていません。マイクロソフトの発言を待ちましょう。
可能な回避策
- msgpack5オプションをハックしますか?独自のmsgpack5をforkし、
forceFloat64
デフォルトでtrueにコンパイルしますか?知りません。
float
サーバー側に切り替え
string
両面使用
decimal
サーバー側に切り替え、カスタムを記述しますIFormatterProvider
。decimal
プリミティブ型ではなくIFormatterProvider<decimal>
、複合型プロパティのために呼び出されます
double
プロパティ値を取得してdouble
-> float
-> decimal
-> double
トリックを行うメソッドを提供する
- あなたが考えることができる他の非現実的な解決策
TL; DR
単一の浮動小数点数をC#バックエンドに送信するJSクライアントの問題により、既知の浮動小数点の問題が発生します。
// 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
この問題は、MessagePack-CSharpのv2バージョンに存在します。私はgithubに問題を提出しましたが、問題は修正されません。