[Java基础]有关Java浮点数的一个有趣的例子以及一个尚未解决的疑惑(java在存储float值时采用的什么策略?)

有关Java浮点数精度丢失的一个有趣的例子以及尚未解决的疑惑

本文章重点在于表述最后的例子和尚未解决的疑问,故对于浮点数相关概念只是简单进行叙述

一、浮点数的存储模式

Java 语言支持两种基本的浮点类型: float 和 double 。java 的浮点类型都依据 IEEE 754 标准。IEEE 754 定义了32 位和 64 位双精度两种浮点二进制小数标准。

IEEE 754 用科学记数法以底数为 2 的小数来表示浮点数。

对于32 位浮点数float用 第1 位表示数字的符号,用第2至9位来表示指数,用 最后23 位来表示尾数,即小数部分。

  • float(32位):
    float

对于64 位双精度浮点数,用 第1 位表示数字的符号,用 11 位表示指数,52 位表示尾数。

  • double(64位):
    double

(1)一个单独的符号位s 直接编码符号s 。

(2)k 位的幂指数E ,移码表示 。

(3)n 位的小数,原码表示 。

底数部分 使用2进制数来表示此浮点数的实际值。
指数部分 占用8-bit的二进制数,可表示数值范围为0-255。 但是指数应可正可负,所以IEEE规定,此处算出的次方须减去127才是真正的指数。所以float的指数可从 -126到128.
底数部分实际是占用24-bit的一个值,由于其最高位始终为 1 ,所以最高位省去不存储,在存储中只有23-bit。

  • 规格化(标准化)与移位
    (以float为例进行阐述)
    规格化的原因:在存储时,需要进行规格化操作,使用尾数来存储数值信息,而指数部分只记录偏移量,所以进行规格化之后能够统一存储格式

  • 移位原因:
    根据IEEE 754标准,需要在指数部分+127
    指数有正有负,+127之后统一存储为正数,节省一位有效位

二、什么时候出现无法表示?

任何一个浮点数字,在底层表示都必须转换成这种科学计数法来表示,那么我们来想想看什么时候这个数字会无法表示呢?那么只有两种情形:

  1. 幂数不够表示了:这种情况往往出现在数字太大了,超过幂数所能承受的范围,那么这个数字就无法表示了。如幂数最大只能是10,但是这个数字用科学计数法表示时,幂数一定会超过10,就没办法了。

  2. 尾数不够表示了:这种情况往往出现在数字精度太长了,如1.3434343233332这样的数字,虽然很小,还不超过2,这种情况下幂数完全满足要求,但是尾数已经不能表示出来了这么长的精度。

三、表示范围及有效位

  • float的范围为-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38
  • double的范围为-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

四、例子及疑惑

在上课的时候,随意瞎敲了一个例子,根据二进制转换想要理解浮点数的存储过程,计算得到的32bit的结果进行比对,发现有一位是不同的,原以为输出该是false,可输出的结果却是true。这是在是让人不解
例子

	float d = 10.0000005f;
	float f = 10.000001f;
	
	System.out.println("(d=10.0000005) == (f=10.000001) ? "+(d == f));

运行结果为:true

可能第一反应就是 丢精度了呗,最后精度丢了呗,有啥稀奇的。当时我也是这么想的,接下来我们手动进行一下二进制存储的转换

d = 10.0000005
直接准换为二进制:
1010.0000000000000000000010000110001101111011110100001
进行规格化,移动三位 E11
1.0100000000000000000000010000110001101111011110100001 E11
根据IEEE 754标准 指数+127,为
	00000011
   +01111111
   ———————————
   	10000010
加上符号位 0 
存储内容应为(尾数取后23位):
0  10000010  01000000000000000000000    《--10.0000005f 


f = 10.000001
直接准换为二进制:
1010.0000000000000000000100001100011011110111101000001
进行规格化,移动三位 E11
1.0100000000000000000000100001100011011110111101000001 E11
根据IEEE 754标准 指数+127,为
	00000011
   +01111111
   ———————————
   	10000010
加上符号位 0 
存储内容应为(尾数取后23位):
0 10000010 01000000000000000000001   《--10.000001f


然后进行对比:
0  10000010  01000000000000000000000    《--10.0000005f 
0  10000010  01000000000000000000001    《--10.000001f

?????问题来了,这tm,判断结果是true?嗯????
哈哈哈,不对啊,32bit最后一位存进去,一个是0,一个是1,结果是 true???
ei?不太对啊这个。

然后直接转成二进制来一把:

public static void main(String[] args){

		float d = 10.0000005f;
		float f = 10.000001f;

		//System.out.println("(d=10.0000005) == (f=10.000001) ? "+(d == f));
		
		int k = Float.floatToIntBits(f);
		System.out.println(Integer.toBinaryString(k));
		k = Float.floatToIntBits(d);
		System.out.println(Integer.toBinaryString(k));
}

结果:
在这里插入图片描述
emmmm,这几个意思???

没招了,再debug一把?
在这里插入图片描述

阿嘞?? debug出来d的值就是10.000001,f的值还是10.000001。
难道是在存储的时候被四舍五入掉了?所以才会返回true吗??可是这个进的一位是从哪来的?jvm是怎么进行的取舍呢?这里面究竟是怎么个一个情况呢???
如果有大佬看到了这里,并且了解相关的内容,一定要来帮我解解惑啊~~~~

猜你喜欢

转载自blog.csdn.net/qq_31749835/article/details/88767035