diff --git a/src/main/java/info/adams/ryu/RyuDouble.java b/src/main/java/info/adams/ryu/RyuDouble.java index 565c6972..7a692e47 100644 --- a/src/main/java/info/adams/ryu/RyuDouble.java +++ b/src/main/java/info/adams/ryu/RyuDouble.java @@ -94,6 +94,11 @@ public static String doubleToString(double value) { } public static String doubleToString(double value, RoundingMode roundingMode) { + // Double.toString semantics requires using scientific notation if and only if outside this range. + return doubleToString(value, roundingMode, -3, 7); + } + + public static String doubleToString(double value, RoundingMode roundingMode, int lowExp, int highExp) { // Step 1: Decode the floating point number, and unify normalized and subnormal cases. // First, handle all the trivial cases. if (Double.isNaN(value)) return "NaN"; @@ -233,7 +238,7 @@ public static String doubleToString(double value, RoundingMode roundingMode) { // Step 4: Find the shortest decimal representation in the interval of legal representations. // // We do some extra work here in order to follow Float/Double.toString semantics. In particular, - // that requires printing in scientific format if and only if the exponent is between -3 and 7, + // that requires printing in scientific format if and only if the exponent is between lowExp and highExp, // and it requires printing at least two decimal digits. // // Above, we moved the decimal dot all the way to the right, so now we need to count digits to @@ -241,8 +246,7 @@ public static String doubleToString(double value, RoundingMode roundingMode) { final int vplength = decimalLength(dp); int exp = e10 + vplength - 1; - // Double.toString semantics requires using scientific notation if and only if outside this range. - boolean scientificNotation = !((exp >= -3) && (exp < 7)); + boolean scientificNotation = !((exp >= lowExp) && (exp < highExp)); int removed = 0; @@ -309,14 +313,15 @@ public static String doubleToString(double value, RoundingMode roundingMode) { } // Step 5: Print the decimal representation. - // We follow Double.toString semantics here. - char[] result = new char[24]; + // We follow Double.toString semantics here, possibly with different boundaries for switching + // to scientific notation. + char[] result = new char[14 - lowExp + highExp]; int index = 0; if (sign) { result[index++] = '-'; } - // Values in the interval [1E-3, 1E7) are special. + // Values in the interval [10^lowExp, 10^highExp) are special. if (scientificNotation) { // Print in the format x.xxxxxE-yy. for (int i = 0; i < olength - 1; i++) { @@ -346,7 +351,7 @@ public static String doubleToString(double value, RoundingMode roundingMode) { result[index++] = (char) ('0' + exp % 10); return new String(result, 0, index); } else { - // Otherwise follow the Java spec for values in the interval [1E-3, 1E7). + // Otherwise follow the Java spec for values in the interval [10^lowExp, 10^highExp). if (exp < 0) { // Decimal dot is before any of the digits. result[index++] = '0'; diff --git a/src/main/java/info/adams/ryu/RyuFloat.java b/src/main/java/info/adams/ryu/RyuFloat.java index c4b05cc9..9aa5d48a 100644 --- a/src/main/java/info/adams/ryu/RyuFloat.java +++ b/src/main/java/info/adams/ryu/RyuFloat.java @@ -93,6 +93,10 @@ public static String floatToString(float value) { } public static String floatToString(float value, RoundingMode roundingMode) { + return floatToString(value, roundingMode, -3, 7); + } + + public static String floatToString(float value, RoundingMode roundingMode, int lowExp, int highExp) { // Step 1: Decode the floating point number, and unify normalized and subnormal cases. // First, handle all the trivial cases. if (Float.isNaN(value)) return "NaN"; @@ -222,7 +226,7 @@ public static String floatToString(float value, RoundingMode roundingMode) { // Step 4: Find the shortest decimal representation in the interval of legal representations. // // We do some extra work here in order to follow Float/Double.toString semantics. In particular, - // that requires printing in scientific format if and only if the exponent is between -3 and 7, + // that requires printing in scientific format if and only if the exponent is between lowExp and highExp, // and it requires printing at least two decimal digits. // // Above, we moved the decimal dot all the way to the right, so now we need to count digits to @@ -231,7 +235,7 @@ public static String floatToString(float value, RoundingMode roundingMode) { int exp = e10 + dplength - 1; // Float.toString semantics requires using scientific notation if and only if outside this range. - boolean scientificNotation = !((exp >= -3) && (exp < 7)); + boolean scientificNotation = !((exp >= lowExp) && (exp < highExp)); int removed = 0; if (dpIsTrailingZeros && !roundingMode.acceptUpperBound(even)) { @@ -287,13 +291,15 @@ public static String floatToString(float value, RoundingMode roundingMode) { } // Step 5: Print the decimal representation. - // We follow Float.toString semantics here. - char[] result = new char[15]; + // We follow Float.toString semantics here, + // but adjusting the boundaries at which we switch to scientific notation + char[] result = new char[5 - lowExp + highExp]; int index = 0; if (sign) { result[index++] = '-'; } + // Values in the interval [10^lowExp, 10^highExp) are special. if (scientificNotation) { // Print in the format x.xxxxxE-yy. for (int i = 0; i < olength - 1; i++) { @@ -318,7 +324,7 @@ public static String floatToString(float value, RoundingMode roundingMode) { } result[index++] = (char) ('0' + exp % 10); } else { - // Otherwise follow the Java spec for values in the interval [1E-3, 1E7). + // Otherwise follow the Java spec for values in the interval [10^lowExp, 10^highExp). if (exp < 0) { // Decimal dot is before any of the digits. result[index++] = '0'; diff --git a/src/test/java/info/adams/ryu/RyuDoubleTest.java b/src/test/java/info/adams/ryu/RyuDoubleTest.java index efaa85f0..ce8136ea 100644 --- a/src/test/java/info/adams/ryu/RyuDoubleTest.java +++ b/src/test/java/info/adams/ryu/RyuDoubleTest.java @@ -14,13 +14,31 @@ package info.adams.ryu; +import org.junit.Assert; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class RyuDoubleTest extends DoubleToStringTest { - @Override + + @Override String f(double f, RoundingMode roundingMode) { return RyuDouble.doubleToString(f, roundingMode); } -} \ No newline at end of file + + @Test + public void testNoExpNeg() { + Assert.assertEquals("-1.234E-8", RyuDouble.doubleToString(-1.234e-8, RoundingMode.ROUND_EVEN, -3, 7)); + Assert.assertEquals("-0.00000001234", RyuDouble.doubleToString(-1.234e-8, RoundingMode.ROUND_EVEN, -9, 7)); + Assert.assertEquals("-0.00000000000000000001234", RyuDouble.doubleToString(-1.234e-20, RoundingMode.ROUND_EVEN, -21, 7)); + } + + @Test + public void testNoExpPos() { + Assert.assertEquals("-1.234E8", RyuDouble.doubleToString(-1.234e8, RoundingMode.ROUND_EVEN, -3, 7)); + Assert.assertEquals("-123400000.0", RyuDouble.doubleToString(-1.234e8, RoundingMode.ROUND_EVEN, -3, 9)); + Assert.assertEquals("-123400000000000000000.0", RyuDouble.doubleToString(-1.234e20, RoundingMode.ROUND_EVEN, -3, 21)); + } + +} diff --git a/src/test/java/info/adams/ryu/RyuFloatTest.java b/src/test/java/info/adams/ryu/RyuFloatTest.java index 8ec5e95b..4748b7e9 100644 --- a/src/test/java/info/adams/ryu/RyuFloatTest.java +++ b/src/test/java/info/adams/ryu/RyuFloatTest.java @@ -14,13 +14,31 @@ package info.adams.ryu; +import org.junit.Assert; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class RyuFloatTest extends FloatToStringTest { + @Override String f(float f, RoundingMode roundingMode) { return RyuFloat.floatToString(f, roundingMode); } -} \ No newline at end of file + + @Test + public void testNoExpNeg() { + Assert.assertEquals("-1.234E-8", RyuFloat.floatToString(-1.234e-8f, RoundingMode.ROUND_EVEN, -3, 7)); + Assert.assertEquals("-0.00000001234", RyuFloat.floatToString(-1.234e-8f, RoundingMode.ROUND_EVEN, -9, 7)); + Assert.assertEquals("-0.00000000000000000001234", RyuFloat.floatToString(-1.234e-20f, RoundingMode.ROUND_EVEN, -21, 7)); + } + + @Test + public void testNoExpPos() { + Assert.assertEquals("-1.234E8", RyuFloat.floatToString(-1.234e8f, RoundingMode.ROUND_EVEN, -3, 7)); + Assert.assertEquals("-123400000.0", RyuFloat.floatToString(-1.234e8f, RoundingMode.ROUND_EVEN, -3, 9)); + Assert.assertEquals("-123400000000000000000.0", RyuFloat.floatToString(-1.234e20f, RoundingMode.ROUND_EVEN, -3, 21)); + } + +}