深入理解C语言-03-有符号数,定点数,浮点数

负数

关于负数,一般采用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;
}
展开阅读全文

没有更多推荐了,返回首页