这个系列不是一个系统性地学习数学相关知识的专题,更多是一种程序开发中基础性数学知识的归纳和总结。
让我们从数字开始说起吧。1. 按位计数法
人类解决问题一个方法就是从大量问题中分析和定义出系统的、普遍的、简单的规则,以此快速解决同类问题。
定义指数法则如下:
N^a * N^b = N^(a+b)
这个指数法则有什么含义呢?有:
N^0 = 1
因为:N^0 * N^1 = N^(0+1) = N^1
因此:N^0 = (N^1) / (N^1) = 1
还有:
N^(-b) = 1 / (N^b)
因为:N^(-b) * N^b = N^(b-b) = N^0 = 1
因此:N^(-b) = 1 / (N^b)
由此,在以N为基数的情况下,一个整数可以被表达为:
Ak * N^k + ... + A2 * N^2 + A1 * N^1 + A0 * N^0
而一个小数可以被表达为:
Ak * N^k + ... + A2 * N^2 + A1 * N^1 + A0 * N^0 + B0 * N^(-1) + B1 * N^(-2) + ...
这里值得强调的是,通过定义这样一个简单的指数规则,人类获得了强大的工具。这不是记忆力的问题,而是想象力的问题。请记住这种思维方式。
2. "0" 的故事
0是人类智慧的又一体现。公元前3000年,巴比伦人就已经懂得使用零来避免混淆。
在公元5世纪时,“0”已经传入罗马。但罗马教皇凶残而且守旧。他不允许任何人使用“0”。有一位罗马学者在笔记中记载了关于使用“0”的一些好处和说明,就被教皇召去,施行了酷刑,使他再也不能握笔写字。这里我想说的是,并非所有的人都有足够的智力能意识到先进的东西。
国际上规定0是最小的自然数。我国自1993年起也规定0属于自然数。
用0来表示没有或者用来占空位,可以极大地简化规则。
3. 计算机中除0的结果
以单精度浮点小数为例。一个合理的数值除以0,会产生INF值,也就是无穷大。
inf 0x7f800000 (符号位:1,指数位:+127,尾数位:0)-inf 0xff800000 (符号位:0,指数位:+127,尾数位:0)
使用0除以0,或者执行非法操作,如Sqrt(-1),则会产生NAN值,也就是无法被计算机表达的非法数字:
-nan(ind) 0xffc00000 (符号位:1,指数位:+127,尾数位:0x400000)
nan 0x7fc00000 (符号位:0,指数位:+127,尾数位:0x400000)
对于单精度浮点数来说,只要指数位为+127,尾数为零就是inf;而指数位为+127,尾数位为非零时就是Nan。这意味着Nan并非只有一个,而是一族。不过,大多数程序仅在检测一个数字是否为Nan时,仅考虑尾数的第一个bit,而忽略其后的bits。
注:IEEE 754所定义的单精度浮点数格式为:s eee eeee e xxx xxxx xxxx xxxx xxxx xxxx
4. 60进制
约公元前3200至2340年的古巴比伦人,使用了60进制计数法。在这种计数法下,一个数字可以表示为:
Ak * 60^k + ... + A2 * 60^2 + A1 * 60 + A0
这种计数法至今仍然在时间、角度计算中被使用。原因如下:
- 将圆周定义为360度,则采用60进制,可以轻易地6等分、5等分、4等分、3等分、2等分一个圆周或者60度角而不产生小数位
- 一个等边三角形的任意一个内角刚好是60度
- 一个圆的内接正六边形刚好等分圆周
例子:
- 北极圈的纬度 66°33'38" N
- 北回归线的纬度 23°26'22" N
- 耶路撒冷的经度 35°13' E
如果要得到具体的度数,则 66°33'38" = (66 * 60^0 + 33 * 60^(-1) + 38 * 60^(-2))°
5. 10进制
不做阐述
6. 2进制
以2为基数的表达方法。
2进制采用0和1来表达,这就给了我们各种奇淫巧技的施展空间。
判断一个整数X能否被2整除(是否为偶数):
bool canDivBy2 = ((X & 1) == 0);
也可以写成:
bool canDivBy2 = ((X % 2) == 0);
计算一个数字除以2的n次方的结果:
int divBy2n = (X >> n);
乘以2的n次方:
int mulBy2n = (X << n);
不使用临时变量,交换两个数字:
a ^= b;
b ^= a;
a ^= b;
判断一个整数X是否为2的幂:
bool isPowOf2 = ((X != 0) && ((X & (~X + 1)) == X));
根据整数X,获取小于或等于X的最大的2的幂:
unsigned int getPowerOf2Floor(unsigned int x)
{
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
return x - (x >> 1);
}
根据整数X,获取大于或等于X的最小的2的幂:
unsigned int getPowerOf2Ceil(unsigned int x)
{
x = x - 1;
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
return x + 1;
}
7. 64进制 (Base64)
编码后数据长度大约为原长的135.1%。
8. 斐波那契编码 (Fibonacci coding)
使用斐波那契序列作为基数的编码方式。这种编码方式在某个位被破坏或者发生偏移后,仅破坏位置附近的编码会发生错乱,而后续的编码依然能够正常读取。这是其他编码方式所不具备的。
不做深入阐述。