A brief analysis of how integers and floating point numbers are stored in memory

The way integers and floating-point numbers are stored in memory are completely different. Let’s briefly analyze their storage methods using code:

1, positive integer

#include <stdio.h>

// 举例说明整数与浮点数在内存中的存储方式
int main()
{
	// 1、正整数
	short a = 10;
	/*
	10的二进制为1010,short类型为2个字节,就是16bit,所以变量a转换为二进制是
	a -> 0000 0000 0000 1010
	将上面的二进制转换为十六进制为 00 0a。
    该二进制在内存中可能存为 00 0a(大端存储)或 0a 00(小端存储)。
    大小端存储与很多因素有关,具体可找度娘细谈。
	*/
	printf("这是short类型变量a=10的内存地址:%p\n",&a);
	// 2、负整数


	// 3、浮点数


	// 在这加调试
	getchar();

	return 0;
}

[Small note]: The maximum hexadecimal value f corresponds to 1111 in binary, so each hexadecimal number corresponds to 4 bits, which is half a byte, so we often see two hexadecimal arrays in memory as One group represents a complete byte (i.e. 8 bits). Here 0a 00 represents 2 bytes, which is exactly the range of the short type.

2. Negative integers

#include <stdio.h>

// 举例说明整数与浮点数在内存中的存储方式
int main()
{
	// 2、负整数
	short b = -23;
	/*
	负整数的存储涉及到原码、反码,补码,事比较多,得多唠2块钱的。
	变量b转换为二进制为 1000 0000 0001 0111(第1bit是符号位,0代表正数,1代表负数),我们称这个码为【原码】。
	那么计算机是直接把这个码存储到内存中吗?显然答案不是的。
	计算机CPU中只有加法处理器而没有减法处理器(就好像电饭煲不仅能做干饭还能做粥,总不能做干饭一个电饭煲,做粥再用一个电饭煲吧。其实主要还是为了节约成本)
	因此 10 - 23 = 10 + (-23),这减法不就可以用加法来做了嘛。
	 10     -> 0000 0000 0000 1010
	-23     -> 1000 0000 0001 0111
	---------------------------------
	10 - 23 -> 1000 0000 0010 0001 -> -33
	这个结果明显不对啊!!!!!!!!!!!!!!!!!!!!
	这时天降大牛,牛牛说负整数不能用原码,得用反码来算。反码就是符号位不变,其他位1变0,0变1。[注]正整数原码与反码是一样的,不用变。
	我们用反码再重新计算该数
	 10(反码)      -> 0000 0000 0000 1010    (正整数原码与反码一样)
	-23(反码)      -> 1111 1111 1110 1000    (-23的原码=1000 0000 0001 0111)
	-------------------------------------------
	10 - 23 (反码) -> 1111 1111 1111 0010 -> 这里的结果是反码啊,下面还得转换为原码才是真正的结果.
	10 - 23 (原码) -> 1000 0000 0000 1101 -> -13
	===============结果对了,说明负整数用反码是正确的,撒花撒花================
	到此为止真的就完全正确了吗?那你也太小瞧计算机的智慧了吧!
	既然小的整数减大的整数结果是正确的,那也未必能证明大的整数减小的整数结果也是正确的。让我们再计算一道题
	 50(反码)      ->   0000 0000 0011 0010    (正整数原码与反码一样)
	-23(反码)      ->   1111 1111 1110 1000    (-23的原码=1000 0000 0001 0111)
	-------------------------------------------
	50 - 23 (反码) -> 1 0000 0000 0001 1010 -> 由于最左边那个1已经超出short取值范围,要舍去,因此正确的反码如下 
	50 - 23 (反码√) ->  0000 0000 0001 1010 -> 这里的结果是反码啊,下面还得转换为原码才是真正的结果.
	50 - 23 (原码)  ->  0000 0000 0001 1010 -> 26 (正整数的原码与反码是一样的)
	这结果不对啊,差1啊,快去西方请大牛!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	大牛一来,看这结果也发蒙了,这个我解决不了,得请我哥来。于是大大牛来了。
	大大牛一看,笑了。就这?一个能打的都没有。不就差1嘛,小case,差1就加1嘛,反码加1变成补码再运算不就行了。
	 50(补码)      ->   0000 0000 0011 0010    (正整数原码、反码、补码3码合一,都一样)
	-23(补码)      ->   1111 1111 1110 1001    (-23的原码=1000 0000 0001 0111=>反码=1111 1111 1110 1000=>再加1变补码)
	-------------------------------------------
	50 - 23 (补码) -> 1 0000 0000 0001 1011 -> 由于最左边比特那个1已经超出short取值范围,要舍去,因此正确的补码如下
	50 - 23 (补码√) ->  0000 0000 0001 1011 -> 这里的结果是补码啊,下面还得转换为反码
	50 - 23 (反码)  ->  0000 0000 0001 1011 -> (正整数的反码与补码是一样的)
	50 - 23 (原码)  ->  0000 0000 0001 1011 -> 27 (正整数的原码与反码是一样的)
	这回大的整数减小的整数结果对了,那用补码计算小的整数减大的整数能对吗?
	 10(补码)      ->   0000 0000 0000 1010    (正整数原码、反码、补码3码合一,都一样)
	-23(补码)      ->   1111 1111 1110 1001    (-23的原码=1000 0000 0001 0111=>反码=1111 1111 1110 1000=>再加1变补码)
	-------------------------------------------
	10 - 23 (补码) ->   1111 1111 1111 0011 -> 这里的结果是补码啊,下面还得转换为反码
	10 - 23 (反码)  ->  1111 1111 1111 0010 -> 补码减1就是反码
	10 - 23 (原码)  ->  1000 0000 0000 1101 -> -13
	==================这回终于完全正确了,撒花,撒花====================
	你说啥,我是蒙对的?就算我是蒙的大牛还能是蒙的啊,就算大牛是蒙的,大大牛还能是蒙的啊,银行没算错少给你钱吧!
	到此我们总结出无论是正整数还是负整数,在内存中都是以补码的方式存储的,这样做的好处是计算方便,坏处是在内存中取值时得换算回原码才行。
	===============================================================
	那么-23的内存地址到底存储的是什么呢?
	-23(补码)      ->   1111 1111 1110 1001
                         f    f    e    9
	答案:ff e9(大端存储)或 e9 ff(小端存储)。
	*/
	printf("这是short类型变量b=-23的内存地址:%p\n", &b);

	// 3、浮点数


	// 在这加调试
	getchar();

	return 0;
}

