一直想要去跑跑数值计算的坑
今天终于还是抽出了时间
不知道你有没有用手机计数器计算过10%+10%
其实结果是:
惊不惊喜,意不意外
为什么不是0.2呢?
因为国外的计数器使用的都是单步计算法
a+b%
代表的是 a*(1+b%)
Java采用了IEEE745标准实现浮点数的表达和计算
0.1
二进制为:0.0 0011 0011 0011无线循环
十进制为:0.1 000 000 000 000 000 555 111 512 312 …5625
下面是实际的展示:
1.使用BigDecimal表示和计算浮点数,务必使用字符串的构造方法来初始化BigDecimal
new BigDecimal(0.1).add(new BigDecimal(0.2));//错误的方式
new BigDecimal("0.1").add(new BigDecimal("0.2"));//正确的方式
2.浮点数的字符串格式化也要通过BigDecimal进行(不可使用valueOf/toString)
//(1)直接删 ,结果为 2.34
System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_DOWN));
//(2)直接进位 , 结果为 2.35
System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_UP));
//(3)四舍五入,为5向上取 , 结果为2.35
System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_HALF_UP));
//(4)五为分水岭,为5向下取 , 结果为2.34
System.out.println(new BigDecimal("2.3457").setScale(2,BigDecimal.ROUND_HALF_DOWN));
//(5)接近正无穷大的舍入模式
System.out.println(new BigDecimal("2.335").setScale(1,BigDecimal.ROUND_CEILING));//结果2.4
System.out.println(new BigDecimal("-2.335").setScale(1,BigDecimal.ROUND_CEILING));//结果-2.3
//(6)接近负无穷大的舍入模式
System.out.println( new BigDecimal("2.33").setScale(1,BigDecimal.ROUND_FLOOR));//结果2.3
System.out.println( new BigDecimal("-2.33").setScale(1,BigDecimal.ROUND_FLOOR));//结果-2.4
//(7)向“最接近”的数字舍入,距离相等,相相邻的偶数舍入
System.out.println( new BigDecimal("1.15").setScale(1,BigDecimal.ROUND_HALF_EVEN));//结果1.2 ,也称“”银行家舍入法”(美国多用))
System.out.println( new BigDecimal("1.25").setScale(1,BigDecimal.ROUND_HALF_EVEN));//结果1.2
//(8)断言请求的操作具有精确的结果,因此不需要舍入
System.out.println(new BigDecimal("1.15").setScale(1,BigDecimal.ROUND_HALF_UP));//结果1.2
3.BigDecimal使用equals 与 compareTo 做判等的区别:
(1)BigDecimal 的 equals 方法中注释说明:equals 比较的是value和scale
1.0的scale 是1,1的dcale 是0,所以使用equals的比较结果为false
源码为:
/**
* Compares this {@code BigDecimal} with the specified
* {@code Object} for equality. Unlike {@link
* #compareTo(BigDecimal) compareTo}, this method considers two
* {@code BigDecimal} objects equal only if they are equal in
* value and scale (thus 2.0 is not equal to 2.00 when compared by
* this method).
*
* @param x {@code Object} to which this {@code BigDecimal} is
* to be compared.
* @return {@code true} if and only if the specified {@code Object} is a
* {@code BigDecimal} whose value and scale are equal to this
* {@code BigDecimal}'s.
* @see #compareTo(java.math.BigDecimal)
* @see #hashCode
*/
@Override
public boolean equals(Object x) {
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
if (scale != xDec.scale)
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
}
(2)只是希望比较BigDecimal的 value,可以使用compareTo方法
源码如下:
/**
* Compares this {@code BigDecimal} with the specified
* {@code BigDecimal}. Two {@code BigDecimal} objects that are
* equal in value but have a different scale (like 2.0 and 2.00)
* are considered equal by this method. This method is provided
* in preference to individual methods for each of the six boolean
* comparison operators ({@literal <}, ==,
* {@literal >}, {@literal >=}, !=, {@literal <=}). The
* suggested idiom for performing these comparisons is:
* {@code (x.compareTo(y)} <op> {@code 0)}, where
* <op> is one of the six comparison operators.
*
* @param val {@code BigDecimal} to which this {@code BigDecimal} is
* to be compared.
* @return -1, 0, or 1 as this {@code BigDecimal} is numerically
* less than, equal to, or greater than {@code val}.
*/
public int compareTo(BigDecimal val) {
// Quick path for equal scale and non-inflated case.
if (scale == val.scale) {
long xs = intCompact;
long ys = val.intCompact;
if (xs != INFLATED && ys != INFLATED)
return xs != ys ? ((xs > ys) ? 1 : -1) : 0;
}
int xsign = this.signum();
int ysign = val.signum();
if (xsign != ysign)
return (xsign > ysign) ? 1 : -1;
if (xsign == 0)
return 0;
int cmp = compareMagnitude(val);
return (xsign > 0) ? cmp : -cmp;
}
4.HashSet 与 HashMap 不可与BigDecimal一起使用
HashMap中使用键对象来计算hashcode值,HashSet中使用成员对象来计算hashcode值,HashSet中的对象重写了equals()和hashCode()方法
因此,如果放进去的是1.0,取时用 1 来取,是取不到的,参考上面BigDecimal 的 equals() 方法
解决办法:
(1)使用TreeSet 替换HashSet(TreeSet使用CompareTo方法)
(2)在BigDecimal存入HashSet 与HashMap 中前,用stripTrailingZeros()方法去掉尾部的零,确保value相同的BigDecimal,scale也是相同的。
5.可以写一些简单实用的工具方法, 结合 BigDecimal 一起使用
/**
* 保留两位小数(四舍五入)
* @param value
* @return
*/
private double retain2Decimals(double value) {
return new BigDecimal(value)
.setScale(2, BigDecimal.ROUND_HALF_UP)
.doubleValue();
}
1.使用Math类的 addExact.subtractExact 等XXExact方法进行数值运算
这样数值溢出时,会主动抛出异常
2.使用大数类BigInteger
BigInteget 执行Long 最大值加一,不会有问题
但溢出的数据,使用BigIngeter 的longValueExact(),转为long时会报错 ArithmeticException.