巨大的 printf float/double windows/linux 上的整数数字差异
huge printf float/double difference in integer digits on windows/linux
#include <float.h>
#include <stdio.h>
int main(int argc, char** argv)
{
printf("[0] %f\n", FLT_MAX);
printf("[1] %lf\n", FLT_MAX);
printf("[2] %Lf\n", FLT_MAX); // gcc warning: expects argument of type ‘long double’
printf("[3] %f\n", DBL_MAX);
printf("[4] %lf\n", DBL_MAX);
printf("[5] %Lf\n", DBL_MAX); // gcc warning: expects argument of type ‘long double’
//using C++ und std::numeric_limits<float/double>::max() gives same results
return 0;
}
Linux:
x64
lsb_release -d 打印 "Description: Ubuntu 15.04"
gcc --version 打印 "gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2"
ldd --version 打印 "ldd (Ubuntu GLIBC 2.21-0ubuntu4) 2.21"
[0] 340282346638528859811704183484516925440.000000
[1] 340282346638528859811704183484516925440.000000
[2] --> warning-line disabled
[3] 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
[4] 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
[5] --> warning-line disabled
Windows 7 x64:
VS2010(最新版本 10.0.40219.1 SP1Rel)Debug/Win32
[0] 340282346638528860000000000000000000000.000000
[1] 340282346638528860000000000000000000000.000000
[2] 340282346638528860000000000000000000000.000000
[3] 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
[4] 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
[5] 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
FLT_MAX 的差异
VS2010:340282346638528860000000000000000000000.000000
GCC4.9.2: 340282346638528859811704183484516925440.000000
是 1.8829581651548307456e+20(不是那么小)- 使用双精度变得更大
更新:实际问题
有没有办法(只对代码做一点改动)在 Linux 和 Windows(以及其他)上获得相同的结果,或者我是否需要使用完全相同的实现在所有系统上?我害怕在我的 Windows/Linux/Linux-ARM/VxWorks/Solaris 平台上有自己的实现。
printf
函数在这些平台上的实现方式不同。
看这段代码:
#include <stdio.h>
int main()
{
printf("%lf\n", ((double)1e100)/3);
return 0;
}
这个用 VC++ 编译的程序给出:
3333333333333333200000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
而使用 g++ 编译的同一程序给出:
3333333333333333224453896013722304246165110619355184909726539264904319486405759542029132894851563520.000000
平台之间的区别在于数字的打印方式,而不是数字本身。
您似乎误解了浮点数的工作原理。它们的准确性与它们的大小有关。幅度由数字的指数表示,值由其尾数表示。尾数的大小是固定的,对于 float
它是 23 位加上一个隐式位。转换为十进制,这意味着您可以准确表示大约七位有效的小数位。
FLT_MAX
大约是 3.40282346639e+38。下一个可以表示为 float
的较小数字约为 3.40282326356e+38。这是 2.02824096037e+31 的差异,比您的感知误差大十个数量级。
即使数字之间的明显差异似乎很大,但两个打印值都比 FLT_MAX
更接近于任何其他单精度浮点数并将文本表示重新转换为 ´浮动should yield
FLT_MAX`.
简而言之:printf
的两种实现都是有效的。
Is there a way (with only a small change of the code) to get the same result on Linux and Windows?
是的 - 主要是。
在 Windows 上使用 gcc
。 "Windows" 当然,OP 指的是 Visual Stdio 编译器或相关产品。 gcc
在 Windows、Linux 和许多其他平台上可用,并且结果比 OP 的示例更一致。这确实是一个 编译器 问题,而不是 OS 问题。
使用base-2/16输出。
printf("%a\n", FLT_MAX);
// 0x1.fffffep+127 gcc 4.9.2
// 0x1.fffffep+127 VS 2010
使用精度有限的 "%.e"
。 C 规范只为 float
指定了 最小 精度为 6 位,为 double
指定了 10 位精度,否则使用 FLT_DIG/DBL_DIG
。
要修正 2/3 位指数,请参阅 and visual studio _set_output_format
请注意,"%.*e"
中的精度字段是前导数字后的位数,因此代码使用 -1
.
printf("%.*e\n", FLT_DIG - 1, FLT_MAX);
// 3.40282e+38 gcc 4.9.2
// 3.40282e+038 VS 2010
使用 "%.e"
更准确,但不过分。 FLT_DECIMAL_DIG/DBL_DECIMAL_DIG
是要打印的位数,以读回该值并以相同的 float
值结束。打印更多数字会导致 OP 的问题。考虑 double
:注意 VS 在 OP 的 post 中打印到 17 个正确舍入的有效数字。如果在 VS 中定义,DBL_DECIMAL_DIG
将是 17。 VS 打印到 17 位数字以保留 "round-tripping" 个数字。通过指示 gcc
打印到 17 位有效数字,我们得到相同的结果。
#ifdef FLT_DECIMAL_DIG
// FLT_DECIMAL_DIG/DBL_DECIMAL_DIG typically not available in VS
#define OP_FLT_Digs (FLT_DECIMAL_DIG)
#define OP_DLB_Digs (DBL_DECIMAL_DIG)
#else
#define OP_FLT_Digs (FLT_DIG + 3)
#define OP_DBL_Digs (DBL_DIG + 2)
#endif
printf("%.*e\n", OP_FLT_Digs - 1, FLT_MAX);
// 3.40282347e+38 gcc 4.9.2
// 3.40282347e+038 VS 2010
有关 "%.e"
的更多信息。由于角落情况,当扫描回数字将导致 next FP 数字时,不打印超过 FLT_DECIMAL_DIG/DBL_DECIMAL_DIG
有效数字是有价值的。本质上是一个双舍入问题 - 这个 post 有点深 - 所以没有细节。
当然,如果各种系统使用截然不同的 FP 格式,那么这一切都没有实际意义,long double
很有可能。精确的 FP 一致性很困难,但以上肯定有助于减少差异。
#include <float.h>
#include <stdio.h>
int main(int argc, char** argv)
{
printf("[0] %f\n", FLT_MAX);
printf("[1] %lf\n", FLT_MAX);
printf("[2] %Lf\n", FLT_MAX); // gcc warning: expects argument of type ‘long double’
printf("[3] %f\n", DBL_MAX);
printf("[4] %lf\n", DBL_MAX);
printf("[5] %Lf\n", DBL_MAX); // gcc warning: expects argument of type ‘long double’
//using C++ und std::numeric_limits<float/double>::max() gives same results
return 0;
}
Linux: x64 lsb_release -d 打印 "Description: Ubuntu 15.04" gcc --version 打印 "gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2" ldd --version 打印 "ldd (Ubuntu GLIBC 2.21-0ubuntu4) 2.21"
[0] 340282346638528859811704183484516925440.000000
[1] 340282346638528859811704183484516925440.000000
[2] --> warning-line disabled
[3] 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
[4] 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
[5] --> warning-line disabled
Windows 7 x64: VS2010(最新版本 10.0.40219.1 SP1Rel)Debug/Win32
[0] 340282346638528860000000000000000000000.000000
[1] 340282346638528860000000000000000000000.000000
[2] 340282346638528860000000000000000000000.000000
[3] 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
[4] 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
[5] 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
FLT_MAX 的差异 VS2010:340282346638528860000000000000000000000.000000 GCC4.9.2: 340282346638528859811704183484516925440.000000
是 1.8829581651548307456e+20(不是那么小)- 使用双精度变得更大
更新:实际问题
有没有办法(只对代码做一点改动)在 Linux 和 Windows(以及其他)上获得相同的结果,或者我是否需要使用完全相同的实现在所有系统上?我害怕在我的 Windows/Linux/Linux-ARM/VxWorks/Solaris 平台上有自己的实现。
printf
函数在这些平台上的实现方式不同。
看这段代码:
#include <stdio.h>
int main()
{
printf("%lf\n", ((double)1e100)/3);
return 0;
}
这个用 VC++ 编译的程序给出:
3333333333333333200000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
而使用 g++ 编译的同一程序给出:
3333333333333333224453896013722304246165110619355184909726539264904319486405759542029132894851563520.000000
平台之间的区别在于数字的打印方式,而不是数字本身。
您似乎误解了浮点数的工作原理。它们的准确性与它们的大小有关。幅度由数字的指数表示,值由其尾数表示。尾数的大小是固定的,对于 float
它是 23 位加上一个隐式位。转换为十进制,这意味着您可以准确表示大约七位有效的小数位。
FLT_MAX
大约是 3.40282346639e+38。下一个可以表示为 float
的较小数字约为 3.40282326356e+38。这是 2.02824096037e+31 的差异,比您的感知误差大十个数量级。
即使数字之间的明显差异似乎很大,但两个打印值都比 FLT_MAX
更接近于任何其他单精度浮点数并将文本表示重新转换为 ´浮动should yield
FLT_MAX`.
简而言之:printf
的两种实现都是有效的。
Is there a way (with only a small change of the code) to get the same result on Linux and Windows?
是的 - 主要是。
在 Windows 上使用
gcc
。 "Windows" 当然,OP 指的是 Visual Stdio 编译器或相关产品。gcc
在 Windows、Linux 和许多其他平台上可用,并且结果比 OP 的示例更一致。这确实是一个 编译器 问题,而不是 OS 问题。使用base-2/16输出。
printf("%a\n", FLT_MAX); // 0x1.fffffep+127 gcc 4.9.2 // 0x1.fffffep+127 VS 2010
使用精度有限的
"%.e"
。 C 规范只为float
指定了 最小 精度为 6 位,为double
指定了 10 位精度,否则使用FLT_DIG/DBL_DIG
。
要修正 2/3 位指数,请参阅 _set_output_format
请注意,"%.*e"
中的精度字段是前导数字后的位数,因此代码使用 -1
.
printf("%.*e\n", FLT_DIG - 1, FLT_MAX);
// 3.40282e+38 gcc 4.9.2
// 3.40282e+038 VS 2010
使用
"%.e"
更准确,但不过分。FLT_DECIMAL_DIG/DBL_DECIMAL_DIG
是要打印的位数,以读回该值并以相同的float
值结束。打印更多数字会导致 OP 的问题。考虑double
:注意 VS 在 OP 的 post 中打印到 17 个正确舍入的有效数字。如果在 VS 中定义,DBL_DECIMAL_DIG
将是 17。 VS 打印到 17 位数字以保留 "round-tripping" 个数字。通过指示gcc
打印到 17 位有效数字,我们得到相同的结果。#ifdef FLT_DECIMAL_DIG // FLT_DECIMAL_DIG/DBL_DECIMAL_DIG typically not available in VS #define OP_FLT_Digs (FLT_DECIMAL_DIG) #define OP_DLB_Digs (DBL_DECIMAL_DIG) #else #define OP_FLT_Digs (FLT_DIG + 3) #define OP_DBL_Digs (DBL_DIG + 2) #endif printf("%.*e\n", OP_FLT_Digs - 1, FLT_MAX); // 3.40282347e+38 gcc 4.9.2 // 3.40282347e+038 VS 2010
有关
"%.e"
的更多信息。由于角落情况,当扫描回数字将导致 next FP 数字时,不打印超过FLT_DECIMAL_DIG/DBL_DECIMAL_DIG
有效数字是有价值的。本质上是一个双舍入问题 - 这个 post 有点深 - 所以没有细节。当然,如果各种系统使用截然不同的 FP 格式,那么这一切都没有实际意义,
long double
很有可能。精确的 FP 一致性很困难,但以上肯定有助于减少差异。