【问题标题】:Serializing float with maximum precision without changing target type在不更改目标类型的情况下以最大精度序列化浮点数
【发布时间】:2018-08-06 08:30:04
【问题描述】:

我需要反序列化原始二进制数据(BinaryFormatter),然后序列化为 JSON(用于编辑),然后再次将其序列化回二进制。 显然,我在花车上输了。当我从 原始二进制文件 序列化时,原始浮点值 0xF9FF4FC1(大端,大致 -12.9999933)被四舍五入为 0xF6FF4FC1-12.99999) >(正确数据和中间数据在内存中是 1:1)到 JSON。我知道这不是什么大损失,而且我知道浮点数是有问题的,但由于以后可能出现不兼容问题,我希望尽可能保持精度。

之前有人用 JSON 解决过这个问题吗?如何强制它以最大精度写入浮点数?我尝试了将浮点数处理为十进制或双精度的内置选项,但不幸的是,输出没有区别,而且我无法更改目标值,因为当我进行二进制序列化时它们仍然需要写为浮点数,所以会有舍入无论在隐式转换期间。

我尝试往返的包含浮点数的特定类型是来自https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.csVector2

tl:dr 有一个浮点数,希望 JsonNET 将其尽可能精确地序列化为最终的 json 字符串。

附:我在这里阅读了大量的问题和其他地方的博客文章,但没有发现任何人试图解决相同的问题,大多数搜索结果都与浮动阅读问题有关(我稍后也需要解决)。

更新: 正如下面的@dbc 所指出的 - Jsont.NET 尊重“TypeConverter”属性,因此我必须制作自己的转换器来覆盖它。

【问题讨论】:

  • 在 SO 上发誓不好
  • 我标记为重复的问题是相反的情况 - 限制小数位数。但两者都在控制小数位数。而不是 `writer.WriteRawValue(JsonConvert.ToString(value));` 你会使用 `writer.WriteRawValue(((float)value).ToString('r'));` 作为往返格式。
  • 无法复制,请参阅dotnetfiddle.net/qBqBdW。我想我们可能需要查看minimal reproducible example 来帮助您。另外,您使用的是什么版本的 Json.NET?
  • 哦,该死,你说得对,它有 TypeConverter:https://github.com/FNA-XNA/FNA/blob/c77c82837af89e28e9e71106a3637236c215ada3/src/Vector2.cs --- 有没有办法绕过它/忽略它?

标签: c# json floating-point json.net


【解决方案1】:

Json.NET 将使用往返精度格式 "R" (source) 序列化 float 值。但是,您使用的类型https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs 应用了TypeConverter

    [Serializable]
    [TypeConverter(typeof(Vector2Converter))]
    [DebuggerDisplay("{DebugDisplayString,nq}")]
    public struct Vector2 : IEquatable<Vector2>
    {
        //...

Newtonsoft docs 中所述,此类类型将使用转换器序列化为字符串:

原始类型

.Net:TypeConverter(可转换为字符串)
JSON:字符串

而且,检查https://github.com/FNA-XNA/FNA/blob/master/src/Design/Vector2Converter.cs 的代码,在around line 60around line 60 处,该转换器似乎使用往返精度格式转换为字符串:

    return string.Join(
        culture.TextInfo.ListSeparator,
        new string[]
        {
            vec.X.ToString(culture),
            vec.Y.ToString(culture)
        }
    );

因此,内置的TypeConverter 本身就是您失去精度的地方。

为了避免这个问题,你可以

  1. Vector2 创建一个custom JsonConverter,如下所示:

    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

    工作样本小提琴here

    转换器将Vector2 序列化为具有XY 属性的对象,但如果您愿意,也可以将其序列化为具有两个值的数组。我不建议将string 序列化为原语,因为Vector2 不对应于 JSON 原语。

  2. 使用custom contract resolver 不会为应用了TypeConverter 的类型创建原始契约,例如Newtonsoft.JSON cannot convert model with TypeConverter attribute 的答案中显示的那个。

注意事项:

  • 往返格式"R" 显然没有保留+0.0-0.0 之间的区别,因此Json.NET 也没有,请参阅https://dotnetfiddle.net/aereJ2https://dotnetfiddle.net/DwoGyX 进行演示。 Microsoft docs 没有提到在使用这种格式时是否应该保留零号。

    因此,这是一种往返float 可能导致二进制更改的情况。

    (感谢@chuxcomments 中提出此问题。)

  • 顺便说一句,Json.NET 在编写double 时也使用"R"(如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,此格式被记录为可能会丢失精度。来自The Round-trip ("R") Format Specifier

    在某些情况下,如果使用 /platform:x64/platform:anycpu 开关编译并在 64 位系统上运行,则使用“R”标准数字格式字符串格式化的 Double 值无法成功往返。

    因此,通过 Json.NET 往返 double 可能会导致小的二进制差异。但是,这并不严格适用于这里的问题,具体是关于float

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-19
  • 2016-12-06
  • 2018-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多