C++ 中的舍入值。为啥 printf、iostream 和 round 函数 [可能] 表现不同,具体取决于 Visual Studio 和 Windows 版本?

Posted

技术标签:

【中文标题】C++ 中的舍入值。为啥 printf、iostream 和 round 函数 [可能] 表现不同,具体取决于 Visual Studio 和 Windows 版本?【英文标题】:Rounding values in C++. Why do printf, iostream and round function [could] behave differently, depending on the Visual Studio and Windows version?C++ 中的舍入值。为什么 printf、iostream 和 round 函数 [可能] 表现不同,具体取决于 Visual Studio 和 Windows 版本? 【发布时间】:2021-09-06 05:44:53 【问题描述】:

一开始是一个非常简单的问题,现在变成了一场噩梦。 C++ 中舍入值的行为会因某些因素而有所不同。

从下面一段简单的代码开始,您将在 2 个整数值中间的值传递给其他函数:

#include <stdio.h>

extern void print(double d);
extern void stream(double d);
extern void rounding(double d);

int main()

   for (auto i=0;i<10;++i)
      print(i+0.5);
   printf("\n");

   for (auto i=0;i<10;++i)
      stream(i+0.5);
   printf("\n");

   for (auto i=0;i<10;++i)
      rounding(i+0.5);
   printf("\n");

这 3 个函数以 3 种不同的方式打印出值:使用 printf、使用 operator&lt;&lt; 和使用 round 函数:

#include <stdio.h>
#include <iomanip>
#include <iostream>

void print(double d)

   printf("%.0lf ",d);


void stream(double d)

   std::cout << std::fixed << std::setprecision(0) << d << " ";


void rounding(double d)

   auto r = round(d);
   printf("%.0lf ",r);

在所有这些情况下,我想打印出小数点后没有数字的值。

我得到了所有这些组合:

使用 Visual Studio 2015 或 2017 编译,在 Windows Server 2019 上运行,构建 14393:

1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10

使用 Visual Studio 2015 或 2017 编译,在 Windows 10 上运行,构建 19041:

1 2 3 4 5 6 7 8 9 10
0 2 2 4 4 6 6 8 8 10
1 2 3 4 5 6 7 8 9 10

如你所见,使用iostreams,operator&lt;&lt;突然决定从这个Windows版本开始使用Bankers Rounding。

使用 Visual Studio 2019 编译,在 Windows Server 2019 上运行,构建 14393:

1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10

使用 Visual Studio 2019 编译,在 Windows 10 上运行,构建 19041:

0 2 2 4 4 6 6 8 8 10
0 2 2 4 4 6 6 8 8 10
1 2 3 4 5 6 7 8 9 10

现在printf 函数也开始使用银行家四舍五入(使用 VS2015 或 VS2017 编译时不是这种情况)。

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fprintf-fprintf-l-fwprintf-fwprintf-l?view=msvc-160 页面指出,如果您在 legacy_stdio_float_rounding.obj 目标文件中链接,则可以恢复旧行为。事实上,如果你把它链接进去,你就会得到这个:

使用 Visual Studio 2019 编译,链接旧目标文件,在 Windows 10 上运行,内部版本 19041:

1 2 3 4 5 6 7 8 9 10
0 2 2 4 4 6 6 8 8 10
1 2 3 4 5 6 7 8 9 10

不幸的是,我似乎无法恢复流输出运算符的旧行为。

还有其他人也在为这个问题苦苦挣扎吗? 获得一致舍入的最佳解决方案是什么? 由于 C 标准清楚地指定了 round 函数的行为方式(根据值的符号向上舍入到 +/- 无穷大),因此 printfoperator&lt;&lt; 的行为方式似乎也是合乎逻辑的。那么我们是否应该告诉我们的开发人员在流式传输浮点值时不要使用输出运算符(更具体地说,std::fixedstd::setprecision)?

