Java Math类库的一些问题

背景

在我设计分析算法的时候,有很多数学上的运算。

比如求最大最小值、四舍五入、向上向下取整等等。

这些操作都是需要进行一些处理、又比较通用的方法。推荐使用java提供的通用类Math。

了解和使用类库

在这里我们可以参考《Effective Java》中47条:了解和使用类库

《Effective Java》 中讲出这样做有四个好处:

  1. 通过使用标准类库,可以充分利用这些编写标准类库专家的知识,以及在你之前的其他人的使用经验。
  2. 不必浪费时间为哪些与工作不太相关的问题提供特别的解决方案,把时间花在业务逻辑上,而不是底层细节上。
  3. 维护和优化的工作可以交给专家去完成。
  4. 代码融入了主流,更易读。

新发现

但是在使用过程中却发现了一些有趣的事情。

为什么ceil和floor返回类型为double?

ceil是取一个浮点数的上界(大于或等于参数的的double值,等于一个数学整数)

floor与之对应,取一个浮点数的下界(小于或等于参数的的double值,等于一个数学整数)

这里的疑惑是,为什么明明结果是一个数学整数,却返回了一个double值呢?为什么不返回int值呢?

答案其实也很简单:因为double的取值范围比int大。如果返回int,会导致方法数据不准。

为什么round返回类型为int/long?

round类似于数学中的四舍五入,返回的是与参数最接近的长整型。

为什么这里不同样返回double呢?实际上long的范围也比double要小。

基本类型:long 二进制位数:64
包装类:java.lang.Long
最小值:Long.MIN_VALUE=-9223372036854775808 (-2的63次方)
最大值:Long.MAX_VALUE=9223372036854775807 (2的63次方-1)

基本类型:double 二进制位数:64
包装类:java.lang.Double
最小值:Double.MIN_VALUE=4.9E-324 (2的-1074次方)
最大值:Double.MAX_VALUE=1.7976931348623157E308 (2的1024次方-1)

答案:round的声明就是返回与double参数最接近的long型。如果出现超出范围的现象,会返回long的上下界。

    /**
     * Returns the closest {@code long} to the argument, with ties
     * rounding to positive infinity.
     *
     * <p>Special cases:
     * <ul><li>If the argument is NaN, the result is 0.
     * <li>If the argument is negative infinity or any value less than or
     * equal to the value of {@code Long.MIN_VALUE}, the result is
     * equal to the value of {@code Long.MIN_VALUE}.
     * <li>If the argument is positive infinity or any value greater than or
     * equal to the value of {@code Long.MAX_VALUE}, the result is
     * equal to the value of {@code Long.MAX_VALUE}.</ul>
     *
     * @param   a   a floating-point value to be rounded to a
     *          {@code long}.
     * @return  the value of the argument rounded to the nearest
     *          {@code long} value.
     * @see     java.lang.Long#MAX_VALUE
     * @see     java.lang.Long#MIN_VALUE
     */

为什么round和ceil/floor设计理念的不一致?

round返回的是int/long,而ceil/floor返回的是double,这种约分操作的设计理念不一致。

答案:没有不一致,在JDK设计中,它们本来做的就不是一件事情。与round对应的是rint,它会返回与参数double最接近的double类型。

在这里我们可以看下《Effective Java》第40条:谨慎设计方法签名。

When in doubt, look to the Java library APIs for guidance. While there are plenty of inconsistencies — inevitable, given the size and scope of these libraries — there are also fair amount of consensus.
如果有疑问,应当参考Java库api。尽管设计存在大量的不一致性(考虑到这些库的规模和范围,这是不可避免的),但也存在相当多的共识。

为什么round的四舍五入有的时候有问题?

0.49999999999999994在四舍五入时会变成1

原因:在Java 6中(可能更早),round(x)实现为floor(x+0.5)。这是一个规范错误,是这一个病理案例。 Java 7不再要求这种破坏的实现。

另外round还可能有奇进偶舍的国际惯例问题,意思是当舍入位前面一位是奇数时,就进,为偶数时,就舍,这也是体现公平性的原理。

总之我想说的就是:四舍五入其实很复杂,浮点数也不靠谱。当我们在使用精确计算时(尤其是金额),首先要考虑的就是要不要用浮点数。

参考《Effective Java》48条:需要精确的答案,避免使用float和double。

float和double类型主要是为了科学计算和工程计算设计的。它执行二进制浮点运算。这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而他们并没有提供完全精确的结果。

猜你喜欢

转载自www.cnblogs.com/coder-chi/p/11706019.html