When serializing and deserializing values between JavaScript and C# using SignalR with MessagePack I am seeing a bit of precision loss in C# on the receiving end.
As an example I am sending the value 0.005 from JavaScript to C#. When the deserialized value appears on the C# side I am getting the value 0.004999999888241291, which is close, but not 0.005 exactly. The value on the JavaScript side is Number and on the C# side I am using double.
I have read that JavaScript can't represent floating point numbers exactly which can lead to results like 0.1 + 0.2 == 0.30000000000000004. I suspect the issue I am seeing is related to this feature of JavaScript.
The interesting part is that I am not seeing the same issue going the other way. Sending 0.005 from C# to JavaScript results in the value 0.005 in JavaScript.
Edit: The value from C# is just shortened in the JS debugger window. As @Pete mentioned it does expand to something that is not 0.5 exactly (0.005000000000000000104083408558). This means the discrepancy happens on both sides at least.
JSON serialization does not have the same issue since I am assuming it goes via string which leaves the receiving environment in control wrt parsing the value into its native numerical type.
I am wondering if there is a way using binary serialization to have matching values on both sides.
If not, does this mean that there is no way to have 100% accurate binary conversions between JavaScript and C#?
Technology used:
- JavaScript
- .Net Core with SignalR and msgpack5
My code is based on this post.
The only difference is that I am using ContractlessStandardResolver.Instance.
UPDATE
This has been fixed in next release (5.0.0-preview4).
Original Answer
I tested
floatanddouble, and interestingly in this particular case, onlydoublehad the problem, whereasfloatseems to be working (i.e. 0.005 is read on server).Inspecting on the message bytes suggested that 0.005 is sent as type
Float32Doublewhich is a 4-byte / 32-bit IEEE 754 single precision floating point number despiteNumberis 64 bit floating point.Run the following code in console confirmed the above:
mspack5 does provide an option to force 64 bit floating point:
However, the
forceFloat64option is not used by signalr-protocol-msgpack.Though that explains why
floatworks on the server side, but there isn't really a fix for that as of now. Let's wait what Microsoft says.Possible workarounds
forceFloat64default to true?? I don't know.floaton server sidestringon both sidesdecimalon server side and write customIFormatterProvider.decimalis not primitive type, andIFormatterProvider<decimal>is called for complex type propertiesdoubleproperty value and do thedouble->float->decimal->doubletrickTL;DR
The problem with JS client sending single floating point number to C# backend causes a known floating point issue:
For direct uses of
doublein methods, the issue could be solved by a customMessagePack.IFormatterResolver:And use the resolver:
The resolver is not perfect, as casting to
decimalthen todoubleslows the process down and it could be dangerous.However
As per the OP pointed out in the comments, this cannot solve the issue if using complex types having
doublereturning properties.Further investigation revealed the cause of the problem in MessagePack-CSharp:
The above decoder is used when needing to convert a single
floatnumber todouble:v2
This issue exists in v2 versions of MessagePack-CSharp. I have filed an issue on github, though the issue is not going to be fixed.