更糟糕的是:一些外部模块是用 javascript 编写的,它甚至有不同的舍入方式(总是向 +infinity 舍入,即使是负数)。 正如我在开头所说的那样:一开始是一个简单的问题,现在变成了一致性的噩梦。

你有遇到同样的问题吗?你是怎么处理的?

【问题讨论】:

阅读Floating-point migration issues 为了投入工作,我正在运行 Windows 10,构建 19042 使用 Visual Studio 2019 编译,我得到 @ 987654343@ 所有 3 个案例的输出。 还有docs.microsoft.com/en-us/cpp/c-runtime-library/… "...在大多数情况下,产生的结果在正确舍入结果的+/-1 ulp之内,..." 行为是否还取决于 Visual Studio 中选择的 C++ 标准?默认标准是 C++14。 @Patrick 好吧,我不知道自己在做什么,所以在 vcxproj 中构建调试的默认选项:/c /ZI /JMC /nologo /W3 /WX- /diagnostics:column /sdl /Od /D _DEBUG /D _CONSOLE /D _UNICODE /D UNICODE /Gm- /EHsc /RTC1 /MDd /GS /fp:precise /permissive- /Zc:wchar_t /Zc:forScope /Zc:inline /Fo"x64\Debug\\" /Fd"x64\Debug\vc142.pdb" /Gd /TP /FC /errorReport:prompt - 老实说,我只知道什么就像其中 3 个开关一样。编译器是 19.28.29914。 【参考方案1】:

round, roundf, roundl:将浮点值四舍五入为最接近的整数值。

printf, _printf_l, wprintf,_wprintf_l:从 Windows 10 版本 2004 (build 19041) 开始,printf 系列函数根据 IEEE 754 舍入规则打印完全可表示的浮点数。在以前的 Windows 版本中,完全可表示的浮点数结束在'5'中总是会四舍五入。 IEEE 754 声明它们必须四舍五入到最接近的偶数(也称为“银行家四舍五入”)。此更改仅影响使用 Visual Studio 2019 版本 16.2 及更高版本构建的程序。要使用旧的浮点舍入行为,请与 legacy_stdio_float_rounding.obj 链接。

std::fixed

【讨论】:

这确实是我观察到的。我的观点是:如何在应用程序中正确处理这种行为,并向客户解释应用程序的输出取决于 Windows 版本。如果用户在输入字段中输入 2.5,然后生成不需要小数点后数字的报告,那么他将在 19041 之前的 Windows 版本中看到 3,在从 19041 开始的 Windows 版本中看到 2。我们可以还原通过链接到旧目标文件中的旧行为,但仅适用于 printf 函数。 iostream 逻辑继续使用银行家逻辑。向客户解释。 编译器选项似乎也会影响行为。当使用 /MD 编译并在 Windows 10 20H2 上运行时,前 2 种情况(printf 和流操作符)得到 0 2 2 4 4 6 6 8 8 10 当使用 /MDd 编译并在 Windows 10 20H2 上运行时,我得到 1前 2 例为 2 3 4 5 6 7 8 9 10。在 Windows Server 2019 上运行任一 exe 时,我总是得到 1 2 3 4 5 6 7 8 9 10。这是预期的行为还是只是一个错误??? 使用在 Windows 10 20H2 上运行的 /MD 和 /MDd 编译时,前两种情况我得到 0 2 2 4 4 6 6 8 8 10。我正在使用 Visual Studio2019 16.10.0。 我们可以在多台机器上重现该问题,并由我们的多个同事检查。该问题现已在 Microsoft 记录。

以上是关于C++ 中的舍入值。为啥 printf、iostream 和 round 函数 [可能] 表现不同,具体取决于 Visual Studio 和 Windows 版本?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 舍入算法中的 0.501,C++ 中的 Excel ROUND

在 C++ 中使用 floor 函数的舍入错误

如何从 laravel 中的对象获取舍入值 [关闭]

openjdk8中的舍入问题

舍入值的条件格式

存储过程输出参数返回舍入值