在 C 中的某些整数上使用位操作中断将整数转换为浮点数

Posted

技术标签:

【中文标题】在 C 中的某些整数上使用位操作中断将整数转换为浮点数【英文标题】:Cast Integer to Float using Bit Manipulation breaks on some integers in C 【发布时间】:2017-02-02 22:32:52 【问题描述】:

在进行类分配时,我试图仅使用位操作将整数转换为浮点数(仅限于任何整数/无符号操作,包括 ||、&&。还有 if、while)。我的代码适用于大多数值,但有些值没有生成我正在寻找的结果。

例如,如果 x 是 0x807fffff,我得到 0xceff0001,但正确的结果应该是 0xceff0000。我想我的尾数和舍入缺少一些东西,但不能完全确定。我还查看了 SO 上的其他一些线程 converting-int-to-float 和 how-to-manually

unsigned dl22(int x) 


    int tmin = 0x1 << 31;
    int tmax = ~tmin;

    unsigned signBit = 0;
    unsigned exponent;
    unsigned mantissa;
    int bias = 127;

    if (x == 0) 
        return 0;
    

    if (x == tmin) 
        return 0xcf << 24;
    

    if (x < 0) 
        signBit = x & tmin;
        x = (~x + 1);
    


    exponent = bias + 31;

    while ( ( x & tmin) == 0 ) 
        exponent--;
        x <<= 1;
    

    exponent <<= 23;
    int mantissaMask = ~(tmin >> 8);
    mantissa = (x >> 8) & mantissaMask;

    return (signBit | exponent | mantissa);

编辑/更新 找到了一个可行的解决方案 - 见下文

【问题讨论】:

您对“仅使用按位运算”有一个奇怪的定义。我也在您的代码中看到了关系运算和算术运算。 (以及简单和复合的赋值操作,但我不认为你的意思是排除赋值。) 我们可以使用关系比较器以及整数和无符号乘法,但不能进行强制转换(也可以使用 if/while 循环) 注意:0x1 &lt;&lt; 31 是未定义的行为,尽管它可能对您有用。 有趣的@chux,您是否有更多关于其未定义行为或其含义的详细信息? 在 C 中,int 溢出是 UB。 1 在 32 位 int 上左移 31 次就像 2 的 31 次方。超出int 范围的值。编译器没有义务生成可靠的代码。可以使用int tmin = INT_MIN; 【参考方案1】:

您的代码在您提供的示例中为我生成了预期的输出。然而,正如 cmets 中所讨论的,从 C 的角度来看,它确实表现出未定义的行为——不仅在 tmin 的计算中,而且出于同样的原因,在计算指数的循环中也是如此。无论此代码在何种程度上产生因环境而异的结果,都将遵循未定义的行为或您对 [unsigned] int 的大小对于正在使用的 C 实现不正确的假设。

不过,如果我们假设(不安全)

    ints 的移位操作就像将左操作数重新解释为具有相同位模式的 unsigned int,对其进行操作,并将生成的位模式重新解释为 int,并且 intunsigned int 至少为 32 位宽,

那么您的代码似乎是正确的,模舍入。

如果输入 int 的绝对值有超过 24 个有效二进制数字(即至少为 224),但是,转换中会丢失一些精度.在这种情况下,正确的结果将取决于您打算实现的 FP 舍入模式。不正确舍入的结果将在最后一位减少 1 个单位;影响多少结果取决于舍入模式。

只需截断/移出多余的位,就可以向零模式舍入。这是标准舍入模式之一,但不是默认值。默认舍入模式是舍入到最接近的可表示数字,解决平局以支持具有最低有效位 0 的结果(舍入到偶数);还有其他三种标准模式。要实现除向零舍入以外的任何模式,您需要在缩放之后和将其移出之前捕获有效数的 8 个最低有效位。这些以及取决于所选舍入模式的其他详细信息将决定如何应用正确的舍入。

大约一半的 32 位二进制补码在舍入到零模式下的舍入方式与在其他任何一种模式下转换时的舍入方式不同; 哪些数字显示出差异取决于您考虑的舍入模式。

【讨论】:

我认为这很正确,再次感谢所有指导!我现在意识到我忘记包含我正在尝试复制的这段代码,这是我们一个未签名的浮点数:: float u2f(unsigned u) union unsigned u; float f; a; a.u = u; return a.f; 【参考方案2】:

我最初并没有提到我试图模仿 U2F 联合声明:

float u2f(unsigned u) 
  union 
    unsigned u;
    float f;
   a;
  a.u = u;
  return a.f;

感谢 postieee-754-bit-manipulation-rounding-error 中提供的指导,我能够通过在我的 while 语句后面添加以下内容来管理舍入问题。这澄清了正在发生的四舍五入。

lsb = (x >> 8) & 1;
roundBit = (x >> 7) & 1;
stickyBitFlag = !!(x & 0x7F);

exponent <<= 23;

int mantissaMask = ~(tmin >> 8);
mantissa = (x >> 8);
mantissa &= mantissaMask;

roundBit = (roundBit & stickyBitFlag) | (roundBit & lsb);

return (signBit | exponent | mantissa) + roundBit;

【讨论】:

以上是关于在 C 中的某些整数上使用位操作中断将整数转换为浮点数的主要内容,如果未能解决你的问题,请参考以下文章

如何在 x86(32 位)程序集中将无符号整数转换为浮点数?

Python:将 2 个整数转换为浮点数

将 INT_MAX 转换为浮点数,然后再转换回整数。

使用 AVX 的有符号/无符号整数的最小值

关于整数输出为浮点数的问题

LLVM 通过将整数转换为浮点数