我对Beej的网络编程指南7.4节中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
of 0.5f
,它表示在例程中使用和float
进行指定是荒谬的介绍。数学在该函数中没有应用。float
long double f
fnorm
float
但是添加0.5f
并不意味着代码仅限于float
in中的数学(1LL<<significandbits) + 0.5f
。看看FLT_EVAL_METHOD
哪一个可能允许更高精度的中间结果,并且在测试中欺骗了代码作者。
舍入尝试确实有意义,因为参数是实际的long double
,并且目标表示形式更窄。添加0.5
是一种常见的方法-但此处未完成添加。国际海事组织,缺乏作者在此发表的评论,0.5f
暗示其意图是“显而易见的”-不是微妙的,尽管不正确。
至于评论,移动0.5
是呼之欲出的四舍五入正确的,但是可能会误导致一些以为加入与做float
数学,(这是long double
数学添加long double
产品float
原因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
舍入时,不调用首选舍入<math.h>
例程,例如rintl(), roundl(), nearbyintl(), llrintl()
,添加显式类型0.5仍然是舍入的较弱尝试。它很弱,因为在许多情况下它舍入不正确。+0.5技巧依赖于精确的和。
考虑
long double product = fnorm * (1LL<<significandbits);
long long significand = product + 0.5; // double rounding?
product + 0.5
本身可能会在截断/赋值之前经过舍入long long
-实际上是两次舍入。
最好在标准库函数集中使用正确的工具。
significand = llrintl(fnorm * (1ULL<<significandbits));
此舍入仍然存在significand
一个极端情况,即现在太大了,significand , exp
需要调整。正如@Nayuki所指出的那样,代码也有其他缺点。此外,它在上失败-0.0
。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句