在计算机底层所有的数据都是二进制表示的,在C语言中,基本数据类型大致有以下几种:
char
short
int
long
float
double
定点数的二进制表示
定点数的二进制表示很好理解,计组书上说过有符号整数的二进制表示法大致有四种:
- 原码
- 反码
- 补码
- 移码
现代计算机基本都采用补码来表示有符号数,而浮点数用到了移码,今天就来具体研究一下
下面的代码用于打印有符号整数的bit位
#include <stdio.h>
#include <string.h>
void pretty_print(char* str) {
char buffer[256];
char* p = buffer;
int length = strlen(str);
for (int i = 0; i < length; i++) {
if (i > 0 && i % 8 == 0) {
sprintf(p++, " ");
}
sprintf(p++, "%c", str[i]);
}
printf("%s\n", buffer);
}
void print_bits_of_int(int value) {
const int size = sizeof(value) * 8;
char buffer[size + 1];
buffer[size] = '\0';
for (int i = size - 1; i >= 0; i--) {
int bit = value & (unsigned int) 1;
buffer[i] = bit ? '1' : '0';
value >>= 1;
}
pretty_print(buffer);
}
看看测试结果
int num;
num = 8;
print_bits_of_int(num); // 00000000 00000000 00000000 00001000
num = -8;
print_bits_of_int(num); // 11111111 11111111 11111111 11111000
负整数的补码是其相反数的按位取反加一
浮点数的二进制表示
浮点数又可以分为单精度浮点数和双精度浮点数两种,根据IEEE-754标准,一个二进制浮点数可以分为三个 部分:
- 符号位:用于表示正负,正数为0,负数为1
- 指数域:用于表示指数值,其实际值=编码值-偏移值,这就是移码表示法
- 尾数域:用于表示有效数位,只表示小数部分,整数部分一般为1而被省略
32位单精度数指数域有8位,尾数域有23位,即32=1+8+23
64位双精度数指数域有11位,尾数域有52位,即64=1+11+52
偏移值是一个固定值,它等于:\(2^{e-1}-1\),其中\(e\)是指数域的位数
32位单精度数的偏移值是127
64位双精度数的偏移值是1023
浮点数的小数部分也是由二进制表示的,其最高位表示\(1/2\),次高位表示\(1/4\),以此类推直到 \(1/2^f\),其中\(f\)是尾数域的位数
先测试单精度浮点数,因为float
会隐式转化为int
,因此需要借助指针
float num = 1;
int* addr = (int*) #
print_bits_of_int(*addr); // 00111111 10000000 00000000 00000000
// sign: 0
// exp : 01111111
// frac: 00000000000000000000000
指数域的编码值为127,减去偏移值,实际值为0,整体表示为:\(1.0*2^0\)
print_bits_of_float(0.0);
// 00000000 00000000 00000000 00000000
// 1.0*2^-127
print_bits_of_float(-0.0);
// 10000000 00000000 00000000 00000000
// -1.0*2^-127
print_bits_of_float(2.0);
// 01000000 00000000 00000000 00000000
// 1.0*2^1
print_bits_of_float(3.0);
// 01000000 01000000 00000000 00000000
// (1+0.5)*2^1
那现在我们就反过来验证一下:
对于小数\(18.75\),可以表示为:\(1.171875*2^4\)
其中\(0.171875\)又可以表示为:\(1/8+1/32+1/64\)
因此
sign: 0
exp : 4+127=131 : 10000011
frac: \(1/8+1/32+1/64\) : 00101100 00000000 0000000
综合起来:01000001 10010110 00000000 00000000
print_bits_of_float(18.75);
// 01000001 10010110 00000000 00000000
结果和我们的推断是一致的
那么单精度浮点数所能表示的最大最小值是什么呢?可以先猜测:max=0x7fffffff,min=0xffffffff, 下面就验证一下
print_as_float(0x7fffffff); // nan
print_as_float(0xffffffff); // -nan
和我们的预期不一致,说明这是一个特殊值
特殊值
根据规范,浮点数有三种特殊值
- 指数域全0,尾数域全0,则为\(\pm0\)
- 指数域全1,尾数域全0,则为\(\pm\infty\)
- 指数域全1,尾数域不全0,则为\(\pm nan\)
// ------------ |s| e || f |
print_as_float(0b00000000000000000000000000000000); // 0.000000
print_as_float(0b10000000000000000000000000000000); // -0.000000
print_as_float(0b01111111100000000000000000000000); // inf
print_as_float(0b11111111100000000000000000000000); // -inf
print_as_float(0b01111111100000100000000000000000); // nan
print_as_float(0b11111111100000001100000000000000); // -nan
最值
之前的问题还没解决呢,不过了解规则后就好办了,只要保证指数域不全为1,即可求得最大值与最小值
// ------------ |s| e || f |
print_as_float(0b01111111011111111111111111111111); // 3.40e+38
print_as_float(0b11111111011111111111111111111111); // -3.40e+38
参考资料:
[1] wikipedia - Signed Number Representations
[2] wikipedia - Floating Number
[3] wikipedia - IEEE-754