【问题标题】:Understanding double to int64_t conversion了解 double 到 int64_t 的转换
【发布时间】:2016-03-23 01:17:31
【问题描述】:

所以我有两个函数,一个只是从double 转换为int64_t,另一个调用std::round

std::int64_t my_cast(double d)
{
  auto t = static_cast<std::int64_t>(d);
  return t;
}

std::int64_t my_round(double d)
{
  auto t = std::round(d);
  return t;
}

它们工作正常:cast(3.64) = 3round(3.64) = 4。但是,当我查看程序集时,他们似乎在做同样的事情。所以想知道他们如何得到不同的结果?

$ g++ -std=c++1y -c -O3 ./round.cpp -o ./round.o 
$ objdump -dS ./round.o
./round.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z7my_castd>:
   0:   f2 48 0f 2c c0          cvttsd2si %xmm0,%rax
   5:   c3                      retq
   6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
   d:   00 00 00

0000000000000010 <_Z8my_roundd>:
  10:   48 83 ec 08             sub    $0x8,%rsp
  14:   e8 00 00 00 00          callq  19 <_Z7my_castd+0x19> <========!!!
  19:   48 83 c4 08             add    $0x8,%rsp
  1d:   f2 48 0f 2c c0          cvttsd2si %xmm0,%rax
  22:   c3                      retq

Disassembly of section .text.startup:

0000000000000030 <_GLOBAL__sub_I__Z7my_castd>:
  30:   48 83 ec 08             sub    $0x8,%rsp
  34:   bf 00 00 00 00          mov    $0x0,%edi
  39:   e8 00 00 00 00          callq  3e <_GLOBAL__sub_I__Z7my_castd+0xe>
  3e:   ba 00 00 00 00          mov    $0x0,%edx
  43:   be 00 00 00 00          mov    $0x0,%esi
  48:   bf 00 00 00 00          mov    $0x0,%edi
  4d:   48 83 c4 08             add    $0x8,%rsp
  51:   e9 00 00 00 00          jmpq   56 <_Z8my_roundd+0x46>

我不确定14 线上的callq 的目的是什么,但即便如此,my_castmy_round 似乎只是在做一个cvttsd2si,我相信是截断转换。

但是,正如我之前提到的,这两个函数在相同的输入(比如3.64)上产生不同的(正确的)值

发生了什么?

【问题讨论】:

  • 请注意,round(x) = trunc(x + 0.5)。我怀疑你没有正确识别这里的所有机器代码。
  • GCC 5.3.0 会调用 round gcc.godbolt.org @Cheersandhth.-Alf 这仅适用于非负值
  • callq 19 这是一个尚未解决的对std::round 的引用。对象链接时填写。
  • @Cheers 和 hth。 - Alf 那是不正确的 - 这些操作是不等价的。例如,使用您的 cast 方法尝试 0.499999975 + 0.5-1.4f + 0.5。然后尝试将0.499999975-1.4 传递给舍入函数。
  • @JesperJuhl:是的,我只是为 OP 指明了一个好的方向,我并没有提出实现的全部细节(例如,添加一个符号检查)。后者需要付出更多的努力。 ;-) 不过还是谢谢。

标签: c++ double


【解决方案1】:

汇编输出更有用(g++ ... -S &amp;&amp; cat round.s):

...
_Z7my_castd:
.LFB225:
    .cfi_startproc
    cvttsd2siq  %xmm0, %rax
    ret
    .cfi_endproc
...
_Z8my_roundd:
.LFB226:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    call    round             <<< This is what callq 19 means
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    cvttsd2siq  %xmm0, %rax
    ret
    .cfi_endproc

如你所见,my_round 调用std::round 然后执行cvttsd2siq 指令。这是因为std::round(double)返回double,所以它的结果还是要转换成int64_t。这就是cvttsd2siq 在你的两个函数中所做的。

【讨论】:

    【解决方案2】:

    使用 g++,您可以使用-fdump-tree-optimized 开关对正在发生的事情有更高级别的了解:

    $ g++ -std=c++1y -c -O3 -fdump-tree-optimized ./round.cpp
    

    这会产生一个round.cpp.165t.optimized 文件:

    ;; Function int64_t my_cast(double) (_Z7my_castd, funcdef_no=224, decl_uid=4743$
    
    int64_t my_cast(double) (double d)
    {
      long int t;
    
      <bb 2>:
      t_2 = (long int) d_1(D);
      return t_2;
    }
    
    
    ;; Function int64_t my_round(double) (_Z8my_roundd, funcdef_no=225, decl_uid=47$
    
    int64_t my_round(double) (double d)
    {
      double t;
      int64_t _3;
    
      <bb 2>:
      t_2 = round (d_1(D));
      _3 = (int64_t) t_2;
      return _3;
    }
    

    这里的区别非常明显(对round函数的调用很明显)。

    【讨论】:

    • 对于 -fdump-tree-optimized 选项加 1。非常有用。
    【解决方案3】:

    使用objdump -d 转储目标文件时,添加选项-r 非常重要,该选项命令实用程序也转储重定位:

    $ objdump -dr round.o
    ...
    0000000000000010 <_Z8my_roundd>:
      10:   48 83 ec 28             sub    $0x28,%rsp
      14:   e8 00 00 00 00          callq  19 <_Z8my_roundd+0x9>
                            15: R_X86_64_PC32       _ZSt5roundd
      19:   48 83 c4 28             add    $0x28,%rsp
      1d:   f2 48 0f 2c c0          cvttsd2si %xmm0,%rax
    

    现在,请注意出现的新行。这是包含在目标文件中的重定位指令。它指示链接器将_Z8my_roundd+0x9_ZSt5roundd 之间的距离添加到在偏移量15 处找到的值。

    偏移量 14 处的e8 是相对调用的操作码。接下来的 4 个字节必须包含被调用函数的 IP 相对偏移量(执行时的 IP 指向下一条指令)。因为编译器不知道这个距离,所以它用零填充它并插入一个重定位,以便链接器稍后可以填充它。

    在没有-r 选项的情况下进行反汇编时,会忽略重定位,这会造成函数_Z8my_roundd 调用自身中间的错觉。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多