负数
关于负数,一般采用2的补码方式。为什么采用这种方式?
主要是考虑计算机通常是用加法计算器来做减法。
x - x =x + (-x) = 0
显然,计算机中x和-x均为2进制, x 与-x相加一般会大于0.
什么情况下会等于0呢? 取余数。对谁取余数? 2的N次方。
N是什么? 数据的大小。对于int来说,通常N=32.
这样我们便有了 -x = 2的N次方 - x
定义 y为 对x每个bit位取反,显然有 x + y = 111...1 (N个1) = 2的N次方 -1
这样 -x = 2的N次方 - (2的N次方 -1 -y) = y + 1
对于 -5 我们可以在32位int下这样表示:
5的二进制是 00000000 00000000 00000000 00000101
每位取反是 11111111 11111111 11111111 11111010
加1后为: 11111111 11111111 11111111 11111011
对应16进制为: 0xFF FF FF FB
可以写个代码测试下:
int testMinusInt()
{
int x = -5;
printf("-5 = 0x%8x\n", x);
}
深入理解2的补码表示,一般需要了解离散数学中的闭包概念。
详细分析可以参考:
Computer Systems--A Programmer_s Perspective 第2章第2节
深入理解计算机系统(程序员的观点)
无符号数与有符号数的转换
疑问1: 相互之间转换有数据丢失吗?
写一个简单的测试代码
int testSign2Unsign()
{
int x;
unsigned int y;
x = -5;
printf("x = 0x%8x\n", x);
y = x;
printf("y = 0x%8x\n", x);
x = y;
printf("x = 0x%8x\n", x);
return x;
}
可以发现,对应内存数据并无变化。
反汇编后发现赋值操作均用 mov指令完成。
疑问2: 为什么相互转换有风险?
因为信息的编码意义发生改变了。
if (x > 0) {
printf("x > 0\n");
}
if (y > 0) {
printf("y > 0\n");
}
反汇编后发现:
x使用的是 jle 指令,对应有符号数
y使用的是 jbe 指令, 对应无符号数
可见,虽然有符号数和无符号数在内存角度来说是一样的,但在编译器角度来说却完全不一样,
编译器在目标代码生成时,会采用不同的指令。
一般,对无符号数与有符号数做减法需要特别注意 (问题之源)
建议gcc下加如下警告选项:
-Wsign-compare -Wconversion
编译器默认是将有符号数隐式转换为无符号数。
浮点数的表示:
一般有两种表示方法:
定点数(Decimal fixed point)
浮点数 (floating point,一般采用IEEE754)
定点数一般在没有FPU寄存器的嵌入式系统中使用比较多。比如常见的32位系统中,将高16位作为整数部分,低16位作为小数部分。
这样就可以用整数来模拟定点数的 + - * / 运算。
关于定点数的数学分析,请参考以下文档:
http://www.digitalsignallabs.com/fp.pdf
代码实现可以参考以下文档:
http://www.eetimes.com/author.asp?section_id=36&doc_id=1287491
浮点数:
一般采用IEEE 754 规格
优点:表示范围更大。
当然,在G.726中还有11位表示法。
格式:
s符号位 exp指数 frac尾数
32位 1 8 23
64位 1 11 52
11位 1 4 6
表示的数为: (-1)的s次方 * 2的(exp -base)次方 * (1 + frac)
base = 2的(exp位数 -1) -1 对于32位,为127 = 2的7次方 -1
比如0.325 =1.3 / 4 (规范化,这种方式在信息处理中很常见)
则s为0, exp为 127 + (-2) = 125, frac为0.3
近一步把0.3表示为 1/4 + 1/20 = (1/4) * ( 1 + 1/5)
注意到1/5可以表示为 (3/16) / (1 - 1/16) = (3/16) ( 1 + 1/16 + (1/16)*(1/16) + ... (无穷级数展开)
对应的二进制表示为
0. 01 (0011) (0011) ...
取前23位,则为:
0100110 01100110 01100110
这样,0.325在内存中的2进制表示为:
0 01111101 0100110 01100110 01100110
对应16进制为:3E A6 66 66
可以写以下代码来测试下:
float f = 0.325;
printf("float 0.325 = 0x%x \n", *(int*)&f);
很明显,有限集合无法表示一个无穷级数展开,浮点数必然会有精度损失。
所以,不能直接将浮点数与0做比较。
gcc下建议添加以下警告:
-Wfloat-equal
特殊值:
0 全部bit位为 0
无穷大 exp全部为1 frac全部为0
NaN exp全部为1 frac不全为0
关于无穷大和NaN的定义参考<math.h>
问题:
32位float能精确表示0到多少之间的所有整数?
答案: [-2的(23 + 1)次方, 2的(23 + 1)次方]
可以写个代码测试下2的24次方以上不能完全表示。
int testfloatRange()
{
int x = 0x40000;
float f;
int newX;
f = (float) x;
newX = (int) f;
while (x == newX) {
x ++;
f = (float) x;
newX = (int) f;
}
printf("x = %d \n", x);
return 0;
}