为什么我们将归一化分数乘以 0.5 以获得 IEEE 754 表示中的有效数字?
Why do we multiply normalized fraction with 0.5 to get the significand in IEEE 754 representation?
我对 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
在此代码中的作用是什么?
+ 0.5f
在代码中没有任何作用,可能有害或具有误导性。
表达式 (1LL<<significandbits) + 0.5f
的结果是 float
。但即使对于单精度浮点数 significandbits = 23
的小情况,表达式的计算结果也是 (float)(223 + 0.5),它四舍五入到恰好 223(四舍五入)。
将 + 0.5f
替换为 + 0.0f
会导致相同的行为。哎呀,完全放弃该术语,因为 fnorm
无论如何都会导致 *
的右侧参数被强制转换为 long double
。这将是重写该行的更好方法:long long significand = fnorm * (long double)(1LL << significandbits);
旁注:pack754()
的这种实现正确处理了零(并将负零折叠为正零),但错误地处理了次正规数(错误位)、无穷大(无限循环)和 NaN(错误位) .最好不要把它当作参考模型函数。
该代码是不正确的四舍五入尝试。
long double fnorm;
long long significand;
unsigned significandbits
...
significand = fnorm * ((1LL<<significandbits) + 0.5f); // bad code
第一个不正确的线索是0.5f
的f
,它表示float
,是在例程中指定float
与[=18]的无意义介绍=] 和 fnorm
。 float
数学在函数中没有应用。
然而添加 0.5f
并不意味着代码仅限于 (1LL<<significandbits) + 0.5f
中的 float
数学。请参阅 FLT_EVAL_METHOD
,它可能允许更高精度的中间结果并在测试中欺骗了代码作者。
舍入尝试确实有意义,因为参数是 long double
并且目标表示更窄。添加 0.5
是一种常见的方法 - 但它并没有在这里完成。 IMO,作者在这里没有评论 0.5f
暗示其意图是 "obvious" - 虽然不正确,但并不微妙。
与 一样,移动 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
本身可能会在 truncation/assignment 到 long long
之前进行四舍五入 - 实际上 double rounding.
最好在标准库函数的 C 库中使用正确的工具。
significand = llrintl(fnorm * (1ULL<<significandbits));
这个四舍五入的一个极端情况是 significand
现在太大了,significand , exp
需要调整。正如 所指出的那样,代码也有其他缺点。此外,它在 -0.0
.
上失败
我对 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
在此代码中的作用是什么?
+ 0.5f
在代码中没有任何作用,可能有害或具有误导性。
表达式 (1LL<<significandbits) + 0.5f
的结果是 float
。但即使对于单精度浮点数 significandbits = 23
的小情况,表达式的计算结果也是 (float)(223 + 0.5),它四舍五入到恰好 223(四舍五入)。
将 + 0.5f
替换为 + 0.0f
会导致相同的行为。哎呀,完全放弃该术语,因为 fnorm
无论如何都会导致 *
的右侧参数被强制转换为 long double
。这将是重写该行的更好方法:long long significand = fnorm * (long double)(1LL << significandbits);
旁注:pack754()
的这种实现正确处理了零(并将负零折叠为正零),但错误地处理了次正规数(错误位)、无穷大(无限循环)和 NaN(错误位) .最好不要把它当作参考模型函数。
该代码是不正确的四舍五入尝试。
long double fnorm;
long long significand;
unsigned significandbits
...
significand = fnorm * ((1LL<<significandbits) + 0.5f); // bad code
第一个不正确的线索是0.5f
的f
,它表示float
,是在例程中指定float
与[=18]的无意义介绍=] 和 fnorm
。 float
数学在函数中没有应用。
然而添加 0.5f
并不意味着代码仅限于 (1LL<<significandbits) + 0.5f
中的 float
数学。请参阅 FLT_EVAL_METHOD
,它可能允许更高精度的中间结果并在测试中欺骗了代码作者。
舍入尝试确实有意义,因为参数是 long double
并且目标表示更窄。添加 0.5
是一种常见的方法 - 但它并没有在这里完成。 IMO,作者在这里没有评论 0.5f
暗示其意图是 "obvious" - 虽然不正确,但并不微妙。
与 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
本身可能会在 truncation/assignment 到 long long
之前进行四舍五入 - 实际上 double rounding.
最好在标准库函数的 C 库中使用正确的工具。
significand = llrintl(fnorm * (1ULL<<significandbits));
这个四舍五入的一个极端情况是 significand
现在太大了,significand , exp
需要调整。正如 -0.0
.