考虑以下两个非常简单的乘法:
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;
其他操作(添加,除法和余数)怎么样?例如,当添加两个以上的正单精度值时,如果用于保存表达式的中间结果,则使用额外的双精度位可以减少舍入误差。
答案 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,则r1
和r2
以及s1
和s2
的计算分别等效:< / 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,r1
和r2
以及s1
和s2
,则并非总是相同。计算r1
和s1
时的乘法是以double
的精度完成的。计算r2
和s2
时的乘法是以long double
的精度完成的。
如果您计算的结果将存储在比操作数类型更广泛的结果类型中,如问题中的result1
和result2
,则应始终将参数转换为至少与目标一样宽的类型,如下所示:
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
中的转换不是必需的,但它们也不会造成任何损害:表达式表示使用和不使用它们完全相同。
矛盾的是,对于单个操作,最好只舍入一次目标精度。在扩展精度中计算单个乘法的唯一效果是,结果将四舍五入为扩展精度,然后精确到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)
答案 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