浮点型常见的类型有:float、double、long double。
浮点型表示的范围:float.h中定义(定义了浮点数取值范围的相关信息)。
下面分别是float.h文件中double、float、long double 的精度、最大值、最小值。(下个Everything 直接搜 float.h 特别快!)
整型的取值范围在 limits.h 文件中,如下:
以后如果想直接用float/整型的最值的时候,可以引头文件然后直接用,比如用 int 最大值可以直接用 INT_MAX (但是要包含头文件),下面回归正题。
下面这段代码:
#include <stdio.h>
int main()
{
int n = 9;
float* pFloat = (float*) &n; // 因为 n 地址是 int* 类型,所以强制类型转换为 float* ,才能给 pFloat
printf("n的值是:%d\n", n); // n 以 %d 形式打印是 9 ?
printf("*pFloat的值是:%f\n", *pFloat); // *pFloat 也访问 4 个字节,以 %f 打印是 9.0 ?
*pFloat = 9.0;
printf("n的值是:%d\n", n); // 也是 9?
printf("*pFloat的值是:%f\n", *pFloat); // 也是 9.0?
return 0;
}
运行代码如下:
第 2 个与第 3 个是错的,所以不是想的那样,那怎么回事呢?
int n = 9;
float* pFloat = (float*)&n; // 因为 n 地址是 int* 类型,所以强制类型转换为 float* ,才能给 pFloat
printf("n的值是:%d\n", n); // n 以 %d 形式打印是 9 ?
// n 是整型,以整型打印是 9 没有问题
//
printf("*pFloat的值是:%f\n", *pFloat); // *pFloat 也访问 4 个字节,以 %f 打印是 9.0 ?
// 但是 n 强转了 float* 之后,以 float* 打印是访问 4 个字节的
// *pFloat 解引用取 n 的时候,站在 pFloat 的角度去看的时候,它认为是内存放的是 float* 的类型
// 然后当我们解引用拿的时候访问的是 4 个字节,并且认为放的是浮点型数据(但我们放的是整型)
// 所以我们以整型方式放进去,以浮点型取的时候,不是 9 ,所以整型与浮点型是有差异的
*pFloat = 9.0;
printf("n的值是:%d\n", n);
// 与上面相反,浮点型放进去,整型拿出来
// 以浮点型方式放进去,以整型取的时候,不是 9 ,所以整型与浮点型是有差异的
printf("*pFloat的值是:%f\n", *pFloat);
// 浮点型存 浮点型取 没问题 只是精度原因少些几个 0
所以可以得出:
我们以整型方式放进去,以浮点型取的时候;或者以浮点型方式放进去,以整型型取的时候,两个都不是预测的那样,所以整型与浮点型在内存中的存储方式是有差异的。
浮点数存储规则:
根据国际标准 IEEE (电子和电子工程协会) 754,任意一个二进制浮点数 V 可以表示成下面的形式:
1. (-1) ^ S * M * 2 ^ E
2. (-1) ^ S 表示符号位,当 S = 0,V 为正数;当 S = 1,V 为负数。
3. M 表示有效数字,大于等于 1 ,小于 2。
4. 2 ^ E 表示指数位。
举例说:
5.25 —— 10 进制的浮点数
二进制表示为:101.01 (为什么这么写?)
扩展一下:
所以 5 的二进制 101.01 按照标准写法:(小数点向左移 2 位)
再举个例子:
3.125 —— 10 进制浮点数
二进制数为:11.001 (转法与 5.25 一样)
那么 3.125 的二进制 11.001 标准写法是:
那么浮点数这样写怎么存的?
IEEE 754规定:
对于 32 位的浮点数,最高的 1 位是符号位 S,接着的 8 位是指数 E,剩下的 23 位为有效数字 M,如下:
对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数 E,剩下的 52 位为有效数字 M,如下:
IEEE 754 对于有效数字 M 和指数 E,还有一些规定:
1 <= M < 2,所以 M 也可以写成 1. xxxxx....的形式,xxxxxx 表示小数部分。
IEEE 754 规定,在计算机内部保存 M 时,默认这个数的第一位总是 1 ,因此可以被舍去,只保存后面的 xxxxxx.... 部分,比如保存 1. 10001 的时候,只保存 10001 ,等到读取的时候,再把第一位的 1 加上,这样可以节省 1 位有效数字,以 32 位为例,留给 M 只有 23 位将第一位的 1 舍去等于可以保存 24 位有效数字。(精度更高)
指数 E 比较复杂。
首先,E 是无符号整数(unsigned int),所以如果 E 为 8 位,它的取值范围是 0 — 255;如果 E 为 11 位,它的取值范围为 0 —— 2047 。但科学计数法中的 E 是可以负数的,所以 IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数,对于 8 位的 E ,这个中间数是 127 ;对于 11 位的 E ,这个中间数是 1023,比如 2^10 的 E 是10,所以保存成 32 位浮点数时,必须保存成 10+127 = 137 即 10001001。
然后,指数 E 从内存中取出还可以分为三种情况:
E 不全为 0 或者 不全为 1:
这时候,浮点数用下面的规则表示,即指数 E 的计算值减去 127 (或者1023),得到真实值,再将有效数字 M 前加上第一位的 1。
例如:
#include <stdio.h>
int main()
{
float a = 2.125;
// 2.125 二进制为===> 10.001
// (-1)^0 * 1.0001 * 2^1
// S = 0
// M = 1.0001
// E = 1
// 因为是正数最高位是 0
// 因为要加中间数所以 1 + 127 = 128 ; 128 的二进制是 ==> 10000000
// M 的话,不存小数点前面的数,只存小数点后面的数,但是位不够后面补 0
// 所以存储到内存为:
// 01000000000010000000000000000000
//
// 可以查看一下内存中是不是这个数?
// 0100 0000 0000 1000 0000 0000 0000 0000
// 4 0 0 8 0 0 0 0
// 0x40080000
return 0;
}
调试查看内存如下:
根据小端存储规则,在内存中存的就是 0x40080000
比如:
0.5 (10 进制)的二进制形式为 0.1 ,由于规定正数部分必须为 1,即将小数点右移 1 位,则位 1.0*2^(-1),其阶码为 -1 + 127 =126(如果是 11 位的 E 的话=> -1+1023=1022),表示为 01111110,而尾数 1.0 去掉整数部分为 0 ,补齐 0 到 23 位 00000000000000000000000,则其二进制表示为:
0 01111110 00000000000000000000000
E 全为 0:
这时,浮点数的指数 E 等于 1 - 127(或者 1 - 1023)即为真实值。有效数字 M 不再加上第一位的 1,而是还原为 0.xxxxxx....的小数。这样做是为了表示 ±0 ,以及接近于 0 的很小的数字。
E 全为 1:
这时,如果有效数字 M 全为 0,表示 ± 无穷大(正负取决于符号位 S)。
回到最开始的题:
int n = 9;
// 00000000000000000000000000001001 —— 9 的二进制(原码、反码、补码相同)
float* pFloat = (float*)&n;
printf("n的值是:%d\n", n); // 以 %d 打印 还是 9
printf("*pFloat的值是:%f\n", *pFloat);
// 以浮点数打印的话, pFloat 就当它是浮点型, 就按浮点类型算:
// 最高位 S 是 1 bit ; M 是 8 个bit ; E 是 23 个bit
// 0 00000000 00000000000000000001001
// 这里 E 是全 0, 所以 E 的真实值是 1 - 127 = -126
// M 就是 0.00000000000000000001001 (不在加前面的 1)
// (-1)^0 * 0.00000000000000000001001 * 2^-126
// M 已经很小了, 再 *2^-126 这是无限接近于 0 的数
// 所以打印 0
*pFloat = 9.0;
// 1001.0 —— 9.0 二进制
// 9.0 科学表示法==> (-1)^0 * 1.001 * 2^3
// S = 0
// M = 1.001
// E = 3
// 往内存存的话是:
// E 加上修正值 127 ====> 3+127=130 ; 130 二进制是 10000010
// 0 10000010 00100000000000000000000
printf("n的值是:%d\n", n);
// 以 %d 打印的话, n 就把那串二进制当作是有符号数, 高位是 0 那就是正数(原码、反码、补码相同)
// 那么打印的就是 01000001000100000000000000000000 ===> 1091567616
printf("*pFloat的值是:%f\n", *pFloat); // 浮点数放进去拿出来还是 9.0
运行结果对比一下,所以浮点型在内存中的存储就介绍完了。