使用位操作将整数转换为浮点数在 C 中的某些整数上中断
Cast Integer to Float using Bit Manipulation breaks on some integers in C
在进行 class 赋值时,我尝试仅使用位操作将整数转换为浮点数(限于任何 integer/unsigned 操作,包括 ||、&&。如果,尽管)。我的代码适用于大多数值,但某些值未生成我正在寻找的结果。
比如x是0x807fffff,我得到0xceff0001,但是正确的结果应该是0xceff0000。我想我的尾数和四舍五入遗漏了一些东西,但不能完全确定。我也在 SO 上查看了其他一些线程 converting-int-to-float and 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);
}
EDIT/UPDATE
找到可行的解决方案 - 见下文
您的代码在您提供的示例中为我生成了预期的输出。但是,正如评论中所讨论的那样,从 C 的角度来看,它确实表现出未定义的行为——不仅在 tmin
的计算中,而且出于同样的原因,在计算指数的循环中也是如此。无论此代码在何种程度上产生因环境而异的结果,这些结果要么来自未定义的行为,要么来自您关于 [unsigned
] int
的大小的假设对于正在使用的 C 实现不正确.
然而,如果我们假设(不安全)
int
的移位操作就像左操作数被重新解释为具有相同位模式的 unsigned int
一样,对其进行操作,结果位模式被重新解释为 int
,以及
int
和 unsigned int
至少有 32 位宽,
那么你的代码似乎是正确的,模数舍入。
如果输入int
的绝对值超过24位有效二进制数字(即至少为224),然而,一些精度将在转换中丢失。在这种情况下,正确的结果将取决于您打算实施的 FP 舍入模式。不正确的四舍五入结果将在最后一位偏离 1 个单位;影响多少结果取决于舍入模式。
像您那样简单地截断/移出额外的位会产生向零模式的舍入。这是标准舍入模式之一,但不是默认模式。默认的舍入模式是舍入到最接近的可表示数字,解决平局有利于结果 least-significant 位 0(舍入为偶数);还有其他三种标准模式。要实现 round-toward-zero 以外的任何模式,您需要在缩放之后和将它们移出之前捕获有效数的 8 least-significant 位。这些以及取决于所选舍入模式的其他详细信息将决定如何应用正确的舍入。
大约一半的 32 位二进制补码数在 round-to-zero 模式下转换时的舍入方式与在任何其他模式下转换时的舍入方式不同; 哪些 数字会出现差异取决于您考虑的舍入模式。
我最初并没有提到我正在尝试模仿 U2F 联合声明:
float u2f(unsigned u) {
union {
unsigned u;
float f;
} a;
a.u = u;
return a.f;
}
感谢 post 中提供的指导,我能够通过在 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;
在进行 class 赋值时,我尝试仅使用位操作将整数转换为浮点数(限于任何 integer/unsigned 操作,包括 ||、&&。如果,尽管)。我的代码适用于大多数值,但某些值未生成我正在寻找的结果。
比如x是0x807fffff,我得到0xceff0001,但是正确的结果应该是0xceff0000。我想我的尾数和四舍五入遗漏了一些东西,但不能完全确定。我也在 SO 上查看了其他一些线程 converting-int-to-float and 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);
}
EDIT/UPDATE 找到可行的解决方案 - 见下文
您的代码在您提供的示例中为我生成了预期的输出。但是,正如评论中所讨论的那样,从 C 的角度来看,它确实表现出未定义的行为——不仅在 tmin
的计算中,而且出于同样的原因,在计算指数的循环中也是如此。无论此代码在何种程度上产生因环境而异的结果,这些结果要么来自未定义的行为,要么来自您关于 [unsigned
] int
的大小的假设对于正在使用的 C 实现不正确.
然而,如果我们假设(不安全)
int
的移位操作就像左操作数被重新解释为具有相同位模式的unsigned int
一样,对其进行操作,结果位模式被重新解释为int
,以及int
和unsigned int
至少有 32 位宽,
那么你的代码似乎是正确的,模数舍入。
如果输入int
的绝对值超过24位有效二进制数字(即至少为224),然而,一些精度将在转换中丢失。在这种情况下,正确的结果将取决于您打算实施的 FP 舍入模式。不正确的四舍五入结果将在最后一位偏离 1 个单位;影响多少结果取决于舍入模式。
像您那样简单地截断/移出额外的位会产生向零模式的舍入。这是标准舍入模式之一,但不是默认模式。默认的舍入模式是舍入到最接近的可表示数字,解决平局有利于结果 least-significant 位 0(舍入为偶数);还有其他三种标准模式。要实现 round-toward-zero 以外的任何模式,您需要在缩放之后和将它们移出之前捕获有效数的 8 least-significant 位。这些以及取决于所选舍入模式的其他详细信息将决定如何应用正确的舍入。
大约一半的 32 位二进制补码数在 round-to-zero 模式下转换时的舍入方式与在任何其他模式下转换时的舍入方式不同; 哪些 数字会出现差异取决于您考虑的舍入模式。
我最初并没有提到我正在尝试模仿 U2F 联合声明:
float u2f(unsigned u) {
union {
unsigned u;
float f;
} a;
a.u = u;
return a.f;
}
感谢 post
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;