在不更改目标类型的情况下以最大精度序列化float

时间:2018-08-06 08:30:04

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

我需要反序列化原始二进制数据(BinaryFormatter),然后序列化为JSON(用于编辑),然后再次将其序列化回二进制。 显然,我在彩车上输了。当我从原始序列进行序列化时,原始浮点值0xF9FF4FC1(大端,大约 -12.9999933 )被四舍五入为0xF6FF4FC1 -12.99999 )二进制(正确的数据和中间数据在内存中为1:1)转换为 JSON 。我知道损失不大,而且我知道浮动有问题,但由于以后可能会出现不兼容问题,我想使精度尽可能接近。

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

我要往返的包含浮点数的特定类型是https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs中的Vector2

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

P.S。我在这里读过很多问题,在其他地方也读过博客条目,但是没有找到任何人试图解决同一问题,大多数搜索命中都涉及浮动阅读问题(稍后我也需要解决)。

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

1 个答案:

答案 0 :(得分:2)

Json.NET将使用往返精度格式floatsource)来序列化"R"值。但是,您使用的类型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 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. 使用不会为应用了TypeConverter的类型创建原始协定的custom contract resolver,例如在Newtonsoft.JSON cannot convert model with TypeConverter attribute的答案中所示的类型。

    < / li>

注意:

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

    因此,在这种情况下,往返float可能会导致二进制更改。

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

  • 顺便说一句,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);
    }
    

    仅对 double 记录,此格式可能会失去精度。来自The Round-trip ("R") Format Specifier

      

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

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