Skip to content

Commit 2b5c096

Browse files
lowassernetdpb
authored andcommitted
Implement BigIntegerMath.roundToDouble, which rounds to the nearest representable double value.
Partially implements #3895 RELNOTES=`math`: Added `BigIntegerMath.roundToDouble`. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=314837286
1 parent f87596b commit 2b5c096

File tree

6 files changed

+850
-6
lines changed

6 files changed

+850
-6
lines changed

android/guava-tests/test/com/google/common/math/BigIntegerMathTest.java

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static com.google.common.math.MathTesting.NEGATIVE_BIGINTEGER_CANDIDATES;
2323
import static com.google.common.math.MathTesting.NONZERO_BIGINTEGER_CANDIDATES;
2424
import static com.google.common.math.MathTesting.POSITIVE_BIGINTEGER_CANDIDATES;
25+
import static com.google.common.truth.Truth.assertThat;
26+
import static com.google.common.truth.Truth.assertWithMessage;
2527
import static java.math.BigInteger.ONE;
2628
import static java.math.BigInteger.TEN;
2729
import static java.math.BigInteger.ZERO;
@@ -33,6 +35,7 @@
3335
import static java.math.RoundingMode.HALF_UP;
3436
import static java.math.RoundingMode.UNNECESSARY;
3537
import static java.math.RoundingMode.UP;
38+
import static java.math.RoundingMode.values;
3639
import static java.util.Arrays.asList;
3740

3841
import com.google.common.annotations.GwtCompatible;
@@ -41,6 +44,9 @@
4144
import java.math.BigDecimal;
4245
import java.math.BigInteger;
4346
import java.math.RoundingMode;
47+
import java.util.EnumMap;
48+
import java.util.EnumSet;
49+
import java.util.Map;
4450
import junit.framework.TestCase;
4551