3. Floating point numbers

#include <stdio.h>

// 举例说明整数与浮点数在内存中的存储方式
int main()
{
	// 3、浮点数
	float c = 19.14f;	// 你问我加f干啥?19.14对计算机来说默认是double型,加f告诉计算机我这是浮点数,不要截取转换了。
	/*
	浮点数与整数的存储方式完全不一样,也复杂得多,必须唠10块钱的。
	浮点指小数点位置不固定,这就涉及到科学计数法了。
	十进制举例:
		12345.6789 -> 1.23456789 * 10^4
		0.000987654321 -> 9.87654321 * 10^(-4)
		[注]整数部分范围[1,10)即大于等于1,小于10,因为是十进制,整数部分不会让你超过10的。
	二进制举例
		101100.010111 -> 1.01100010111 * 2^5
		0.0001011001 -> 1.011001 * 2^(-4)
		[注]整数部分固定是1,因为是二进制,整数部分不会让你超过2的。为啥不会是0?晕,加钱。
		你说二进制没科学计数法?凭啥十进制有二进制就没有。
		二进制是以2作为底的啊,可不是10。
	好了,回归正题。
	将19.14转换为二进制小数
	先将整数部分19转换为二进制 19->10011
	再将小数部分.14转换为二进制 .14->.00100011110101110001.... 十进制小数转换为二进制小数方法(小数部分乘以2,取整数部分,剩下小数部分继续乘2,再取整数部分......直到小数部分为0或取到固定位数了)
	这里.14取不尽,就只取到20位小数了,加上整数部分够23位了,余下的不算了。
	则 19.14 -> 10011.00100011110101110001
	用科学计数法表示:10011.00100011110101110001 -> 1.001100100011110101110001 * 2^4
	========重点到了=========
	公式:flt = (-1)^sign × mantissa × base^exponent
	flt  -> 是要表示的小数。
	sign -> 用来表示该小数的符号,它的取值只能是0或1。取值为0表示正数,取值为1表示是负数。
	base -> 是基数,或者说进制,它的取值大于等于 2(例如2表示二进制、10表示十进制、16表示十六进制)。
	mantissa -> 尾数,或者说精度,是base进制的小数,范围 1 ≤ mantissa < base;
	exponent -> 指数,是一个整数,可正可负,并且为了直观一般采用十进制表示
	以float在内存中存储举例:
       	     32bit        31~24bit                  23~1bit  
	      符号位(1b)      指数部分(8b)              尾数部分(23b)
	以19.14 -> 10011.00100011110101110001 -> 1.001100100011110101110001 * 2^4 举例
	符号位:该小数为正,符号位为 【0】。
	指数部分:该小数的指数部分为4。但这里不能直接填4的二进制。因为指数有可能为负。
			解决方法,可以将该指数部分的8个bit中最高bit作为符号位,其他bit填指数的二进制,但该方法被否,因为前面已经有一个整个小数的符号位了。出现2个符号位总感觉不对劲。
			因此就采取一种折中的办法,8个bit(范围0-255),取中间数127【double的指数部分占11bit,取中间数1023】,用指数加上127后的值放入到指数部分。
			比如说指数为4,则127+4=131,将该数的二进制数放入。读取时再减去127就能得到指数为4了。
			131->10000011,小数19.14的指数部分放入【10000011】。
	尾数部分:因为整数部分固定为1,因此直接写入小数部分就行,还能省1bit。这里放入【00110010001111010111000】,超过的部分舍去。
	到此得到该小数在内存中的正确存储方式了。

	符号位(1b)      指数部分(8b)              尾数部分(23b)
	  0             10000011             00110010001111010111000 (如果位数不足可以用0补足,这里.14能算够位数,不但没有补0还舍去一部分呢)
	整理后得:
	  0100 0001 1001 1001 0001 1110 1011 1000
	转换为十六进制
	    4    1   9    9     1    e    b    8

	内存存储结果: 41 99 1e b8(大端)  或 b8 1e 99 41(小端)
	*/
	printf("这是short类型变量c=19.14的内存地址:%p\n", &c);

	// 在这加调试
	getchar();

	return 0;
}

=============End==============

Guess you like

Origin blog.csdn.net/lag_csdn/article/details/134641790