代码之家  ›  专栏  ›  技术社区  ›  Dennis Williamson

为什么这个显式强制转换的结果不同于隐式强制转换?

  •  12
  • Dennis Williamson  · 技术社区  · 17 年前

    为什么这个显式强制转换的结果不同于隐式强制转换?

    #include <stdio.h>
    
    double  a;
    double  b;
    double  c;
    
    long    d;
    
    double    e;
    
    int main() {
        a = 1.0;
        b = 2.0;
        c = .1;
    
        d = (b - a + c) / c;
        printf("%li\n", d);        //    10
    
        e = (b - a + c) / c;
        d = (long) e;
        printf("%li\n", d);        //    11
        }
    

    如果我做d=(long)((b-a+c)/c);我也得到10。为什么分配给双人间会有区别?

    5 回复  |  直到 17 年前
        1
  •  16
  •   Jon Skeet    17 年前

    我怀疑差异是从80位浮点值到长浮点值的转换,而不是从80位浮点值到64位浮点值的转换,以及 然后 向长型转变。

    (80位出现的原因是,这是实际算术使用的典型精度,以及浮点寄存器的宽度。)

    假设80位的结果类似于10.99999999999999—从该结果到长结果的转换为10。然而,与80位值最接近的64位浮点值实际上是11.0,因此两级转换最终得到11。

    编辑:为了增加重量…

    这里有一个Java程序,它使用任意精确的算术来进行同样的计算。请注意,它将最接近0.1的双精度值转换为BigDecimal-该值为0.10000000000000055511151231257827021181583404541015625。(换句话说,计算的确切结果是 11,不管怎样)

    import java.math.*;
    
    public class Test
    {
        public static void main(String[] args)
        {
            BigDecimal c = new BigDecimal(0.1d);        
            BigDecimal a = new BigDecimal(1d);
            BigDecimal b = new BigDecimal(2d);
    
            BigDecimal result = b.subtract(a)
                                 .add(c)
                                 .divide(c, 40, RoundingMode.FLOOR);
            System.out.println(result);
        }
    }
    

    结果如下:

    10.9999999999999994448884876874217606030632
    

    换句话说,这是正确的约40位十进制数字(远远超过64位或80位浮点可以处理)。

    现在,让我们考虑一下这个数字在二进制中是什么样子的。我没有任何工具可以轻松地进行转换,但我们可以再次使用Java来帮助。假设一个标准化的数字,“10”部分最终使用三个位(比11=1011少一个)。剩下60位尾数表示扩展精度(80位),48位表示双精度(64位)。

    那么,每个精度中最接近11的数字是什么?再次,让我们使用Java:

    import java.math.*;
    
    public class Test
    {
        public static void main(String[] args)
        {
            BigDecimal half = new BigDecimal("0.5");        
            BigDecimal eleven = new BigDecimal(11);
    
            System.out.println(eleven.subtract(half.pow(60)));
            System.out.println(eleven.subtract(half.pow(48)));        
        }
    }
    

    结果:

    10.999999999999999999132638262011596452794037759304046630859375
    10.999999999999996447286321199499070644378662109375
    

    所以,我们得到的三个数字是:

    Correct value: 10.999999999999999444888487687421760603063...
    11-2^(-60): 10.999999999999999999132638262011596452794037759304046630859375
    11-2^(-48): 10.999999999999996447286321199499070644378662109375
    

    现在为每个精度计算出最接近正确值的值-对于扩展精度,它小于11。将这些值四舍五入为一个长整数,结果分别为10和11。

    希望这是足以说服怀疑者的证据;)

        2
  •  2
  •   user47559    17 年前

    我的32位x86 Linux系统运行gcc 4.3.2也得到了10&11。

    相关的C/ASM如下:

    26:foo.c         ****     d = (b - a + c) / c;                                               
      42                            .loc 1 26 0
      43 0031 DD050000              fldl    b
      43      0000
      44 0037 DD050000              fldl    a
      44      0000
      45 003d DEE9                  fsubrp  %st, %st(1)
      46 003f DD050000              fldl    c
      46      0000
      47 0045 DEC1                  faddp   %st, %st(1)
      48 0047 DD050000              fldl    c
      48      0000
      49 004d DEF9                  fdivrp  %st, %st(1)
      50 004f D97DFA                fnstcw  -6(%ebp)
      51 0052 0FB745FA              movzwl  -6(%ebp), %eax
      52 0056 B40C                  movb    $12, %ah
      53 0058 668945F8              movw    %ax, -8(%ebp)
      54 005c D96DF8                fldcw   -8(%ebp)
      55 005f DB5DF4                fistpl  -12(%ebp)
      56 0062 D96DFA                fldcw   -6(%ebp)
      57 0065 8B45F4                movl    -12(%ebp), %eax
      58 0068 A3000000              movl    %eax, d
      58      00
      27:foo.c         ****
      28:foo.c         ****     printf("%li\n", d);                                                
      59                            .loc 1 28 0
      60 006d A1000000              movl    d, %eax
      60      00
      61 0072 89442404              movl    %eax, 4(%esp)
      62 0076 C7042400              movl    $.LC3, (%esp)
      62      000000
      63 007d E8FCFFFF              call    printf
      63      FF
      29:foo.c         ****     //    10                                                           
      30:foo.c         ****
      31:foo.c         ****     e = (b - a + c) / c;                                               
      64                            .loc 1 31 0
      65 0082 DD050000              fldl    b
      65      0000
      66 0088 DD050000              fldl    a
      66      0000
      67 008e DEE9                  fsubrp  %st, %st(1)
      68 0090 DD050000              fldl    c
      68      0000
      69 0096 DEC1                  faddp   %st, %st(1)
      70 0098 DD050000              fldl    c
      70      0000
      71 009e DEF9                  fdivrp  %st, %st(1)
      72 00a0 DD1D0000              fstpl   e
      72      0000
      32:foo.c         ****
      33:foo.c         ****     d = (long) e;                                                      
      73                            .loc 1 33 0
      74 00a6 DD050000              fldl    e
      74      0000
      75 00ac D97DFA                fnstcw  -6(%ebp)
      76 00af 0FB745FA              movzwl  -6(%ebp), %eax
      77 00b3 B40C                  movb    $12, %ah
      78 00b5 668945F8              movw    %ax, -8(%ebp)
      79 00b9 D96DF8                fldcw   -8(%ebp)
      80 00bc DB5DF4                fistpl  -12(%ebp)
      81 00bf D96DFA                fldcw   -6(%ebp)
      82 00c2 8B45F4                movl    -12(%ebp), %eax
      83 00c5 A3000000              movl    %eax, d
      83      00
    

    答案留给感兴趣的读者作为练习。

        3
  •  1
  •   Simon Broadhead    17 年前

    codepad.org(gcc 4.1.2)反转示例的结果,而在我的本地系统(gcc 4.3.2)中,这两种情况下我都得到了11。这对我来说意味着这是一个浮点问题。或者,理论上可以是截断(b-a+c),在整数上下文中,它的值为(2-1+0)/.1,即10,而在浮点上下文(2.0-1.0+0.1)/.1=1.1/.1=11。不过那会很奇怪。

        4
  •  0
  •   dwc    17 年前

    在Linux上直接复制/粘贴和编译给了我11个。添加 d = (long) ((b - a + c) / c); 也给出了11。OpenBSD也是如此。

        5
  •  0
  •   JP Alioto    17 年前

    Here is a bunch of detail on floating point issues and a really good article. 但基本上,并不是所有的浮点值都可以用一定数量的位(32位或64位或其他)来表示。这是一个深奥的主题,但我喜欢它,因为它让我想起 Prof. Kahan . :)