更新
此问题已在下一版本(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]
但是,signalr-protocol-msgpackforceFloat64
不使用该选项。
尽管这解释了为什么float
可以在服务器端工作,但是到目前为止,还没有真正的解决方案。让我们等一下微软怎么说。
可能的解决方法
- 破解msgpack5选项?分叉并编译您自己的msgpack5,
forceFloat64
默认设置为true?我不知道。
- 切换到
float
服务器端
string
双面使用
- 切换到
decimal
服务器端并编写custom IFormatterProvider
。decimal
不是原始类型,IFormatterProvider<decimal>
而是为复杂类型属性调用
- 提供方法来检索
double
属性值并执行double
-> float
-> decimal
-> double
技巧
- 您可能想到的其他不切实际的解决方案
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
在MessagePack-CSharp的v2版本中存在此问题。我已经在github上提出了一个问题,尽管这个问题不会得到解决。