计算机基础一 -- 程序的机器表示

来源:互联网 发布:淘宝网大衣柜 编辑:程序博客网 时间:2024/04/30 18:11

    了解计算机的位级运算规则是非常重要的,因为这些规则可能和我们日常生活中认识到的加法,乘法,等等的规则并不完全一致,而且要做出32位64位机兼容的程序,甚至以后如果要做手机等设备和传统计算机兼容的程序,这些知识都必不可少。况且,这些内容可算是基础中的基础了。很惭愧我在碰到过很多类似溢出问题,精度问题之后才有意识的补补这里,但是也因此更对这方面的重要性深信不疑。

    闲话少提,这篇文章将从编码方式开始,总结下机器中整数和浮点数的运算方式,中间会穿插些c、java在这些方面的异同(c++对c向下兼容,所以略去不提),最后总结下程序中容易出现的一些问题。

 

    这里是传送门:

    编码方式:为什么有十六进制?

    字节顺序:大端法和小端法,CPU相关还是操作系统相关?

    数据类型:C和JAVA

    整数表示和运算

       有符号数VS无符号数

         移位

       溢出

         转型:字节截断和扩展   

    浮点数表示和运算

         IEEE标准与浮点数表示

      类型转换:整型 and 浮点型

 

   

    编码方式:为什么有十六进制?

    计算机常用的表示方式有二进制,十六进制,十进制。十进制不用说,是对人看起来最自然的表达方式;二进制在表示和位相关的运算时,或者表示地址时,是很自然的一种方式,缺点是通常写起来太长,看起来费劲;所以十六进制由于能每一个数对应一个四位二进制数,看起来更清晰直观,用来作为一种更简单直观的表达方式。

    看到过好些人问到过一个问题:为什么各种组成原理或类似的书上总会提到十六进制,它有什么用?为什么学它?哪里要用到它?有十进制和二进制还不够么?我的理解是这样,十进制用来表示我们日常生活中的一些数字,比如价格,数量等等;而十六进制就作为比二进制更简短,和更易读的方式,通常用来表示和位相关的数字;常用到十六进制的地方有如地址,颜色的编码等,如RGB颜色#FFFFFF表示白色…

    这里有一件很有意思的事情。对同一个数x,二进制、十进制、十六进制是表达它的三种方式,比如1010,10和0xA,表达的是同一个数值,且它们是一一对应的。这让我想起前段时间比较火的本体论,或者类似柏拉图的理想国中说的,世间万物在神国皆有一理念本体,现实中的事物都是这些理念的不同投影。一闪念想到的,如要拍砖请轻拍^_^

   

    字节顺序:大端法和小端法,CPU相关还是操作系统相关?

    数据是按字节存放在存储器的。比如一个四字节的整型数字0x01234567,会按一定的顺序存储在存储器连续的四个位置上,这里假设存储的地址是0x100,0x101,0x102,0x103。如下图所示,有些机器选择将高位存在前面(小地址),这就叫做大端法;有些机器则相反,将低位存储在前面,这种方式称作小端法。需要特别注意的是,还有一些机器,如Motorola的PowerPC,可以运行在任一模式中(更多这些机器的例子请猛击这里),这时就要看机器上运行的操作系统来判断字节序是大端法还是小端法。以上所说的字节序都是主机字节序。JAVA由于有虚拟机的存在,具有平台无关性和良好的可移植性,它使用的字节序都为大端法,和平台无关。

0x1000x1010x1020x10301234567

图1. 大端法 

0x1000x1010x1020x10367452301

图2. 小端法

    浮点型数据需要和整型一样,遵守相同的字节序规则,而字符串不管在什么机器中都是按字符出现的顺序存储的,如图:

0x1000x1010x1020x103'a''b''c''/0'

图3. 字符串的存储

    网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用大端法排序方式。开发网络程序时应注意主机字节序和网络字节序的转换。

 

    数据类型:C和JAVA

    下面先放出c和java数据类型各自的字节长度,其中JAVA的数据类型的长度在各种机器上是保持一致的。而c的数据大小可能在不同的机器上有变化,这可能在移植程序的时候引起一些问题。

C类型32位机字节数64位机字节数char11short22int44long48char *48float44double88

图4. c语言中数字类型数据的大小(以字节为单位)--摘自CSAPP 

JAVA类型长度boolean1(位)byte1(字节,下同)char2short2int4long8float4double8

