在不更改目标类型的情况下以最大精度序列化float [英] Serializing float with maximum precision without changing target type

查看:47
本文介绍了在不更改目标类型的情况下以最大精度序列化float的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要反序列化原始二进制数据(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

然后,检查 https://github.com/FNA-XNA/FNA/blob/master/src/Design/Vector2Converter.cs ,当转换为字符串时,此转换器似乎不是使用往返精度格式,在第60行附近:

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.

为避免此问题,您可以

  1. 创建一个自定义 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.

注意:

  • 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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