计算机最优美的地方就在于底层架构的搭建。数学世界抽象的数值、符号、运算, 都被用二进制的形式物理地在一个精心设计的平台上实现。这种看似简单实则冗杂、繁琐、同时需要大量数学知识的工作是整个计算机领域能够发展到今天的基础。
这两个星期以来,213倾向的重点都在于如何用bits&bytes表达整数、小数(浮点数),以及与之相关的bitwise operation。除了了解到任何信息在计算机中不过知识一串bits之外,最大的收获是在lab中掌握的用最基本bit-op完成很多相对复杂工作的tricks。其实以一个规则制定者的角度出发,如何最有效、快速地让计算机做加减法运算、逻辑判断等基础工作是一个很大难题。 Big-Endian还是Little-Endian? 哪些运算是必须集合在硬件的,哪些是可以通过已有运算衍推的? 如何在空间和速度上取舍? 曾经不同的工作组们都做出了自己的选择,带来的问题是代码的兼容性受到很大影响。 目前32位系统通用的x86 instruction set是硬件设计者的福音, 也是我们这些有可能从事底层程序开发的设计者赖以生存、必须研究透彻的圣经。这部分内容会在以后的课程中涉及,所以暂不讨论。 今天的重点是用有限的bitwise operation做很强大的工作。
首先是两点基础性的知识:
1. 对于一个32位的int, signed的范围(Two's Complement)是-232 到 231-1。 最大数是0xffffffff, 最小数是0x80000000。 如果出现了最小数-1的运算, 新得的数是最大数。 相反,如果出现最大数+1的情况,得到的数是最小数。 这种类似于一个环的整数体系很有趣,在有些时候是可以加以利用的。 另一方面, unsigned int (One's Complement)的范围是0 到 232-1。 值得注意的是,电脑真正在对两个data做运算时是不知道它们真正的类型的, 它只是个运算者, 类型的界定和安全保证是一个语言及其配套的编译器的工作。这给我们带来的启示是对于有些编译器禁止的运算,只要通过改变变量的类型也许就可以绕过去。 当然随之而来的风险是一个编程者要控制的,但同时带来的能量也更大。 一个类似的能量/风险交易的例子是malloc。 通过malloc得到的memory是完全归用户使用的,即使是在一个block结束了之后。 这相比statically declared的memory带来了更多的功能和用处,但同时带来的风险是用户没有free这部分memory造成的memory leak。对于一个成熟的开发者来说,这应该是能熟练规避的问题。
2. &0 可以将任何bit变成0
| 1 可以将任何bit变成1
|0 ^0 和 &1 可以维持原信息不变
^1 可以反转原信息
~x 和 x ^-1 效果相同 反转
x << 1 = x*2
x - y = x + ~y +1 实际硬件的减法解决方案
x | (1<<j) 将第jth bit变成1
!x x是否为0
!(x^y) 判断x/y是否相等
(x>>31)+1 判断是否为正(对于signed)
((x-y)>>31)+1 判断x 是否比y 大
...
这种基础的运算很多。 <<Hacker's Delight>> 一书中也罗列一些更高级的, 在此不一一阐述。
牢牢抓住以上两点是以下解题的关键。 我准备重新开一贴专门总结每一题的解法和思维过程。 今天总结至此。