C++数据类型:浮点数
1. 浮点数的概念与表示
在C++中,浮点数用于表示带有小数部分的数值。浮点数在内存中的表示遵循IEEE 754标准。
浮点数类型主要有float(单精度浮点数)、double(双精度浮点数)和long double(扩展精度浮点数,不同编译器实现可能有所不同)。
例如,float类型通常占用32位(4字节)内存,double类型通常占用64位(8字节)内存。
2. float类型
定义与初始化:
可以通过直接赋值的方式定义和初始化float类型的变量。例如:
float num1 = 3.14f;
注意在给float类型变量赋常量值时,常量后面需要加上f或F后缀,以区分是float类型而不是double类型。
精度和范围:
float类型的有效数字大约为6 - 7位。
其数值范围大致为(1.175494351e - 38)到(3.402823466e + 38)。
运算特性:
在进行算术运算时,由于其有限的精度,可能会出现舍入误差。例如:
float num2 = 0.1f; float num3 = 0.2f; float sum = num2 + num3; std::cout << "0.1f + 0.2f的结果: " << sum << std::endl; // 结果可能不是精确的0.3,而是接近0.3的值,如0.30000001
3. double类型
定义与初始化:
同样可以直接赋值来定义和初始化double类型变量。例如:
double num4 = 3.1415926;
不需要添加特殊后缀(如果添加d或D后缀也可以,但通常省略)。
精度和范围:
double类型的有效数字大约为15 - 16位,比float类型精度更高。
其数值范围大致为(2.2250738585072014e - 308)到(1.7976931348623157e + 308)。
运算特性:
虽然double比float精度高,但在一些复杂计算中仍然可能存在舍入误差。例如:
double num5 = 1.0 / 3.0; std::cout << "1.0 / 3.0的结果: " << num5 << std::endl; // 结果是一个近似值,如0.3333333333333333
4. long double类型
定义与初始化:
例如:
long double num6 = 3.14159265358979323846L;
常量后面需要加上L或l后缀(建议使用L,因为l容易与数字1混淆)。
精度和范围:
long double的精度和范围在不同编译器和平台上可能有较大差异。在一些平台上,其精度可能高于double,有效数字可能达到18 - 19位甚至更多,范围也可能更大。
运算特性:
同样会存在舍入误差,但由于其更高的精度,在一些对精度要求极高的计算中可能会比double更合适。
5. 浮点数的输入输出
输入:
使用std::cin进行输入。例如:
float inputFloat; std::cin >> inputFloat;
输出:
使用std::cout进行输出。例如:
double num7 = 2.71828; std::cout << "e的近似值: " << num7 << std::endl;
6. 类型转换
隐式转换:
在表达式中,float类型会自动转换为double类型进行计算(因为double精度更高)。例如:
float num8 = 1.2f; double result = num8 + 3.4; // num8会隐式转换为double类型后再计算
显式转换(强制类型转换):
可以使用static_cast等方式进行显式转换。例如:
double num9 = 3.14159; float num10 = static_cast<float>(num9);
十进制小数表示浮点数
float singlePrecision = 3.14f; double doublePrecision = 3.1415926; long double longDoublePrecision = 3.14159265358979323846L;
对于float类型,为了与double类型区分开,如果直接写小数常量,默认是double类型。如果要表示float类型的浮点数,需要在小数后面加上f或F后缀。
对于long double类型,在小数后面加上L或l(建议使用L,因为l容易与数字1混淆)后缀。
科学计数法表示浮点数
科学计数法的格式为:mantissa E exponent,其中mantissa是尾数部分,E(也可以是e)表示10的幂次,exponent是指数部分。例如:
float num1 = 3.0e-2f; // 表示0.03 double num2 = 6.02e23; // 阿伏伽德罗常数的近似值 long double num3 = 1.23e-5L; // 表示0.0000123
在科学计数法表示中,同样需要注意float类型后面加f或F后缀,long double类型后面加L或l后缀。
7. float类型存储格式
float类型的变量在内存中是按照IEEE 754标准进行存储的,其存储方式分为三个部分:
1. 符号位(Sign bit):占用1位。用于表示浮点数的正负,0 代表正数,1 代表负数。例如,对于数值 3.14,其符号位为 0;而对于数值 -2.718,其符号位为 1。
2. 指数位(Exponent):占用8位。存储科学计数法中的指数数据,但并不是直接存储指数的真值,而是采用了一种偏移的方式进行存储。在单精度浮点数中,指数部分的偏移量为 127。例如,如果一个浮点数经过计算得到的指数为 5,那么在存储时,实际存入的指数值为 $5 + 127 = 132$;如果指数为 -3,存入的指数值为 $-3 + 127 = 124$。这样做的目的是为了能够用无符号数来表示指数,简化了硬件实现。
3. 尾数部分(Mantissa):占用23位。用于存储小数点后的有效数字。不过,在存储时,默认最高位的 1 是不存储的,因为经过归一化处理后,浮点数的首位总是 1(除了特殊情况),这样可以节省一位存储空间,从而提高存储精度。例如,对于数值 1.101,在存储时只存储 0.101,然后在读取时会自动加上最高位的 1。
以浮点数 13.625 为例:
1. 首先将其转换为二进制形式,整数部分 13 的二进制表示为 1101,小数部分 0.625 每次乘 2 取出整数留下小数,可得到 0.101,所以 13.625 的二进制表示为 1101.101。
2. 然后将小数点移动至整数部只有一位,得到 1.101101,小数点移动了 3 位,所以指数为 3,加上偏移量 127 后,阶码为 130(82H),二进制表示为 10000010。
3. 尾数部分则是去掉整数部分的 1.101101 中的 1,取 101101,后面再补齐到 23 位:10110100000000000000000。
最终,13.625 在内存中的存储形式就是由1位符号位(0,因为是正数)、8位阶码(10000010)和尾数(0101101 后面补齐若干个 0 到 23 位)组成:
+13.625就是:0100 0001 0101 1010 0000 0000 0000 0000 即:41 5A 00 00
-13.625就是:1100 0001 0101 1010 0000 0000 0000 0000 即:C1 5A 00 00
计算过程:
1. 首先将13.625转换为二进制:
对于整数部分13:
13÷2 = 6......1
6÷2 = 3......0
3÷2 = 1......1
1÷2 = 0......1
所以13的二进制表示为1101。
对于小数部分0.625:
0.625×2 = 1.25,取整数部分1,剩余小数0.25。
0.25×2 = 0.5,取整数部分0,剩余小数0.5。
0.5×2 = 1,取整数部分1,小数部分为0。
所以0.625的二进制表示为0.101。
则13.625的二进制表示为1101.101。
2. 然后将其转换为单精度浮点数(float)的二进制存储格式:
先将其转换为科学计数法形式1.101101×23。
对于单精度浮点数,符号位:因为13.625是正数,所以符号位为0。
指数位:指数为3,在单精度浮点数中,指数部分需要加上偏移量127,即3 + 127=130,130的二进制表示为10000010。
尾数位:去掉科学计数法表示中的前面的1,只取101101,然后将其补全到23位,得到10110100000000000000000。
所以13.625在内存中的单精度浮点数二进制表示为0100 0001 0101 1010 0000 0000 0000 0000。
将这个二进制数转换为十六进制数:每4位二进制数对应1位十六进制数。
0100对应4
0001对应1
0101对应5
1010对应A
0000对应0
0000对应0
0000对应0
0000对应0
所以13.625在内存中的单精度浮点数十六进制表示为415A0000。
8. double类型存储格式
double类型的变量在内存中也是按照 IEEE 754 标准进行存储的,其存储方式具体如下:
1. 符号位(Sign bit):占用 1 位。和 float类型一样,0 表示正数,1 表示负数。例如,对于数值 5.678(正数),其符号位为 0;对于数值 -9.123(负数),其符号位为 1。
2. 指数位(Exponent):占用 11 位。同样,存储的不是指数的真值,而是采用偏移的方式。在双精度浮点数中,指数部分的偏移量为 1023。比如,如果一个浮点数经过计算得到的指数为 8,那么在存储时,实际存入的指数值为 8 + 1023 = 1031,其二进制表示形式会存储在这 11 位中。
3. 尾数部分(Mantissa):占用 52 位。在存储时,默认最高位的 1 是不存储的,这样可以节省一位存储空间,从而提高存储精度。例如,对于一个经过归一化处理后的浮点数,其小数点后的有效数字部分会存储在这 52 位中。
double类型可以表示的数字的绝对值范围大约是 -1.79e+308 到 +1.79e+308,有效数字大约为 15 - 16 位。相比 float 类型,double 类型具有更高的精度和更大的数值范围。
9. long double类型存储格式
long double类型的变量在内存中的存储方式较为复杂且在不同的编译器和操作系统下可能会有所不同。以下是一般情况下的介绍:
1. 符号位(Sign bit):与float和double类型类似,long double的符号位也占用 1 位,用于表示数值的正负,0 代表正数,1 代表负数。
2. 指数位(Exponent):指数位的长度通常比double类型的更长,以支持更大范围的指数值。不过具体的位数没有统一的标准,不同的实现可能会有所差异。其存储方式也是采用偏移的方式,类似于double类型中指数部分的存储,但偏移量的值通常与double的不同。
3. 尾数部分(Mantissa):尾数部分的位数也比double类型更多,这使得long double能够表示更高的精度。同样,在存储时默认最高位的 1 是不存储的。
在常见的 C++ 编译器实现中,long double可能占用 80 位(10 个字节)或 128 位(16 个字节)。例如,在一些 Windows 系统上的 Visual C++ 编译器中,long double和double类型的存储方式和精度是相同的,都占用 8 个字节;而在一些 Linux 系统上的 GCC 编译器中,long double可能占用 12 个字节或 16 个字节,具体取决于编译器的设置和硬件平台。
总之,long double类型的存储方式是为了在尽可能节省存储空间的前提下,提供比double类型更高的精度和更大的数值范围,但由于缺乏统一的标准,其在不同环境下的具体表现可能会有所不同。
10. float类型浮点数的加法
用二进制模拟float型浮点数的加法A + B:
(1). 分解A和B:将A和B分别分解为符号位、指数位和尾数位。
假设A = 1.50,其二进制表示为0 01111110 10000000000000000000000,符号位为0,指数位为01111110(对应十进制的 126),尾数位为10000000000000000000000(去掉隐含的首位1后为0.1)。
假设B = 2.25,其二进制表示为0 01111111 11000000000000000000000,符号位为0,指数位为01111111(对应十进制的 127),尾数位为11000000000000000000000(去掉隐含的首位1后为0.11)。
(2). 指数对齐:
比较A和B的指数大小。
将指数较小的数的尾数向右移位,直到两个数的指数相等。在移位的过程中,注意要保持符号位不变。
在这个例子中,A的指数为 126,B的指数为 127,所以将A的尾数向右移一位,得到0 01111111 01000000000000000000000(此时相当于A = 0.75)。
(3). 尾数相加:
将对齐后的两个数的尾数相加。
如果相加结果的尾数大于1,则需要进行规范化处理,将尾数向右移位,指数加一。
这里A的尾数变为0.1(去掉隐含的首位1后),B的尾数为0.11,相加得到0.1 + 0.11 = 1.01(这里不考虑隐含的首位1)。
(4). 结果规范化:
如果尾数相加的结果小于1,则需要将尾数向左移位,指数减一,直到尾数大于等于1。
如果尾数相加的结果大于1,则需要将尾数向右移位,指数加一,直到尾数小于2。
在这个例子中,尾数相加结果为1.01,大于1,所以将尾数向右移一位,指数加一,得到0 10000000 10100000000000000000000。
(5). 处理符号位:
如果两个数的符号位相同,则结果的符号位与它们相同。
如果两个数的符号位不同,则需要根据具体情况确定结果的符号位。
这里A和B都是正数,所以结果也是正数,符号位为0。
11. float类型浮点数的减法A-B
(1). 分解A和B:
与加法步骤相同,将A和B分别分解为符号位、指数位和尾数位。
(2). 指数对齐:
同样比较A和B的指数大小,将指数较小的数的尾数向右移位,直到两个数的指数相等。
(3). 尾数相减:
将对齐后的两个数的尾数相减。
如果相减结果的尾数小于0,则需要进行借位处理,将尾数向左移位,指数减一。
假设A = 2.5,其二进制表示为0 01111111 10000000000000000000000
假设B = 1.5,其二进制表示为0 01111110 10000000000000000000000。
经过指数对齐后,A不变,B的尾数向右移一位,得到0 01111111 01000000000000000000000(此时相当于B = 0.75)。
然后进行尾数相减,A的尾数为0.1(去掉隐含的首位1后),B的尾数为0.1,相减得到0.1 - 0.1 = 0。
(4). 结果规范化:
与加法步骤中的规范化处理类似,如果尾数相减的结果小于1,则需要将尾数向左移位,指数减一,直到尾数大于等于1。
如果尾数相减的结果大于1,则需要将尾数向右移位,指数加一,直到尾数小于2。
在这个例子中,结果为0.0,需要进行规范化处理,将尾数向左移位,指数减一,直到尾数大于等于1。但是由于结果为0,无论怎么移位都无法满足条件,所以结果为0。
(5). 处理符号位:
如果两个数的符号位相同,则结果的符号位取决于相减的结果。如果结果为正数,则符号位为0;如果结果为负数,则符号位为1。
如果两个数的符号位不同,则需要根据具体情况确定结果的符号位。
在这个例子中,A和B都是正数,结果为正数,符号位为0。
需要注意的是,这种二进制模拟的方法只是为了说明float类型的加减法在底层的实现原理,在实际编程中,应该使用 C++提供的浮点数运算操作符来进行浮点数的加减法运算,这样可以保证运算的准确性和效率。同时,由于浮点数的精度有限,在进行浮点数运算时可能会出现舍入误差。