Improve size-in-bytes parser's use of BigInteger/BigDecimal

- use bitLength to see if we overflow Long
 - use BigInteger and BigDecimal to parse, instead of parseLong/parseDouble
This commit is contained in:
Havoc Pennington 2014-06-25 16:52:14 -04:00
parent 6311ef8d4d
commit 02867c8aec
2 changed files with 13 additions and 13 deletions

View File

@ -624,12 +624,6 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
} }
} }
private static boolean isValidLong(BigInteger v) {
BigInteger max = BigInteger.valueOf(Long.MAX_VALUE);
BigInteger min = BigInteger.valueOf(Long.MIN_VALUE);
return max.compareTo(v) >= 0 && min.compareTo(v) <= 0;
}
/** /**
* Parses a size-in-bytes string. If no units are specified in the string, * Parses a size-in-bytes string. If no units are specified in the string,
* it is assumed to be in bytes. The returned value is in bytes. The purpose * it is assumed to be in bytes. The returned value is in bytes. The purpose
@ -673,14 +667,12 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
// if the string is purely digits, parse as an integer to avoid // if the string is purely digits, parse as an integer to avoid
// possible precision loss; otherwise as a double. // possible precision loss; otherwise as a double.
if (numberString.matches("[0-9]+")) { if (numberString.matches("[0-9]+")) {
Long v = Long.parseLong(numberString); result = units.bytes.multiply(new BigInteger(numberString));
result = units.bytes.multiply(BigInteger.valueOf(v));
} else { } else {
Double v = Double.parseDouble(numberString); BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(new BigDecimal(numberString));
BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(BigDecimal.valueOf(v));
result = resultDecimal.toBigInteger(); result = resultDecimal.toBigInteger();
} }
if (isValidLong(result)) if (result.bitLength() < 64)
return result.longValue(); return result.longValue();
else else
throw new ConfigException.BadValue(originForException, pathForException, throw new ConfigException.BadValue(originForException, pathForException,

View File

@ -59,7 +59,10 @@ class UnitParserTest extends TestUtils {
@Test @Test
def parseMemorySizeInBytes(): Unit = { def parseMemorySizeInBytes(): Unit = {
def parseMem(s: String) = SimpleConfig.parseBytes(s, fakeOrigin(), "test") def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
assertEquals(Long.MaxValue, parseMem(s"${Long.MaxValue} bytes"))
assertEquals(Long.MinValue, parseMem(s"${Long.MinValue} bytes"))
val oneMebis = List("1048576", "1048576b", "1048576bytes", "1048576byte", val oneMebis = List("1048576", "1048576b", "1048576bytes", "1048576byte",
"1048576 b", "1048576 bytes", "1048576 b", "1048576 bytes",
@ -123,13 +126,18 @@ class UnitParserTest extends TestUtils {
// later on we'll want to check this with BigInteger version of getBytes // later on we'll want to check this with BigInteger version of getBytes
@Test @Test
def parseHugeMemorySizes(): Unit = { def parseHugeMemorySizes(): Unit = {
def parseMem(s: String) = SimpleConfig.parseBytes(s, fakeOrigin(), "test") def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
def assertOutOfRange(s: String) = { def assertOutOfRange(s: String) = {
val fail = intercept[ConfigException.BadValue] { val fail = intercept[ConfigException.BadValue] {
parseMem(s) parseMem(s)
} }
assertTrue("number was too big", fail.getMessage.contains("out of range")) assertTrue("number was too big", fail.getMessage.contains("out of range"))
} }
import java.math.BigInteger
assertOutOfRange(s"${BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)).toString} bytes")
assertOutOfRange(s"${BigInteger.valueOf(Long.MinValue).subtract(BigInteger.valueOf(1)).toString} bytes")
var result = 1024L * 1024 * 1024 var result = 1024L * 1024 * 1024
for (unit <- Seq("zebi", "yobi")) { for (unit <- Seq("zebi", "yobi")) {
val first = unit.substring(0, 1).toUpperCase() val first = unit.substring(0, 1).toUpperCase()