浮点算术运算的精度是多少?

Poo*_*ria 7 c++ floating-point arithmetic-expressions rounding floating-point-precision

考虑下面两个非常简单的乘法:

double result1;
long double result2;
float var1=3.1;
float var2=6.789;
double var3=87.45;
double var4=234.987;

result1=var1*var2;
result2=var3*var4;
Run Code Online (Sandbox Code Playgroud)

默认情况下,乘法是否以高于操作数的精度完成?我的意思是,在第一次乘法的情况下,它是以双精度完成的,如果在x86架构中第二次,它是以80位扩展精度完成的,或者我们应该在表达式中将操作数转换为更高的精度,如下所示?

result1=(double)var1*(double)var2;
result2=(long double)var3*(long double)var4;
Run Code Online (Sandbox Code Playgroud)

其他操作(添加,除法和余数)怎么样?例如,当添加两个以上的正单精度值时,如果用于保存表达式的中间结果,则使用双精度的额外有效位可以减少舍入误差.

Pas*_*uoq 8

浮点计算的精度

C++ 11 包含FLT_EVAL_METHODC99 的定义cfloat.

FLT_EVAL_METHOD     

Possible values:
-1 undetermined
 0 evaluate just to the range and precision of the type
 1 evaluate float and double as double, and long double as long double.
 2 evaluate all as long double 

如果您的编译器定义FLT_EVAL_METHOD为2,那么r1r2,和s1以及s2之下的计算分别是等价的:

double var3 = …;
double var4 = …;

double r1 = var3 * var4;
double r2 = (long double)var3 * (long double)var4;

long double s1 = var3 * var4;
long double s2 = (long double)var3 * (long double)var4;
Run Code Online (Sandbox Code Playgroud)

如果您的编译器将FLT_EVAL_METHOD定义为2,那么在上面的所有四个计算中,乘法都是以long double类型的精度完成的.

但是,如果编译器定义FLT_EVAL_METHOD为0或1,则r1r2,并且分别s1s2,并不总是相同.计算时的乘法r1s1以精度计算的乘法double.计算时的乘法r2s2以精度计算的乘法long double.

从狭隘的论点中获得广泛的结

如果您计算那些注定要被存储在一个更宽的结果类型比操作数的类型,因为是结果result1result2你的问题,你应该总是在争论的宽度至少转换成类型为目标,为你做这里:

result2=(long double)var3*(long double)var4;
Run Code Online (Sandbox Code Playgroud)

如果没有这种转换(如果你写的话var3 * var4),如果编译器的定义FLT_EVAL_METHOD是0或1,那么产品将以精度计算double,这是一种耻辱,因为它注定要存储在a中long double.

如果编译器定义FLT_EVAL_METHOD为2,那么转换(long double)var3*(long double)var4不是必需的,但它们也不会受到任何影响:表达式表示使用和不使用它们时完全相同.

Digression:如果目标格式与参数一样窄,那么中间结果的扩展精度何时更好?

矛盾的是,对于单个操作,最好只舍入一次目标精度.在扩展精度中计算单个乘法的唯一效果是结果将四舍五入到扩展精度然后再double精确到精度.这使得它不太准确.换句话说,对于FLT_EVAL_METHOD0或1,r2上面的结果有时不如r1因为双舍入而准确,并且如果编译器使用IEEE 754浮点,则永远不会更好.

包含多个操作的较大表达式的情况不同.对于这些,通常通过显式转换或编译器使用,以扩展精度计算中间结果通常更好FLT_EVAL_METHOD == 2.这个问题和其接受的回答表明,与对binary64 IEEE 754参数和结果的80位扩展精度中间计算的计算时,内插式u2 * (1.0 - u1) + u1 * u3总是产生之间的结果u2u3u10和1之间该属性可以不保持binary64精密中间计算,因为更大的舍入误差.

  • @Pooria FPU中发生的事情只是FPU设计者的问题.在讨论C99的网页上,"以精度P计算"意味着"使用取宽度为P的操作数并产生宽度为P的正确舍入结果的指令",无论该指令如何实现(如果操作是乘法的话)如Garp所说,它很可能在处理器中实现更广泛的内部结果,但不存储FPU中暂时存在的更广泛的结果. (2认同)

Rob*_*ahy 1

浮点类型的常见算术转换在乘法、除法和模数之前应用

\n\n
\n

通常的算术转换是对操作数执行并确定结果的类型。

\n\n

\xc2\xa75.6 [expr.mul]

\n
\n\n

类似地,对于加法和减法:

\n\n
\n

通常的算术转换是针对算术或枚举类型的操作数执行的。

\n\n

\xc2\xa75.7 [expr.add]

\n
\n\n

标准中对浮点类型的常见算术转换进行了如下规定

\n\n
\n

许多期望算术或枚举类型操作数的二元运算符会以类似的方式导致转换并产生结果类型。目的是产生一个通用类型,这也是结果的类型。这种模式称为通常的算术转换,其定义如下:

\n\n

[...]

\n\n

\xe2\x80\x94 如果任一操作数的类型为long double,则另一个操作数应转换为long double

\n\n

\xe2\x80\x94 否则,如果任一操作数为double,则另一个应转换为double

\n\n

\xe2\x80\x94 否则,如果任一操作数为float,则另一个应转换为float

\n\n

\xc2\xa75 [表达式]

\n
\n\n

这些浮点类型的实际形式/精度是​​实现定义的:

\n\n
\n

该类型double提供的精度至少与 一样多float,并且该类型long double提供的精度至少与 一样多double。该类型的值集float是该类型的值集的子集double;type 的值集double是 type 的值集的子集long double。浮点类型的值表示是实现定义的。

\n\n

\xc2\xa73.9.1 [基本.基础]

\n
\n

  • 这个答案没有抓住问题的关键;这些计算在幕后执行的精度是多少? (4认同)