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

时间:2014-08-14 07:36:00

标签: c++ floating-point rounding floating-point-precision arithmetic-expressions

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

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;

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

result1=(double)var1*(double)var2;
result2=(long double)var3*(long double)var4;

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

4 个答案:

答案 0 :(得分:8)

浮点计算的精度

C ++ 11 incorporates来自FLT_EVAL_METHOD中C99的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以及s1s2的计算分别等效:< / p>

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;

如果您的编译器将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;

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

如果编译器将FLT_EVAL_METHOD定义为2,则(long double)var3*(long double)var4中的转换不是必需的,但它们也不会造成任何损害:表达式表示使用和不使用它们完全相同。

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

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

包含多个操作的较大表达式的情况不同。对于这些,通常通过显式转换或编译器使用FLT_EVAL_METHOD == 2以扩展精度计算中间结果通常更好。此question及其接受的答案显示,当使用针对binary64 IEEE 754参数和结果的80位扩展精度中间计算进行计算时,插值公式u2 * (1.0 - u1) + u1 * u3始终在u2和{u3之间产生结果{1}} u1介于0和1之间。由于当前更大的舍入误差,此属性可能不适用于二进制64精度中间计算。

答案 1 :(得分:1)

浮点类型的通常的关节转换在乘法,除法和模数之前应用:

  

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

     

§5.6[expr.mul]

类似的加法和减法:

  

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

     

§5.7[expr.add]

浮点类型的常用算术转换在标准中列出如下:

  

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

     

[...]

     

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

     

- 否则,如果任一操作数为double,则另一个操作数应转换为double

     

- 否则,如果任一操作数为float,则另一个操作数应转换为float

     

§5[expr]

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

  

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

     

§3.9.1[basic.fundamental]

答案 2 :(得分:1)

  1. 对于浮点乘法:FP乘法器在内部使用操作数宽度的两倍来生成中间结果,该结果等于无限精度内的实际结果,然后将其四舍五入到目标精度。因此,你不应该担心乘法。结果是正确的舍入。
  2. 对于浮点加法,结果也正确舍入,因为标准FP加法器使用额外足够的3个保护位来计算正确舍入的结果。
  3. 对于除法,余数和其他复杂函数,如sin,log,exp等超越,它主要取决于体系结构和使用的库。如果您为划分或任何其他复杂函数寻找正确的舍入结果,我建议您使用MPFR库。

答案 3 :(得分:0)

不是您问题的直接答案,但对于常量浮点值(例如问题中指定的值),产生最少精度损失的方法将使用每个值的合理表示形式整数分子除以整数分母,并在实际浮点除法之前执行尽可能多的整数乘法。

对于问题中指定的浮点值:

int var1_num = 31;
int var1_den = 10;
int var2_num = 6789;
int var2_den = 1000;
int var3_num = 8745;
int var3_den = 100;
int var4_num = 234987;
int var4_den = 1000;
double result1 = (double)(var1_num*var2_num)/(var1_den*var2_den);
long double result2 = (long double)(var3_num*var4_num)/(var3_den*var4_den);

如果任何整数乘积太大而无法放入int,那么您可以使用更大的整数类型:

unsigned int
signed   long
unsigned long
signed   long long
unsigned long long