为什么在Java 6上Math.round(0.499999999999999917)舍入为1

总览

错误表示错误和算术舍入错误有两种类型,它们在浮点计算中很常见。 在此简单示例中,这两个错误组合在一起,在Java 6中Math.round(0.4999999999999999999917)舍入为1。

表示错误

浮点数是以2为底的格式,表示所有数字都表示为2的幂的和。例如6.25是2 ^ 2 + 2 ^ 1 + 2 ^ -2。 但是,即使像0.1这样的简单数字也无法准确表示。 转换为BigDecimal时,这一点变得很明显,因为它将保留实际表示的值而无需取整。

new BigDecimal(0.1)= 
    0.1000000000000000055511151231257827021181583404541015625
BigDecimal.valueOf(0.1)= 0.1

使用构造函数获取实际表示的值,使用valueOf给出与打印 精度字相同的舍入值

解析数字时,会将其舍入为最接近的表示值。 这意味着存在一个略小于0.5的数字,由于它是最接近的表示值,因此将四舍五入为0.5。

下面用蛮力搜索舍入为1.0的最小值

public static final BigDecimal TWO = BigDecimal.valueOf(2);

public static void main(String... args) {
    int digits = 80;

    BigDecimal low = BigDecimal.ZERO;
    BigDecimal high = BigDecimal.ONE;

    for (int i = 0; i <= 10 * digits / 3; i++) {
        BigDecimal mid = low.add(high).divide(TWO, digits, RoundingMode.HALF_UP);
        if (mid.equals(low) || mid.equals(high))
            break;
        if (Math.round(Double.parseDouble(mid.toString())) > 0)
            high = mid;
        else
            low = mid;
    }

    System.out.println("Math.round(" + low + ") is " + 
            Math.round(Double.parseDouble(low.toString())));
    System.out.println("Math.round(" + high + ") is " + 
            Math.round(Double.parseDouble(high.toString())));
}

源代码

在Java 7上,您得到以下结果。

Math.round(0.49999999999999997224442438437108648940920829772949218749999999999999999999999999) is 0
Math.round(0.49999999999999997224442438437108648940920829772949218750000000000000000000000000) is 1

令人惊讶的是,在Java 6中,您获得了关注。

Math.round(0.49999999999999991673327315311325946822762489318847656250000000000000000000000000) is 0
Math.round(0.49999999999999991673327315311325946822762489318847656250000000000000000000000001) is 1

这些数字从何而来?

Java 7值是0.5和前一个表示值之间的中点。 高于此中点时,解析时该值将舍入为0.5。

Java 6值是0.5之前的值与其之前的值之间的中点。

Value 0.5 is 0.5
The previous value is 0.499999999999999944488848768742172978818416595458984375
... and the previous is 0.49999999999999988897769753748434595763683319091796875

The mid point between 0.5
 and 0.499999999999999944488848768742172978818416595458984375
 is 0.4999999999999999722444243843710864894092082977294921875

... and the mid point between 0.499999999999999944488848768742172978818416595458984375
 and 0.49999999999999988897769753748434595763683319091796875
 is 0.4999999999999999167332731531132594682276248931884765625

为什么Java 6的值更小

在Java 6 Javadoc中, Math.round(double)被定义为

(long)Math.floor(a + 0.5d)

此定义的问题在于0.49999999999999994 + 0.5的舍入误差为1.0。

在Java 7 Javadoc Math.round(double)中,它仅声明:

返回最接近参数的长整数,并舍入四舍五入。

那么Java 7如何解决这个问题?

Java 7的Math.round的源代码如下所示

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5
        return (long)floor(a + 0.5d);
    else
        return 0;
}

最大值小于0.5的结果将进行硬编码。

那么0x1.fffffffffffffp-2是什么呢?

它是浮点值的十六进制表示。 它很少使用,但是它很精确,因为所有值都可以无错误地表示(最多53位)。

相关链接

错误ID:6430675 Math.round对于0x1.fffffffffffffpp-2具有令人惊讶的行为
为什么Math.round(0.49999999999999994)返回1

参考: 为什么在Java 6上 ,我们的JCG合作伙伴 Peter Lawrey在Vanilla Java博客上将Math.round(0.499999999999999917)舍入为1


翻译自: https://www.javacodegeeks.com/2012/04/why-mathround0499999999999999917-rounds.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值