c++ (double)0.700 * int(1000) => 699(不是双精度问题)

c++ (double)0.700 * int(1000) => 699 (Not the double precision issue)

使用 g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3

我尝试了 scaledvalue2 的不同类型转换,但直到我将乘法存储在 double 变量中,然后再存储到 int 中,我才能得到想要的结果..但我可以'解释为什么 ???

我知道双精度(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

model : 54

model name : Intel(R) Atom(TM) CPU N2600 @ 1.60GHz

由于舍入误差,大多数浮点数最终都有些不精确。 对于下面的 double 到 int 的转换,使用 std::ceil() API

int scaledvalue2 = (double)((double)(scaleFactor) * value); // = 699 ??

根据编译器和优化标志,涉及变量的 scaledvalue_a 可以在运行时使用您的处理器浮点指令求值,而仅涉及常量的 scaledvalue_b 可以在编译时求值使用数学库的时间(例如 gcc 使用 GMP - GNU 多精度数学库)。您看到的差异似乎是运行时的精度和舍入与该表达式的编译时评估之间的差异。

这会有点麻烦;昨晚看小熊队夺得世界大赛冠军,我起得太晚了,所以不要一味求精。

计算浮点表达式的规则有些灵活,编译器通常比规则正式允许的更灵活地处理浮点表达式。这使得浮点表达式的计算速度更快,但代价是结果的可预测性有所降低。速度对于浮点计算很重要。 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 所要求的),即使 doublefloat 分别是 64 位和 32 位。因此,对于算术运算,操作数最多转换为 80 位,结果转换回 64 或 32 位。这很慢,因此生成的代码通常会尽可能长时间地延迟转换,以 80 位精度进行所有计算。

但是C和C++都要求当一个值存储到一个浮点变量中时,必须进行转换。因此,正式地,在 //1 行中,编译器必须将总和转换回 64 位以将其存储到变量 d 中。那么在行 //2 中计算的 dd1 的值必须使用存储到 d 中的值来计算,即 64 位值,而 dd2 的值,在 //3 行中计算,可以使用 f() + g() 计算,即完整的 80 位值。这些额外的位可能会有所不同,dd1 的值可能与 dd2.

的值不同

并且编译器通常会保留 f() + g() 的 80 位值,并在计算 dd1 的值时使用它而不是存储在 d 中的值。这是一个不符合规范的优化,但据我所知,默认情况下每个编译器都会做这种事情。它们都有命令行开关来强制执行严格要求的行为,所以如果你想要更慢的代码,你可以得到它。

对于严肃的数字运算,速度至关重要,因此欢迎这种灵活性,并且仔细编写数字运算代码以避免对这种细微差别敏感。人们获得博士学位是因为弄清楚了如何使浮点代码快速有效,所以不要因为看到的结果似乎没有意义而难过。他们没有,但他们足够接近,小心处理,他们给出正确的结果而没有速度损失。

由于 x86 浮点单元以扩展精度浮点类型(80 位宽)执行计算,结果可能很容易取决于中间值是否被强制转换为 double(64 位浮点) -点类型)。在这方面,在未优化的代码中,编译器按字面意思处理对 double 变量的内存写入,但忽略 "unnecessary" 对 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 的中间转换(而在第二个变体中不存在)是造成这种差异的原因。

我将 mindriot 的示例汇编代码转换为 Intel 语法以使用 Visual Studio 进行测试。我只能通过将浮点控制字设置为使用扩展精度来重现错误。

问题是在存储双精度时从扩展精度转换为双精度时执行舍入,而在存储整数时从扩展精度转换为整数时执行 t运行cation。

扩展精度乘法产生的乘积为 699.999...,但当乘积存储到 doubleScaled 时,在从扩展精度到双精度的转换过程中,该乘积被四舍五入为 700.000...。

double doubleScaled = (double)scaleFactor * value; 

由于 doubleScaled == 700.000..., 当 t运行 为整数时,它仍然是 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() 函数可以避免 t运行 阳离子问题。

int scaledvalue2 = (int)round(scaleFactor * value);  // should == 700