Skip to content

Commit 633abf2

Browse files
lowassernick-someone
authored andcommitted
Add LongMath.roundToDouble.
RELNOTES=Add LongMath.roundToDouble (#3895) ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=316951853
1 parent bbf3e1b commit 633abf2

File tree

4 files changed

+522
-0
lines changed

4 files changed

+522
-0
lines changed

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

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,28 @@
2525
import static com.google.common.math.MathTesting.NONZERO_LONG_CANDIDATES;
2626
import static com.google.common.math.MathTesting.POSITIVE_INTEGER_CANDIDATES;
2727
import static com.google.common.math.MathTesting.POSITIVE_LONG_CANDIDATES;
28+
import static com.google.common.truth.Truth.assertThat;
2829
import static com.google.common.truth.Truth.assertWithMessage;
2930
import static java.math.BigInteger.valueOf;
31+
import static java.math.RoundingMode.CEILING;
32+
import static java.math.RoundingMode.DOWN;
3033
import static java.math.RoundingMode.FLOOR;
34+
import static java.math.RoundingMode.HALF_DOWN;
35+
import static java.math.RoundingMode.HALF_EVEN;
36+
import static java.math.RoundingMode.HALF_UP;
3137
import static java.math.RoundingMode.UNNECESSARY;
38+
import static java.math.RoundingMode.UP;
39+
import static java.math.RoundingMode.values;
3240

3341
import com.google.common.annotations.GwtCompatible;
3442
import com.google.common.annotations.GwtIncompatible;
3543
import com.google.common.testing.NullPointerTester;
3644
import java.math.BigDecimal;
3745
import java.math.BigInteger;
3846
import java.math.RoundingMode;
47+
import java.util.EnumMap;
48+
import java.util.EnumSet;
49+
import java.util.Map;
3950
import java.util.Random;
4051
import junit.framework.TestCase;
4152

@@ -949,6 +960,159 @@ public void testIsPrimeThrowsOnNegative() {
949960
}
950961
}
951962

