如果两种语言都遵循 IEEE 754,那么两种语言的计算结果是否相同?
If two languages follow IEEE 754, will calculations in both languages result in the same answers?
我正在将程序从 Scilab 代码转换为 C++。特别是一个循环产生的结果与原始 Scilab 代码略有不同(这是一段很长的代码,所以我不打算将它包含在问题中,但我会尽力在下面总结这个问题)。
问题是,循环的每一步都使用上一步的计算。此外,计算之间的差异仅在第 100,000 次迭代(大约 300,000 次)时变得明显。
注意:我正在使用 "format(25);" 命令将我的 C++ 程序的输出与 Scilab 5.5.2 的输出进行比较。这意味着我正在比较 25 位有效数字。我还想指出,我理解在一定数量的位之后如何不能保证精度,但在评论之前阅读下面的部分。到目前为止,两种语言之间的所有计算在 25 位以内都是相同的。
为了深入了解这个问题,到目前为止我已经尝试过:
- 正在检查正在使用的数据类型:
我已经设法确认 Scilab 正在使用 IEEE 754 双精度(根据语言文档)。此外,根据维基百科,C++ 不需要 来使用 IEEE 754 进行双打,但据我所知,我在 C++ 中使用双打的任何地方都与 Scilab 的结果完全匹配。
- 检查超越函数的使用:
我还从 What Every Computer Scientist Should Know About Floating-Point Arithmetic 了解到 IEEE 不要求先验函数精确舍入。考虑到这一点,我比较了这些函数(sin()、cos()、exp())在两种语言中的结果,结果似乎是相同的(最多 25 位数字)。
- 使用其他函数和预定义值:
对于sqrt()和pow()的使用,我重复了上面的步骤。以及 Pi 的值(我在 C++ 中使用 M_PI,在 Scilab 中使用 %pi)。结果还是一样。
- 最后,我重写了循环(非常仔细)以确保两种语言的代码相同。
注意:有趣的是,我注意到对于上述所有计算,两种语言之间的结果比计算的实际结果(浮点运算除外)的匹配程度更高。例如:
使用 Wolfram Alpha 的 sin(x) 值 = 0.123456789.....
使用 Scilab 和 C++ 的 sin(x) 的值 = 0.12345yyyyy.....
即使使用 Scilab 或 C++ 计算的值开始与实际结果(来自 Wolfram)不同。每种语言的结果仍然相互匹配。这让我相信大多数值都是以相同的方式计算的(在两种语言之间)。即使 IEEE 754 不要求它们。
我最初的想法是上述前三点之一在两种语言之间的实现方式不同。但据我所知,一切似乎都产生了相同的结果。
是否可能即使这些循环的所有输入都相同,但结果可能不同?可能是因为随着时间的推移累积了一个非常小的错误(超过我能看到的 25 位数字)?如果是这样,我该如何解决这个问题?
一些架构提供了使用扩展精度浮点寄存器的能力(例如内部 80 位,与 RAM 中的 64 位值)。因此,对于相同的计算,可能会得到略有不同的结果,具体取决于计算的结构以及用于编译代码的优化级别。
不,编号系统的格式不能保证不同语言的函数得到相同的答案。
功能,例如sin(x)
,可以使用相同的语言(以及不同的语言)以不同的方式实现。 sin(x)
函数就是一个很好的例子。许多实现将使用查找 table 或带插值的查找 table。这具有速度优势。但是,某些实现可能会使用泰勒级数来评估函数。一些实现可能会使用多项式来得出一个近似值。
具有相同的数字格式是不同语言之间需要解决的一个障碍。功能实现是另一个。
记住,您还需要考虑平台。使用 80 位浮点处理器的程序将与使用 64 位浮点软件实现的程序产生不同的结果。
请注意,Scilab 和 C++ 可能没有使用完全相同的指令序列,或者一个使用 FPU 而另一个使用 SSE,因此可能无法使它们完全相同.
正如 IInspectable 评论的那样,如果您的编译器有 _control87() 或类似的东西,您可以使用它来更改精度 and/or 舍入设置。您可以尝试组合使用它以查看它是否有任何效果,但同样,即使您设法为 Scilab 和 C++ 设置相同的设置,实际指令序列的差异也可能是问题所在。
http://msdn.microsoft.com/en-us/library/e9b52ceh.aspx
如果使用 SSE,我不确定可以调整什么,因为我认为 SSE 没有 80 位精度模式。
在 32 位模式下使用 FPU 的情况下,如果您的编译器没有类似 _control87 的东西,您可以使用汇编代码。如果不允许内联汇编,则需要调用汇编函数。这个例子来自一个旧的测试程序:
static short fcw; /* 16 bit floating point control word */
/* ... */
/* set precision control to extended precision */
__asm{
fnstcw fcw
or fcw,0300h
fldcw fcw
}
是的,可能会有不同的结果。即使您在同一平台上使用相同编程语言的完全相同的源代码也是可能的。有时有一个不同的编译器开关就足够了;例如 -ffastmath
会导致编译器优化您的代码以提高速度而不是准确性,并且,如果您的计算问题一开始就条件不佳,结果可能 显着 不同。
例如,假设您有以下代码:
x_8th = x*x*x*x*x*x*x*x;
一种计算方法是执行 7 次乘法。这将是大多数编译器的默认行为。但是,您可能希望通过指定编译器选项 -ffastmath
来加快速度,并且生成的代码将只有 3 次乘法:
temp1 = x*x; temp2 = temp1*temp1; x_8th = temp2*temp2;
结果会略有不同,因为有限精度算术不是关联的,但对于大多数应用程序来说足够接近并且速度更快。但是,如果您的计算不是 well-conditioned,那么小错误会很快放大为大错误。
我正在将程序从 Scilab 代码转换为 C++。特别是一个循环产生的结果与原始 Scilab 代码略有不同(这是一段很长的代码,所以我不打算将它包含在问题中,但我会尽力在下面总结这个问题)。
问题是,循环的每一步都使用上一步的计算。此外,计算之间的差异仅在第 100,000 次迭代(大约 300,000 次)时变得明显。
注意:我正在使用 "format(25);" 命令将我的 C++ 程序的输出与 Scilab 5.5.2 的输出进行比较。这意味着我正在比较 25 位有效数字。我还想指出,我理解在一定数量的位之后如何不能保证精度,但在评论之前阅读下面的部分。到目前为止,两种语言之间的所有计算在 25 位以内都是相同的。
为了深入了解这个问题,到目前为止我已经尝试过:
- 正在检查正在使用的数据类型:
我已经设法确认 Scilab 正在使用 IEEE 754 双精度(根据语言文档)。此外,根据维基百科,C++ 不需要 来使用 IEEE 754 进行双打,但据我所知,我在 C++ 中使用双打的任何地方都与 Scilab 的结果完全匹配。
- 检查超越函数的使用:
我还从 What Every Computer Scientist Should Know About Floating-Point Arithmetic 了解到 IEEE 不要求先验函数精确舍入。考虑到这一点,我比较了这些函数(sin()、cos()、exp())在两种语言中的结果,结果似乎是相同的(最多 25 位数字)。
- 使用其他函数和预定义值:
对于sqrt()和pow()的使用,我重复了上面的步骤。以及 Pi 的值(我在 C++ 中使用 M_PI,在 Scilab 中使用 %pi)。结果还是一样。
- 最后,我重写了循环(非常仔细)以确保两种语言的代码相同。
注意:有趣的是,我注意到对于上述所有计算,两种语言之间的结果比计算的实际结果(浮点运算除外)的匹配程度更高。例如:
使用 Wolfram Alpha 的 sin(x) 值 = 0.123456789.....
使用 Scilab 和 C++ 的 sin(x) 的值 = 0.12345yyyyy.....
即使使用 Scilab 或 C++ 计算的值开始与实际结果(来自 Wolfram)不同。每种语言的结果仍然相互匹配。这让我相信大多数值都是以相同的方式计算的(在两种语言之间)。即使 IEEE 754 不要求它们。
我最初的想法是上述前三点之一在两种语言之间的实现方式不同。但据我所知,一切似乎都产生了相同的结果。
是否可能即使这些循环的所有输入都相同,但结果可能不同?可能是因为随着时间的推移累积了一个非常小的错误(超过我能看到的 25 位数字)?如果是这样,我该如何解决这个问题?
一些架构提供了使用扩展精度浮点寄存器的能力(例如内部 80 位,与 RAM 中的 64 位值)。因此,对于相同的计算,可能会得到略有不同的结果,具体取决于计算的结构以及用于编译代码的优化级别。
不,编号系统的格式不能保证不同语言的函数得到相同的答案。
功能,例如sin(x)
,可以使用相同的语言(以及不同的语言)以不同的方式实现。 sin(x)
函数就是一个很好的例子。许多实现将使用查找 table 或带插值的查找 table。这具有速度优势。但是,某些实现可能会使用泰勒级数来评估函数。一些实现可能会使用多项式来得出一个近似值。
具有相同的数字格式是不同语言之间需要解决的一个障碍。功能实现是另一个。
记住,您还需要考虑平台。使用 80 位浮点处理器的程序将与使用 64 位浮点软件实现的程序产生不同的结果。
请注意,Scilab 和 C++ 可能没有使用完全相同的指令序列,或者一个使用 FPU 而另一个使用 SSE,因此可能无法使它们完全相同.
正如 IInspectable 评论的那样,如果您的编译器有 _control87() 或类似的东西,您可以使用它来更改精度 and/or 舍入设置。您可以尝试组合使用它以查看它是否有任何效果,但同样,即使您设法为 Scilab 和 C++ 设置相同的设置,实际指令序列的差异也可能是问题所在。
http://msdn.microsoft.com/en-us/library/e9b52ceh.aspx
如果使用 SSE,我不确定可以调整什么,因为我认为 SSE 没有 80 位精度模式。
在 32 位模式下使用 FPU 的情况下,如果您的编译器没有类似 _control87 的东西,您可以使用汇编代码。如果不允许内联汇编,则需要调用汇编函数。这个例子来自一个旧的测试程序:
static short fcw; /* 16 bit floating point control word */
/* ... */
/* set precision control to extended precision */
__asm{
fnstcw fcw
or fcw,0300h
fldcw fcw
}
是的,可能会有不同的结果。即使您在同一平台上使用相同编程语言的完全相同的源代码也是可能的。有时有一个不同的编译器开关就足够了;例如 -ffastmath
会导致编译器优化您的代码以提高速度而不是准确性,并且,如果您的计算问题一开始就条件不佳,结果可能 显着 不同。
例如,假设您有以下代码:
x_8th = x*x*x*x*x*x*x*x;
一种计算方法是执行 7 次乘法。这将是大多数编译器的默认行为。但是,您可能希望通过指定编译器选项 -ffastmath
来加快速度,并且生成的代码将只有 3 次乘法:
temp1 = x*x; temp2 = temp1*temp1; x_8th = temp2*temp2;
结果会略有不同,因为有限精度算术不是关联的,但对于大多数应用程序来说足够接近并且速度更快。但是,如果您的计算不是 well-conditioned,那么小错误会很快放大为大错误。