「C++」float、double、long double
总览
类型 | 字节数 | 比特数 | 有效数字 | 数值范围 |
---|---|---|---|---|
float | 4 | 32 | 6-7 | -3.410(-38)~3.410(38) |
double | 8 | 64 | 15-16 | -1.710(-308)~1.710(308) |
long double | 16 | 128 | 18-19 | -1.210(-4932)~1.210(4932) |
C语言和C#语言中,对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit, double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?不论是float还是double在存储方式上都是遵从IEEE的规范 的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。
- 符号位(Sign) : 0代表正,1代表为负
- 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
- 尾数部分(Mantissa):尾数部分
存储方式
R32.24和R64.53的存储方式都是用科学计数法来存储数据的。
十进制与二进制
8.25用十进制的科学计数法表示为 \(8.25*10^0\),计算机只识别二进制为\(1000.01\)。
二进制小数的计算过程:
0.25的二进制为0.01
0.25 * 2=0.5 取整是0 0.5 * 2=1.0 取整是1
0.8125的二进制为0.1101
0.8125 * 2=1.625 取整是1 0.625 * 2=1.25 取整是1 0.25 * 2=0.5 取整是0 0.5 * 2=1.0 取整是1
第一次所得到为最高位,最后一次得到为最低位。
小数点前,除2取余, 小数点后,乘2取整。 顺序是以小数点为中心,向两边散开。
120.5用二进制表示为:1110110.1,二进制的科学计数法可以表示为\(1.1101101*2^6\)。
在计算机中,任何一个数都可以表示成\(1.xxxxxx*2^n\)这样的形式,其中xxxxx就表示尾数部分,n表示指数部分
计算过程
所有的小数被转换成指数形式后,尾数的整数部分都为1,无需在内存中提现出来,所以干脆将其截去,只把小数点后面的二进制放入内存中的尾数部分(23Bits)。对于 1.0011101,尾数部分就是 0011101。
C语言把整数作为定点数,而把小数作为浮点数。定点数必须转换为补码再写入内存,浮点数没有这个过程,直接写入原码。小数被转换成指数形式后,指数有正有负,在内存中不但要能表现其值,还要能表现其正负。而指数是以原码形式存储的,没有符号位,所以要设计一个巧妙的办法来区分正负。
对于 float,指数占用8Bits,能表示从 0~255 的值,取其中间值 127,指数在写入内存前先加上127,读取时再减去127,正数负数就显而易见了。19.625 转换后的指数为 4,4+127 = 131 = 1000 0011。
综上所述,float 类型的 19.625 在内存中的值为:
0 - 10000011 - 001 1101 0000 0000 0000 0000
代码验证:
#include <stdio.h>
#include <stdlib.h>
int main()
{
typedef struct _FP_SIGLE{
unsigned int nMantissa : 23; //尾数部分
unsigned int nExponent : 8; //指数部分
unsigned int nSign : 1; //符号位
} FP_SINGLE;
float a = 19.625;
FP_SINGLE* p = (FP_SINGLE*)&a;
printf("%d, %#X, %#X\n", p->nSign, p->nExponent-127, p->nMantissa);
system("pause");
return 0;
}
//运行结果:
//0, 0X4, 0X1D0000
//C语言不能直接输出二进制形式,一般输出十六进制即可,十六进制能够很方便地转换成二进制。
精度
精度指测量值与真实值的接近程度,在C语言中表现为输出值和真实值的接近程度。
float 和 double 的精度是由尾数的位数决定。内存中的尾数只保存了小数点后面的部分,其整数部分始终是一个隐含着的“1“,它是不变的,不会对精度造成影响。
float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即 float 的精度为 6~7 位有效数字。
double:2^52 = 4503599627370496,一共16位,同理,double 的精度为 15~16 位。
取值范围和近似值
float 和 double 在内存中的指数和尾数的位数都是有限的,小数过大或过小都会发生溢出。float 的取值范围为 -2^128 ~ +2^128,也即 -3.40E+38 ~ +3.40E+38;double 的取值范围为 -2^1024 ~ +2^1024,也即 -1.79E+308 ~ +1.79E+308。
当小数的尾数部分过长时,多出的位数就会被直接截去,这时保存的就不是小数的真实值,而是一个近似值。在《C语言中的浮点数(float,double)》一节的示例中,我们看到 128.101 的输出结果就是一个近似值。
128.101 转换成二进制为 10000000.0001100111011011001000101101,向左移动7位后为 1.00000000001100111011011001000101101,由此可见,尾数部分为 000 0000 0001 1001 1101 1011 001000101101,将多出的二进制截去后为 000 0000 0001 1001 1101 1011。下面的代码有力地证明了这一点:
#include <stdio.h>
#include <stdlib.h>
int main()
{
typedef struct _FP_SIGLE{
unsigned int nMantissa : 23; //尾数部分
unsigned int nExponent : 8; //指数部分
unsigned int nSign : 1; //符号位
} FP_SINGLE;
float a = 128.101f;
FP_SINGLE* p = (FP_SINGLE*)&a;
printf("%f\n", a);
printf("%d, %#X, %#X\n", p->nSign, p->nExponent-127, p->nMantissa);
system("pause");
return 0;
}
//运行结果:
//128.100998
//0, 0X7, 0X19DB
参考:
1、https://blog.csdn.net/u011362822/article/details/30245967