Java中的小数运算与精度损失

float、double类型的问题

咱们都知道,计算机是使用二进制存储数据的。而日常生活中,大多数状况下咱们都是使用的十进制,所以计算机显示给咱们看的内容大多数也是十进制的,这就使得不少时候数据须要在二进制与十进制之间进行转换。对于整数来讲,两种进制能够作到一一对应。而对于小数来说就不是这样的啦。java

咱们先来看看十进制小数转二进制小数的方法ide

对小数点之后的数乘以2,会获得一个结果,取结果的整数部分(不是1就是0),而后再用小数部分再乘以2,再取结果的整数部分……以此类推,直到小数部分为0或者位数已经够了。顺序取每次运算获得的整数部分,即为转换后的小数部分。工具

演示: 
0.125 ×2=0.25 .......................0
0.25×2=0.5.............................0
0.5×2=1.0................................1
即 0.125的二进制表示为小数部分为0.001

其实咱们能够看出,这种方法实质上就是用1/2,1/4,8/1...来组合加出咱们要转换的数据值,但显然不是全部的数都可以组合出来的。如0.1。测试

0.1×2=0.2 .....................0

 0.2×2=0.4 ......................0

 0.4×2=0.8 .....................0

 0.8×2=1.6.......................1

 0.6×2=1.2.......................1

 0.2×2=0.4.......................0
 .....

从上述计算过程咱们能够看出,这是个无限小数,因此在这种状况下咱们的float、double只能舍去一些位。优化

那为何咱们在直接给float赋值在输出时没有看到精度损失而在运算时却会出现呢?code

确实是这样,以下ip

float a = 0.2f;
System.out.println(a);
//输出0.2

对于上述状况我只是查了资料,好像是由于编译器会进行优化,当咱们存储的数据特别接近的时候,编译器会很贴心的返回咱们想看到的数值(即二进制浮点数并不能准确的表示0.1这个十进制小数,它使用了0.100000001490116119384765625来代替0.1。),至于到了运算中,就会出现精度损失较大从而看到了真相。若是这块说的不对欢迎小伙伴们在评论区指正!ci

解决方法

BigDecimal 原理

咱们通常会使用
BigDecimal 来避免出现精度丢失问题,至于为何BigDecimal 能够避免,而float或double不行,咱们在此不详细讨论,简单来讲就是BigDecimal 经过借助整数来表示小数的方式,由于对于整数而言,二进制和十进制是彻底一一对应的,用整数来表示小数,再记录下小数的位数,就能够完美的解决该问题。编译器

BigDecimal 用法

java.math.BinInteger 类和 java.math.BigDecimal 类都是Java提供的用于高精度计算的类.其中 BigInteger 类是针对大整数的处理类,而 BigDecimal 类则是针对大小数的处理类.源码

BigDecimal构造方法

BigDecimal BigDecimal(double d); //不容许使用
BigDecimal BigDecimal(String s); //经常使用,推荐使用
static BigDecimal valueOf(double d); //经常使用,推荐使用
  1. double 参数的构造方法,不容许使用!!!!由于它不能精确的获得相应的值;
  2. String 构造方法是彻底可预知的: 写入 new BigDecimal("0.1") 将建立一个 BigDecimal,它正好等于预期的0.1; 所以,一般建议优先使用 String 构造方法;
  3. 静态方法 valueOf(double val) 内部实现,还是将 double 类型转为 String 类型; 这一般是将 double(或float)转化为 BigDecimal 的首选方法;

测试

System.out.println(new BigDecimal(0.1));
System.out.println(BigDecimal.valueOf(0.1));
\\输出*****************************************
0.1000000000000000055511151231257827021181583404541015625
0.1

BigDecimal经常使用操做

咱们经过一个工具类源码来体会BigDecimal的常规用法

package com.util;
 
import java.math.BigDecimal;
 
/**
 * 提供精确的浮点数运算(包括加、减、乘、除、四舍五入)工具类
 */
public class ArithUtil {
 
	// 除法运算默认精度
	private static final int DEF_DIV_SCALE = 10;
 
	private ArithUtil() {
 
	}
 
	/**
	 * 精确加法
	 */
	public static double add(double value1, double value2) {
		BigDecimal b1 = BigDecimal.valueOf(value1);
		BigDecimal b2 = BigDecimal.valueOf(value2);
		return b1.add(b2).doubleValue();
	}
 
	/**
	 * 精确减法
	 */
	public static double sub(double value1, double value2) {
		BigDecimal b1 = BigDecimal.valueOf(value1);
		BigDecimal b2 = BigDecimal.valueOf(value2);
		return b1.subtract(b2).doubleValue();
	}
 
	/**
	 * 精确乘法
	 */
	public static double mul(double value1, double value2) {
		BigDecimal b1 = BigDecimal.valueOf(value1);
		BigDecimal b2 = BigDecimal.valueOf(value2);
		return b1.multiply(b2).doubleValue();
	}
 
	/**
	 * 精确除法 使用默认精度
	 */
	public static double div(double value1, double value2) throws IllegalAccessException {
		return div(value1, value2, DEF_DIV_SCALE);
	}
 
	/**
	 * 精确除法
	 * @param scale 精度
	 */
	public static double div(double value1, double value2, int scale) throws IllegalAccessException {
		if(scale < 0) {
			throw new IllegalAccessException("精确度不能小于0");
		}
		BigDecimal b1 = BigDecimal.valueOf(value1);
		BigDecimal b2 = BigDecimal.valueOf(value2);
		// return b1.divide(b2, scale).doubleValue();
		return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
	}
 
	/**
	 * 四舍五入
	 * @param scale 小数点后保留几位
	 */
	public static double round(double v, int scale) throws IllegalAccessException {
		return div(v, 1, scale);
	}
	
	/**
	 * 比较大小
	 */
	public static boolean equalTo(BigDecimal b1, BigDecimal b2) {
		if(b1 == null || b2 == null) {
			return false;
		}
		return 0 == b1.compareTo(b2);
	}
}
相关文章
相关标签/搜索