我有一个关于Section 7.4 of Beej's Guide to Network Programming中定义的pack754()
函数的问题。
此函数将浮点数f
转换为其ieee 754表示形式,其中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
不正确的第一条线索是
f
的0.5f
,这表明float
,是在带有float
和long double f
的例程中指定fnorm
的无意义介绍。float
数学在函数中没有应用。然而,添加
0.5f
并不意味着代码仅限于float
中的数学。请参见(1LL<<significandbits) + 0.5f
,它可能允许更高精度的中间结果,并且在测试中欺骗了代码作者。舍入尝试确实有意义,因为参数
FLT_EVAL_METHOD
并且目标表示更窄。添加long double
是一种常见的方法-但这里并没有这样做。在国际海事组织,作者在这里没有对0.5
发表评论,这暗示其意图是“明显的”——不是微妙的,尽管是不正确的。当commented时,移动
0.5f
更接近于取整的正确值,但可能会误导某些人认为加法是用0.5
数学完成的(这是float
数学将long double
乘积添加到long double
导致float
首先被提升为0.5f
)。// 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
舍入例程(如<math.h>
),添加显式类型0.5仍然是舍入的弱尝试。它很弱,因为它在很多情况下都是不正确的。+0.5技巧依赖于精确的总和。考虑
long double product = fnorm * (1LL<<significandbits);
long long significand = product + 0.5; // double rounding?
rintl(), roundl(), nearbyintl(), llrintl()
本身可能在截断/赋值到product + 0.5
之前经过舍入-实际上double rounding。最好在标准库函数的c库中使用正确的工具。
significand = llrintl(fnorm * (1ULL<<significandbits));
在这种舍入的情况下仍然存在一个角点,此时
long long
太大,需要调整。正如@Nayuki所指出的,代码也有其他不足之处。而且,它在significand
上失败。关于c - 为什么我们将标准化分数乘以0.5以获得IEEE 754表示的有效位数?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40099056/