4652
/**
@@ -542,6 +548,219 @@ public void testBinomialOutside() {
542548
}
543549
}
544550

551+
@GwtIncompatible
552+
private static final class RoundToDoubleTester {
553+
private final BigInteger input;
554+
private final Map<RoundingMode, Double> expectedValues = new EnumMap<>(RoundingMode.class);
555+
private boolean unnecessaryShouldThrow = false;
556+
557+
RoundToDoubleTester(BigInteger input) {
558+
this.input = input;
559+
}
560+
561+
RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) {
562+
for (RoundingMode mode : modes) {
563+
Double previous = expectedValues.put(mode, expectedValue);
564+
if (previous != null) {
565+
throw new AssertionError();
566+
}
567+
}
568+
return this;
569+
}
570+
571+
public RoundToDoubleTester roundUnnecessaryShouldThrow() {
572+
unnecessaryShouldThrow = true;
573+
return this;
574+
}
575+
576+
public void test() {
577+
assertThat(expectedValues.keySet())
578+
.containsAtLeastElementsIn(EnumSet.complementOf(EnumSet.of(UNNECESSARY)));
579+
for (Map.Entry<RoundingMode, Double> entry : expectedValues.entrySet()) {
580+
RoundingMode mode = entry.getKey();
581+
Double expectation = entry.getValue();
582+
assertWithMessage("roundToDouble(" + input + ", " + mode + ")")
583+
.that(BigIntegerMath.roundToDouble(input, mode))
584+
.isEqualTo(expectation);
585+
}
586+
587+
if (!expectedValues.containsKey(UNNECESSARY)) {
588+
assertWithMessage("Expected roundUnnecessaryShouldThrow call")
589+
.that(unnecessaryShouldThrow)
590+
.isTrue();
591+
try {
592+
BigIntegerMath.roundToDouble(input, UNNECESSARY);
593+
fail("Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)");
594+
} catch (ArithmeticException expected) {
595+
// expected
596+
}
597+
}
598+
}
599+
}
600+
601+
@GwtIncompatible
602+
public void testRoundToDouble_Zero() {
603+
new RoundToDoubleTester(BigInteger.ZERO).setExpectation(0.0, values()).test();
604+
}
605+
606+
@GwtIncompatible
607+
public void testRoundToDouble_smallPositive() {
608+
new RoundToDoubleTester(BigInteger.valueOf(16)).setExpectation(16.0, values()).test();
609+
}
610+
611+
@GwtIncompatible
612+
public void testRoundToDouble_maxPreciselyRepresentable() {
613+
new RoundToDoubleTester(BigInteger.valueOf(1L << 53))
614+
.setExpectation(Math.pow(2, 53), values())
615+
.test();
616+
}
617+
618+
@GwtIncompatible
619+
public void testRoundToDouble_maxPreciselyRepresentablePlusOne() {
620+
double twoToThe53 = Math.pow(2, 53);
621+
// the representable doubles are 2^53 and 2^53 + 2.
622+
// 2^53+1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down.
623+
new RoundToDoubleTester(BigInteger.valueOf((1L << 53) + 1))
624+
.setExpectation(twoToThe53, DOWN, FLOOR, HALF_DOWN, HALF_EVEN)
625+
.setExpectation(Math.nextUp(twoToThe53), CEILING, UP, HALF_UP)
626+
.roundUnnecessaryShouldThrow()
627+
.test();
628+
}
629+
630+
@GwtIncompatible
631+
public void testRoundToDouble_twoToThe54PlusOne() {
632+
double twoToThe54 = Math.pow(2, 54);
633+
// the representable doubles are 2^54 and 2^54 + 4
634+
// 2^54+1 is less than halfway between, so HALF_DOWN and HALF_UP will both go down.
635+
new RoundToDoubleTester(BigInteger.valueOf((1L << 54) + 1))
636+
.setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN)
637+
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP)
638+
.roundUnnecessaryShouldThrow()
639+
.test();
640+
}
641+
642+
@GwtIncompatible
643+
public void testRoundToDouble_twoToThe54PlusThree() {
644+
double twoToThe54 = Math.pow(2, 54);
645+
// the representable doubles are 2^54 and 2^54 + 4
646+
// 2^54+3 is more than halfway between, so HALF_DOWN and HALF_UP will both go up.
647+
new RoundToDoubleTester(BigInteger.valueOf((1L << 54) + 3))
648+
.setExpectation(twoToThe54, DOWN, FLOOR)
649+
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP, HALF_DOWN, HALF_UP, HALF_EVEN)
650+
.roundUnnecessaryShouldThrow()
651+
.test();
652+
}
653+
654+
@GwtIncompatible
655+
public void testRoundToDouble_twoToThe54PlusFour() {
656+
new RoundToDoubleTester(BigInteger.valueOf((1L << 54) + 4))
657+
.setExpectation(Math.pow(2, 54) + 4, values())
658+
.test();
659+
}
660+
661+
@GwtIncompatible
662+
public void testRoundToDouble_maxDouble() {
663+
BigInteger maxDoubleAsBI = DoubleMath.roundToBigInteger(Double.MAX_VALUE, UNNECESSARY);
664+
new RoundToDoubleTester(maxDoubleAsBI).setExpectation(Double.MAX_VALUE, values()).test();
665+
}
666+
667+
@GwtIncompatible
668+
public void testRoundToDouble_maxDoublePlusOne() {
669+
BigInteger maxDoubleAsBI =
670+
DoubleMath.roundToBigInteger(Double.MAX_VALUE, UNNECESSARY).add(BigInteger.ONE);
671+
new RoundToDoubleTester(maxDoubleAsBI)
672+
.setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN)
673+
.setExpectation(Double.POSITIVE_INFINITY, UP, CEILING)
674+
.roundUnnecessaryShouldThrow()
675+
.test();
676+
}
677+
678+
@GwtIncompatible
679+
public void testRoundToDouble_wayTooBig() {
680+
BigInteger bi = BigInteger.ONE.shiftLeft(2 * Double.MAX_EXPONENT);
681+
new RoundToDoubleTester(bi)
682+
.setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN)
683+
.setExpectation(Double.POSITIVE_INFINITY, UP, CEILING)
684+
.roundUnnecessaryShouldThrow()
685+
.test();
686+
}
687+
688+
@GwtIncompatible
689+
public void testRoundToDouble_smallNegative() {
690+
new RoundToDoubleTester(BigInteger.valueOf(-16)).setExpectation(-16.0, values()).test();
691+
}
692+
693+
@GwtIncompatible
694+
public void testRoundToDouble_minPreciselyRepresentable() {
695+
new RoundToDoubleTester(BigInteger.valueOf(-1L << 53))
696+
.setExpectation(-Math.pow(2, 53), values())
697+
.test();
698+
}
699+
700+
@GwtIncompatible
701+
public void testRoundToDouble_minPreciselyRepresentableMinusOne() {
702+
// the representable doubles are -2^53 and -2^53 - 2.
703+
// -2^53-1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down.
704+
new RoundToDoubleTester(BigInteger.valueOf((-1L << 53) - 1))
705+
.setExpectation(-Math.pow(2, 53), DOWN, CEILING, HALF_DOWN, HALF_EVEN)
706+
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 53)), FLOOR, UP, HALF_UP)
707+
.roundUnnecessaryShouldThrow()
708+
.test();
709+
}
710+
711+
@GwtIncompatible
712+
public void testRoundToDouble_negativeTwoToThe54MinusOne() {
713+
new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 1))
714+
.setExpectation(-Math.pow(2, 54), DOWN, CEILING, HALF_DOWN, HALF_UP, HALF_EVEN)
715+
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP)
716+
.roundUnnecessaryShouldThrow()
717+
.test();
718+
}
719+
720+
@GwtIncompatible
721+
public void testRoundToDouble_negativeTwoToThe54MinusThree() {
722+
new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 3))
723+
.setExpectation(-Math.pow(2, 54), DOWN, CEILING)
724+
.setExpectation(
725+
DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP, HALF_DOWN, HALF_UP, HALF_EVEN)
726+
.roundUnnecessaryShouldThrow()
727+
.test();
728+
}
729+
730+
@GwtIncompatible
731+
public void testRoundToDouble_negativeTwoToThe54MinusFour() {
732+
new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 4))
733+
.setExpectation(-Math.pow(2, 54) - 4, values())
734+
.test();
735+
}
736+
737+
@GwtIncompatible
738+
public void testRoundToDouble_minDouble() {
739+
BigInteger minDoubleAsBI = DoubleMath.roundToBigInteger(-Double.MAX_VALUE, UNNECESSARY);
740+
new RoundToDoubleTester(minDoubleAsBI).setExpectation(-Double.MAX_VALUE, values()).test();
741+
}
742+
743+
@GwtIncompatible
744+
public void testRoundToDouble_minDoubleMinusOne() {
745+
BigInteger minDoubleAsBI =
746+
DoubleMath.roundToBigInteger(-Double.MAX_VALUE, UNNECESSARY).subtract(BigInteger.ONE);
747+
new RoundToDoubleTester(minDoubleAsBI)
748+
.setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN)
749+
.setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR)
750+
.roundUnnecessaryShouldThrow()
751+
.test();
752+
}
753+
754+
@GwtIncompatible
755+
public void testRoundToDouble_negativeWayTooBig() {
756+
BigInteger bi = BigInteger.ONE.shiftLeft(2 * Double.MAX_EXPONENT).negate();
757+
new RoundToDoubleTester(bi)
758+
.setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN)
759+
.setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR)
760+
.roundUnnecessaryShouldThrow()
761+
.test();
762+
}
763+
545764
@GwtIncompatible // NullPointerTester
546765
public void testNullPointers() {
547766
NullPointerTester tester = new NullPointerTester();

android/guava/src/com/google/common/math/BigIntegerMath.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary;
2222
import static java.math.RoundingMode.CEILING;
2323
import static java.math.RoundingMode.FLOOR;
24+
import static java.math.RoundingMode.HALF_DOWN;
2425
import static java.math.RoundingMode.HALF_EVEN;
26+
import static java.math.RoundingMode.UNNECESSARY;
2527

2628
import com.google.common.annotations.Beta;
2729
import com.google.common.annotations.GwtCompatible;
@@ -56,7 +58,7 @@ public final class BigIntegerMath {
5658
*/
5759
@Beta
5860
public static BigInteger ceilingPowerOfTwo(BigInteger x) {
59-
return BigInteger.ZERO.setBit(log2(x, RoundingMode.CEILING));
61+
return BigInteger.ZERO.setBit(log2(x, CEILING));
6062
}
6163

