代码之家  ›  专栏  ›  技术社区  ›  KreonZZ

在不改变目标类型的情况下以最大精度串行化浮点

  •  3
  • KreonZZ  · 技术社区  · 6 年前

    我需要反序列化原始二进制数据(BinaryFormatter),然后序列化为JSON(用于编辑),然后再次序列化为二进制。 很明显,我在花车上输了。原始浮动值 0xF9FF4FC1 (大端,大致上 -1999年93月12日 )四舍五入到 0xF6FF4FC1 ( -1999年12月 )当我从 原始二进制 (正确的数据和中间数据在内存中是1:1)到 JSON格式 . 我知道这不是一个很大的损失,我知道浮动是有问题的,但我想保持精度尽可能接近,因为以后可能不兼容的问题。

    以前有人用JSON解决过这个问题吗?我怎么能强迫它以最大的精度写浮点呢?我已经尝试了将浮点处理为十进制或双精度的内置选项,但是输出没有差异,不幸的是,我无法更改目标值,因为在执行二进制序列化时,它们仍然需要作为浮点写入,因此无论在隐式转换期间如何,都会有舍入。

    我试图往返的包含浮动的特定类型是 Vector2 https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs .

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

    我在这里读了很多问题,在其他地方也读了很多博客文章,但是还没有发现有人试图解决同样的问题,大部分的搜索结果都是浮动阅读问题(我以后也需要解决这个问题)。

    更新: 正如下面@dbc指出的那样-Jsont.NET尊重“TypeConverter”属性,因此我必须创建自己的转换器来重写它。

    1 回复  |  直到 6 年前
        1
  •  2
  •   dbc    6 年前

    Json.NET将序列化 float 使用往返精度格式的值 "R" ( source ). 但是,你使用的类型, 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版本: 类型转换器 (可转换为字符串)
    JSON:字符串

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

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

    因此,内置 类型转换器 它本身就是你失去精确性的地方。

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

    1. 创建 custom 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 它将取代 类型转换器 .

      工作样品小提琴 here .

      转换器序列化 二维向量 作为一个对象 X Y 属性,但如果愿意,也可以将其序列化为具有两个值的数组。我不建议序列化为原始的 string 自从 二维向量 与JSON原语不对应。

    2. 使用 custom contract resolver 它不会为 类型转换器 应用,例如 Newtonsoft.JSON cannot convert model with TypeConverter attribute .

    笔记:

    • 往返形式 “R” 显然不能保留 +0.0 -0.0 ,因此Json.NET也没有,请参见 https://dotnetfiddle.net/aereJ2 https://dotnetfiddle.net/DwoGyX 为了演示。这个 Microsoft docs 在使用此格式时,不要提及是否应保留零符号。

      因此,这是一个往返 浮动 可能导致二进制更改。

      (多亏了 @chux 因为在年提出这个问题 comments .)

    • 另外,Json.NET还使用 “R” 写作时 double (如 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);
      }
      

      为了 双重的 只有 记录此格式可能会丢失精度。从 The Round-trip ("R") Format Specifier :

      在某些情况下,如果使用 /platform:x64 /platform:anycpu 在64位系统上切换和运行。

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