1、浮点数不是精确存储
参考: 关于浮点数存储
System.out.println(0.1+0.2);
System.out.println(1.0-0.8);
System.out.println(4.015*100);
System.out.println(123.3/100);
double amount1 = 2.15;
double amount2 = 1.10;
System.out.println(amount1 - amount2);
可以看到数据在未计算前输出结果不是精确的:
2、正确使用BigDecimal
System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));
和你想的可能不一样,以数值型赋值计算结果仍旧是不精确的:
我们以字符串方式计算,看看结果:
// 使用 BigDecimal 表示和计算浮点数,且务必使用字符串的构造方法来初始化
System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));
// BigDecimal 有 scale 和 precision 的概念,scale 表示小数点右边的位数,而 precision 表示精度,也就是有效数字的长度。
System.out.println(new BigDecimal(Double.toString(100)));
System.out.println(BigDecimal.valueOf(100));
3、浮点数的精确度与格式化输出
浮点数格式化输出通过BigDecimal实现
double num1 = 3.35;
float num2 = 3.35f;
System.out.println(String.format("%.1f", num1)); // 3.4
System.out.println(String.format("%.1f", num2)); // 3.3
// 默认舍入模式是 HALF_UP, 即四舍五入
DecimalFormat format = new DecimalFormat("#.##");
format.setRoundingMode(RoundingMode.DOWN);
System.out.println(format.format(num1)); // 3.35
format.setRoundingMode(RoundingMode.DOWN);
System.out.println(format.format(num2)); // 3.34
// 格式化float与double存在异常,
// 浮点数的字符串格式化也要通过BigDecimal 进行
BigDecimal k1 = new BigDecimal("3.35");
BigDecimal k2 = k1.setScale(1, BigDecimal.ROUND_DOWN);
System.out.println(k2); // 3.3
BigDecimal k3 = k1.setScale(1, BigDecimal.ROUND_HALF_UP);
System.out.println(k3); // 3.4
4、浮点数运算溢出
- Math工具类运算溢出时抛出异常
long l = Long.MAX_VALUE;
System.out.println(l);
System.out.println(l + 1);
System.out.println(l + 1 == Long.MIN_VALUE);
//方法一是,考虑使用 Math 类的 addExact、subtractExact 等
// xxExact 方法进行数值运算,这些方法可以在数值溢出时主动抛出异常
try {
long l1 = Long.MAX_VALUE;
System.out.println(Math.addExact(l1, 1));
} catch (Exception ex) {
ex.printStackTrace();
}
- 使用BigInteger处理, 正常计算但是不能转为long型
long l = Long.MAX_VALUE;
System.out.println(l);
BigInteger i = new BigInteger(String.valueOf(Long.MAX_VALUE));
System.out.println(i.add(BigInteger.ONE).toString());
try {
long l2 = i.add(BigInteger.ONE).longValueExact();
} catch (Exception ex) {
ex.printStackTrace();
}
5、浮点数判断相等
通常我们采用是做差判断是否小于某个范围
// equals 比较的是 BigDecimal 的 value 和 scale
System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1"))); // false
// 只比较 BigDecimal 的 value,可以使用 compareTo 方法
System.out.println(new BigDecimal("1.0").compareTo(new BigDecimal("1")) == 0); // true
// 集合为数字类型问题
Set<BigDecimal> hashSet1 = new HashSet<>();
hashSet1.add(new BigDecimal("1.0"));
System.out.println(hashSet1.contains(new BigDecimal("1"))); //返回false
// 解决: 第一个方法是,使用 TreeSet 替换 HashSet。
// TreeSet 不使用 hashCode 方法,也不使用 equals 比较元素,
// 而是使用 compareTo 方法,所以不会有问题。
Set<BigDecimal> treeSet = new TreeSet<>();
treeSet.add(new BigDecimal("1.0"));
System.out.println(treeSet.contains(new BigDecimal("1"))); //返回true
// 第二个方法是,把 BigDecimal 存入 HashSet 或 HashMap 前,先使用stripTrailingZeros
// 方法去掉尾部的零,比较的时候也去掉尾部的 0,确保 value 相同的BigDecimal,scale 也是一致的
Set<BigDecimal> hashSet2 = new HashSet<>();
hashSet2.add(new BigDecimal("1.0").stripTrailingZeros());
System.out.println(hashSet2.contains(new BigDecimal("1.000").stripTrailingZeros())); // true