如果两种语言遵循IEEE 754标准,那么在这两种语言中进行的计算是否会得出相同的答案?

11

我正在将一个程序从Scilab代码转换为C ++。特别是一个循环产生的结果与原始的Scilab代码略有不同(这是一段长代码,所以我不会在问题中包含它,但我会尽力总结下面的问题)。

问题是,循环的每个步骤都使用前一步的计算。此外,仅在第100,000次迭代(约为300,000次迭代之一)时才出现计算之间的差异。

注意:我正在使用“format(25);”命令将我的C ++程序的输出与Scilab 5.5.2的输出进行比较。这意味着我正在比较25个有效数字。 我还想指出,在评论之前,请阅读以下部分。到目前为止,两种语言之间的所有计算都相同,最多可达25位小数。

为了找出问题的根源,到目前为止我已经尝试过:

  1. 检查所使用的数据类型:

我已成功确认Scilab正在使用IEEE 754双精度浮点数(根据语言文档)。 此外,根据维基百科,C ++不需要对双精度浮点数使用IEEE 754,但从我所知道的,我在C ++中使用double的每个地方都与Scilab的结果完全匹配。

  1. 检查超越函数的使用:

我还从What Every Computer Scientist Should Know About Floating-Point Arithmetic中了解到,IEEE不要求将超越函数精确四舍五入。考虑到这一点,我比较了两种语言中这些函数(sin(),cos(),exp())的结果,结果看起来是相同的(最多达到25位小数)。

  1. 其他函数和预定义值的使用:

我对sqrt()和pow()函数的使用,以及圆周率的值进行了与前面相同的步骤(在C++中使用M_PI,在Scilab中使用%pi)。结果仍然相同。

  1. 最后,我非常小心地重写了循环,以确保两种语言之间的代码完全相同。

注意:有趣的是,我发现所有上述计算结果在两种语言之间的匹配程度远高于实际计算结果(在浮点运算以外)。例如:

使用Wolfram Alpha计算sin(x)的值 = 0.123456789......

使用Scilab和C++计算sin(x)的值 = 0.12345yyyyy......

即使使用Scilab或C++计算出的值开始与实际结果(来自Wolfram)不同,每种语言的结果仍然彼此匹配。这让我相信大部分值是以相同的方式计算出来的(在两种语言之间),尽管它们不必按照IEEE 754标准。


我的最初想法是前三点中有一些东西在两种语言之间实现方式不同。但据我所知,似乎所有内容都可以产生相同的结果。

即使这些循环的所有输入都相同,它们的结果可能会不同吗?可能是由于一个非常小(超出我所能看到的25位数字)的错误随着时间的推移而累积导致的吗?如果是这样,我该如何解决这个问题?


