浮点表示(使用按位运算符)
Floating point representation (using bitwise operators)
这是打印浮点表示的问题的解决方案(即:x = (−1)^sign · (1.m 22 m 21 m 20 . . . m 0 ) · 2^(e −bias) ) 我还没有理解其中的一些东西:
1)union的使用,为什么?
2) MANTISSA_MASK 和 EXPONENET_MASK, 它们是做什么用的?
3) 在这里使用 & :
uint32_t exponent = ( t.bits >> MANTISSA_WIDTH ) & EXPONENT_MASK;
uint32_t mantissa = ( t.bits & MANTISSA_MASK );
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#define ABSOLUTE_WIDTH 31
#define MANTISSA_WIDTH 23
#define EXPONENT_WIDTH 8
#define EXPONENT_MASK 0xffu
#define MANTISSA_MASK 0x007fffffu
#define EXPONENT_BIAS 127
union float_bits {
float f;
uint32_t bits;
};
void print_float( FILE *output, float f ) {
union float_bits t; t.f = f;
uint32_t sign_bit = ( t.bits >> ABSOLUTE_WIDTH );
uint32_t exponent = ( t.bits >> MANTISSA_WIDTH ) & EXPONENT_MASK;
uint32_t mantissa = ( t.bits & MANTISSA_MASK );
if( sign_bit != 0 ) {
fprintf( output, "-" );
}
if( exponent > 2 * EXPONENT_BIAS ) {
fprintf( output, "Inf\n" ); /* Infinity */
return;
} else if( exponent == 0 ) {
fprintf( output, "0." ); /* Zero or Denormal */
exponent = ( mantissa != 0 ) ? exponent + 1 : exponent;
} else {
fprintf( output, "1." ); /* Usual */
}
for( int k = MANTISSA_WIDTH - 1; k >= 0; --k ) {
fprintf( output, "%d", ( mantissa >> k ) & 1 );
}
if( exponent != 0 || mantissa != 0 ) {
fprintf( output, " * 2^%d\n", (int) ( exponent - EXPONENT_BIAS ) );
}
}
int main() {
FILE *input = fopen( "floating.in", "r" ),
*output = fopen( "floating.out", "w" );
size_t N; float f;
fscanf( input, "%zu", &N );
for( size_t i = 0; i < N; ++i ) {
fscanf( input, "%f", &f );
print_float( output, f );
}
fclose( input );
fclose( output );
return 0;
}
1) The use of the union, why ?
位运算符仅适用于整数类型。出于显而易见的原因,您不能将浮点数转换为整数。但是联合定位组件的内存重叠。所以通过写入浮点数分量然后读入整数分量returns一个浮点数的整数表示。明确一点:这是不是浮点数的整数值。在计算中将其用作整数会产生意想不到的结果。但是您可以访问整数的位,就像访问浮点数的位一样。
2) MANTISSA_MASK and EXPONENET_MASK, what are they for?
浮点数由指定尾数(数字字符串)的位数和表示数字 "location" 的指数部分表示。 "conversion"的浮点数转化为整型后,这两部分混合在整型值中。 MANTISSA_MASK
和 EXPONENT_MASK
(你的 Q 中有错字)掩盖了那部分。 MANTISSA_BITS
将指数移动到正确的位置。
3) the use of &
in here:
屏蔽位的是位和运算符。
让我们来一个——完全虚拟的——例子:
根据您的代码,您有 23 位尾数和 8 位指数。 32 位中的一位保留用于符号。让我们有一个数字:
00000001000010011010011010101010
有 1 个符号位、8 个指数位和 23 个尾数位你可以这样读
0 00100010 00010011010011010101010
s exponent --------mantissa-------
要获得尾数,您可以使用仅设置了尾数位的掩码:
0 00000000 11111111111111111111111
bit-and时,只有两个操作数都为1的位为1,其他位为0:
0 00100010 00010011010011010101010 A
0 00000000 11111111111111111111111 B
- -------- -----------------------
0 00000000 00010011010011010101010 A&B
尾数与指数分离(现在是表示尾数的实整数值。
要获得指数,首先将整个单词右移,以便指数从位 0(最右边)开始:
0 00100010 00010011010011010101010
00000000000000000000000 0 00100010 >> 23 (mantissa bist)
要将指数与符号位隔离开来,您必须再次位:
00000000000000000000000 0 00100010 A
00000000000000000000000 0 11111111 B
------------------------------------
00000000000000000000000 0 00100010 A&B
瞧瞧。
您的代码采用的格式是 IEEE Std 754-2008(C 标准使用 IEC 60559,它是相同的)在第 3.4 节中描述的 "Binary interchange floating-point format",甚至还有一个图表(图 3.1)。
对于 32 位浮点数,它是
bits: 0 1-9 10-32
sign bit exponent significant (or mantissa)
正如 Jens Gustedt 在他的评论中解释的那样,所使用的联合说服编译器允许将浮点数用作 int(大小相同!),反之亦然。一旦你有了一个整数,你就可以处理这些位了。
符号位是最左边的位,可以除以2^31或者右移31得到。
指数在后面的8位。代码通过右移有效数字大小的 23 位并屏蔽掉指数(不包括符号位)来获得它。
他们只通过屏蔽最右边的 23 位就得到了重要信息。
指数本身是有偏差的。为什么?您希望数字 0 < n < 1 具有负指数,数字 >= 1 具有正指数。指数减半,而不是为符号添加额外的位。低于一定限度(偏差)的一切都必须被视为负面的,而高于一定限度的一切都必须被视为正面的。要获得具有正确值的指数符号,您只需减去偏差即可。
有一些标准定义的特殊值:Inf
和NaN
(信号NaN
和安静NaN
)编码为
NaN
(IEEE 标准 754-2008 第 6.2.1 节)
All binary NaN bit strings have all the bits of the biased exponent field E set to 1 (see 3.4). A quiet NaN bit string should be encoded with the first bit (d1) of the trailing significand field T being 1. A signaling NaN bit string should be encoded with the first bit of the trailing significand field being 0. If the first bit of the trailing significand field is 0, some other bit of the trailing significand field must be non-zero to distinguish the NaN from infinity. In the preferred encoding just described, a signaling NaN shall be quieted by setting d1 to 1, leaving the remaining bits of T unchanged.
For binary formats, the payload is encoded in the p-2 least significand bits of the trailing significant field.
Inf
Inf
的编码在 IEEE-754 中没有这样明确的描述(或者我没有找到),仅针对 3.5.2 节中的十进制编码但是它通常是最大指数(所有位都设置为 1),不变的符号位以区分正负无穷大,有效数的所有位都设置为 0 以区别于任何有限数。轻松测试。
代码中的位杂耍相当复杂,假定特定的字节序并且 float
和 uint32_t
具有相同的字节序,并且 float
被编码为单个IEEE Std 754-2008/IEC 60559 中描述的精度格式(您需要使用 C 标准宏 __STDC_IEC_559__
对其进行检查)并且 union
技巧适用于所使用的编译器。如果你需要像 frexp(3)
这样的东西,你真的应该使用内置插件。
A frexp()
(对于 double
,懒得重写它。它来自我自己的 libmath 版本,因为只需要少数函数并且内存稀少而编写)假设少了很多,只是浮点数符合 IEC 60559:
double frexp(double x, int *eptr)
{
int sign, exponent;
int i;
/*
* The exponent of an IEEE-754 double (binary64) is an 11-bit large integer
*/
double ap_2[11] = {
2.0000000000000000000000000000000000000,
4.0000000000000000000000000000000000000,
16.000000000000000000000000000000000000,
256.00000000000000000000000000000000000,
65536.000000000000000000000000000000000,
4294967296.0000000000000000000000000000,
18446744073709551616.000000000000000000,
3.4028236692093846346337460743176821146e38,
1.1579208923731619542357098500868790785e77,
1.3407807929942597099574024998205846128e154,
1.7976931348623157e308 // DBL_MAX
};
double ap_half[11] = {
0.50000000000000000000000000000000000000,
0.25000000000000000000000000000000000000,
0.062500000000000000000000000000000000000,
0.0039062500000000000000000000000000000000,
1.5258789062500000000000000000000000000e-5,
2.3283064365386962890625000000000000000e-10,
5.4210108624275221700372640043497085571e-20,
2.9387358770557187699218413430556141946e-39,
8.6361685550944446253863518628003995711e-78,
7.4583407312002067432909653154629338374e-155,
5.5626846462680034577255817933310101606e-309 // < DBL_MIN
};
if (isinf(x)) {
*eptr = 0;
return x;
}
if (isnan(x)) {
*eptr = 0;
return x;
}
if (x == 0.0) {
*eptr = 0;
return x;
}
exponent = 0.0;
/*
* Easier to work with positive values
*/
if (x < 0) {
x = -x;
sign = 1;
}
else {
sign = 0;
}
if (x >= 1.0) {
/*
* Big steps
*/
for (i = 0; x >= ap_2[i]; i++) {
exponent += (1 << i);
x *= ap_half[i];
}
/*
* Small steps
*/
if (x < 0.5) {
while (x < 0.5) {
x *= 2.0;
exponent--;
}
} else {
while (x > 1.0) {
x /= 2.0;
exponent++;
}
}
} else {
/*
* Same as above, but in the opposite direction
*/
for (i = 0; x < ap_half[i]; i++) {
exponent -= (1 << i);
x *= ap_2[i];
}
if (x < 0.5) {
while (x < 0.5) {
x *= 2.0;
exponent--;
}
} else {
while (x > 1.0) {
x /= 2.0;
exponent++;
}
}
}
if (sign) {
x = -x;
}
*eptr = exponent;
return x;
}
函数isinf()
有点,怎么说呢,加粗,不是所有的编译器都支持:
int isinf(double x){
// TODO: not every compiler might eat this check for Inf
// GCC-4.8.4 does
// TCC 0.9.25 does
// clang 3.4-1ubuntu3 (based on LLVM 3.4) does
return (x == 1.0/0.0 || x == -1.0/0.0);
}
int isnan(double x){
return (x != x);
}
我用两个表替换了 2 的倍数的复杂内联计算(正如我之前提到的:内存稀疏)。我希望这样做不会破坏其余代码。
Aaaaa 和往常一样,我太慢了。这次被 Amin Negm-Awad 击败了 43 分钟。
这是打印浮点表示的问题的解决方案(即:x = (−1)^sign · (1.m 22 m 21 m 20 . . . m 0 ) · 2^(e −bias) ) 我还没有理解其中的一些东西: 1)union的使用,为什么? 2) MANTISSA_MASK 和 EXPONENET_MASK, 它们是做什么用的? 3) 在这里使用 & :
uint32_t exponent = ( t.bits >> MANTISSA_WIDTH ) & EXPONENT_MASK;
uint32_t mantissa = ( t.bits & MANTISSA_MASK );
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#define ABSOLUTE_WIDTH 31
#define MANTISSA_WIDTH 23
#define EXPONENT_WIDTH 8
#define EXPONENT_MASK 0xffu
#define MANTISSA_MASK 0x007fffffu
#define EXPONENT_BIAS 127
union float_bits {
float f;
uint32_t bits;
};
void print_float( FILE *output, float f ) {
union float_bits t; t.f = f;
uint32_t sign_bit = ( t.bits >> ABSOLUTE_WIDTH );
uint32_t exponent = ( t.bits >> MANTISSA_WIDTH ) & EXPONENT_MASK;
uint32_t mantissa = ( t.bits & MANTISSA_MASK );
if( sign_bit != 0 ) {
fprintf( output, "-" );
}
if( exponent > 2 * EXPONENT_BIAS ) {
fprintf( output, "Inf\n" ); /* Infinity */
return;
} else if( exponent == 0 ) {
fprintf( output, "0." ); /* Zero or Denormal */
exponent = ( mantissa != 0 ) ? exponent + 1 : exponent;
} else {
fprintf( output, "1." ); /* Usual */
}
for( int k = MANTISSA_WIDTH - 1; k >= 0; --k ) {
fprintf( output, "%d", ( mantissa >> k ) & 1 );
}
if( exponent != 0 || mantissa != 0 ) {
fprintf( output, " * 2^%d\n", (int) ( exponent - EXPONENT_BIAS ) );
}
}
int main() {
FILE *input = fopen( "floating.in", "r" ),
*output = fopen( "floating.out", "w" );
size_t N; float f;
fscanf( input, "%zu", &N );
for( size_t i = 0; i < N; ++i ) {
fscanf( input, "%f", &f );
print_float( output, f );
}
fclose( input );
fclose( output );
return 0;
}
1) The use of the union, why ?
位运算符仅适用于整数类型。出于显而易见的原因,您不能将浮点数转换为整数。但是联合定位组件的内存重叠。所以通过写入浮点数分量然后读入整数分量returns一个浮点数的整数表示。明确一点:这是不是浮点数的整数值。在计算中将其用作整数会产生意想不到的结果。但是您可以访问整数的位,就像访问浮点数的位一样。
2) MANTISSA_MASK and EXPONENET_MASK, what are they for?
浮点数由指定尾数(数字字符串)的位数和表示数字 "location" 的指数部分表示。 "conversion"的浮点数转化为整型后,这两部分混合在整型值中。 MANTISSA_MASK
和 EXPONENT_MASK
(你的 Q 中有错字)掩盖了那部分。 MANTISSA_BITS
将指数移动到正确的位置。
3) the use of
&
in here:
屏蔽位的是位和运算符。
让我们来一个——完全虚拟的——例子:
根据您的代码,您有 23 位尾数和 8 位指数。 32 位中的一位保留用于符号。让我们有一个数字:
00000001000010011010011010101010
有 1 个符号位、8 个指数位和 23 个尾数位你可以这样读
0 00100010 00010011010011010101010
s exponent --------mantissa-------
要获得尾数,您可以使用仅设置了尾数位的掩码:
0 00000000 11111111111111111111111
bit-and时,只有两个操作数都为1的位为1,其他位为0:
0 00100010 00010011010011010101010 A
0 00000000 11111111111111111111111 B
- -------- -----------------------
0 00000000 00010011010011010101010 A&B
尾数与指数分离(现在是表示尾数的实整数值。
要获得指数,首先将整个单词右移,以便指数从位 0(最右边)开始:
0 00100010 00010011010011010101010
00000000000000000000000 0 00100010 >> 23 (mantissa bist)
要将指数与符号位隔离开来,您必须再次位:
00000000000000000000000 0 00100010 A
00000000000000000000000 0 11111111 B
------------------------------------
00000000000000000000000 0 00100010 A&B
瞧瞧。
您的代码采用的格式是 IEEE Std 754-2008(C 标准使用 IEC 60559,它是相同的)在第 3.4 节中描述的 "Binary interchange floating-point format",甚至还有一个图表(图 3.1)。
对于 32 位浮点数,它是
bits: 0 1-9 10-32
sign bit exponent significant (or mantissa)
正如 Jens Gustedt 在他的评论中解释的那样,所使用的联合说服编译器允许将浮点数用作 int(大小相同!),反之亦然。一旦你有了一个整数,你就可以处理这些位了。
符号位是最左边的位,可以除以2^31或者右移31得到。
指数在后面的8位。代码通过右移有效数字大小的 23 位并屏蔽掉指数(不包括符号位)来获得它。
他们只通过屏蔽最右边的 23 位就得到了重要信息。
指数本身是有偏差的。为什么?您希望数字 0 < n < 1 具有负指数,数字 >= 1 具有正指数。指数减半,而不是为符号添加额外的位。低于一定限度(偏差)的一切都必须被视为负面的,而高于一定限度的一切都必须被视为正面的。要获得具有正确值的指数符号,您只需减去偏差即可。
有一些标准定义的特殊值:Inf
和NaN
(信号NaN
和安静NaN
)编码为
NaN
(IEEE 标准 754-2008 第 6.2.1 节)
All binary NaN bit strings have all the bits of the biased exponent field E set to 1 (see 3.4). A quiet NaN bit string should be encoded with the first bit (d1) of the trailing significand field T being 1. A signaling NaN bit string should be encoded with the first bit of the trailing significand field being 0. If the first bit of the trailing significand field is 0, some other bit of the trailing significand field must be non-zero to distinguish the NaN from infinity. In the preferred encoding just described, a signaling NaN shall be quieted by setting d1 to 1, leaving the remaining bits of T unchanged.
For binary formats, the payload is encoded in the p-2 least significand bits of the trailing significant field.
Inf
Inf
的编码在 IEEE-754 中没有这样明确的描述(或者我没有找到),仅针对 3.5.2 节中的十进制编码但是它通常是最大指数(所有位都设置为 1),不变的符号位以区分正负无穷大,有效数的所有位都设置为 0 以区别于任何有限数。轻松测试。
代码中的位杂耍相当复杂,假定特定的字节序并且 float
和 uint32_t
具有相同的字节序,并且 float
被编码为单个IEEE Std 754-2008/IEC 60559 中描述的精度格式(您需要使用 C 标准宏 __STDC_IEC_559__
对其进行检查)并且 union
技巧适用于所使用的编译器。如果你需要像 frexp(3)
这样的东西,你真的应该使用内置插件。
A frexp()
(对于 double
,懒得重写它。它来自我自己的 libmath 版本,因为只需要少数函数并且内存稀少而编写)假设少了很多,只是浮点数符合 IEC 60559:
double frexp(double x, int *eptr)
{
int sign, exponent;
int i;
/*
* The exponent of an IEEE-754 double (binary64) is an 11-bit large integer
*/
double ap_2[11] = {
2.0000000000000000000000000000000000000,
4.0000000000000000000000000000000000000,
16.000000000000000000000000000000000000,
256.00000000000000000000000000000000000,
65536.000000000000000000000000000000000,
4294967296.0000000000000000000000000000,
18446744073709551616.000000000000000000,
3.4028236692093846346337460743176821146e38,
1.1579208923731619542357098500868790785e77,
1.3407807929942597099574024998205846128e154,
1.7976931348623157e308 // DBL_MAX
};
double ap_half[11] = {
0.50000000000000000000000000000000000000,
0.25000000000000000000000000000000000000,
0.062500000000000000000000000000000000000,
0.0039062500000000000000000000000000000000,
1.5258789062500000000000000000000000000e-5,
2.3283064365386962890625000000000000000e-10,
5.4210108624275221700372640043497085571e-20,
2.9387358770557187699218413430556141946e-39,
8.6361685550944446253863518628003995711e-78,
7.4583407312002067432909653154629338374e-155,
5.5626846462680034577255817933310101606e-309 // < DBL_MIN
};
if (isinf(x)) {
*eptr = 0;
return x;
}
if (isnan(x)) {
*eptr = 0;
return x;
}
if (x == 0.0) {
*eptr = 0;
return x;
}
exponent = 0.0;
/*
* Easier to work with positive values
*/
if (x < 0) {
x = -x;
sign = 1;
}
else {
sign = 0;
}
if (x >= 1.0) {
/*
* Big steps
*/
for (i = 0; x >= ap_2[i]; i++) {
exponent += (1 << i);
x *= ap_half[i];
}
/*
* Small steps
*/
if (x < 0.5) {
while (x < 0.5) {
x *= 2.0;
exponent--;
}
} else {
while (x > 1.0) {
x /= 2.0;
exponent++;
}
}
} else {
/*
* Same as above, but in the opposite direction
*/
for (i = 0; x < ap_half[i]; i++) {
exponent -= (1 << i);
x *= ap_2[i];
}
if (x < 0.5) {
while (x < 0.5) {
x *= 2.0;
exponent--;
}
} else {
while (x > 1.0) {
x /= 2.0;
exponent++;
}
}
}
if (sign) {
x = -x;
}
*eptr = exponent;
return x;
}
函数isinf()
有点,怎么说呢,加粗,不是所有的编译器都支持:
int isinf(double x){
// TODO: not every compiler might eat this check for Inf
// GCC-4.8.4 does
// TCC 0.9.25 does
// clang 3.4-1ubuntu3 (based on LLVM 3.4) does
return (x == 1.0/0.0 || x == -1.0/0.0);
}
int isnan(double x){
return (x != x);
}
我用两个表替换了 2 的倍数的复杂内联计算(正如我之前提到的:内存稀疏)。我希望这样做不会破坏其余代码。
Aaaaa 和往常一样,我太慢了。这次被 Amin Negm-Awad 击败了 43 分钟。