-
Notifications
You must be signed in to change notification settings - Fork 10.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement BigDecimalMath.roundToDouble.
RELNOTES=Add BigDecimalMath.roundToDouble. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=318342542
- Loading branch information
Showing
11 changed files
with
805 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
295 changes: 295 additions & 0 deletions
295
android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
/* | ||
* Copyright (C) 2020 The Guava Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
|
||
package com.google.common.math; | ||
|
||
import static com.google.common.truth.Truth.assertThat; | ||
import static com.google.common.truth.Truth.assertWithMessage; | ||
import static java.math.RoundingMode.CEILING; | ||
import static java.math.RoundingMode.DOWN; | ||
import static java.math.RoundingMode.FLOOR; | ||
import static java.math.RoundingMode.HALF_DOWN; | ||
import static java.math.RoundingMode.HALF_EVEN; | ||
import static java.math.RoundingMode.HALF_UP; | ||
import static java.math.RoundingMode.UNNECESSARY; | ||
import static java.math.RoundingMode.UP; | ||
import static java.math.RoundingMode.values; | ||
|
||
import com.google.common.annotations.GwtCompatible; | ||
import com.google.common.annotations.GwtIncompatible; | ||
import java.math.BigDecimal; | ||
import java.math.MathContext; | ||
import java.math.RoundingMode; | ||
import java.util.EnumMap; | ||
import java.util.EnumSet; | ||
import java.util.Map; | ||
import junit.framework.TestCase; | ||
|
||
@GwtCompatible(emulated = true) | ||
public class BigDecimalMathTest extends TestCase { | ||
@GwtIncompatible | ||
private static final class RoundToDoubleTester { | ||
private final BigDecimal input; | ||
private final Map<RoundingMode, Double> expectedValues = new EnumMap<>(RoundingMode.class); | ||
private boolean unnecessaryShouldThrow = false; | ||
|
||
RoundToDoubleTester(BigDecimal input) { | ||
this.input = input; | ||
} | ||
|
||
RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) { | ||
for (RoundingMode mode : modes) { | ||
Double previous = expectedValues.put(mode, expectedValue); | ||
if (previous != null) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
return this; | ||
} | ||
|
||
public RoundToDoubleTester roundUnnecessaryShouldThrow() { | ||
unnecessaryShouldThrow = true; | ||
return this; | ||
} | ||
|
||
public void test() { | ||
assertThat(expectedValues.keySet()) | ||
.containsAtLeastElementsIn(EnumSet.complementOf(EnumSet.of(UNNECESSARY))); | ||
for (Map.Entry<RoundingMode, Double> entry : expectedValues.entrySet()) { | ||
RoundingMode mode = entry.getKey(); | ||
Double expectation = entry.getValue(); | ||
assertWithMessage("roundToDouble(" + input + ", " + mode + ")") | ||
.that(BigDecimalMath.roundToDouble(input, mode)) | ||
.isEqualTo(expectation); | ||
} | ||
|
||
if (!expectedValues.containsKey(UNNECESSARY)) { | ||
assertWithMessage("Expected roundUnnecessaryShouldThrow call") | ||
.that(unnecessaryShouldThrow) | ||
.isTrue(); | ||
try { | ||
BigDecimalMath.roundToDouble(input, UNNECESSARY); | ||
fail("Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)"); | ||
} catch (ArithmeticException expected) { | ||
// expected | ||
} | ||
} | ||
} | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_zero() { | ||
new RoundToDoubleTester(BigDecimal.ZERO).setExpectation(0.0, values()).test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_oneThird() { | ||
new RoundToDoubleTester( | ||
BigDecimal.ONE.divide(BigDecimal.valueOf(3), new MathContext(50, HALF_EVEN))) | ||
.roundUnnecessaryShouldThrow() | ||
.setExpectation(0.33333333333333337, UP, CEILING) | ||
.setExpectation(0.3333333333333333, HALF_EVEN, FLOOR, DOWN, HALF_UP, HALF_DOWN) | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_halfMinDouble() { | ||
BigDecimal minDouble = new BigDecimal(Double.MIN_VALUE); | ||
BigDecimal halfMinDouble = minDouble.divide(BigDecimal.valueOf(2)); | ||
new RoundToDoubleTester(halfMinDouble) | ||
.roundUnnecessaryShouldThrow() | ||
.setExpectation(Double.MIN_VALUE, UP, CEILING, HALF_UP) | ||
.setExpectation(0.0, HALF_EVEN, FLOOR, DOWN, HALF_DOWN) | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_halfNegativeMinDouble() { | ||
BigDecimal minDouble = new BigDecimal(-Double.MIN_VALUE); | ||
BigDecimal halfMinDouble = minDouble.divide(BigDecimal.valueOf(2)); | ||
new RoundToDoubleTester(halfMinDouble) | ||
.roundUnnecessaryShouldThrow() | ||
.setExpectation(-Double.MIN_VALUE, UP, FLOOR, HALF_UP) | ||
.setExpectation(-0.0, HALF_EVEN, CEILING, DOWN, HALF_DOWN) | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_smallPositive() { | ||
new RoundToDoubleTester(BigDecimal.valueOf(16)).setExpectation(16.0, values()).test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_maxPreciselyRepresentable() { | ||
new RoundToDoubleTester(BigDecimal.valueOf(1L << 53)) | ||
.setExpectation(Math.pow(2, 53), values()) | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_maxPreciselyRepresentablePlusOne() { | ||
double twoToThe53 = Math.pow(2, 53); | ||
// the representable doubles are 2^53 and 2^53 + 2. | ||
// 2^53+1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down. | ||
new RoundToDoubleTester(BigDecimal.valueOf((1L << 53) + 1)) | ||
.setExpectation(twoToThe53, DOWN, FLOOR, HALF_DOWN, HALF_EVEN) | ||
.setExpectation(Math.nextUp(twoToThe53), CEILING, UP, HALF_UP) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_twoToThe54PlusOne() { | ||
double twoToThe54 = Math.pow(2, 54); | ||
// the representable doubles are 2^54 and 2^54 + 4 | ||
// 2^54+1 is less than halfway between, so HALF_DOWN and HALF_UP will both go down. | ||
new RoundToDoubleTester(BigDecimal.valueOf((1L << 54) + 1)) | ||
.setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN) | ||
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_twoToThe54PlusOneHalf() { | ||
double twoToThe54 = Math.pow(2, 54); | ||
// the representable doubles are 2^54 and 2^54 + 4 | ||
// 2^54+1 is less than halfway between, so HALF_DOWN and HALF_UP will both go down. | ||
new RoundToDoubleTester(BigDecimal.valueOf(1L << 54).add(new BigDecimal(0.5))) | ||
.setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN) | ||
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_twoToThe54PlusThree() { | ||
double twoToThe54 = Math.pow(2, 54); | ||
// the representable doubles are 2^54 and 2^54 + 4 | ||
// 2^54+3 is more than halfway between, so HALF_DOWN and HALF_UP will both go up. | ||
new RoundToDoubleTester(BigDecimal.valueOf((1L << 54) + 3)) | ||
.setExpectation(twoToThe54, DOWN, FLOOR) | ||
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP, HALF_DOWN, HALF_UP, HALF_EVEN) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_twoToThe54PlusFour() { | ||
new RoundToDoubleTester(BigDecimal.valueOf((1L << 54) + 4)) | ||
.setExpectation(Math.pow(2, 54) + 4, values()) | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_maxDouble() { | ||
BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE); | ||
new RoundToDoubleTester(maxDoubleAsBD).setExpectation(Double.MAX_VALUE, values()).test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_maxDoublePlusOne() { | ||
BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE).add(BigDecimal.ONE); | ||
new RoundToDoubleTester(maxDoubleAsBD) | ||
.setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN) | ||
.setExpectation(Double.POSITIVE_INFINITY, UP, CEILING) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_wayTooBig() { | ||
BigDecimal bi = BigDecimal.valueOf(2).pow(2 * Double.MAX_EXPONENT); | ||
new RoundToDoubleTester(bi) | ||
.setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN) | ||
.setExpectation(Double.POSITIVE_INFINITY, UP, CEILING) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_smallNegative() { | ||
new RoundToDoubleTester(BigDecimal.valueOf(-16)).setExpectation(-16.0, values()).test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_minPreciselyRepresentable() { | ||
new RoundToDoubleTester(BigDecimal.valueOf(-1L << 53)) | ||
.setExpectation(-Math.pow(2, 53), values()) | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_minPreciselyRepresentableMinusOne() { | ||
// the representable doubles are -2^53 and -2^53 - 2. | ||
// -2^53-1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down. | ||
new RoundToDoubleTester(BigDecimal.valueOf((-1L << 53) - 1)) | ||
.setExpectation(-Math.pow(2, 53), DOWN, CEILING, HALF_DOWN, HALF_EVEN) | ||
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 53)), FLOOR, UP, HALF_UP) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_negativeTwoToThe54MinusOne() { | ||
new RoundToDoubleTester(BigDecimal.valueOf((-1L << 54) - 1)) | ||
.setExpectation(-Math.pow(2, 54), DOWN, CEILING, HALF_DOWN, HALF_UP, HALF_EVEN) | ||
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_negativeTwoToThe54MinusThree() { | ||
new RoundToDoubleTester(BigDecimal.valueOf((-1L << 54) - 3)) | ||
.setExpectation(-Math.pow(2, 54), DOWN, CEILING) | ||
.setExpectation( | ||
DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP, HALF_DOWN, HALF_UP, HALF_EVEN) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_negativeTwoToThe54MinusFour() { | ||
new RoundToDoubleTester(BigDecimal.valueOf((-1L << 54) - 4)) | ||
.setExpectation(-Math.pow(2, 54) - 4, values()) | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_minDouble() { | ||
BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE); | ||
new RoundToDoubleTester(minDoubleAsBD).setExpectation(-Double.MAX_VALUE, values()).test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_minDoubleMinusOne() { | ||
BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE).subtract(BigDecimal.ONE); | ||
new RoundToDoubleTester(minDoubleAsBD) | ||
.setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN) | ||
.setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
|
||
@GwtIncompatible | ||
public void testRoundToDouble_negativeWayTooBig() { | ||
BigDecimal bi = BigDecimal.valueOf(2).pow(2 * Double.MAX_EXPONENT).negate(); | ||
new RoundToDoubleTester(bi) | ||
.setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN) | ||
.setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR) | ||
.roundUnnecessaryShouldThrow() | ||
.test(); | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
android/guava/src/com/google/common/math/BigDecimalMath.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright (C) 2020 The Guava Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
|
||
package com.google.common.math; | ||
|
||
import com.google.common.annotations.GwtIncompatible; | ||
import java.math.BigDecimal; | ||
import java.math.RoundingMode; | ||
|
||
/** | ||
* A class for arithmetic on {@link BigDecimal} that is not covered by its built-in methods. | ||
* | ||
* @author Louis Wasserman | ||
* @since NEXT | ||
*/ | ||
@GwtIncompatible | ||
public class BigDecimalMath { | ||
private BigDecimalMath() {} | ||
|
||
/** | ||
* Returns {@code x}, rounded to a {@code double} with the specified rounding mode. If {@code x} | ||
* is precisely representable as a {@code double}, its {@code double} value will be returned; | ||
* otherwise, the rounding will choose between the two nearest representable values with {@code | ||
* mode}. | ||
* | ||
* <p>For the case of {@link RoundingMode#HALF_DOWN}, {@code HALF_UP}, and {@code HALF_EVEN}, | ||
* infinite {@code double} values are considered infinitely far away. For example, 2^2000 is not | ||
* representable as a double, but {@code roundToDouble(BigDecimal.valueOf(2).pow(2000), HALF_UP)} | ||
* will return {@code Double.MAX_VALUE}, not {@code Double.POSITIVE_INFINITY}. | ||
* | ||
* <p>For the case of {@link RoundingMode#HALF_EVEN}, this implementation uses the IEEE 754 | ||
* default rounding mode: if the two nearest representable values are equally near, the one with | ||
* the least significant bit zero is chosen. (In such cases, both of the nearest representable | ||
* values are even integers; this method returns the one that is a multiple of a greater power of | ||
* two.) | ||
* | ||
* @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} | ||
* is not precisely representable as a {@code double} | ||
* @since NEXT | ||
*/ | ||
public static double roundToDouble(BigDecimal x, RoundingMode mode) { | ||
return BigDecimalToDoubleRounder.INSTANCE.roundToDouble(x, mode); | ||
} | ||
|
||
private static class BigDecimalToDoubleRounder extends ToDoubleRounder<BigDecimal> { | ||
static final BigDecimalToDoubleRounder INSTANCE = new BigDecimalToDoubleRounder(); | ||
|
||
private BigDecimalToDoubleRounder() {} | ||
|
||
@Override | ||
double roundToDoubleArbitrarily(BigDecimal bigDecimal) { | ||
return bigDecimal.doubleValue(); | ||
} | ||
|
||
@Override | ||
int sign(BigDecimal bigDecimal) { | ||
return bigDecimal.signum(); | ||
} | ||
|
||
@Override | ||
BigDecimal toX(double d, RoundingMode mode) { | ||
return new BigDecimal(d); | ||
} | ||
|
||
@Override | ||
BigDecimal minus(BigDecimal a, BigDecimal b) { | ||
return a.subtract(b); | ||
} | ||
} | ||
} |
Oops, something went wrong.