6264
/**
@@ -68,7 +70,7 @@ public static BigInteger ceilingPowerOfTwo(BigInteger x) {
6870
*/
6971
@Beta
7072
public static BigInteger floorPowerOfTwo(BigInteger x) {
71-
return BigInteger.ZERO.setBit(log2(x, RoundingMode.FLOOR));
73+
return BigInteger.ZERO.setBit(log2(x, FLOOR));
7274
}
7375

7476
/** Returns {@code true} if {@code x} represents a power of two. */
@@ -306,6 +308,57 @@ private static BigInteger sqrtApproxWithDoubles(BigInteger x) {
306308
return DoubleMath.roundToBigInteger(Math.sqrt(DoubleUtils.bigToDouble(x)), HALF_EVEN);
307309
}
308310

311+
/**
312+
* Returns {@code x}, rounded to a {@code double} with the specified rounding mode. If {@code x}
313+
* is precisely representable as a {@code double}, its {@code double} value will be returned;
314+
* otherwise, the rounding will choose between the two nearest representable values with {@code
315+
* mode}.
316+
*
317+
* <p>For the case of {@link RoundingMode#HALF_DOWN}, {@code HALF_UP}, and {@code HALF_EVEN},
318+
* infinite {@code double} values are considered infinitely far away. For example, 2^2000 is not
319+
* representable as a double, but {@code roundToDouble(BigInteger.valueOf(2).pow(2000), HALF_UP)}
320+
* will return {@code Double.MAX_VALUE}, not {@code Double.POSITIVE_INFINITY}.
321+
*
322+
* <p>For the case of {@link RoundingMode#HALF_EVEN}, this implementation uses the IEEE 754
323+
* default rounding mode: if the two nearest representable values are equally near, the one with
324+
* the least significant bit zero is chosen. (In such cases, both of the nearest representable
325+
* values are even integers; this method returns the one that is a multiple of a greater power of
326+
* two.)
327+
*
328+
* @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x}
329+
* is not precisely representable as a {@code double}
330+
* @since NEXT
331+
*/
332+
@GwtIncompatible
333+
public static double roundToDouble(BigInteger x, RoundingMode mode) {
334+
return BigIntegerToDoubleRounder.INSTANCE.roundToDouble(x, mode);
335+
}
336+
337+
@GwtIncompatible
338+
private static class BigIntegerToDoubleRounder extends ToDoubleRounder<BigInteger> {
339+
private static final BigIntegerToDoubleRounder INSTANCE = new BigIntegerToDoubleRounder();
340+
341+
@Override
342+
double roundToDoubleArbitrarily(BigInteger bigInteger) {
343+
return DoubleUtils.bigToDouble(bigInteger);
344+
}
345+
346+
@Override
347+
int sign(BigInteger bigInteger) {
348+
return bigInteger.signum();
349+
}
350+
351+
@Override
352+
BigInteger toX(double d, RoundingMode mode) {
353+
return DoubleMath.roundToBigInteger(d, mode);
354+
}
355+
356+
@Override
357+
BigInteger minus(BigInteger a, BigInteger b) {
358+
return a.subtract(b);
359+
}
360+
}
361+
309362
/**
310363
* Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code
311364
* RoundingMode}.
@@ -432,7 +485,7 @@ public static BigInteger binomial(int n, int k) {
432485
long numeratorAccum = n;
433486
long denominatorAccum = 1;
434487

435-
int bits = LongMath.log2(n, RoundingMode.CEILING);
488+
int bits = LongMath.log2(n, CEILING);
436489

437490
int numeratorBits = bits;
438491

0 commit comments

Comments
 (0)