.net core 3 产生与 2.2 版不同的浮点结果

Posted

技术标签:

【中文标题】.net core 3 产生与 2.2 版不同的浮点结果【英文标题】:.net core 3 yields different floating point results from version 2.2 【发布时间】:2020-01-17 05:39:17 【问题描述】:

这是一段示例代码,其中包含来自 .net core 2.2 和 3.1 的输出。它显示了基本浮点表达式 a^b 的不同计算结果。

在此示例中,我们计算 1.9 的 3 次方。以前的 .NET 框架产生了正确的结果,但 .net core 3.0 和 3.1 产生了不同的结果。

这是有意更改吗?我们如何将财务计算代码迁移到新版本,同时保证数值计算仍会产生相同的结果? (如果 .NET 也有十进制数学库就好了)。

    public static class Program
    
        public static void Main(string[] args)
        
            Console.WriteLine("--- Decimal ---------");
            ComputeWithDecimalType();
            Console.WriteLine("--- Double ----------");
            ComputeWithDoubleType();

            Console.ReadLine();
        

        private static void ComputeWithDecimalType()
        
            decimal a = 1.9M;
            decimal b = 3M;
            decimal c = a * a * a;
            decimal d = (decimal) Math.Pow((double) a, (double) b);

            Console.WriteLine($"a * a * a                        = c");
            Console.WriteLine($"Math.Pow((double) a, (double) b) = d");
        

        private static void ComputeWithDoubleType()
        
            double a = 1.9;
            double b = 3;
            double c = a * a * a;
            double d = Math.Pow(a, b);

            Console.WriteLine($"a * a * a      = c");
            Console.WriteLine($"Math.Pow(a, b) = d");
        
    

.NET Core 2.2

--- 十进制 ---------

a * a * a                        = 6.859
Math.Pow((double) a, (double) b) = 6.859

--- 双 ----------

a * a * a      = 6.859
Math.Pow(a, b) = 6.859

.NET Core 3.1

--- 十进制 ---------

a * a * a                        = 6.859
Math.Pow((double) a, (double) b) = 6.859

--- 双 ----------

a * a * a      = 6.858999999999999
Math.Pow(a, b) = 6.858999999999999

【问题讨论】:

在 .NET Core 3.1 中有很多记录在案的浮点改进。请使问题更具可读性。原始值和预期值是什么?为什么预期值被认为是正确的? 旧结果有一点点错误。实际的双精度值是相同的,但旧的字符串格式化程序生成的字符串相差一位 是的。 1.9 ^ 3 正好是 6.859。带有示例和链接的公认答案帮助我解决了问题。 【参考方案1】:

.NET Core 在 IEEE 浮点合规性方面引入了很多 floating point parsing and formatting improvements。其中之一是 IEEE 754-2008 格式合规性。

在 .NET Core 3.0 之前,ToString() 在内部将精度限制为“仅”15 个位置,从而产生无法解析回原始字符串的字符串。问题的值相差一位

在 .NET 4.7 和 .NET Core 3 中,实际字节数保持不变。在这两种情况下,调用

BitConverter.GetBytes(d*d*d)

生产

85, 14, 45, 178, 157, 111, 27, 64

另一方面,BitConverter.GetBytes(6.859) 产生:

86, 14, 45, 178, 157, 111, 27, 64

即使在 .NET Core 3 中,解析“6.859”也会产生第二个字节序列:

BitConverter.GetBytes(double.Parse("6.859"))

这是一个位的差异。旧行为产生了一个无法解析回原始值的字符串

这种变化解释了差异:

ToString()、ToString("G") 和 ToString("R") 现在将返回最短的可往返字符串。这可以确保用户最终得到默认情况下可以使用的东西。

这就是为什么我们在处理浮点数时总是需要指定精度的原因。在这种情况下也有改进:

对于采用精度的“G”格式说明符(例如 G3),现在始终尊重精度说明符。对于精度小于 15(含)的 double 和精度小于 6(含)的 float,这意味着您将获得与以前相同的字符串。对于大于此的精度,您将获得那么多有效数字

使用ToString("G15") 生成6.859,而ToString("G16") 生成6.858999999999999,它有16 个小数位。

提醒我们在处理浮点数时总是需要指定精度,无论是比较还是格式化

【讨论】:

这是一个很好的答案,因为 double.ToString 页面仍然错误地指出“默认情况下,返回值仅包含 15 位精度”。

以上是关于.net core 3 产生与 2.2 版不同的浮点结果的主要内容,如果未能解决你的问题,请参考以下文章

.NET Core - IEnumerable 与 IQueryable 产生不同的空间/地理结果

CentOS7下Docker与.net Core 2.2

.net core 3 依赖注入服务作为“配置”的参数

从.Net core 2.2迁移到.Net Core 3.0

如何将 .NET Core 2.2 Web API 迁移到 .NET Core 3.0?

HTTP/2 与 ASP.NET Core 2.2