3
到目前为止,所有的计算结果在前 25 位小数上都是相同的。考虑到 IEEE 双精度浮点数的精度通常为 15-17 位小数,这是一个比平均水平更好的结果。 - n. m.
如果你使用不同的编译方式(例如x87 vs SSE,对于C#来说是x86 vs x64),那么即使是C++或C#本身也不会得到相同的结果。 - harold
@harold 到什么程度?也就是说,如果你在 C++ 中执行 1 + 1,大多数编译器都会得到相同的输出结果。这是否只会对大多数计算产生轻微影响?而且,我发现在不同编程语言中,25个数字是匹配的。这种误差可能会发生在那些数字之后,并仍然影响程序吗? - Paul Warnick
你为什么需要那么多位数的精度? - Jesper Juhl
1
我赞同尝试使用编译器标志的建议。一些编译器默认采用激进的优化设置,允许重新排列浮点表达式(例如以数学上等价但在有限精度浮点算术中不等价的方式),可能将乘法和加法合并为FMA,或使用SIMD约简。查看编译器文档,了解承诺最严格遵守IEEE-754和其他相关标准的开关。例如,在我的英特尔编译器上,是/fp:strict。使用这样的标志可能会损失很多性能。 - njuffa
显示剩余7条评论
4个回答

7
不,数字系统的格式并不保证不同语言的函数会得出相等的答案。
例如sin(x)等函数可以使用不同的实现方式在同一语言(以及不同语言)中实现。这是一个很好的例子。许多实现将使用查找表或带插值的查找表来加速计算。然而,有些实现可能会使用泰勒级数来评估该函数,而有些实现可能会使用多项式来得出近似值。
在不同语言之间解决相同数字格式的问题只是其中一个难点。函数实现是另一个难点。还要记住需要考虑平台。使用80位浮点处理器的程序将与使用64位浮点软件实现的程序产生不同结果。

实际上,那正是我计划接下来要做的事情。唯一的问题是结果需要完全相同。因此,我需要使用Scilab当前正在使用的任何函数来计算结果,但我在源代码中找不到它们。 - Paul Warnick
关于你的回答,当你提到相同的数字格式时,你是否知道我已经有了吗?我知道Scilab遵循双精度IEEE标准,我假设C++也是如此。C++可能只是足够相似以看起来相同,但实际上它是不同的实现吗? - Paul Warnick
最后,我在同一平台上运行这两个程序(即我的个人电脑)。这样做是否可以避免您回答中提到的最后一个问题? - Paul Warnick
1
不,代码才是最重要的。我可以在我的计算机上拥有一个浮点处理器,但却运行一个不使用它的程序。你必须验证两个函数之间的所有依赖关系都相同。当你在两种语言中编写自己的函数时,这会变得更加简单。 - Thomas Matthews
3
C++编译器是否使用IEEE 754浮点表示法在编译器手册中有说明。C++语言的实现并没有对使用特定的浮点表示法提出要求。 - IInspectable
显示剩余2条评论

6
一些架构提供了使用扩展精度浮点寄存器的能力(例如,内部使用80位,而在RAM中使用64位值)。因此,在计算结构和编译代码所使用的优化级别不同的情况下,进行相同计算可能会得到稍微不同的结果。

所以,即使我使用完全相同的函数(也就是将Scilab用于sin(),cos()等的内容复制到C++中),结果仍然可能不同? - Paul Warnick
如果一种语言/实现正在使用x87,而另一种语言/实现正在使用SSE或在SPARC或Power上运行或其他某些东西,则是的。您不能保证与最后一位相同的结果。 - Jesper Juhl
@JesperJuhl 我该如何确定这两种语言是否使用相同的架构? - Paul Warnick
1
如果它们在同一CPU上运行,则它们使用相同的架构。至于它们是否使用该架构的相同部分-您需要了解Scilab使用的内容,然后找出如何使您的C ++编译器使用相同的指令(例如,禁用AVX,禁用快速数学等)。 - Useless

3
是的,有可能会得到不同的结果。即使你在相同的平台上使用相同编程语言的源代码。有时只需要更改编译器设置就可以了。例如,使用-ffastmath编译器选项会让编译器优化代码以提高速度而非准确性,如果你的计算问题本来就不太稳定,结果可能会显著不同。
例如,假设你有以下代码:
 x_8th = x*x*x*x*x*x*x*x;

计算这个的一种方法是进行7次乘法。这将是大多数编译器的默认行为。然而,您可能希望通过指定编译器选项-ffastmath来加速此过程,从而得到仅有3次乘法的代码:

temp1 = x*x; temp2 = temp1*temp1; x_8th = temp2*temp2;

结果会稍有不同,因为有限精度算术不是结合的,但对于大多数应用程序来说足够接近且速度更快。然而,如果您的计算条件良好不佳,那么这种小误差可能很快被放大成大误差。

非常好的回答。我开始意识到实现我所尝试的内容有多么棘手。 - Paul Warnick

1
请注意,Scilab和C ++可能没有使用完全相同的指令序列,或者一个使用FPU,另一个使用SSE,因此可能没有办法使它们完全相同。
正如IInspectable所评论的那样,如果您的编译器具有_control87()或类似的功能,则可以使用它来更改精度和/或舍入设置。您可以尝试这些组合,以查看它是否有任何效果,但是即使您设法使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
}

这样做到底会有什么效果呢?它会增加我的C++计算的精度吗?如果是这样,我并不完全确定这能解决问题,因为我试图要完全匹配Scilab代码的结果(显然已经具有与我的C++代码相同的精度)。 - Paul Warnick
64位编译器在x64上通常不使用FPU,而是在SSE单元中实现所有浮点数运算。这意味着可以使用64位浮点数,但没有切换到扩展精度的选项。如果您确实想设置控制字,则不需要使用汇编语言,只需在Visual Studio中进行编译即可。它提供_control87函数来完成此操作。 - IInspectable
好的,这很有道理(虽然对我来说都是新的,所以我需要做一些谷歌搜索)。如果有任何区别,我正在通过gcc编译我的C++,并且只需在Scilab控制台(5.5.2)中点击执行即可运行Scilab。关于这些执行方法,您可能想知道一些有用的建议吗? - Paul Warnick
@PaulWarnick - 我更新了我的答案,以澄清问题并反映IInspectable的评论。 - rcgldr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接