.NET JSON 序列化器在某些情况下似乎会将 double 类型的值四舍五入到 16 位数字

问题描述 投票:0回答:1

我有一个 ASP.NET Core 7 Web API,它使用 .NET JSON 序列化程序(即

System.Text.Json
,版本=7.0.0.0)将 API 结果数据序列化为 JSON 响应。

我注意到,对于某些双精度值,它实际上将其四舍五入为 16 位数字,而有些则为 17 位数字 - 我已阅读 Microsoft 的文档,其中指出双精度值可以具有 15-17 位数字,但我不是确定为什么在某些情况下它决定将其四舍五入到 16 位数字(这可能与位等表示有关?)

更有趣的是在调试时,双精度值被正确分配,如下所示 - 6.5784774568779181 ..然后当它被序列化为 JSON 响应时,它变成这样 - 6.578477456877918 (我还有其他示例,例如 99.999999999999986 vs 99.99999999999999 )

最初我认为这可能是我的 JSON 序列化器选项,如下所示:

builder.Services.AddControllers()
    .AddJsonOptions(o =>
    {
        o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        // This will add support for serializing double NaN and Infinity values to the response JSON.
        o.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
    });

..所以我认为 NaN 和正/负无穷大处理可能会保留一些位或弄乱输出结果,但后来我将数字处理选项设置为

JsonNumberHandling.WriteAsString
以及
JsonNumberHandling.Strict
- 但得到相同的结果。

有谁知道为什么会发生这种情况 - 这是一个错误还是预期的行为?如果这是预期的,是否有一个解释为什么会发生,因为我在互联网上找不到任何太相关的内容。对于上下文,我的 ASP.NET Core 7 Web API 使用旧版 WCF .NET Framework 服务,并且该服务能够序列化整个双精度值并将其发送到我的 API。

//编辑 可能是我原来的帖子说得不够清楚。 我并没有真正决定是否使用精确的数字。正如我所说,我有一个遗留的 WCF .NET Framework 服务,我需要通过某种 REST 完整的 API 来公开它(希望出于显而易见的原因)。所以我的 .NET 7 Web API 只是底层 WCF 服务的代理。在我的 .NET 7 API 上,我有一个合约/DTO,它公开了一个 double 类型的字段,该字段只是底层 WCF double 字段的传递。现在,WCF 字段生成一个双精度值 6.5784774568779181 ..它被正确使用并存储在我的 API 双精度字段的内存(即调试会话)中,但是当它被序列化为 JSON 响应时,它变成 6.578477456877918。我们采取的方法是运行回归测试,这些测试应该模拟 2 个类似(相同)的 API 调用 - 一个直接到 WCF 服务,另一个到新的 Web API。然后,我们对从 WCF 服务和 Web API 接收的数据进行比较。在某些情况下,Web API 服务四舍五入到 16 位数字 - 显然,这不是一个巨大的障碍,但我的主要问题是为什么会出现这种情况,因为 .NET Framework 序列化似乎更加一致。

c# .net asp.net-core double system.text.json
1个回答
0
投票

经过一番搜索和评论: 正如您所说,当您调试双值时,双精度值已正确分配,但在序列化为 JSON 响应后,它被四舍五入,这与序列化器如何处理浮点精度有关。

实际上,根据我的搜索,.NET 中的 double 值是具有不同精度级别的浮点数,可以根据表示它们的方式进行更改,并且由于其二进制表示形式而在精度上受到限制。

此外,System.Text.Json 序列化器有自己的内部逻辑来处理浮点数。当这些数字被序列化为 JSON 时,序列化程序可能会选择一种平衡精度和效率的表示形式,从而导致观察到的舍入行为(如您的情况)。这实际上并不是一个错误,而是序列化器如何优化和标准化数据表示的一个方面。

我认为你可以做这两个动作:

1- 配置和调整串行器选项或自定义转换器。但如果你使用 float 或 double,它可能会再次发生,因为行为是它们的特性。

2- 在我看来,我会推荐这种方法。如果可以的话,请使用十进制而不是双精度。它可以处理高精度,并且最适合这些情况,但在大计算中,与 double 相比可能会出现一些性能问题,但在大计算中。

根据我的搜索,如果你想使用方法1,你可以像下面的代码一样:

public class DoubleConverter : JsonConverter<double>
{
    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.GetDouble();
    }

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("G17")); // G17 format specifier for maximum precision
    }
}

为您服务:

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new DoubleConverter());
    });

在您的应用程序中:

使用 Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class SampleController : ControllerBase
{
    [HttpGet]
    public ActionResult<SampleModel> Get()
    {
        var model = new SampleModel
        {
            Value = 6.5784774568779181
        };

        return model;
    }
}

当您运行此示例并输入 /sample 端点时,您可能会看到 SampleModel JSON 输出,其中的 Value 属性是使用自定义 DoubleConverter 序列化的。

© www.soinside.com 2019 - 2024. All rights reserved.