From 7ed24b043039bd3020f2ab93e1b1354f9314adb9 Mon Sep 17 00:00:00 2001 From: Luc Maisonobe Date: Thu, 25 Mar 2021 10:54:10 +0100 Subject: [PATCH 1/3] Allow customizable switch boundaries for scientific notation. --- src/main/java/info/adams/ryu/RyuDouble.java | 17 +++++++++----- src/main/java/info/adams/ryu/RyuFloat.java | 16 +++++++++----- .../java/info/adams/ryu/RyuDoubleTest.java | 22 +++++++++++++++++-- .../java/info/adams/ryu/RyuFloatTest.java | 20 ++++++++++++++++- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/main/java/info/adams/ryu/RyuDouble.java b/src/main/java/info/adams/ryu/RyuDouble.java index 565c6972..e3d9f7d3 100644 --- a/src/main/java/info/adams/ryu/RyuDouble.java +++ b/src/main/java/info/adams/ryu/RyuDouble.java @@ -94,6 +94,10 @@ public static String doubleToString(double value) { } public static String doubleToString(double value, RoundingMode roundingMode) { + 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 +237,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 @@ -242,7 +246,7 @@ public static String doubleToString(double value, RoundingMode roundingMode) { 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, + // but adjusting the boundaries at which we switch 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)); + } + +} From d93328227c3053949dc159b82907673bc70d3ece Mon Sep 17 00:00:00 2001 From: Ulf Adams Date: Sat, 11 Sep 2021 10:22:55 +0200 Subject: [PATCH 2/3] Update docs, trigger CI --- src/main/java/info/adams/ryu/RyuDouble.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/info/adams/ryu/RyuDouble.java b/src/main/java/info/adams/ryu/RyuDouble.java index e3d9f7d3..028369d2 100644 --- a/src/main/java/info/adams/ryu/RyuDouble.java +++ b/src/main/java/info/adams/ryu/RyuDouble.java @@ -94,7 +94,8 @@ public static String doubleToString(double value) { } public static String doubleToString(double value, RoundingMode roundingMode) { - return doubleToString(value, roundingMode, -3, 7); + // 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) { @@ -245,7 +246,6 @@ public static String doubleToString(double value, RoundingMode roundingMode, int 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 >= lowExp) && (exp < highExp)); int removed = 0; From 1e4c47773882c16f4315a528439b8d8d5be2a976 Mon Sep 17 00:00:00 2001 From: Ulf Adams Date: Sat, 11 Sep 2021 11:27:03 +0200 Subject: [PATCH 3/3] Update comment, trigger CI --- src/main/java/info/adams/ryu/RyuDouble.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/info/adams/ryu/RyuDouble.java b/src/main/java/info/adams/ryu/RyuDouble.java index 028369d2..7a692e47 100644 --- a/src/main/java/info/adams/ryu/RyuDouble.java +++ b/src/main/java/info/adams/ryu/RyuDouble.java @@ -313,8 +313,8 @@ public static String doubleToString(double value, RoundingMode roundingMode, int } // Step 5: Print the decimal representation. - // We follow Double.toString semantics here, - // but adjusting the boundaries at which we switch to scientific notation + // 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) {