963+
@GwtIncompatible
964+
private static final class RoundToDoubleTester {
965+
private final long input;
966+
private final Map<RoundingMode, Double> expectedValues = new EnumMap<>(RoundingMode.class);
967+
private boolean unnecessaryShouldThrow = false;
968+
969+
RoundToDoubleTester(long input) {
970+
this.input = input;
971+
}
972+
973+
RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) {
974+
for (RoundingMode mode : modes) {
975+
Double previous = expectedValues.put(mode, expectedValue);
976+
if (previous != null) {
977+
throw new AssertionError();
978+
}
979+
}
980+
return this;
981+
}
982+
983+
public RoundToDoubleTester roundUnnecessaryShouldThrow() {
984+
unnecessaryShouldThrow = true;
985+
return this;
986+
}
987+
988+
public void test() {
989+
assertThat(expectedValues.keySet())
990+
.containsAtLeastElementsIn(EnumSet.complementOf(EnumSet.of(UNNECESSARY)));
991+
for (Map.Entry<RoundingMode, Double> entry : expectedValues.entrySet()) {
992+
RoundingMode mode = entry.getKey();
993+
Double expectation = entry.getValue();
994+
assertWithMessage("roundToDouble(" + input + ", " + mode + ")")
995+
.that(LongMath.roundToDouble(input, mode))
996+
.isEqualTo(expectation);
997+
}
998+
999+
if (!expectedValues.containsKey(UNNECESSARY)) {
1000+
assertWithMessage("Expected roundUnnecessaryShouldThrow call")
1001+
.that(unnecessaryShouldThrow)
1002+
.isTrue();
1003+
try {
1004+
LongMath.roundToDouble(input, UNNECESSARY);
1005+
fail("Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)");
1006+
} catch (ArithmeticException expected) {
1007+
// expected
1008+
}
1009+
}
1010+
}
1011+
}
1012+
1013+
@GwtIncompatible
1014+
public void testRoundToDouble_zero() {
1015+
new RoundToDoubleTester(0).setExpectation(0.0, values()).test();
1016+
}
1017+
1018+
@GwtIncompatible
1019+
public void testRoundToDouble_smallPositive() {
1020+
new RoundToDoubleTester(16).setExpectation(16.0, values()).test();
1021+
}
1022+
1023+
@GwtIncompatible
1024+
public void testRoundToDouble_maxPreciselyRepresentable() {
1025+
new RoundToDoubleTester(1L << 53).setExpectation(Math.pow(2, 53), values()).test();
1026+
}
1027+
1028+
@GwtIncompatible
1029+
public void testRoundToDouble_maxPreciselyRepresentablePlusOne() {
1030+
double twoToThe53 = Math.pow(2, 53);
1031+
// the representable doubles are 2^53 and 2^53 + 2.
1032+
// 2^53+1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down.
1033+
// 2^53 is "more even" -- it's a multiple of a larger power of two -- so HALF_EVEN goes to it.
1034+
new RoundToDoubleTester((1L << 53) + 1)
1035+
.setExpectation(twoToThe53, DOWN, FLOOR, HALF_DOWN, HALF_EVEN)
1036+
.setExpectation(Math.nextUp(twoToThe53), CEILING, UP, HALF_UP)
1037+
.roundUnnecessaryShouldThrow()
1038+
.test();
1039+
}
1040+
1041+
@GwtIncompatible
1042+
public void testRoundToDouble_twoToThe54PlusOne() {
1043+
double twoToThe54 = Math.pow(2, 54);
1044+
// the representable doubles are 2^54 and 2^54 + 4
1045+
// 2^54+1 is less than halfway between, so HALF_* will all go down.
1046+
new RoundToDoubleTester((1L << 54) + 1)
1047+
.setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN)
1048+
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP)
1049+
.roundUnnecessaryShouldThrow()
1050+
.test();
1051+
}
1052+
1053+
@GwtIncompatible
1054+
public void testRoundToDouble_twoToThe54PlusThree() {
1055+
double twoToThe54 = Math.pow(2, 54);
1056+
// the representable doubles are 2^54 and 2^54 + 4
1057+
// 2^54+3 is more than halfway between, so HALF_* will all go up.
1058+
new RoundToDoubleTester((1L << 54) + 3)
1059+
.setExpectation(twoToThe54, DOWN, FLOOR)
1060+
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP, HALF_DOWN, HALF_UP, HALF_EVEN)
1061+
.roundUnnecessaryShouldThrow()
1062+
.test();
1063+
}
1064+
1065+
@GwtIncompatible
1066+
public void testRoundToDouble_twoToThe54PlusFour() {
1067+
new RoundToDoubleTester((1L << 54) + 4).setExpectation(Math.pow(2, 54) + 4, values()).test();
1068+
}
1069+
1070+
@GwtIncompatible
1071+
public void testRoundToDouble_smallNegative() {
1072+
new RoundToDoubleTester(-16).setExpectation(-16.0, values()).test();
1073+
}
1074+
1075+
@GwtIncompatible
1076+
public void testRoundToDouble_minPreciselyRepresentable() {
1077+
new RoundToDoubleTester(-1L << 53).setExpectation(-Math.pow(2, 53), values()).test();
1078+
}
1079+
1080+
@GwtIncompatible
1081+
public void testRoundToDouble_minPreciselyRepresentableMinusOne() {
1082+
// the representable doubles are -2^53 and -2^53 - 2.
1083+
// -2^53-1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down.
1084+
// -2^53 is "more even" -- a multiple of a greater power of two -- so HALF_EVEN will go to it.
1085+
new RoundToDoubleTester((-1L << 53) - 1)
1086+
.setExpectation(-Math.pow(2, 53), DOWN, CEILING, HALF_DOWN, HALF_EVEN)
1087+
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 53)), FLOOR, UP, HALF_UP)
1088+
.roundUnnecessaryShouldThrow()
1089+
.test();
1090+
}
1091+
1092+
@GwtIncompatible
1093+
public void testRoundToDouble_negativeTwoToThe54MinusOne() {
1094+
new RoundToDoubleTester((-1L << 54) - 1)
1095+
.setExpectation(-Math.pow(2, 54), DOWN, CEILING, HALF_DOWN, HALF_UP, HALF_EVEN)
1096+
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP)
1097+
.roundUnnecessaryShouldThrow()
1098+
.test();
1099+
}
1100+
1101+
@GwtIncompatible
1102+
public void testRoundToDouble_negativeTwoToThe54MinusThree() {
1103+
new RoundToDoubleTester((-1L << 54) - 3)
1104+
.setExpectation(-Math.pow(2, 54), DOWN, CEILING)
1105+
.setExpectation(
1106+
DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP, HALF_DOWN, HALF_UP, HALF_EVEN)
1107+
.roundUnnecessaryShouldThrow()
1108+
.test();
1109+
}
1110+
1111+
@GwtIncompatible
1112+
public void testRoundToDouble_negativeTwoToThe54MinusFour() {
1113+
new RoundToDoubleTester((-1L << 54) - 4).setExpectation(-Math.pow(2, 54) - 4, values()).test();
1114+
}
1115+
9521116
private static void failFormat(String template, Object... args) {
9531117
assertWithMessage(template, args).fail();
9541118
}

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.common.annotations.GwtCompatible;
3030
import com.google.common.annotations.GwtIncompatible;
3131
import com.google.common.annotations.VisibleForTesting;
32+
import com.google.common.primitives.Longs;
3233
import com.google.common.primitives.UnsignedLongs;
3334
import java.math.BigInteger;
3435
import java.math.RoundingMode;
@@ -1203,5 +1204,101 @@ private boolean testWitness(long base, long n) {
12031204
}
12041205
}
12051206

