浅谈java的浮点数精度问题及如何解决精度缺失问题.docx
- 文档编号:8438339
- 上传时间:2023-01-31
- 格式:DOCX
- 页数:7
- 大小:22.68KB
浅谈java的浮点数精度问题及如何解决精度缺失问题.docx
《浅谈java的浮点数精度问题及如何解决精度缺失问题.docx》由会员分享,可在线阅读,更多相关《浅谈java的浮点数精度问题及如何解决精度缺失问题.docx(7页珍藏版)》请在冰豆网上搜索。
浅谈java的浮点数精度问题及如何解决精度缺失问题
javafloatdouble精度为什么会丢失?
浅谈java的浮点数精度问题
由于对float或double的使用不当,可能会出现精度丢失的问题。
问题大概情况可以通过如下代码理解:
[java] viewplaincopyprint?
1.public class FloatDoubleTest {
2.public static void main(String[] args) {
3.float f = 20014999;
4.double d = f;
5.double d2 = 20014999;
6.System.out.println("f=" + f);
7.System.out.println("d=" + d);
8.System.out.println("d2=" + d2);
9. System.out.println(0.05+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(123.3/100);
10.}
11.}
publicclassFloatDoubleTest{publicstaticvoidmain(String[]args){floatf=20014999;doubled=f;doubled2=20014999;System.out.println("f="+f);System.out.println("d="+d);System.out.println("d2="+d2);}}
得到的结果如下:
f=2.0015E7
d=2.0015E7
d2=2.0014999E7
从输出结果可以看出double可以正确的表示20014999,而float没有办法表示20014999,得到的只是一个近似值。
这样的结果很让人讶异。
20014999这么小的数字在float下没办法表示。
于是带着这个问题,做了一次关于float和double学习,做个简单分享,希望有助于大家对java浮点数的理解。
关于java的float和double
Java语言支持两种基本的浮点类型:
float和double。
java的浮点类型都依据IEEE754标准。
IEEE754定义了32位和64位双精度两种浮点二进制小数标准。
IEEE754用科学记数法以底数为2的小数来表示浮点数。
32位浮点数用1位表示数字的符号,用8位来表示指数,用23位来表示尾数,即小数部分。
作为有符号整数的指数可以有正负之分。
小数部分用二进制(底数2)小数来表示。
对于64位双精度浮点数,用1位表示数字的符号,用11位表示指数,52位表示尾数。
如下两个图来表示:
float(32位):
double(64位):
都是分为三个部分:
(1)一个单独的符号位s直接编码符号s。
(2)k位的幂指数E,移码表示。
(3)n位的小数,原码表示。
那么20014999为什么用float没有办法正确表示?
结合float和double的表示方法,通过分析20014999的二进制表示就可以知道答案了。
以下程序可以得出20014999在double和float下的二进制表示方式。
[java] viewplaincopyprint?
1.public class FloatDoubleTest3 {
2.public static void main(String[] args) {
3.double d = 8;
4.long l = Double.doubleToLongBits(d);
5.System.out.println(Long.toBinaryString(l));
6.float f = 8;
7.int i = Float.floatToIntBits(f);
8.System.out.println(Integer.toBinaryString(i));
9.}
10.}
publicclassFloatDoubleTest3{publicstaticvoidmain(String[]args){doubled=8;longl=Double.doubleToLongBits(d);System.out.println(Long.toBinaryString(l));floatf=8;inti=Float.floatToIntBits(f);System.out.println(Integer.toBinaryString(i));}}
输出结果如下:
Double:
100000101110011000101100111100101110000000000000000000000000000
Float:
1001011100110001011001111001100
对于输出结果分析如下。
对于都不double的二进制左边补上符号位0刚好可以得到64位的二进制数。
根据double的表示法,分为符号数、幂指数和尾数三个部分如下:
0100000101110011000101100111100101110000000000000000000000000000
对于float左边补上符号位0刚好可以得到32位的二进制数。
根据float的表示法,也分为符号数、幂指数和尾数三个部分如下:
01001011100110001011001111001100
绿色部分是符号位,红色部分是幂指数,蓝色部分是尾数。
对比可以得出:
符号位都是0,幂指数为移码表示,两者刚好也相等。
唯一不同的是尾数。
在double的尾数为:
0011000101100111100101110000000000000000000000000000,省略后面的零,至少需要24位才能正确表示。
而在float下面尾数为:
00110001011001111001100,共23位。
为什么会这样?
原因很明显,因为float尾数最多只能表示23位,所以24位的001100010110011110010111在float下面经过四舍五入变成了23位的00110001011001111001100。
所以20014999在float下面变成了20015000。
也就是说20014999虽然是在float的表示范围之内,但在IEEE754的float表示法精度长度没有办法表示出20014999,而只能通过四舍五入得到一个近似值。
总结:
浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。
往往产生误差不是因为数的大小,而是因为数的精度。
因此,产生的结果接近但不等于想要的结果。
尤其在使用float和double作精确运算的时候要特别小心。
可以考虑采用一些替代方案来实现。
如通过String结合BigDecimal或者通过使用long类型来转换。
解决方案:
packageA;
import java.math.BigDecimal;
/**
* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精
* 确的浮点数运算,包括加减乘除和四舍五入。
*/
public class Arith{
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
//这个类不能实例化
private Arith(){
}
/**
* 提供精确的加法运算。
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1,double v2){
return div(v1,v2,DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。
当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1,double v2,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
};
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 浅谈 java 浮点 精度 问题 如何 解决 缺失