前话真是为题主的细心点个赞,我也是在对浮点数精度有疑问网上搜索时机缘巧合看到题主的问题,
不是看了题主问题描述,平时还真没注意真实打印时的保留位数问题。
带着这些疑问翻查了些资料,于是有了些心得体会在此分享下,全当抛砖引玉了。
正文
要回答此问题,得了解如下的背景知识:任意两个实数之间是有无穷多个实数,故有实数能把数轴填满一说,而浮点数(双精度类似)与实数不同,两个浮点数之间是只包含有限个浮点数的,换句话说,某个浮点数是存在确定的前序和后序的浮点数的
相邻的两个浮点数之间存在一个距离,这个距离被称为最小精度单位(Unit of Least Precision)或最后位置单位(Unit in the Last Place),简称ULP
Java对于任意一个浮点字面量,最终都舍入到所能表示的最靠近的那个浮点值,遇到该值离左右两个能表示的浮点值距离相等时,默认采用偶数优先(Ties to Even)即选择浮点数的二进制值以0结尾的那个
Java中获取某个浮点数的前序或后序浮点数、ULP的方法为Math类的静态方法:
public static float nextDown(float f) // 获取前序,即比所给值小
public static float nextUp(float f) // 获取后序,即比所给值大
public static float ulp(float f) // 获取ULP
了解这些知识后,我们来举个“栗子”:
float e = 1.1110999f;
System.out.println("1.1110999f 打印真实为:" + e);
System.out.println("1.1110999f 的前序:" + Math.nextDown(e));
System.out.println("1.1110999f 的后序:" + Math.nextUp(e));
System.out.println("1.1110999f 的最小精度单位:" + Math.ulp(1.1110999f));
// e会等于d吗?
float d = 1.1111f;
System.out.println(e == d);
// 打印结果:
1.1110999f 打印真实为:1.1111 // ①
1.1110999f 的前序:1.1110998 // ②
1.1110999f 的后序:1.1111001 // ③
1.1110999f 的最小精度单位:1.1920929E-7 // ④
true // ⑤①⑤可以验证背景知识的第3条,字面量‘1.1110999f’的确舍入到最靠近的所能表示的浮点数‘1.1111f’,至于它为何只是诡异的保留5位(还压根没达到7位或8位)看问题总结
①②③结合,可以得知在[1.1110998, 1.1111001]范围内其实只存在三个浮点数,即:
{ 1.1110998、 1.1111、 1.1111001}④可以得知,‘1.1110999f’这个区段的ULP为:0.00000011920929,将上面三个数字任意相邻两个数字相减,确实也能印证,只是精度稍差点
总结
现在再来回答题主的问题,为啥输出有时保留7位或8位,甚至是像上面例子中的5位‘1.1111’
其实就是根据字面量的值(本例为:1.1110999f),选取两个可表示的浮点值A和B,
使得A
然后根据浮点数规范(IEEE_754)的舍入规则,选取其中之一(本例选取B,因为A的二进制值尾数为1,而B刚好尾数为0)
至于结果到底保留几位,由最终被选择的可表示的浮点数决定,本例由于选择1.1111,故有效位就是5位