为什么我们在 x86 上将 64 位无符号整数转换为 32 位浮点数时要设置最低有效位?

Sea*_*hin 8 c++ assembly x86-64 clang floating-point-conversion

#include <cstddef>
float cast(size_t sz){return sz;}
Run Code Online (Sandbox Code Playgroud)

在 Clang 13.0.1 上编译上述代码会-mavx2 -O3生成以下代码:

cast(unsigned long):                               # @cast(unsigned long)
        test    rdi, rdi
        js      .LBB0_1
        vcvtsi2ss       xmm0, xmm0, rdi
        ret
.LBB0_1:
        mov     rax, rdi
        shr     rax
        and     edi, 1
        or      rdi, rax
        vcvtsi2ss       xmm0, xmm0, rdi
        vaddss  xmm0, xmm0, xmm0
        ret
Run Code Online (Sandbox Code Playgroud)

GCC、MSVC 和 Intel 编译器以及旧版本也可以生成类似的代码。

我理解该算法的总体目标是解决以下事实:在 AVX-512 之前,没有从 64 位无符号 int 到 float 或 double 的转换指令。

因此,如果数字大到足以被解释为负数,则会将其减半、转换,然后将其加倍。但是,如果在原始整数中设置最低有效位,那么设置它的目的是什么?

这似乎是浪费时间,因为浮点数只有 23 个有效位,并且该位保证不重要。也许如果它是增量指令,则在某些情况下可能会影响有效位。但仅仅一个 or 指令似乎没有做任何事情。

Mik*_*ine 6

你假设底部的位永远都不重要——这不是真的。在转换中有一些极端情况值,其中底部位确实很重要。考虑:

#include <iostream>

int main()
{
    uint64_t start = 0x8000000000000000;

    for(uint64_t i=start; i<(start+0x10000000000000ull); i+=0x1000000ull)
    {
        float f = (float)(i);
        float g = (float)(i+1);
        if (f != g)
        {
            std::cout << "From = " << std::hex << i << " we get " << std::hexfloat << f << " and from " << (i+1) << " we get " << std::hexfloat << g << std::endl;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当底部位产生差异时,它会迭代许多大值并打印出来(使用方便的十六进制浮点修饰符)。它表明有一些需要进行此调整:

From = 8000008000000000 we get 0x1p+63 and from 8000008000000001 we get 0x1.000002p+63
From = 8000028000000000 we get 0x1.000004p+63 and from 8000028000000001 we get 0x1.000006p+63
From = 8000048000000000 we get 0x1.000008p+63 and from 8000048000000001 we get 0x1.00000ap+63
From = 8000068000000000 we get 0x1.00000cp+63 and from 8000068000000001 we get 0x1.00000ep+63
From = 8000088000000000 we get 0x1.00001p+63 and from 8000088000000001 we get 0x1.000012p+63
From = 80000a8000000000 we get 0x1.000014p+63 and from 80000a8000000001 we get 0x1.000016p+63
...
Run Code Online (Sandbox Code Playgroud)

链接: https: //godbolt.org/z/jTeo1Ko4s

  • @SeaErchin:我们在这里讨论的是非常大的整数,当没有最后一位的大整数恰好位于两个可能的浮点表示之间时。如果原始整数的最后一位是 0,则无需执行任何操作,因为移位不会改变原始整数正好是一半的事实(因此舍入结果将是正确的)。但如果最后一位是 1,那么原始整数就不是正好是一半,而是大了一点。因此,该数字必须向上舍入。那个“或”运算的效果就是这样。 (2认同)