Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow customizable switch boundaries for scientific notation. #196

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/main/java/info/adams/ryu/RyuDouble.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -233,16 +238,15 @@ 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
// figure out the correct exponent for scientific notation.
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;

Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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';
Expand Down
16 changes: 11 additions & 5 deletions src/main/java/info/adams/ryu/RyuFloat.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand All @@ -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)) {
Expand Down Expand Up @@ -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++) {
Expand All @@ -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';
Expand Down
22 changes: 20 additions & 2 deletions src/test/java/info/adams/ryu/RyuDoubleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

@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));
}

}
20 changes: 19 additions & 1 deletion src/test/java/info/adams/ryu/RyuFloatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

@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));
}

}