一. 数据类型定义
1.float类型
占用 4 个字节(32 位)的存储空间。 单精度,可以精确到7位小数
double类型
占用 8 个字节(64 位)的存储空间,双精度,可以精确到15位小数
double是常用的
float f=3.14; //编译报错
//3.14默认是double类型,8个字节
//f变量是4个字节,大容量不能直接转换成小容量
//修改
//第一种方案:不存在类型转换
float f =3.14F;
//第二种方案:借助强制类型转换
float f=(float) 3.14;
另外,可以通过以下程序的输出结果看到,double精度高于float:
double d = 1.5656856894;
System.out.println(d);
float f = 1.5656856894F;
System.out.println(f);
2.浮点型的字面量默认都会被当作double类型来处理,如果想让其当作float类型来处理的话,需要字面量后面添加F/f
二、精度和表示范围
三、运算规则
1.当float类型和double类型参与运算时,float类型会自动转换为double类型,运算结果为double类型。例如:
float a = 3.14f;
double b = 2.71828;
double result = a + b;
2.在进行浮点型数据运算时,可能会因为二进制与十进制的转换问题导致一些看似不符合常理的结果。例如:
double num4 = 0.1;
double num5 = 0.2;
double sum = num4 + num5;
System.out.println(sum);
理论上0.1 + 0.2等于0.3,但在 Java 中,由于浮点型数据在计算机内部以二进制存储,上述代码输出的结果可能是0.30000000000000004,这就是浮点型数据运算的精度问题。
四、浮点型数据两种表示形式
1、十进制形式
(1).定义
十进制形式就是日常使用的带小数点的数字表示方法。
例如:3.14、0.25、 - 5.67等都是十进制形式的浮点型数据。
(2).数据类型
如果写成这样的十进制形式且没有后缀,在 Java 中默认是double类型。例如:
double num1 = 3.14;
double num2 = - 0.25;
如果要表示float类型的十进制形式,需要在数字后面加上f或F后缀。例如:
float num3 = 3.14f;
float num4 = - 0.25F;
2.科学计数法形式
(1).定义
科学计数法形式用于表示非常大或非常小的数,它由两部分组成:尾数和指数。
格式为:尾数E指数或尾数e指数,其中E或e表示 10 的幂次方。
例如:1.23E5表示1.23×10⁵,即123000;4.56e - 3表示4.56×10⁻³,即0.00456。
(2).数据类型
与十进制形式类似,如果没有后缀,默认是double类型。例如:
double num5 = 1.23E5;
double num6 = 4.56e - 3;
若要表示float类型的科学计数法形式,需要加上f或F后缀。例如:
float num7 = 1.23E5f;
float num8 = 4.56e - 3F;
五、浮点型数据存储原理
浮点型数据在计算机中的存储方式遵循 IEEE 754 标准
1.IEEE 754 标准
符号位(Sign)
对于float和double类型,最左边的 1 位用来表示符号位。0 表示正数,1 表示负数。
指数位(Exponent)
在float类型中,接下来的 8 位用于表示指数。它采用偏移码(Excess - K)的形式存储。对于float类型,偏移量k=127。
在double类型中,有 11 位用于表示指数,偏移量k=1023。
指数位表示的是实际指数加上偏移量的值。例如,对于float类型,如果指数位存储的值是130,那么实际指数是130-127=3。
尾数位(Mantissa)
float类型剩下的 23 位用于表示尾数。
double类型剩下的 52 位用于表示尾数。
尾数是一个二进制小数,在存储时省略了小数点前面的 1(因为规格化的浮点数小数点前总是 1)。例如,一个浮点数的二进制表示可能是1.01101...,在存储尾数时只存储01101...。
2.存储示例(以float
为例)
以存储浮点数3.75
为例,我们来看看它在float
类型中的存储方式:
-
二进制转换:首先,将
3.75
转换为二进制数。整数部分3
转换为二进制是11
,小数部分0.75
转换为二进制是0.11
(这里采用了二进制小数点的表示方法)。因此,3.75
的二进制表示是11.11
。然后,我们将其规格化为1.111 × 2^1
。 -
符号位:由于
3.75
是正数,所以符号位为0。 -
指数位:实际指数是1,加上
float
类型的偏移量127,得到128。将128转换为二进制数,得到10000000
。这就是指数位的值。 -
尾数位:省略小数点前的1,只存储
111
。然后,在111
后面补0,直到填满23位,得到11100000000000000000000
。这就是尾数位的值。 -
最终存储:将符号位、指数位和尾数位组合起来,得到
3.75
在float
类型中的存储形式:01000000011100000000000000000000
。
3.精度问题
由于浮点型数据在存储时的尾数长度有限,一些十进制小数在转换为二进制时会出现无限循环的情况。例如,0.1
的二进制表示为0.000110011001100110011001100110011...
(无限循环)。在存储时,由于尾数位长度有限,只能截取一定长度的二进制数来近似表示这个十进制小数。这就会导致浮点型数据在计算机中的存储和运算出现精度损失。
例如,在Java中执行以下代码:
double a = 0.1;
double b = 0.2;
double sum = a + b;
System.out.println(sum);
理论上,0.1 + 0.2
应该等于0.3
。但是,由于浮点型数据的精度问题,实际输出结果可能是0.30000000000000004
。这就是浮点型数据存储和运算中的精度损失所导致的。
六、浮点型数据使用注意事项
在Java中浮点型数据运算结果的相等比较问题
在 Java 中,一旦有浮点型数据参与运算得出的结果,确实不建议使用 “==” 与其他数字进行相等比较,主要原因如下:
1、浮点型数据的存储与精度问题
-
存储原理:
浮点型数据在计算机内部遵循IEEE 754标准存储。由于尾数位长度有限,一些十进制小数在转换为二进制时会出现无限循环,因此存储时只能截取一定长度的尾数,导致精度损失。 -
例如,十进制数
0.1
转换为二进制是0.0001100110011...
(无限循环),在计算机中存储double
类型的0.1
时,实际存储的是截取后的近似值,与理论上的0.1
存在微小偏差。 -
运算时的精度误差积累:
当浮点型数据参与运算时,这些精度误差可能会进一步累积。例如double num1 = 0.1; double num2 = 0.2; double sum = num1 + num2;
理论上
num1 + num2
的结果应该是0.3
,但由于0.1
和0.2
本身存储就有精度偏差,实际运算后的sum
值可能是0.30000000000000004
之类与理论值不完全相等的数。
2、“==”运算符的比较机制与浮点型数据的不适配
-
“==”运算符的比较机制:
在Java中,“==”运算符用于比较两个基本数据类型的值是否在内存中完全相等(对于引用类型,则是比较它们是否指向同一个对象)。对于整型等精确表示的数据类型,只要数值相等,使用“==”就能得到正确的相等判断结果。 -
浮点型使用 “==” 比较的错误示例及分析:
假设进行如下比较:
double result = 0.1 + 0.2;
if (result == 0.3) {
System.out.println("相等");
} else {
System.out.println("不相等");
}
按照理论预期,可能期望输出 “相等”,但实际因为 0.1 + 0.2
的运算结果 result
由于精度问题并非精准的 0.3
(如前面提到实际可能是 0.30000000000000004
),所以使用 “==” 比较时会得出 “不相等” 的结果,这与实际期望的逻辑不符,从而导致错误的判断。
3、正确的比较方式
- 差值比较法:
可以通过比较两个浮点型数据差值的绝对值是否小于一个极小的阈值(通常称为精度误差范围)来判断它们是否足够接近,从而认定在实际应用场景中它们相等。例如:double result = 0.1 + 0.2; double expected = 0.3; double tolerance = 1e-9; // 设定一个极小的精度误差范围,比如10的-9次方 if (Math.abs(result - expected) < tolerance) { System.out.println("在精度范围内相等"); } else { System.out.println("超出精度范围,不相等"); }
这种方式能更好地适应浮点型数据由于精度问题而不能简单用 “==” 进行相等比较的特性,在实际涉及浮点型运算结果比较的编程场景中更可靠。
总之,由于浮点型数据存储和运算存在精度问题,使用 “==” 进行相等比较往往会得出不符合预期的结果,应该采用更合适的差值比较等方法来判断其是否相等。