我对Beej的网络编程指南第7.4节中定义的pack754()
函数有一个问题。
此函数将浮点数f
转换为其IEEE754表示,其中bits
是表示该数字的总位数,expbits
是仅用于表示指数的位数。
我只关心单精度浮点数,所以对于这个问题,bits
被指定为32
,expbits
被指定为8
。这意味着23
位用于存储有效数(因为一位是符号位)。
我的问题是关于这行代码。
significand = fnorm * ((1LL<<significandbits) + 0.5f);
0.5f
在这段代码中的作用是什么?
这是使用此功能的完整代码。
#include <stdio.h>
#include <stdint.h> // defines uintN_t types
#include <inttypes.h> // defines PRIx macros
uint64_t pack754(long double f, unsigned bits, unsigned expbits)
{
long double fnorm;
int shift;
long long sign, exp, significand;
unsigned significandbits = bits - expbits - 1; // -1 for sign bit
if (f == 0.0) return 0; // get this special case out of the way
// check sign and begin normalization
if (f < 0) { sign = 1; fnorm = -f; }
else { sign = 0; fnorm = f; }
// get the normalized form of f and track the exponent
shift = 0;
while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
fnorm = fnorm - 1.0;
// calculate the binary form (non-float) of the significand data
significand = fnorm * ((1LL<<significandbits) + 0.5f);
// get the biased exponent
exp = shift + ((1<<(expbits-1)) - 1); // shift + bias
// return the final answer
return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}
int main(void)
{
float f = 3.1415926;
uint32_t fi;
printf("float f: %.7f\n", f);
fi = pack754(f, 32, 8);
printf("float encoded: 0x%08" PRIx32 "\n", fi);
return 0;
}
0.5f
在这段代码中有什么用途?
该代码是不正确的四舍五入尝试。
long double fnorm;
long long significand;
unsigned significandbits
...
significand = fnorm * ((1LL<<significandbits) + 0.5f); // bad code
不正确的第一个线索是0.5f
的f
,它表示flo
,是在一个带有long double f
和f不规范
的例程中指定flo
的荒谬介绍。flo
数学在函数中没有应用。
然而,添加0.5f
并不意味着代码仅限于浮点
(1LL
四舍五入的尝试确实有意义,因为参数是long double
,目标表示更窄。添加0.5
是一种常见的方法——但这里没有这样做。IMO,作者在这里没有评论0.5f
暗示意图是“明显的”——不是微妙的,尽管是不正确的。
正如评论的那样,移动0.5
更接近于四舍五入的正确性,但可能会错误地导致一些人认为加法是通过flo
数学完成的,(它是long double
数学在flo
中添加一个long double
乘积会导致0.5f
首先被提升为long double
)。
// closer to rounding but may mislead
significand = fnorm * (1LL<<significandbits) + 0.5f;
// better
significand = fnorm * (1LL<<significandbits) + 0.5L; // or 0.5l or simply 0.5
舍入,不调用首选
考虑一下
long double product = fnorm * (1LL<<significandbits);
long long significand = product + 0.5; // double rounding?
产品0.5
本身在截断/赋值到long long
之前可能会经过四舍五入-实际上是双重四舍五入。
最好在标准库函数的C棚中使用正确的工具。
significand = llrintl(fnorm * (1ULL<<significandbits));
这种舍入的一个极端情况是意义
现在太伟大了,意义,exp
需要调整。正如@奈雪的茶所确定的那样,代码也有其他缺点。此外,它在-0.0
上失败。
0.5f
在代码中没有任何用途,并且可能是有害或误导的。
表达式(1LL
将0.5f
替换为0.0f
会导致相同的行为。见鬼,完全删除该术语,因为f不规范
将导致*
的右侧参数无论如何都被转换为long double
。这将是重写该行的更好方法:long long有意义=f规范*(long double)(1LL
旁注:pack754()
的这种实现正确地处理了零(并将负零折叠为正零),但错误地处理了次常态数(错误位)、无穷大(无限循环)和NaN(错误位)。最好不要将其视为参考模型函数。