在不更改目标类型的情况下以最大精度序列化float [英] Serializing float with maximum precision without changing target type
问题描述
我需要反序列化原始二进制数据(BinaryFormatter),然后序列化为JSON(用于编辑),然后再次将其序列化为二进制.显然,我在彩车上输了.我将原始浮点值 0xF9FF4FC1
(大端,大致为 -12.9999933 )四舍五入为 0xF6FF4FC1
( -12.99999 )从原始二进制(正确的数据和中间数据在内存中为1:1)序列化为 JSON .我知道损失不大,而且我知道浮动有问题,但由于以后可能会出现不兼容问题,我想使精度尽可能接近.
I need to deserialize raw binary data (BinaryFormatter), then serialize into JSON (for editing) and then serialize it back into binary again.
Obviously, I lose on floats. Original float value 0xF9FF4FC1
(big endian, roughly -12.9999933) gets rounded to 0xF6FF4FC1
(-12.99999) when I serialize from original binary (correct data and intermediated data is 1:1 in memory) to JSON. I know it is not a big loss and I know floats are problematic but I want to keep the precision as close as possible due to possible incompatibility issues later.
有人用JSON解决了这个问题吗?如何强制它以最大精度写入浮点数?我尝试使用内置选项将浮点数处理为十进制或双精度值,但是不幸的是,输出没有区别,并且我无法更改目标值,因为在执行二进制序列化时仍需要将它们写为浮点数,因此会四舍五入不管在隐式转换期间.
Anyone tackled this problem before with JSON? How can I force it to write float with max precision? I've tried built in option for handling floats as either decimal or double but there is no difference in output, unfortunately, and I cant change target values because they still need to be written as floats when I do binary serialization so there will be rounding regardless during implicit conversion.
我要往返的包含浮点数的特定类型是 https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs .
The specific type containing floats I am trying to round-trip is Vector2
from https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs.
tl:dr有一个浮点数,希望JsonNET将其尽可能精确地序列化为最终的json字符串.
tl:dr have a float, want JsonNET serialize it as precise as possible into final json string.
P.S.我在这里读过很多问题,在其他地方也读过博客条目,但是没有找到任何人试图解决同一问题,大多数搜索命中都涉及浮动阅读问题(稍后我也需要解决).
P.S. I'e read tons of questions here and blog entries elsewhere but haven't found anyone trying to solve the same issue, most of the search hits were with float-reading issues (which I'm gonna need to solve later on too).
更新:正如下面的@dbc所指出的那样-Jsont.NET尊重"TypeConverter"属性,因此我必须创建自己的转换器来覆盖它.
UPDATE: As @dbc below pointed out - Jsont.NET respects "TypeConverter" attribute thus I had to make my own converter that overrides it.
推荐答案
Json.NET将使用往返精度格式"R"
(源).但是,您使用的类型 https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs ,并已应用 TypeConverter
:
Json.NET will serialize float
values using the round-trip precision format "R"
(source). However, the type you are using, https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs, has a TypeConverter
applied:
[Serializable]
[TypeConverter(typeof(Vector2Converter))]
[DebuggerDisplay("{DebugDisplayString,nq}")]
public struct Vector2 : IEquatable<Vector2>
{
//...
如 Newtonsoft文档中所述,此类类型为使用转换器序列化为字符串:
As explained in the Newtonsoft docs, such types will be serialized as a string using the converter:
原始类型
.Net: TypeConverter
(可转换为String)
JSON:字符串
.Net: TypeConverter
(convertible to String)
JSON: String
然后,检查
And, inspecting the code for https://github.com/FNA-XNA/FNA/blob/master/src/Design/Vector2Converter.cs, it appears this converter is not using round-trip precision format when converting to string, at around line 60:
return string.Join(
culture.TextInfo.ListSeparator,
new string[]
{
vec.X.ToString(culture),
vec.Y.ToString(culture)
}
);
因此,内置的 TypeConverter
本身就是您失去精度的地方.
Thus the built-in TypeConverter
itself is where you are losing precision.
为避免此问题,您可以
-
创建一个自定义
JsonConverter
用于Vector2
,例如:
public class Vector2Converter : JsonConverter
{
class Vector2DTO
{
public float X;
public float Y;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Vector2) || objectType == typeof(Vector2?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// A JSON object is an unordered set of name/value pairs so the converter should handle
// the X and Y properties in any order.
var dto = serializer.Deserialize<Vector2DTO>(reader);
if (dto == null)
return null;
return new Vector2(dto.X, dto.Y);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var vec = (Vector2)value;
serializer.Serialize(writer, new Vector2DTO { X = vec.X, Y = vec.Y });
}
}
如果将转换器添加到 JsonSerializerSettings.Converters
,它将取代 TypeConverter
.
If you add the converter to JsonSerializerSettings.Converters
it will supersede the TypeConverter
.
工作示例小提琴此处.
转换器将 Vector2
序列化为具有 X
和 Y
属性的对象,但是您也可以将其序列化为具有两个值的数组如果你比较喜欢.我不建议序列化为原始的 string
,因为 Vector2
不对应于JSON原语.
The converter serializes the Vector2
as an object with X
and Y
properties, but you could also serialize it as an array with two values if you prefer. I would not recommend serializing as a primitive string
since Vector2
does not correspond to a JSON primitive.
使用自定义合同解析器不会为应用了 TypeConverter
的类型创建原始合约,例如 Newtonsoft的答案中所示的合约.JSON无法转换具有TypeConverter属性的模型.
Use a custom contract resolver that does not create a primitive contract for types with TypeConverter
applied, such as the one shown in the answer to Newtonsoft.JSON cannot convert model with TypeConverter attribute.
注意:
-
往返格式
"R"
显然不能保留+0.0
和-0.0
之间的差异,因此Json.NET也没有,请参见 https://dotnetfiddle.net/aereJ2 或
The round-trip format
"R"
apparently does not preserve the difference between+0.0
and-0.0
, and so Json.NET does not either, see https://dotnetfiddle.net/aereJ2 or https://dotnetfiddle.net/DwoGyX for a demo. The Microsoft docs make no mention of whether the zero sign should be preserved when using this format.
因此,在这种情况下,往返于 float
可能会导致二进制更改.
Thus, this is one case where round-tripping a float
might result in binary changes.
(感谢 @chux 在顺便说一句,Json.NET在编写 double
时也使用"R"
(如
As an aside, Json.NET also uses "R"
when writing double
(as shown in the source for JsonConvert.ToString(double value, ...)
):
internal static string ToString(double value, FloatFormatHandling floatFormatHandling, char quoteChar, bool nullable)
{
return EnsureFloatFormat(value, EnsureDecimalPlace(value, value.ToString("R", CultureInfo.InvariantCulture)), floatFormatHandling, quoteChar, nullable);
}
仅对于 double
,该格式可能会失去精度.来自 往返("R")格式说明符 :
在某些情况下,如果使用
/platform:x64
或/platform:anycpu 编译,则使用"R"标准数字格式字符串格式化的Double值无法成功往返.code>切换并在64位系统上运行.
In some cases, Double values formatted with the "R" standard numeric format string do not successfully round-trip if compiled using the
/platform:x64
or/platform:anycpu
switches and run on 64-bit systems.
因此,通过Json.NET往返访问 double
可能会导致较小的二进制差异.但是,这并不严格适用于此处的问题,特别是关于 float
的问题.
Thus round-tripping a double
via Json.NET might result in small binary differences. However, this does not strictly apply to the question here, which is specifically about float
.
这篇关于在不更改目标类型的情况下以最大精度序列化float的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!