数字用二进制怎么表示(数字进制在计算机中的表示)

今天我们讨论一个最基础的问题:进制。

我们平常使用的自然数是十进制的,在计算机中使用的是二进制运算,使用二进制是因为电子元器件只能用高低电压两种状态来进行组合和计算,这就决定了二进制是现代计算机的基础。为了方便表示,人们还经常会使用八进制和十六进制来记录二进制数。关于数的进位制,例如N位制,常见的一种观点是,低位满N,就向高位进一。除了这种理解之外,我们这里再讨论一种更有用的理解。那就是将数字的每一位赋予一个权重,一个数字的真实值就是每一位上的数字乘以这一位所对应的权重,然后再加起来。例如,对于一个十进制数,个数的权重是1 = 10^0,十位的权重是10^1,百位的权重是10^2,依次类推,那么,一个十进制数1368就可以表示为

数字用二进制怎么表示(数字进制在计算机中的表示)(1)

同样,一个二进制数可以表达为

数字用二进制怎么表示(数字进制在计算机中的表示)(2)

这个分解式看起来很简单,但意义却很重大,这使得我们从“满几进一”的思维中跳出来,可以重新定义每个位的权重。比如在普通的十进制数中,十位的权重是10,百位的权重是100,但是,我们可以根据需要,规定十位是十进制,百位是八进制的,也就是说,十位的权重仍然是10,百位的权重变成了8∗10 = 80。(这句话务必看明白,我们创造了新的混合进制数。在后面的文章里我会介绍递增进位制,这种进位制的每一位都不相同,但它却是编码状态,进行组合计数等等的基础,如果不理解进位制的话,那么这种技巧在你看来可能就是很难想到的淫巧奇技,但是在进制分析法面前,这种技巧毫无秘密可言。)

数字用二进制怎么表示(数字进制在计算机中的表示)(3)

计算机中数字的表示

计算机中是如何表示一个整数的,一个负数呢?一个浮点数呢?搞清楚这个问题有多重要,我来举个例子。曾经有一位网络游戏的服务端同学,在写角色身上的金钱的时候,使用了int型,但在程序的其他地方,却又使用了unsign int来存储,更不幸的是,在扣玩家身上的金钱的地方,又出现了并发的情况,导致金钱会出现负数。一个整型负数,转成无符号整型的时候,悲剧就发生了:玩家变得巨有钱。一个刷金的BUG就这样产生了。要搞明白这个问题,就得先搞明白,在计算机中,每个数是怎么表示的。还有,比如,这样一个BUG,有一次,我发现从网络接收的数据不对,只好把接到的数字一个字节一个字节地去分析,最后发现,发送方发送了一个浮点型,而接受方却是以整型去解释的。那么浮点型的1.0和整数的1到底有什么不同呢?(这还是原始时代的BUG,感谢protobuf,netty,我们再也不用担心应用层网络消息的格式了。)

我们都知道Java中的byte类型是一个字节,8位,short是两个字节,16位,int是四个字节,32位,long是8字节,64位。为了方便描述,我们以byte为例,一个字节,8位能表达的范围是从(00000000)2 ~ (11111111)2,转成十进制,就是从0到255。但书上告诉我们,byte能表达的范围是-128~127。这就牵扯到了负数的表达了。在计算机里,对于有符号的数据类型,会把最高位做为符号位,如果为1,则为负数。我们可以测试一下。

int i = 1; Assert.assertTrue((a << 30) > 0); Assert.assertTrue((a << 31) < 0); Assert.assertEquals((a << 31), Integer.MIN_VALUE);

看,如果我们把1左移31位,也就是送到了最高位上,这个数就变成了int型所能表示的最小值了。

由于32位整数写起来太罗嗦了,我们先只以byte的情况进行分析。负数通常有三种表示方法。第一种,把符号位设为1,后面的数字等于这个负数的绝对值,例如-15就变成了10001111。这种方法称为原码。还有一种,保持符号位不变,后面的数字全部取反,例如-15就变成了11110000,这种方法称为反码。最后一种,称为补码,它是由反码加1得到的。例如,-1的反码是11111110,再加1就是11111111。使用补码的好处包括,0和-0的表示是相同的,正负数可以直接进行加减法运算,不必再做额外的转换。而原码和反码则不具备这样的优点。所以现代计算机都是使用补码来表示负数。

递增进位制数

递增进位制数是这样一种数,它的最右一位(称为个位已经不太准确了,我们使用左右来代替个位十位的称呼)是二进制的,它的右数第二位是三进制的,右数第三位是四进制的 ……它的右数第N是N 1进制的。用权重的观点来看,它的最右一位的权重是1,右数第二位的权重是1∗2 = 2!,右数第三位的权重是1∗2∗3 = 3!,右数第N的权重是N!。 我们也用上面的方法来表示递增进位制数:

数字用二进制怎么表示(数字进制在计算机中的表示)(4)

递增进位制数的最常见的一个作用就是用来编码全排列。通过递增进位制数,我们可以建立N个元素的全排列与自然数[1,N!]的一一对应关系。在下一节,生成全排列那里,我们会进行详细地讲解。

数字用二进制怎么表示(数字进制在计算机中的表示)(5)

好了,看完上面的内容,可以尝试实现下下面的题目:

  1. 写一个程序,输入是一个十进制整数,输出是它的二进制表示的字符串。函数原型 String oct2Bin(int a),例如,输入8,返回值是“1000”。要考虑负数哦。
  2. 写一个程序,进行数制的相互转换,原型是String transform(String s, int radixSrc, int radixTgt),其中,s代表原始的字符串,radixA代表源进制,radixB代表目标进制。例如transform("221", 3, 2),返回“11001”。因为3进制的221,就是十进制的25,而十进制的25就是二进制的11001。同理,transform("11001", 2, 3)返回值就是“221”。
  3. 自己查一下float和double在计算机内是以什么格式输出的,并思考,如果在网络上只能以字节流的方式传送,这些数字应该怎么办?(例如,InputStream/OutputStream都只能处理字节流。)
  4. 猜想一下以下代码的运行结果是什么?并写程序验证,体验一下数据溢出。

short a = Byte.MIN_VALUE; int b = Short.MIN_VALUE; short c = Byte.MAX_VALUE 1; byte d = (byte)(Byte.MAX_VALUE 1);

文章来自知乎专栏:https://zhuanlan.zhihu.com/hinus

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页