From 85b9cdc56a5d2974a33cf49fa3069b80ca68de57 Mon Sep 17 00:00:00 2001 From: serso Date: Sat, 16 Apr 2016 21:40:49 +0200 Subject: [PATCH] Format BigInteger directly --- jscl/src/main/java/jscl/JsclMathEngine.java | 22 +++++- jscl/src/main/java/jscl/MathContext.java | 3 + jscl/src/main/java/jscl/math/JsclInteger.java | 3 +- .../org/solovyev/common/NumberFormatter.java | 79 ++++++++++++++++--- .../solovyev/common/NumberFormatterTest.java | 37 +++++++++ 5 files changed, 130 insertions(+), 14 deletions(-) diff --git a/jscl/src/main/java/jscl/JsclMathEngine.java b/jscl/src/main/java/jscl/JsclMathEngine.java index 5b7ddee5..66508e4f 100644 --- a/jscl/src/main/java/jscl/JsclMathEngine.java +++ b/jscl/src/main/java/jscl/JsclMathEngine.java @@ -17,6 +17,7 @@ import org.solovyev.common.msg.Messages; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.List; import static midpcalc.Real.NumberFormat.*; @@ -166,6 +167,10 @@ public class JsclMathEngine implements MathEngine { return constant.getName(); } } + return prepareNumberFormatter(nb).format(value, nb.radix).toString(); + } + + private NumberFormatter prepareNumberFormatter(@Nonnull NumeralBase nb) { final NumberFormatter nf = numberFormatter.get(); nf.setGroupingSeparator(useGroupingSeparator ? getGroupingSeparatorChar(nb) : NumberFormatter.NO_GROUPING); nf.setPrecision(roundResult ? precision : NumberFormatter.NO_ROUNDING); @@ -180,7 +185,22 @@ public class JsclMathEngine implements MathEngine { nf.useSimpleFormat(); break; } - return nf.format(value, nb.radix).toString(); + return nf; + } + + @Override + public String format(@Nonnull BigInteger value) throws NumeralBaseException { + return format(value, numeralBase); + } + + @Nonnull + public String format(@Nonnull BigInteger value, @Nonnull NumeralBase nb) throws NumeralBaseException { + if (nb == NumeralBase.dec) { + if (BigInteger.ZERO.equals(value)) { + return "0"; + } + } + return prepareNumberFormatter(nb).format(value, nb.radix).toString(); } @Nullable diff --git a/jscl/src/main/java/jscl/MathContext.java b/jscl/src/main/java/jscl/MathContext.java index 45d9e330..c4ef9cc7 100644 --- a/jscl/src/main/java/jscl/MathContext.java +++ b/jscl/src/main/java/jscl/MathContext.java @@ -6,6 +6,7 @@ import jscl.math.operator.Operator; import org.solovyev.common.math.MathRegistry; import javax.annotation.Nonnull; +import java.math.BigInteger; public interface MathContext { @@ -46,6 +47,8 @@ public interface MathContext { @Nonnull String format(double value) throws NumeralBaseException; + String format(@Nonnull BigInteger value) throws NumeralBaseException; + @Nonnull String format(double value, @Nonnull NumeralBase nb) throws NumeralBaseException; diff --git a/jscl/src/main/java/jscl/math/JsclInteger.java b/jscl/src/main/java/jscl/math/JsclInteger.java index a8fc3efa..c365d053 100644 --- a/jscl/src/main/java/jscl/math/JsclInteger.java +++ b/jscl/src/main/java/jscl/math/JsclInteger.java @@ -352,8 +352,7 @@ public final class JsclInteger extends Generic { } public String toString() { - // todo serso: actually better way is to provide custom format() method for integers and not to convert integer to double - return JsclMathEngine.getInstance().format(this.content.doubleValue()); + return JsclMathEngine.getInstance().format(content); } public String toJava() { diff --git a/jscl/src/main/java/org/solovyev/common/NumberFormatter.java b/jscl/src/main/java/org/solovyev/common/NumberFormatter.java index 5eb44bfa..b875774d 100644 --- a/jscl/src/main/java/org/solovyev/common/NumberFormatter.java +++ b/jscl/src/main/java/org/solovyev/common/NumberFormatter.java @@ -1,16 +1,13 @@ package org.solovyev.common; -import java.math.BigDecimal; - -import javax.annotation.Nonnull; - import midpcalc.Real; +import javax.annotation.Nonnull; +import java.math.BigDecimal; +import java.math.BigInteger; + import static java.lang.Math.pow; -import static midpcalc.Real.NumberFormat.FSE_ENG; -import static midpcalc.Real.NumberFormat.FSE_FIX; -import static midpcalc.Real.NumberFormat.FSE_NONE; -import static midpcalc.Real.NumberFormat.FSE_SCI; +import static midpcalc.Real.NumberFormat.*; public class NumberFormatter { @@ -54,11 +51,14 @@ public class NumberFormatter { return format(value, 10); } + @Nonnull + public CharSequence format(@Nonnull BigInteger value) { + return format(value, 10); + } + @Nonnull public CharSequence format(double value, int radix) { - if (radix != 2 && radix != 8 && radix != 10 && radix != 16) { - throw new IllegalArgumentException("Unsupported radix: " + radix); - } + checkRadix(radix); double absValue = Math.abs(value); final boolean simpleFormat = useSimpleFormat(radix, absValue); @@ -88,6 +88,39 @@ public class NumberFormatter { return prepare(value); } + @Nonnull + public CharSequence format(@Nonnull BigInteger value, int radix) { + checkRadix(radix); + final BigInteger absValue = value.abs(); + final boolean simpleFormat = useSimpleFormat(radix, absValue); + + final int effectivePrecision = precision == NO_ROUNDING ? MAX_PRECISION : precision; + if (simpleFormat) { + numberFormat.fse = FSE_FIX; + } else if (format == FSE_NONE) { + // originally, a simple format was requested but we have to use something more appropriate, f.e. scientific + // format + numberFormat.fse = FSE_SCI; + } else { + numberFormat.fse = format; + } + numberFormat.thousand = groupingSeparator; + numberFormat.precision = effectivePrecision; + numberFormat.base = radix; + numberFormat.maxwidth = simpleFormat ? 100 : 30; + + if (radix == 2 && value.compareTo(BigInteger.ZERO) < 0) { + return "-" + prepare(absValue); + } + return prepare(value); + } + + private void checkRadix(int radix) { + if (radix != 2 && radix != 8 && radix != 10 && radix != 16) { + throw new IllegalArgumentException("Unsupported radix: " + radix); + } + } + private boolean useSimpleFormat(int radix, double absValue) { if (radix != 10) { return true; @@ -103,17 +136,41 @@ public class NumberFormatter { return false; } + private boolean useSimpleFormat(int radix, @Nonnull BigInteger absValue) { + if (radix != 10) { + return true; + } + if (format == FSE_NONE) { + return true; + } + if (absValue.compareTo(BigInteger.valueOf((long) pow(10, simpleFormatMagnitude))) < 0) { + return true; + } + return false; + } + @Nonnull private CharSequence prepare(double value) { return stripZeros(realFormat(value)).replace('e', 'E'); } + @Nonnull + private CharSequence prepare(@Nonnull BigInteger value) { + return stripZeros(realFormat(value)).replace('e', 'E'); + } + @Nonnull private String realFormat(double value) { real.assign(Double.toString(value)); return real.toString(numberFormat); } + @Nonnull + private String realFormat(@Nonnull BigInteger value) { + real.assign(value.toString()); + return real.toString(numberFormat); + } + @Nonnull private String stripZeros(@Nonnull String s) { int dot = -1; diff --git a/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java b/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java index 45ce11f1..e7847319 100644 --- a/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java +++ b/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java @@ -3,7 +3,10 @@ package org.solovyev.common; import org.junit.Before; import org.junit.Test; +import java.math.BigInteger; + import static java.lang.Math.pow; +import static java.math.BigInteger.TEN; import static org.junit.Assert.assertEquals; import static org.solovyev.common.NumberFormatter.DEFAULT_MAGNITUDE; import static org.solovyev.common.NumberFormatter.NO_ROUNDING; @@ -99,32 +102,66 @@ public class NumberFormatterTest { // testing simple format with and without rounding private void testSimpleFormat() { assertEquals("0.00001", numberFormatter.format(pow(10, -5))); + assertEquals("100", numberFormatter.format(pow(10, 2))); + assertEquals("1", numberFormatter.format(BigInteger.ONE)); + assertEquals("1000", numberFormatter.format(BigInteger.valueOf(1000))); + assertEquals("1000000000000000000", numberFormatter.format(pow(10, 18))); + assertEquals("1000000000000000000", numberFormatter.format(BigInteger.valueOf(10).pow(18))); + assertEquals("1E19", numberFormatter.format(pow(10, 19))); + assertEquals("1E19", numberFormatter.format(BigInteger.valueOf(10).pow(19))); + assertEquals("1E20", numberFormatter.format(pow(10, 20))); + assertEquals("1E20", numberFormatter.format(BigInteger.valueOf(10).pow(20))); + assertEquals("1E100", numberFormatter.format(pow(10, 100))); + assertEquals("1E100", numberFormatter.format(BigInteger.valueOf(10).pow(100))); assertEquals("0.01", numberFormatter.format(pow(10, -2))); assertEquals("5000000000000000000", numberFormatter.format(5000000000000000000d)); + assertEquals("5000000000000000000", numberFormatter.format(BigInteger.valueOf(5000000000000000000L))); + assertEquals("5000000000000000000", numberFormatter.format(5000000000000000001d)); + assertEquals("5000000000000000001", numberFormatter.format(BigInteger.valueOf(5000000000000000001L))); + assertEquals("5999999999999994900", numberFormatter.format(5999999999999994999d)); + assertEquals("5999999999999994999", numberFormatter.format(BigInteger.valueOf(5999999999999994999L))); + assertEquals("5E19", numberFormatter.format(50000000000000000000d)); + assertEquals("5E19", numberFormatter.format(BigInteger.valueOf(5L).multiply(TEN.pow(19)))); + assertEquals("5E40", numberFormatter.format(50000000000000000000000000000000000000000d)); + assertEquals("5E40", numberFormatter.format(BigInteger.valueOf(5L).multiply(TEN.pow(40)))); } // testing scientific format with and without rounding private void testScientificFormat() { assertEquals("0.00001", numberFormatter.format(pow(10, -5))); assertEquals("1E-6", numberFormatter.format(pow(10, -6))); + assertEquals("100", numberFormatter.format(pow(10, 2))); + assertEquals("100", numberFormatter.format(TEN.pow(2))); + assertEquals("10000", numberFormatter.format(pow(10, 4))); + assertEquals("10000", numberFormatter.format(TEN.pow(4))); + assertEquals("1E5", numberFormatter.format(pow(10, 5))); + assertEquals("1E5", numberFormatter.format(TEN.pow(5))); + assertEquals("1E18", numberFormatter.format(pow(10, 18))); + assertEquals("1E18", numberFormatter.format(TEN.pow(18))); + assertEquals("1E19", numberFormatter.format(pow(10, 19))); + assertEquals("1E19", numberFormatter.format(TEN.pow( 19))); + assertEquals("1E20", numberFormatter.format(pow(10, 20))); + assertEquals("1E20", numberFormatter.format(TEN.pow(20))); + assertEquals("1E100", numberFormatter.format(pow(10, 100))); + assertEquals("1E100", numberFormatter.format(TEN.pow(100))); assertEquals("0.01", numberFormatter.format(pow(10, -2))); assertEquals("1E-17", numberFormatter.format(pow(10, -17)));