额外的精度
这是错误的.您链接到的答案声明"浮点"是确定性的,并澄清"相同的浮点操作,在相同的硬件上运行,总是产生相同的结果."然而,C和C++标准并不要求C和C++实现始终使用"相同的浮点运算".所以float
算术在这个意义上并不是确定性的.
- 当A没有副作用时,
A += B
应该表现得像A = A + B
.
是的,A += B
的行为与A = A + B
类似,只是左值A
只被计算一次.然而,C标准并不要求A = A + B
具有确定性行为.具体地,A = A + B*C;
被允许在两个不同的情况下给出两个不同的结果.
具体来说,C 2018 5.2.4.2.2 10表示:
…具有浮点操作数…的运算符产生的值计算结果的范围和精度可能大于该类型要求的格式.
考虑一下target[i] += g * src[i];
个.C实现可以通过执行g
乘以src[i]
float
和乘积与target[i]
的float
加法来实现这一点.然而,由于上面的许可,还允许通过执行g
与src[i]
的double
倍乘以及该乘积与target[i]
的double
相加来实现这一点.或者它可以用有效的无限精确的乘法和无限精确的加法来实现它.
该标准将这一 Select 留给了实现.它为实现提供了一种方法来报告有关其所做 Select 的一些信息;<float.h>
中定义的值FLT_EVAL_METHOD
是以下值之一:
- 0,表示所有浮点运算都以其标称类型执行,
- 1,表示所有的
float
和double
运算在double
中执行,long double
在long double
中执行,
- 2,表示所有浮点运算都在
long double
中执行,或者
- −1,这意味着该实现不断言如何执行浮点运算.
在您的例子中,编译器似乎对target[i] += g * src[i];
使用了融合的乘法-加法指令,这相当于使用无限精确的算术执行运算并将结果舍入到float
.
请注意,扩展精度不能无限期地携带.5.2.4.2.2 10中更完整的引述如下:
除了赋值和强制转换(它们删除了所有额外的范围和精度)之外,由具有浮点操作数的运算符和要进行常规算术转换的值以及浮点常量生成的值将被计算为其范围和精度可能大于类型要求的格式.
因此,每当执行赋值或强制转换时,必须将该值转换为其标称类型.
因此,对于float x = g * src[i];
后跟a + x
,编译器不能使用融合乘加运算,因为g * src[i]
的乘积在赋值给x
时必须转换为float
的精度;额外的精度不能传递到a + x
.
C++标准具有基本相同的文本.
收缩
C 2018 6.5 8表示:
浮点表达式可以是contracted,即,就好像它是单个运算一样进行求值,从而省略了源代码和表达式求值方法…所隐含的舍入误差
这意味着,即使上面讨论的FLT_EVAL_METHOD
是0并且float
算术仅以float
精度执行,处理器也可以使用融合的乘加指令来执行a = b + c*d
,当将其相加到b
时,其计算就好像c*d
中没有舍入误差一样.6.5 8允许其他形式的压缩,但融合乘法-加法和融合乘法-减法最常见.
C 2018 7.12.2指定了FP_CONTRACT
编译指示,因此,如果翻译单元有#include <math.h>
和#pragma STDC FP_CONTRACT OFF
,编译器就不应该使用缩写.(请注意,让编译器遵守此规则和C标准的其他规则可能需要使用某些switch ,例如将-std=c18
与GCC或Clang一起使用,以请求比其默认行为更好地符合标准.)
避免收缩和额外精度的另一种方法是对单个浮点运算使用赋值或强制转换,例如:
float t0 = c*d;
a = b + t0;
或者:
a = b + (float) (c*d);
从技术上讲,标准中的措辞仍然允许以额外的精度执行上述操作,然后四舍五入为float
.这可能会导致双舍入误差.然而,对于编译器来说,使用double
算术计算单个运算,然后使用另一条指令将其舍入为float
将是低效的,因此启用了优化的编译器应该只使用float
算术来计算这样的表达式.
还要注意,收缩的使用迫使浮点算术的计算具有一种不确定性.在y = a*b + c*d
中,编译器可以使用融合的乘法-加法来将其中一个乘积相加,而不进行舍入,但它不能同时对两个乘积执行这一操作.它会 Select 哪一个呢?对于任何特定的表达式,答案可能是确定的,但当a*b + c*d
作为子表达式出现在较大的表达式中时,我们通常不能确定编译器将 Select 哪一个与融合的乘法-加法相结合.
缺乏准确性
C 2018 5.2.4.2.2 7表示:
浮点运算(+
、-
、*
、/
)以及<math.h>
和<complex.h>
中返回浮点结果的库函数的精度是由实现定义的,…
目前还不清楚这段话是否允许任何形式的非决定论.这可能意味着,虽然精度是由实现定义的,但它必须是C实现始终重现的某种特定精度.现代硬件在很大程度上符合IEEE 754标准,这是处理不正常数字的显著例外.因此,这篇文章可能在很大程度上是对两件事的认可:
- 当编译器在编译时计算浮点表达式时,它可能会使用比运行时更高的精度.
- 浮点算术的软件实现旨在支持没有浮点指令的硬件上的C浮点,其精度可能低于correctly rounded(IEEE 754标准指定的舍入术语).
在任何情况下,句子都足够模糊,以至于我们不能确保C中的浮点表达式在给定相同输入的情况下总是返回相同的结果.