图5.  JAVA中基本类型的大小

    JAVA比c多了一个boolean类型,专门用来表示true和false,这个类型的数据只需要1位就能表示;另外JAVA的char型表示范围更加广,可以表示unicode字符如中文、日文等,而c的char类型数据只可以表示ascii。JAVA的byte类型数据经常用来作位运算,或者将文件流等传输的内容转换成字节流。JAVA没有指针类型,而可以看出来,C里的指针类型总是全字长的,在32位机是32位,在64位机是64位。

 

    整数表示和运算

      有符号数VS无符号数

    在c语言中,可以表示整数的无符号数和有符号数,比如unsigned char和char,unsigned short和short,unsigned int和int,unsigned long和long。它们可以看做对存储器中同样的一段二进制串做出了不同的解释。为了方便讨论,我们把二进制串的位数局限在4位。有一个4位的二进制串1010,从右到左分别为第0位、第1位、第2位、第3位。它每一位的权值对应这一位所在的位置,从右到左即为2^0=1, 2^1=2, 2^2=4, 2^3=8。

    它的无符号数就是这些位为1的位置对应的权相加,这里就是8+2=10。

    有符号数则将最高位定义了一个负的权值,在这里即为-8,其它位的权值不变,它同样是将所有位为1对应的权值相加,这里是-8+2=-6。所以有符号数和无符号数在左首位为0的时候表示的数值相同,而左首位为1的时候,有符号数会表示一个负数。

    当表达式里同时出现有符号数和无符号数的时候,c会隐含的将有符号数强制转型成无符号数,然后两个数就都是非负的了。在表达式中同时使用有符号和无符号数常会导致一些与直觉不符的现象,比如 –1<0u 就会返回假,而2147483647>(int)2147483648u则返回真(>前的数为c中的INT_MAX)。

    JAVA中只有有符号数,没有无符号数。

    ps:本文中都假设有符号数使用二进制补码表示,因为大多数机器都是这么做的。

   

      移位

    c和java中都提供了移位运算符。比如 x<<3 就相当于 x*(2^3) ; 而 x>>3 相当于 x*(2^-3)。

    编译器经常使用移位来优化乘法运算,比如 x<<4+x 就会比 x*17 快上很多。

    移位分为左移和右移。当左移的时候很简单,高位移出去以后被简单丢弃,然后低位补0。右移的时候则有两种方法:逻辑右移和算术右移。逻辑右移只是简单的在高位补0,低位移出并丢弃。算术右移则在高位拷贝移位前的最高位,低位移出丢弃。c语言中,无符号数使用逻辑右移,而大部分编译器/机器的组合实现都对有符号数使用算术右移。

    JAVA只有有符号数,它默认的右移操作>>是算术右移,但是另外还专门提供了一个逻辑右移操作符>>>。

 

      溢出

    由于c系语言只支持固定精度的运算,所以有时候会出现溢出的情况。两个int型数据运算,比如相加或相乘,所得结果可能需要超过4个字节才能表示出来,这时候就会简单的将表示不出来的高位丢弃,从而出现计算结果错误。法国那个广为人知的火箭坠毁的案例就是因为这个原因。容易看出,上面提到的移位也可能因此出现溢出或精度问题。

    需要注意的是,这里的加法乘法可能不满足结合律,比如,INT_MAX + 1 –2  和  INT_MAX + (1-2)的结果会大不相同。

 

       转型:字节截断和扩展   

    类型转换时需要改变表示数据的字节长度,同时保持数值不变。如我们在程序中经常见的,从小的数据类型转到一个大的数据类型总是没什么问题的。比如把short型变量转成int的。无符号数的字节扩展只是在新的高位添加0来实现,这叫做0扩展;有符号数则在新的高位添加原来的最高位,即符号位,这叫做符号扩展。

    当从大的数据类型转到小的数据类型时,比如从int转到short。由于short为2个字节,而int为4个字节,则会将int的高位2个字节丢弃,低位2个字节保留。从而可能在转型中出现数据不一致问题,这也是为什么做这类强制类型转换时有的编译器会提醒可能会损失精度。

 

    浮点数表示和运算

     IEEE标准与浮点数表示

    浮点数将一个数表示为(± 0.xxx * 2^E)或(± 1.xxx * 2^E)的形式,由这个格式可知,要表示一个浮点数需要具有符号位,小数位和指数位三个部分。IEEE为在计算机上如何表示浮点数提供了一个统一的标准,目前所有的计算机都支持这个标准。在c语言的float中,有1个符号位,8个指数位和23个小数位。而double则由1个符号位,11个指数位和52个小数位构成。

    根据指数位的表示,可以将编码表示的数值分为三种情况。在下面表述中,E表示要表示的实际数值的指数,e表示浮点数二进制串中指数位表示的无符号数的值。bias表示偏置值,它等于 2^(k-1) – 1, k为指数位的位数。E和e的转换需要通过这个偏置值。

    1.当指数位全为1时。若小数位全为0,则表示的数值为无穷,符号位为0时就是正无穷,符号位为1就是负无穷;若小数位不全为0,则表示 NaN,即 Not a Number, 比如根号-1,某些应用中,也用来表示未初始化的数据。

    2.当指数位全为0时。E = 1 – bias. 二进制串中的小数位就是实际数值的小数位。如二进制串中小数位为 1101,则表示0.1101。

    3.当指数位不全为0,也不全为1时。此时 E = e – bias . 这时二进制串中的小数位省略了最高位的1。即如二进制串中小数位为1101,则表示1.1101。

   

     类型转换:整型 and 浮点型

    1.从int转到float,不会溢出,但是可能有精度损失。

    2.从int、float转到double,能够保留精确的数值。

    3.从float、double转到int,值会被截断,可能发生溢出。

    4.从double转到float,值可能溢出成正无穷或负无穷,或者损失精度。

    这篇文章(请猛击)对浮点数的表示精度问题描述的很清楚,也提到了扩展精度的问题。这里摘过一句话来:精确是偶然的,误差是必然的。如果做数值算法,惟一能做的就是误差不积累,其它的就不要奢望了。还有这里的一句话:永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。

参考:《深入理解计算机系统》(CSAPP)

        网络字节序VS主机字节序 - jjkkww的博客 http://blog.csdn.net/jjkkww/archive/2009/02/16/3895893.aspx

          JAVA数据类型转换 – hbrqlpf的博客 http://blog.csdn.net/hbrqlpf/archive/2008/03/17/2191296.aspx

        IEEE 754 浮点数的表示精度探讨 – bossin的博客 http://www.cnblogs.com/bossin/archive/2007/04/08/704567.html

          PHP Manual  http://php.net/manual/zh/language.types.float.php

     

原创粉丝点击