请考虑以下代码:
0.1 + 0.2 == 0.3 -> false
Run Code Online (Sandbox Code Playgroud)
0.1 + 0.2 -> 0.30000000000000004
Run Code Online (Sandbox Code Playgroud)
为什么会出现这些不准确之处?
难道C和C++标准要求在数学运算math.h上的浮动点(即sqrt,exp,log,sin,...)返回数值最佳的解决方案?
对于给定的(精确且有效的)输入,通常显然不是这些函数的精确浮点输出.但输出是否必须是最接近数学上精确值的可表示值?
如果没有,是否有任何关于精度的要求(可能是平台特定的/在其他标准中?),以便我能够在我的代码中对计算错误进行最坏情况估计?现代实施的数值误差的典型限制是什么?
我正在将程序从Scilab代码转换为C++.特别是一个循环产生的结果与原始的Scilab代码略有不同(这是一段很长的代码,因此我不会在问题中包含它,但我会尽力总结下面的问题).
问题是,循环的每一步都使用上一步的计算.此外,计算之间的差异仅在第100,000次迭代(大约300,000次)中变得明显.
注意:我正在使用"format(25)"将我的C++程序的输出与Scilab 5.5.2的输出进行比较.命令.含义我正在比较25位有效数字.我还想指出,我理解在一定数量的位之后无法保证精度,但在评论之前阅读下面的部分.到目前为止,两种语言之间的所有计算都相同,最多可达25位.
为了找到问题的根源,到目前为止我已经尝试过:
我已经设法确认Scilab正在使用IEEE 754双打(根据语言文档).另外,根据维基百科,C++ 并不需要使用IEEE 754双打,但我可以告诉,我到处使用双C++中它已完全匹配的Scilab的结果.
我还读过每个计算机科学家应该知道的关于浮点运算的内容,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位数)正在发生,随着时间累积?如果是这样,我该如何解决这个问题呢?
我正在TeX中编写一个(几乎)IEEE 854兼容的浮点实现(它只支持32位整数).该标准仅规定的结果+,-,*,/,比较,余数,并且sqrt:对于这些操作,结果应(根据舍入模式)是相同的舍入准确结果的表示的数.
我似乎记得IEEE指定超越函数(sin,exp...)应该产生忠实的结果(在默认的舍入到最近的模式中,它们应该输出围绕确切结果的两个可表示数字之一).计算小数的正弦是相当简单的:移动2*pi的倍数以获得[0,2*pi]范围内的数字,然后再做一些工作以将范围缩小到[0,pi/4] ,并使用泰勒系列.
现在假设我想计算sin(1e300).为此,我需要找到1e300 modulo 2*pi.这需要知道pi的300(316?)个小数,因为只有16个小数,结果没有任何意义(特别是,它不是忠实的).
关于结果sin(1e300)和类似的非常大的数字应该有标准吗?
其他浮点实现有什么作用?
我想知道在C或C ++中使用浮点运算的任何代码在任何基于x86的体系结构中是否都会产生精确的结果,而不管代码的复杂性如何。
据我所知,自从Intel 8087开始,任何x86架构都使用了准备处理IEEE-754浮点数的FPU单元,而且我看不出任何原因导致不同架构的结果不同。但是,如果它们不同(即由于不同的编译器或不同的优化级别),那么是否有某种方法可以通过仅配置编译器来产生位精确结果?
我有一个程序:
int main()
{
float f = 0.0f;
int i;
for (i = 0 ; i < 10 ; i++)
f = f + 0.1f;
if (f == 1.0f)
printf("f is 1.0 \n");
else
printf("f is NOT 1.0\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
它总是打印f is NOT 1.0. 我知道这与 C 中的浮点精度有关。但我不确定它到底在哪里搞砸了。有人可以解释一下为什么不打印另一行吗?
我正在使用一些跨平台代码。在 Mac 上它是用 Clang 编译的,在 Windows 上它是用 Visual C++ 编译的。
有一个计算可能很敏感,并且触发断言的 Mac 和 Windows 之间存在差异。最终 acos 结果之间存在差异,但我不清楚为什么。
在这两个平台上,acos 的输入恰好是 -1.0f。在 Visual C++ 中,acos(-1.0f)为 3.14159274。这就是 pi 作为浮点数的值,这正是我所期望的。
但在 macOS 上:
float value = acos(-1.0f);
...计算结果为 3.1415925。这足以引发代码中的问题。acos 是一种对于 float 来说可能不精确的操作 - 我理解这一点。并且不同的编译器可以有不同的acos实现。我只是不清楚为什么 Clang 似乎对如此简单的 acos 结果有问题,而 Visual C++ 却没有。浮点数能够代表 3.14159274,但这不是我得到的结果。
可以使用以下命令从 Xcode 版本的 Clang 中获取准确/Visual C++ 对齐的值:
float value = (float)acos((double)-1.0f);
因此,我可以通过提高精度来解决这个问题,然后将值向下转换回浮点数以保留与 Windows 相同的舍入。我只是在寻找一个理由,说明当 VC++ 编译器似乎不存在精度问题时,为什么需要额外的精度。Clang/Xcode/VC++ 数学库之间也可能存在差异。我只是假设 acos(-1.0) 可能在编译器中更稳定。我在舍入模式中找不到任何差异(即使舍入不是必需的),并且 Xcode 和 Visual Studio 中的新项目也显示出相同的差异。两台机器都是英特尔的。
使用long double我得到18/19 = 0.947368421052631578...,并且947368421052631578是重复小数.使用double我得到0.947368421052631526...然而,前者是正确的.为什么这么错误的结果呢?
感谢帮助.