所有整数(正负零)在内存中都是以补码的形式存在。
对于一个正整数来说,它的补码就是它的原码本身。对于一个负整数来说,它的补码为原码取反即反码再加1。具体的计算方式这里就不再赘述。显然,补码是01流,在内存中直接存储,当然,关于内存怎么存1/0,这涉及到微电子的知识,大概来说就是储存为高低电平,具体可去了解二极管——三极管——MOS管知识(模拟电路——数字电路——集成电路)。
每个字符都对应着一个ASCII码(这里仅以ASCII作为引子,对于更复杂的GBK或UTF等这里不作讨论),存储一个字符变量其实就是存储其对应的ASCII码,而ASCII码本质就是0-255的整数,所以存储字符型的数据其实就是存储整型数据。
实型数据也叫浮点型数据, 在计算机中同样也是以二进制的方式存储(所有的数据都是),关键在于计算机是以什么形式来储存浮点数的,或者说如何将浮点数(十进制的小数)转化为计算机储存的二进制数。
以下为例,初步形成认识:
例:12.63
首先整数部分为:1100
小数部分:0.63*2=1.26,得小数后第一位为1,0.26*2=0.52得小数的第二位为0,0.52*2=1.04得第三位为1,0.04*2=0.08得第四位为0,0.08*2得第五位,以此类推,于是得到最后的结果为1100.10100001b(省略了后面的计算)。
那么,计算中可以直接将12.63存储为110010100001吗,很显然 ,不行。因为这样没有标记小数点的位置,举个例子,读的时候怎么知道这个值代表的是1100.10100001b还是1.10010100001b呢?
一个很自然的想法是:规定好浮点型数据的整数位和小数位边界,如32位的float,我们规定好前10位为整数位,后22位为小数位。这乍看起来是个不错的主意,但是好像违反了一个基本常识:float范围要比int大得多,如果float真是以我们刚才所说的形式存储的,那么float范围必然比int小,因为二者都是32位,而float现在还要腾位置来存小数位。
显然,在计算机真正实现中,不是这么做的,实际上,C/C++/Java都遵循IEEE 754的标准,简单说来就是以科学计数法的形式来存储的。
先回顾一下科学计数法,将浮点数分为四个部分:符号位+整数位+小数位+指数位。
以1263.555555555555555555555为例,存储时可存储为:+1.26355555*10^3(这里假设小数点只能精确到8位),则符号位:0,整数位:1,小数位:0.26355555,指数位:3,将4个部分的整数的二进制值“塞进”float/double的相应位置即可。
当然,以上只作为引子启发思考,真正的IEEE 754中存储float/double只有三个区域,对各个区域具体数位长度也有相关规定,具体可见:C语言浮点数存储方式,或者更严谨地,直接去查IEEE 754标准。
当然,如果能在了解了计算方法之后,手工计算同C/C++/Java的实际运行结果相映证,那将更好。(有空我会更新我的实验学习结果)
在支持中文的环境下,中文同样是利用映射表(如GBK表),映射到整数(实际就是二进制数)上去存储的,这一点中英文及标点符号没有本质区别,只是编码集(映射关系)不一样而已。
通过上面的学习,能得到几点启发:
之前我们1100.10100001b来表示12.63这个浮点数,但实际上,0.63是一直“乘不净”的,一直会有尾数,这种情况下,无论float/double分配了多少小数位都是无法完整存储12.63,最终能取多少位取决于编译器对应的浮点类型数据的给小数位分配的bit数,bit越多越精确。故double比float可以提供的数位更多,故而更精确。
而关于哪些数字可以乘干净,这一点可以回到进制定义(跟十进制的关系)中得到解答:
例:1100.1010 = 1 * 2^3 + 1 * 2^2 + 0 * 2^1 + 0 * 2^0 + 1 * 2^-1 +0 * 2^-2 +1 * 2^-3 + 0 * 2^-4
反向思考,如果一个浮点数小数部分不能表示为 a1*1/2 +a2*1/4 + a3*1/8 + ... 的和形式(a可以为1或0),如0.75, 0,875,则是永远“乘不净”的,所以如0.1,0.2, 0.3这样如此“简单”的小数,在计算机内存存储的都是其近似值,而不可能准确表示。
当然,由于float/double的数位是有限的,即使一些能够表示为2的整数幂之和(理论上可以“乘净”)的数也有可能无法精确存储,如1+1*2^(-100)这个数,float/double都不可能有存储2^(-100)的数位。
这给了我们提示:char,short,int,long在计算机内存中是精确表示的,没有一点“失真”,存进是什么样出来还是什么样。但是对于float/double,可以说能够“完璧归赵”的极其少:像1.25 —— 能够表示为2的整数幂和,且不会超过float/double的有限数位的数。这提示我们在实际编码中:
1)存储为float,double类型以后,基本可以认为计算机内存中存储的只是起近似值,不可刻舟求剑。
2)尽量避免带有浮点数的大小判断,如真的必须,可以使用abs(x-y)<0.000001这种形式;
3)尽量避免很小的浮点数运算,由于浮点数存储的特点,小的浮点数精确度丢失的影响将会很大。真的有必要可以使用已有的函数库,如大整数等进行运算。
① 既然char类型的数据在计算机中是以整数形式存储,那么计算机怎么识别int和char呢,有标记吗?(CPU层次不必说,CPU只能执行机器语言,无法识别各种数据类型,但是编译器怎么识别char和int呢)。又如,32位的float和64位的double在计算机中怎么识别首尾呢,如果连续混杂存储,计算机怎么识别从哪里到到哪里是一个float,哪里又是一个double呢
自己的思考(很重要,提出问题固然是好,但是在问题显而易见的情况下,这并不稀奇,在此基础上提出自己的解决方案才更有意思):
其实从上面总结来看,所有类型的数据在内存中都要转化为二进制码(01)存储,以前也知道这么一个“结论”,但是涉及到不同类型数据具体的操作方法还是模棱两可,从上面来看,既然都是二进制,那么char类型和int存储,或者float和double的区分就不应该成为特例问题,这个问题存在于所有类型的数据区分中,应该说,计算机怎么识别内存中不同类型的数据呢,怎么区分“首尾”而不至于混淆呢?
我猜想的方法:分区,在数据区针对不同的数据类型单独分区,这样 每个区内部数据格式长度是固定已知的,这样到具体的数据区存取查找就可以了。