使用g ++(Ubuntu / Linaro 4.6.3-1ubuntu5)4.6.3
我尝试了scaledvalue2
的不同类型转换,但直到我将乘法存储在double
变量中,然后再存储到int
才能获得所需的结果..但我可以&#39解释为什么???
我知道双重精确(0.6999999999999999555910790149937383830547332763671875)是一个问题,但我不明白为什么一种方法可以,另一种方法不是?
如果精度问题,我希望两者都失败。
我不需要解决方案来修复它...但只是一个为什么? (问题是固定的)
void main()
{
double value = 0.7;
int scaleFactor = 1000;
double doubleScaled = (double)scaleFactor * value;
int scaledvalue1 = doubleScaled; // = 700
int scaledvalue2 = (double)((double)(scaleFactor) * value); // = 699 ??
int scaledvalue3 = (double)(1000.0 * 0.7); // = 700
std::ostringstream oss;
oss << scaledvalue2;
printf("convert FloatValue[%f] multi with %i to get %f = %i or %i or %i[%s]\r\n",
value,scaleFactor,doubleScaled,scaledvalue1,scaledvalue2,scaledvalue3,oss.str().c_str());
}
或简称:
value = 0.6999999999999999555910790149937383830547332763671875;
int scaledvalue_a = (double)(1000 * value); // = 699??
int scaledvalue_b = (double)(1000 * 0.6999999999999999555910790149937383830547332763671875); // = 700
// scaledvalue_a = 699
// scaledvalue_b = 700
我无法弄清楚这里出了什么问题。
输出:
convert FloatValue[0.700000] multi with 1000 to get 700.000000 = 700 or 699 or 700[699]
vendor_id:GenuineIntel
cpu family:6
型号:54
型号名称:Intel(R)Atom(TM)CPU N2600 @ 1.60GHz
答案 0 :(得分:1)
这将是一个有点轻松的事情;我昨晚看着小熊队赢得世界大赛的时间太晚了,所以不要坚持精确。
评估浮点表达式的规则有些灵活,编译器通常比正式允许的规则更灵活地处理浮点表达式。这使得浮点表达式的评估更快,但代价是使结果更难以预测。速度对于浮点计算很重要。 Java最初犯了错误,对浮点表达式强加了确切的要求,而数字社区则痛苦地尖叫着。 Java必须屈服于现实世界并放宽这些要求。
double f();
double g();
double d = f() + g(); // 1
double dd1 = 1.6 * d; // 2
double dd2 = 1.6 * (f() + g()); // 3
在x86硬件上(即几乎每个桌面系统都存在),浮点计算实际上是以80位精度完成的(除非你设置了一些像Java所要求的那样会破坏性能的开关),即使{{ 1}}和double
分别是64位和32位。因此,对于算术运算,操作数最多可转换为80位,结果将转换回64位或32位。这很慢,因此生成的代码通常会尽可能地延迟执行转换,以80位精度执行所有计算。
但是C和C ++都要求当一个值存储到浮点变量中时,必须完成转换。因此,正式地,在// 1行中,编译器必须将总和转换回64位以将其存储到变量float
中。然后,在行// 2中计算的d
的值必须使用存储在dd1
中的值(即64位值)计算,而值d
的值在行// 3中计算,可以使用dd2
计算,即完整的80位值。这些额外的位可能有所不同,f() + g()
的值可能与dd1
的值不同。
编译器通常会挂起dd2
的80位值,并在计算f() + g()
的值时使用该值而不是d
中存储的值。这是一个不符合要求的优化,但据我所知,每个编译器都会默认执行此类操作。它们都有命令行开关来强制执行严格要求的行为,所以如果你想要更慢的代码,你就可以得到它。 &LT g取代;
对于严重的数字运算,速度至关重要,因此这种灵活性是受欢迎的,数字运算代码是经过精心编写的,以避免对这种细微差别的敏感性。人们获得博士学位是为了弄清楚如何快速有效地制作浮点代码,所以不要觉得你看到的结果似乎没有意义。他们没有,但他们足够接近,小心处理,他们给出了正确的结果而没有速度惩罚。
答案 1 :(得分:1)
由于x86浮点单元以扩展精度浮点类型(80位宽)执行计算,结果可能很容易取决于中间值是否被强制转换为double
(64位浮点)类型)。在这方面,在非优化代码中,看到编译器按字面意思处理对double
变量的内存写入并且忽略应用于临时中间值的double
的“不必要”强制转换,这并不罕见。
在您的示例中,第一部分涉及将中间结果保存在double
变量
double doubleScaled = (double)scaleFactor * value;
int scaledvalue1 = doubleScaled; // = 700
编译器从字面上理解并确实将产品存储在double
变量doubleScaled
中,这不可避免地要求将80位产品转换为double
。之后,再次从内存中读取double
值,然后转换为int
类型。
第二部分
int scaledvalue2 = (double)((double)(scaleFactor) * value); // = 699 ??
涉及编译器可能认为不必要的转换(从抽象C ++机器的角度来看,它们确实是不必要的)。编译器会忽略它们,这意味着最终int
值是直接从80位产品生成的。
在第一个变体中存在中间转换为double
(并且在第二个变体中不存在)是造成这种差异的原因。
答案 2 :(得分:1)
我将mindriot的示例汇编代码转换为Intel语法,以便使用Visual Studio进行测试。我只能通过将浮点控制字设置为使用扩展精度来重现错误。
问题是在存储双精度时从扩展精度转换为双精度时执行舍入,而在存储整数时从扩展精度转换为整数时执行截断。
扩展精度乘法产生699.999 ...的乘积,但是当产品存储到doubleScaled时,产品在从扩展到双精度的转换过程中舍入到700.000 ......
double doubleScaled = (double)scaleFactor * value;
由于doubleScaled == 700.000 ...,当截断为整数时,它仍然是700:
int scaledvalue1 = doubleScaled; // = 700
产品699.999 ...在转换为整数时被截断:
int scaledvalue2 = (double)((double)(scaleFactor) * value); // = 699 ??
我的猜测是编译器生成了编译时常量0f 700.000 ...而不是在运行时进行乘法运算。
int scaledvalue3 = (double)(1000.0 * 0.7); // = 700
使用C标准库中的round()函数可以避免这种截断问题。
int scaledvalue2 = (int)round(scaleFactor * value); // should == 700
答案 3 :(得分:0)
根据编译器和优化标志,涉及变量的scaledvalue_a可以在运行时使用处理器浮点指令进行评估,而scaledvalue_b(仅涉及常量)可以在编译时使用数学库进行评估(例如gcc使用GMP) - 用于此的GNU多精度数学库)。您看到的差异似乎是运行时的精度和舍入与该表达式的编译时评估之间的差异。
答案 4 :(得分:-3)
由于舍入错误,大多数浮点数最终会略微不精确。 对于以下double到int转换,请使用 std :: ceil() API
int scaledvalue2 =(double)((double)(scaleFactor)* value); // = 699 ??