浮点数上的mult和div操作之间的差异

时间:2014-01-17 14:13:53

标签: optimization floating-point micro-optimization

这两种情况的计算精度有何不同:
1)x = y / 1000d;
2)x = y * 0.001d;

编辑:不要添加C#标签。问题仅来自“浮点”的观点。我不想知道什么是更快,我需要知道什么情况会给我'更好的精确度'。

4 个答案:

答案 0 :(得分:2)

不,它们不一样 - 至少不是C#,在我的处理器上使用我的机器上的版本(只是标准的.NET 4.5.1) - 有足够的细微之处我不喜欢声称它将在所有机器或所有语言上执行相同操作。毕竟,这可能是一个特定于语言的问题。

使用我的DoubleConverter类来显示double完全值,经过几次试验和错误后,这是一个C#程序 at至少在我的机器上显示出差异:

using System;

class Program
{
    static void Main(string[] args)
    {
        double input = 9;
        double x1 = input / 1000d;
        double x2 = input * 0.001d;

        Console.WriteLine(x1 == x2);
        Console.WriteLine(DoubleConverter.ToExactString(x1));
        Console.WriteLine(DoubleConverter.ToExactString(x2));
    }
}

输出:

False
0.00899999999999999931998839741709161899052560329437255859375
0.009000000000000001054711873393898713402450084686279296875

我可以使用Microsoft C编译器在C中重现这一点 - 如果它是可怕的C风格,请道歉,但我认为它至少证明了这些差异:

#include <stdio.h>

void main(int argc, char **argv) {
    double input = 9;
    double x1 = input / 1000;
    double x2 = input * 0.001;
    printf("%s\r\n", x1 == x2 ? "Same" : "Not same");
    printf("%.18f\r\n", x1);
    printf("%.18f\r\n", x2);
}

输出:

Not same
0.008999999999999999
0.009000000000000001

我没有查看确切的细节,但是对我来说有区别是有道理的,因为除以1000并乘以“最近的double到0.001”不是逻辑操作...因为0.001不能完全表示为double。最接近的double到0.001实际上是:

0.001000000000000000020816681711721685132943093776702880859375

...这就是你最终乘以的结果。你早期就丢失了信息,并希望它与你输掉的信息相对应,除以1000.看起来在某些情况下它不是。

答案 1 :(得分:1)

你在基数10编程,但是浮点数是2,你可以在基数2中代表1000,但在基数2中不能代表0.001,所以你选择了不好的数字来问你的问题,在计算机上x / 1000!= x * 0.001,你可能会在大多数情况下通过舍入和更精确得到幸运,但它不是数学身份。

现在也许这是你的问题,也许你想知道为什么x / 1000!= x * 0.001。而这个问题的答案是因为这是一台二进制计算机而且它使用基数2而不是基数10,当转到基数2时存在0.001的转换问题,你不能精确地表示IEEE浮点数中的那个分数。

在基数10中,我们知道如果我们在分母中有一个因子为3的分数(并且在分子中缺少一个分数来取消它),我们最终得到一个无限重复的模式,基本上我们不能准确地表示这个数字用一组有限的数字。

1/3 = 0.33333 ......

当您尝试在基数2中表示1/10时出现相同的问题.10 = 2 * 5 2表示正常1/2,但5表示真正的问题1/5。

1/10(1/1000以相同的方式工作)。初级长师:

       0 000110011
     ----------
1010 | 1.000000
         1010
       ------
          1100 
          1010
          ----
            10000
             1010
             ----
              1100
              1010
              ----
                10

我们必须继续拉下零,直到我们得到10000 10一次进入16,其余6,下降零。 10进入12 1时间剩余2.我们重复该模式,所以你最终重复这个001100110011永远重复。浮点是固定的位数,因此我们不能表示无限模式。

现在,如果你的问题与4除以4乘以相似的东西有关。这是一个不同的问题。 Aanswer应该是相同的,消耗更多的周期和/或逻辑来进行除法而不是乘法,但最终得到相同的答案。

答案 2 :(得分:-2)

可能不是。编译器(或JIT)可能会将第一种情况转换为第二种情况,因为乘法通常比除法更快。您必须通过编译代码(启用或不启用优化)来检查这一点,然后使用IL Disassembler或.NET Reflector等工具检查生成的IL,和/或在运行时使用调试器检查本机代码。

答案 3 :(得分:-2)

不,没有任何区别。除非您设置自定义舍入模式。

gcc产生((双)0.001 - (双)1.0 / 1000)== 0.0e0

当编译器将0.001转换为二进制时,它将1除以1.它使用与目标体系结构兼容的软件浮点模拟来执行此操作。

对于高精度,有长双(80位)和任何精度的软件模拟。

PS我使用gcc用于64位机器,包括sse和x87 FPU。

PPS通过一些优化,1 / 1000.0在x87上可能更精确,因为x87使用80位内部表示,1000 = = 1000.0。如果您将结果用于下一次计算,则确实如此。如果返回/写入内存,它会计算80位值,然后将其舍入为64位。但SSE更常用于双倍。