问题 (浮动)(1.2345f * 6.7809)比1.2345f * 6.7809f更准确吗?


我有一些代码块:

float total = <some float>;
double some_dbl = <some double>;

total *= some_dbl;

这引发了我想要关闭的编译器警告,但我不喜欢关闭这样的警告 - 相反,我宁愿根据需要显式地转换类型。这让我思考......是一个 (float)(total * some_dbl) 比准确更准确 total * (float)some_dbl?它是编译器还是平台特定的?

更好的代码示例(链接如下):

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;

int main() {
    double d_total = 1.2345678;
    float f_total = (float)d_total;
    double some_dbl = 6.7809123;

    double actual = (d_total * some_dbl);
    float no_cast = (float)(f_total * some_dbl);
    float with_cast = (float)(f_total * (float)some_dbl);

    cout << "actual:               " << setprecision(25) << actual << endl;
    cout << "no_cast:              " << setprecision(25) << no_cast << endl;
    cout << "with_cast:            " << setprecision(25) << with_cast << endl;
    cout << "no_cast, nextafter:   " << setprecision(25) << nextafter(no_cast, 500.0f) << endl;

    cout << endl;

    cout << "Diff no_cast:   " << setprecision(25) << actual - no_cast << endl;
    cout << "Diff with_cast: " << setprecision(25) << with_cast - actual << endl;
    return 0;
}

编辑: 所以,我给了他一个机会。通过我尝试的例子,我确实找到了一个快速的地方 total * (float)(some_dbl) 似乎 更多 准确。我认为这并非总是如此,而是抽奖的运气,或者编译器截断双精度以使其浮动,而不是舍入,从而导致可能更糟的结果。看到: http://ideone.com/sRXj1z

编辑2: 我确认使用了 std::nextafter 那 (float)(total * some_dbl) 返回截断的值,并更新链接的代码。这是非常令人惊讶的:如果在这种情况下编译器总是截断双打,那么你可以说 (float)some_dbl <= some_dbl,然后暗示 with_cast <= no_cast。但是,这是  案子! with_cast 不仅大于 no_cast但它更接近实际值,这有点令人惊讶,因为我们在乘法发生之前丢弃信息。


5738
2017-11-04 06:56


起源

(float)(total * some_dbl) 应该更准确,因为,好吧,数学......这对每种语言都是如此。 - No Idea For Name
gcc有一面旗帜 -ffast-math 它不再受标准放在浮点上的某些限制的约束;看看这是否会影响你的结果会很有趣 - M.M
您可能会发现另一个“奇怪”的事情是使用 1.2345678f 在你的来源中给出了不同的结果 (double)1.2345678  - 运行时的舍入可能与编译时的舍入不同。 - M.M
@MattMcNabb,更新了一下试图处理;不确定编译器正在做什么技巧,所以使用编译标志可能会改变这种行为。 - Rollie
当然他们是不同的。后一个版本只需要单精度算术,而前者意味着提升乘法左操作数的双精度,然后降级 (cast)。这完全符合C标准 6.3.1.8通常的算术转换,部分说明: 否则,如果任一操作数的对应实数类型为double,则将另一个操作数转换为对应的实类型为double的类型,而不更改类型域。 四舍五入就是这样,一个过于精确的结果可能会有不同的结果。 - Iwillnotexist Idonotexist


答案:


它将根据所涉及数字的大小产生影响,因为 double 不只是更精确,但也可以保持数字大于 float。这是一个示例,它将显示一个这样的实例:

double d = FLT_MAX * 2.0;
float f = 1.0f / FLT_MAX;

printf("%f\n", d * f);
printf("%f\n", (float)d * f);
printf("%f\n", (float)(d * f));

并输出:

2.000000
inf
2.000000

发生这种情况是因为 float 显然可以持有 结果 的计算 - 2.0,它不能保持中间值 FLT_MAX * 2.0


10
2017-11-04 07:28



这对我来说很有意义,但是没有解释上面关于舍入的行为。也就是说,为什么会这样 with_cast 比准确更准确 no_cast? (见上面的编辑2) - Rollie


如果执行操作,则编译器会将变量转换为该操作的最大数据类型。这是双倍的。在我看来,操作:(float)(var1f * var2)具有更高的准确性。


2
2017-11-04 07:02



我不理解你的答案,但它的意见。请评论一下。 - No Idea For Name
我不能发表评论。 - Wolframm
@NoIdeaForName这不是意见 - 这是事实。请参阅OP的帖子下面的评论,并在部分查看C标准的常用算术转换 6.3.1.8通常的算术转换。 - Iwillnotexist Idonotexist
@IwillnotexistIdonotexist我认为他的意思是 在他看来,答案是不可理解的。 - Kroltan
这是对“意见”的错误使用,而不是“AFAIK”,这也恰好是真实的。 - Lie Ryan


我测试过它们并不相同。结果如下 truehttp://codepad.org/3GytxbFK

#include <iostream>

using namespace std;

int main(){
  double a = 1.0/7;
  float b = 6.0f;
  float c = 6.0f;
  b = b * (float)a;
  c = (float)((double)c * a);
  cout << (b-c != 0.0f) << endl;
  return 0;
}

这导致我理由:乘法结果的演员表示为a double 到了 float 将有更好的机会圆。有些位可能会掉线 float 在进行乘法时可以正确考虑的乘法 double然后铸成了 float

顺便说一下,我选择1/7 * 6,因为它以二进制重复。

编辑: 经过研究,似乎对于从double到float的转换以及浮点数的乘法,舍入应该是相同的,至少在符合IEEE 754的实现中。 https://en.wikipedia.org/wiki/Floating_point#Rounding_modes


1
2017-11-04 07:13



你也应该检查一下 b-c 在进行乘法运算之前,看看这两个结果中的哪一个接近于零 - M.M
@MattMcNabb这就是我对编辑示例的处理方式;泰勒的逻辑也是我所怀疑的,但我也认为在数学上可能没有区别。显然有! - Rollie


根据代码转储中的数字,两个相邻的可能值 float 是:

        d1 =  8.37149524...
        d2 =  8.37149620...

以双精度进行乘法的结果是:

              8.37149598...

当然,这是在这两者之间。将此结果转换为 float实现定义 至于它是“向上”还是“向上”。在您的代码结果中,已选择转换 d1,这是允许的,即使它不是最接近的。混合精度乘法最终得到了 d2

因此,我们可以有点不直观地得出结论:以双精度计算双精度然后转换为 float 在某些情况下,完全不完全准确 float 精确!


1
2017-11-04 07:36



查看最新编辑;一些非常有趣的结果! - Rollie