1207+
/**
1208+
* Returns {@code x}, rounded to a {@code double} with the specified rounding mode. If {@code x}
1209+
* is precisely representable as a {@code double}, its {@code double} value will be returned;
1210+
* otherwise, the rounding will choose between the two nearest representable values with {@code
1211+
* mode}.
1212+
*
1213+
* <p>For the case of {@link RoundingMode#HALF_EVEN}, this implementation uses the IEEE 754
1214+
* default rounding mode: if the two nearest representable values are equally near, the one with
1215+
* the least significant bit zero is chosen. (In such cases, both of the nearest representable
1216+
* values are even integers; this method returns the one that is a multiple of a greater power of
1217+
* two.)
1218+
*
1219+
* @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x}
1220+
* is not precisely representable as a {@code double}
1221+
* @since NEXT
1222+
*/
1223+
@SuppressWarnings("deprecation")
1224+
@GwtIncompatible
1225+
public static double roundToDouble(long x, RoundingMode mode) {
1226+
// Logic copied from ToDoubleRounder. The repeated logic isn't ideal, but this doesn't box.
1227+
double roundArbitrarily = (double) x;
1228+
long roundArbitrarilyAsLong = (long) roundArbitrarily;
1229+
int cmpXToRoundArbitrarily = Longs.compare(x, roundArbitrarilyAsLong);
1230+
switch (mode) {
1231+
case UNNECESSARY:
1232+
checkRoundingUnnecessary(cmpXToRoundArbitrarily == 0);
1233+
return roundArbitrarily;
1234+
case FLOOR:
1235+
return (cmpXToRoundArbitrarily >= 0)
1236+
? roundArbitrarily
1237+
: DoubleUtils.nextDown(roundArbitrarily);
1238+
case CEILING:
1239+
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
1240+
case DOWN:
1241+
if (x >= 0) {
1242+
return (cmpXToRoundArbitrarily >= 0)
1243+
? roundArbitrarily
1244+
: DoubleUtils.nextDown(roundArbitrarily);
1245+
} else {
1246+
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
1247+
}
1248+
case UP:
1249+
if (x >= 0) {
1250+
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
1251+
} else {
1252+
return (cmpXToRoundArbitrarily >= 0)
1253+
? roundArbitrarily
1254+
: DoubleUtils.nextDown(roundArbitrarily);
1255+
}
1256+
case HALF_DOWN:
1257+
case HALF_UP:
1258+
case HALF_EVEN:
1259+
{
1260+
long roundFloor;
1261+
double roundFloorAsDouble;
1262+
long roundCeiling;
1263+
double roundCeilingAsDouble;
1264+
1265+
if (cmpXToRoundArbitrarily >= 0) {
1266+
roundFloorAsDouble = roundArbitrarily;
1267+
roundFloor = roundArbitrarilyAsLong;
1268+
roundCeilingAsDouble = Math.nextUp(roundArbitrarily);
1269+
roundCeiling = (long) Math.ceil(roundCeilingAsDouble);
1270+
} else {
1271+
roundCeilingAsDouble = roundArbitrarily;
1272+
roundCeiling = roundArbitrarilyAsLong;
1273+
roundFloorAsDouble = DoubleUtils.nextDown(roundArbitrarily);
1274+
roundFloor = (long) Math.floor(roundFloorAsDouble);
1275+
}
1276+
1277+
long deltaToFloor = x - roundFloor;
1278+
long deltaToCeiling = roundCeiling - x;
1279+
int diff = Longs.compare(deltaToFloor, deltaToCeiling);
1280+
if (diff < 0) { // closer to floor
1281+
return roundFloorAsDouble;
1282+
} else if (diff > 0) { // closer to ceiling
1283+
return roundCeilingAsDouble;
1284+
}
1285+
// halfway between the representable values; do the half-whatever logic
1286+
switch (mode) {
1287+
case HALF_EVEN:
1288+
return ((DoubleUtils.getSignificand(roundFloorAsDouble) & 1L) == 0)
1289+
? roundFloorAsDouble
1290+
: roundCeilingAsDouble;
1291+
case HALF_DOWN:
1292+
return (x >= 0) ? roundFloorAsDouble : roundCeilingAsDouble;
1293+
case HALF_UP:
1294+
return (x >= 0) ? roundCeilingAsDouble : roundFloorAsDouble;
1295+
default:
1296+
throw new AssertionError("impossible");
1297+
}
1298+
}
1299+
}
1300+
throw new AssertionError("impossible");
1301+
}
1302+
12061303
private LongMath() {}
12071304
}

0 commit comments

Comments
 (0)