更新
此问题已在下一个版本(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
技巧的方法
- 其他您可以想到的不切实际的解决方案
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)
{
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
属性的复杂类型,则decimal
转换为double
并不能解决这个问题。
进一步的调查揭示了MessagePack-CSharp中问题的原因:
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;
return (double) new Float32Bits(bytes, checked (offset + 1)).Value;
}
}
}
当需要将单个float
数转换为double
时,使用上述解码器:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance
v2
这个问题存在于MessagePack-CSharp的v2版本中。我已经在github上提交了一个问题, 尽管这个问题不会被修复。