Added a nextAfter method in MathUtils to return the next
machine-representable number in a specified direction from a given floating point number. Used this to ensure that MathUtils.round does not return incorrect results for numbers with bad IEEE754 representations. JIRA: MATH-151 Reported by Buza Zoltán Patch submitted by Luc Maisonobe git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/math/trunk@418934 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
3ebd273440
commit
7ec35cf7f5
|
@ -164,6 +164,9 @@
|
|||
<contributor>
|
||||
<name>Piotr Kochanski</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Luc Maisonobe</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Fredrik Norin</name>
|
||||
</contributor>
|
||||
|
|
|
@ -473,6 +473,55 @@ public final class MathUtils {
|
|||
return (int)m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest machine representable number
|
||||
* from a number in some direction.
|
||||
|
||||
* @param d base number
|
||||
* @param direction (the only important thing is whether
|
||||
* direction is greater or smaller than d)
|
||||
* @return
|
||||
*/
|
||||
public static double nextAfter(double d, double direction) {
|
||||
|
||||
// handling of some important special cases
|
||||
if (Double.isNaN(d) || Double.isInfinite(d)) {
|
||||
return d;
|
||||
} else if (d == 0) {
|
||||
return (direction < 0) ? -Double.MIN_VALUE : Double.MIN_VALUE;
|
||||
}
|
||||
// special cases MAX_VALUE to infinity and MIN_VALUE to 0
|
||||
// are handled just as normal numbers
|
||||
|
||||
// split the double in raw components
|
||||
long bits = Double.doubleToLongBits(d);
|
||||
long sign = bits & 0x8000000000000000L;
|
||||
long exponent = bits & 0x7ff0000000000000L;
|
||||
long mantissa = bits & 0x000fffffffffffffL;
|
||||
|
||||
if (d * (direction - d) >= 0) {
|
||||
// we should increase the mantissa
|
||||
if (mantissa == 0x000fffffffffffffL) {
|
||||
return Double.longBitsToDouble(sign |
|
||||
(exponent + 0x0010000000000000L));
|
||||
} else {
|
||||
return Double.longBitsToDouble(sign |
|
||||
exponent | (mantissa + 1));
|
||||
}
|
||||
} else {
|
||||
// we should decrease the mantissa
|
||||
if (mantissa == 0L) {
|
||||
return Double.longBitsToDouble(sign |
|
||||
(exponent - 0x0010000000000000L) |
|
||||
0x000fffffffffffffL);
|
||||
} else {
|
||||
return Double.longBitsToDouble(sign |
|
||||
exponent | (mantissa - 1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Round the given value to the specified number of decimal places. The
|
||||
* value is rounded using the {@link BigDecimal#ROUND_HALF_UP} method.
|
||||
|
@ -499,9 +548,18 @@ public final class MathUtils {
|
|||
* @since 1.1
|
||||
*/
|
||||
public static double round(double x, int scale, int roundingMethod) {
|
||||
double sign = indicator(x);
|
||||
double factor = Math.pow(10.0, scale) * sign;
|
||||
return roundUnscaled(x * factor, sign, roundingMethod) / factor;
|
||||
try {
|
||||
return (new BigDecimal
|
||||
(new Double(x).toString())
|
||||
.setScale(scale, roundingMethod))
|
||||
.doubleValue();
|
||||
} catch (NumberFormatException ex) {
|
||||
if (Double.isInfinite(x)) {
|
||||
return x;
|
||||
} else {
|
||||
return Double.NaN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -552,23 +610,24 @@ public final class MathUtils {
|
|||
switch (roundingMethod) {
|
||||
case BigDecimal.ROUND_CEILING :
|
||||
if (sign == -1) {
|
||||
unscaled = Math.floor(unscaled);
|
||||
unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
|
||||
} else {
|
||||
unscaled = Math.ceil(unscaled);
|
||||
unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
|
||||
}
|
||||
break;
|
||||
case BigDecimal.ROUND_DOWN :
|
||||
unscaled = Math.floor(unscaled);
|
||||
unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
|
||||
break;
|
||||
case BigDecimal.ROUND_FLOOR :
|
||||
if (sign == -1) {
|
||||
unscaled = Math.ceil(unscaled);
|
||||
unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
|
||||
} else {
|
||||
unscaled = Math.floor(unscaled);
|
||||
unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
|
||||
}
|
||||
break;
|
||||
case BigDecimal.ROUND_HALF_DOWN : {
|
||||
double fraction = Math.abs(unscaled - Math.floor(unscaled));
|
||||
unscaled = nextAfter(unscaled, Double.NEGATIVE_INFINITY);
|
||||
double fraction = unscaled - Math.floor(unscaled);
|
||||
if (fraction > 0.5) {
|
||||
unscaled = Math.ceil(unscaled);
|
||||
} else {
|
||||
|
@ -577,7 +636,7 @@ public final class MathUtils {
|
|||
break;
|
||||
}
|
||||
case BigDecimal.ROUND_HALF_EVEN : {
|
||||
double fraction = Math.abs(unscaled - Math.floor(unscaled));
|
||||
double fraction = unscaled - Math.floor(unscaled);
|
||||
if (fraction > 0.5) {
|
||||
unscaled = Math.ceil(unscaled);
|
||||
} else if (fraction < 0.5) {
|
||||
|
@ -593,7 +652,8 @@ public final class MathUtils {
|
|||
break;
|
||||
}
|
||||
case BigDecimal.ROUND_HALF_UP : {
|
||||
double fraction = Math.abs(unscaled - Math.floor(unscaled));
|
||||
unscaled = nextAfter(unscaled, Double.POSITIVE_INFINITY);
|
||||
double fraction = unscaled - Math.floor(unscaled);
|
||||
if (fraction >= 0.5) {
|
||||
unscaled = Math.ceil(unscaled);
|
||||
} else {
|
||||
|
@ -607,7 +667,7 @@ public final class MathUtils {
|
|||
}
|
||||
break;
|
||||
case BigDecimal.ROUND_UP :
|
||||
unscaled = Math.ceil(unscaled);
|
||||
unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
|
||||
break;
|
||||
default :
|
||||
throw new IllegalArgumentException("Invalid rounding method.");
|
||||
|
|
|
@ -583,15 +583,100 @@ public final class MathUtilsTest extends TestCase {
|
|||
assertEquals(Float.NEGATIVE_INFINITY, MathUtils.round(Float.NEGATIVE_INFINITY, 2), 0.0f);
|
||||
}
|
||||
|
||||
public void testNextAfterSpecialCases() {
|
||||
assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.NEGATIVE_INFINITY, 0)));
|
||||
assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.POSITIVE_INFINITY, 0)));
|
||||
assertTrue(Double.isNaN(MathUtils.nextAfter(Double.NaN, 0)));
|
||||
assertTrue(Double.isInfinite(MathUtils.nextAfter( Double.MAX_VALUE, Double.POSITIVE_INFINITY)));
|
||||
assertTrue(Double.isInfinite(MathUtils.nextAfter(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY)));
|
||||
assertEquals( Double.MIN_VALUE, MathUtils.nextAfter(0, 1), 0);
|
||||
assertEquals(-Double.MIN_VALUE, MathUtils.nextAfter(0, -1), 0);
|
||||
assertEquals(0, MathUtils.nextAfter( Double.MIN_VALUE, -1), 0);
|
||||
assertEquals(0, MathUtils.nextAfter(-Double.MIN_VALUE, 1), 0);
|
||||
}
|
||||
|
||||
public void testNextAfter() {
|
||||
// 0x402fffffffffffff 0x404123456789abcd -> 4030000000000000
|
||||
assertEquals(16.0, MathUtils.nextAfter(15.999999999999998, 34.27555555555555), 0.0);
|
||||
|
||||
// 0xc02fffffffffffff 0x404123456789abcd -> c02ffffffffffffe
|
||||
assertEquals(-15.999999999999996, MathUtils.nextAfter(-15.999999999999998, 34.27555555555555), 0.0);
|
||||
|
||||
// 0x402fffffffffffff 0x400123456789abcd -> 402ffffffffffffe
|
||||
assertEquals(15.999999999999996, MathUtils.nextAfter(15.999999999999998, 2.142222222222222), 0.0);
|
||||
|
||||
// 0xc02fffffffffffff 0x400123456789abcd -> c02ffffffffffffe
|
||||
assertEquals(-15.999999999999996, MathUtils.nextAfter(-15.999999999999998, 2.142222222222222), 0.0);
|
||||
|
||||
// 0x4020000000000000 0x404123456789abcd -> 4020000000000001
|
||||
assertEquals(8.000000000000002, MathUtils.nextAfter(8.0, 34.27555555555555), 0.0);
|
||||
|
||||
// 0xc020000000000000 0x404123456789abcd -> c01fffffffffffff
|
||||
assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 34.27555555555555), 0.0);
|
||||
|
||||
// 0x4020000000000000 0x400123456789abcd -> 401fffffffffffff
|
||||
assertEquals(7.999999999999999, MathUtils.nextAfter(8.0, 2.142222222222222), 0.0);
|
||||
|
||||
// 0xc020000000000000 0x400123456789abcd -> c01fffffffffffff
|
||||
assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 2.142222222222222), 0.0);
|
||||
|
||||
// 0x3f2e43753d36a223 0x3f2e43753d36a224 -> 3f2e43753d36a224
|
||||
assertEquals(2.308922399667661E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
|
||||
|
||||
// 0x3f2e43753d36a223 0x3f2e43753d36a223 -> 3f2e43753d36a224
|
||||
assertEquals(2.308922399667661E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
|
||||
|
||||
// 0x3f2e43753d36a223 0x3f2e43753d36a222 -> 3f2e43753d36a222
|
||||
assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
|
||||
|
||||
// 0x3f2e43753d36a223 0xbf2e43753d36a224 -> 3f2e43753d36a222
|
||||
assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
|
||||
|
||||
// 0x3f2e43753d36a223 0xbf2e43753d36a223 -> 3f2e43753d36a222
|
||||
assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
|
||||
|
||||
// 0x3f2e43753d36a223 0xbf2e43753d36a222 -> 3f2e43753d36a222
|
||||
assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
|
||||
|
||||
// 0xbf2e43753d36a223 0x3f2e43753d36a224 -> bf2e43753d36a222
|
||||
assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
|
||||
|
||||
// 0xbf2e43753d36a223 0x3f2e43753d36a223 -> bf2e43753d36a222
|
||||
assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
|
||||
|
||||
// 0xbf2e43753d36a223 0x3f2e43753d36a222 -> bf2e43753d36a222
|
||||
assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
|
||||
|
||||
// 0xbf2e43753d36a223 0xbf2e43753d36a224 -> bf2e43753d36a224
|
||||
assertEquals(-2.308922399667661E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
|
||||
|
||||
// 0xbf2e43753d36a223 0xbf2e43753d36a223 -> bf2e43753d36a224
|
||||
assertEquals(-2.308922399667661E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
|
||||
|
||||
// 0xbf2e43753d36a223 0xbf2e43753d36a222 -> bf2e43753d36a222
|
||||
assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
|
||||
|
||||
}
|
||||
|
||||
public void testRoundDouble() {
|
||||
double x = 1.234567890;
|
||||
assertEquals(1.23, MathUtils.round(x, 2), 0.0);
|
||||
assertEquals(1.235, MathUtils.round(x, 3), 0.0);
|
||||
assertEquals(1.2346, MathUtils.round(x, 4), 0.0);
|
||||
|
||||
// JIRA MATH-151
|
||||
assertEquals(39.25,MathUtils.round(39.245, 2), 0.0);
|
||||
assertEquals(39.24,MathUtils.round(39.245, 2,
|
||||
BigDecimal.ROUND_DOWN), 0.0);
|
||||
double xx = 39.0;
|
||||
xx = xx + 245d/1000d;
|
||||
assertEquals(39.25,MathUtils.round(xx, 2), 0.0);
|
||||
|
||||
// BZ 35904
|
||||
assertEquals(30.1d, MathUtils.round(30.095d, 2), 0.0d);
|
||||
assertEquals(30.1d, MathUtils.round(30.095d, 1), 0.0d);
|
||||
assertEquals(33.1d, MathUtils.round(33.095d, 1), 0.0d);
|
||||
assertEquals(33.1d, MathUtils.round(33.095d, 2), 0.0d);
|
||||
assertEquals(50.09d, MathUtils.round(50.085d, 2), 0.0d);
|
||||
assertEquals(50.19d, MathUtils.round(50.185d, 2), 0.0d);
|
||||
assertEquals(50.01d, MathUtils.round(50.005d, 2), 0.0d);
|
||||
|
@ -671,7 +756,10 @@ public final class MathUtilsTest extends TestCase {
|
|||
} catch (IllegalArgumentException ex) {
|
||||
// success
|
||||
}
|
||||
|
||||
|
||||
// MATH-151
|
||||
assertEquals(39.25, MathUtils.round(39.245, 2, BigDecimal.ROUND_HALF_UP), 0.0);
|
||||
|
||||
// special values
|
||||
TestUtils.assertEquals(Double.NaN, MathUtils.round(Double.NaN, 2), 0.0);
|
||||
assertEquals(0.0, MathUtils.round(0.0, 2), 0.0);
|
||||
|
|
|
@ -52,6 +52,13 @@ Commons Math Release Notes</title>
|
|||
<action dev="psteitz" type="fix" issue="MATH-60" due-to="Nhung Nnguyen">
|
||||
Modified ProperFractionFormat to reject embedded minus signs.
|
||||
</action>
|
||||
<action dev="psteitz" type="fix" issue="MATH-151" due-to="Luc Maisonobe">
|
||||
Added a nextAfter method in MathUtils to return the next
|
||||
machine-representable number in a specified direction from a given
|
||||
floating point number. Used this to ensure that MathUtils.round does
|
||||
not return incorrect results for numbers with bad IEEE754
|
||||
representations.
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.1" date="2005-12-17"
|
||||
description="This is a maintenance release containing bug fixes and enhancements.
|
||||
|
|
Loading…
Reference in New Issue