Number formatting
This commit is contained in:
parent
64a58c2047
commit
858f3f32b2
@ -9,6 +9,7 @@ import jscl.math.operator.Percent;
|
|||||||
import jscl.math.operator.Rand;
|
import jscl.math.operator.Rand;
|
||||||
import jscl.math.operator.matrix.OperatorsRegistry;
|
import jscl.math.operator.matrix.OperatorsRegistry;
|
||||||
import jscl.text.ParseException;
|
import jscl.text.ParseException;
|
||||||
|
import midpcalc.Real;
|
||||||
import org.solovyev.common.NumberFormatter;
|
import org.solovyev.common.NumberFormatter;
|
||||||
import org.solovyev.common.math.MathRegistry;
|
import org.solovyev.common.math.MathRegistry;
|
||||||
import org.solovyev.common.msg.MessageRegistry;
|
import org.solovyev.common.msg.MessageRegistry;
|
||||||
@ -19,6 +20,8 @@ import javax.annotation.Nullable;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static midpcalc.Real.NumberFormat.*;
|
||||||
|
|
||||||
public class JsclMathEngine implements MathEngine {
|
public class JsclMathEngine implements MathEngine {
|
||||||
|
|
||||||
public static final AngleUnit DEFAULT_ANGLE_UNITS = AngleUnit.deg;
|
public static final AngleUnit DEFAULT_ANGLE_UNITS = AngleUnit.deg;
|
||||||
@ -38,7 +41,7 @@ public class JsclMathEngine implements MathEngine {
|
|||||||
};
|
};
|
||||||
private char groupingSeparator = GROUPING_SEPARATOR_DEFAULT;
|
private char groupingSeparator = GROUPING_SEPARATOR_DEFAULT;
|
||||||
private boolean roundResult = false;
|
private boolean roundResult = false;
|
||||||
private boolean scienceNotation = false;
|
private int numberFormat = FSE_NONE;
|
||||||
private int precision = 5;
|
private int precision = 5;
|
||||||
private boolean useGroupingSeparator = false;
|
private boolean useGroupingSeparator = false;
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -166,11 +169,17 @@ public class JsclMathEngine implements MathEngine {
|
|||||||
}
|
}
|
||||||
final NumberFormatter nf = numberFormatter.get();
|
final NumberFormatter nf = numberFormatter.get();
|
||||||
nf.setGroupingSeparator(useGroupingSeparator ? groupingSeparator : NumberFormatter.NO_GROUPING);
|
nf.setGroupingSeparator(useGroupingSeparator ? groupingSeparator : NumberFormatter.NO_GROUPING);
|
||||||
nf.setPrecision(roundResult ? precision : NumberFormatter.DEFAULT_PRECISION);
|
nf.setPrecision(roundResult ? precision : NumberFormatter.NO_ROUNDING);
|
||||||
if (scienceNotation) {
|
switch (numberFormat) {
|
||||||
|
case Real.NumberFormat.FSE_ENG:
|
||||||
nf.useEngineeringFormat(NumberFormatter.DEFAULT_MAGNITUDE);
|
nf.useEngineeringFormat(NumberFormatter.DEFAULT_MAGNITUDE);
|
||||||
} else {
|
break;
|
||||||
|
case FSE_SCI:
|
||||||
|
nf.useScientificFormat(NumberFormatter.DEFAULT_MAGNITUDE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
nf.useSimpleFormat();
|
nf.useSimpleFormat();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return nf.format(value, nb.radix).toString();
|
return nf.format(value, nb.radix).toString();
|
||||||
}
|
}
|
||||||
@ -307,7 +316,14 @@ public class JsclMathEngine implements MathEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setScienceNotation(boolean scienceNotation) {
|
public void setScienceNotation(boolean scienceNotation) {
|
||||||
this.scienceNotation = scienceNotation;
|
setNumberFormat(scienceNotation ? FSE_SCI : FSE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumberFormat(int numberFormat) {
|
||||||
|
if (numberFormat != FSE_SCI && numberFormat != FSE_ENG && numberFormat != FSE_NONE) {
|
||||||
|
throw new IllegalArgumentException("Unsupported format: " + numberFormat);
|
||||||
|
}
|
||||||
|
this.numberFormat = numberFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public char getGroupingSeparator() {
|
public char getGroupingSeparator() {
|
||||||
|
@ -5,21 +5,28 @@ import midpcalc.Real;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import static java.lang.Math.pow;
|
||||||
import static midpcalc.Real.NumberFormat.*;
|
import static midpcalc.Real.NumberFormat.*;
|
||||||
|
|
||||||
public class NumberFormatter {
|
public class NumberFormatter {
|
||||||
|
|
||||||
public static final int NO_GROUPING = 0;
|
public static final int NO_GROUPING = 0;
|
||||||
public static final int DEFAULT_PRECISION = 16;
|
public static final int NO_ROUNDING = -1;
|
||||||
public static final int DEFAULT_MAGNITUDE = 5;
|
public static final int DEFAULT_MAGNITUDE = 5;
|
||||||
|
public static final int MAX_PRECISION = 16;
|
||||||
|
|
||||||
private final Real.NumberFormat numberFormat = new Real.NumberFormat();
|
private final Real.NumberFormat numberFormat = new Real.NumberFormat();
|
||||||
private final Real real = new Real();
|
private final Real real = new Real();
|
||||||
private int format = FSE_NONE;
|
private int format = FSE_NONE;
|
||||||
private int simpleFormatMagnitude = DEFAULT_MAGNITUDE;
|
private int simpleFormatMagnitude = DEFAULT_MAGNITUDE;
|
||||||
private int precision = DEFAULT_PRECISION;
|
private int precision = MAX_PRECISION;
|
||||||
private char groupingSeparator;
|
private char groupingSeparator;
|
||||||
|
|
||||||
|
public void useScientificFormat(int simpleFormatMagnitude) {
|
||||||
|
this.format = FSE_SCI;
|
||||||
|
this.simpleFormatMagnitude = simpleFormatMagnitude;
|
||||||
|
}
|
||||||
|
|
||||||
public void useEngineeringFormat(int simpleFormatMagnitude) {
|
public void useEngineeringFormat(int simpleFormatMagnitude) {
|
||||||
this.format = FSE_ENG;
|
this.format = FSE_ENG;
|
||||||
this.simpleFormatMagnitude = simpleFormatMagnitude;
|
this.simpleFormatMagnitude = simpleFormatMagnitude;
|
||||||
@ -49,19 +56,27 @@ public class NumberFormatter {
|
|||||||
throw new IllegalArgumentException("Unsupported radix: " + radix);
|
throw new IllegalArgumentException("Unsupported radix: " + radix);
|
||||||
}
|
}
|
||||||
double absValue = Math.abs(value);
|
double absValue = Math.abs(value);
|
||||||
final boolean dec = radix == 10;
|
final boolean simpleFormat = useSimpleFormat(radix, absValue);
|
||||||
final boolean fixedFormat = !dec || format == FSE_NONE || Math.pow(10, -simpleFormatMagnitude) <= absValue && absValue < Math.pow(10, simpleFormatMagnitude);
|
|
||||||
|
|
||||||
if (fixedFormat) {
|
final int effectivePrecision = precision == NO_ROUNDING ? MAX_PRECISION : precision;
|
||||||
final int newScale = (int) (precision * Math.max(1, radix / 10f));
|
if (simpleFormat) {
|
||||||
|
final int newScale = (int) (effectivePrecision * Math.max(1, radix / 10f));
|
||||||
value = BigDecimal.valueOf(value).setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue();
|
value = BigDecimal.valueOf(value).setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue();
|
||||||
absValue = Math.abs(value);
|
absValue = Math.abs(value);
|
||||||
}
|
}
|
||||||
numberFormat.fse = fixedFormat ? FSE_FIX : FSE_ENG;
|
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.thousand = groupingSeparator;
|
||||||
numberFormat.precision = precision;
|
numberFormat.precision = effectivePrecision;
|
||||||
numberFormat.base = radix;
|
numberFormat.base = radix;
|
||||||
numberFormat.maxwidth = fixedFormat ? 100 : 30;
|
numberFormat.maxwidth = simpleFormat ? 100 : 30;
|
||||||
|
|
||||||
if (radix == 2 && value < 0) {
|
if (radix == 2 && value < 0) {
|
||||||
return "-" + prepare(absValue);
|
return "-" + prepare(absValue);
|
||||||
@ -69,6 +84,23 @@ public class NumberFormatter {
|
|||||||
return prepare(value);
|
return prepare(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean useSimpleFormat(int radix, double absValue) {
|
||||||
|
if (radix != 10) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (absValue < pow(10, -MAX_PRECISION)) {
|
||||||
|
// should never use simple format for small numbers
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (format == FSE_NONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (pow(10, -simpleFormatMagnitude) <= absValue && absValue < pow(10, simpleFormatMagnitude)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private CharSequence prepare(double value) {
|
private CharSequence prepare(double value) {
|
||||||
return stripZeros(realFormat(value)).replace('e', 'E');
|
return stripZeros(realFormat(value)).replace('e', 'E');
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package jscl;
|
package jscl;
|
||||||
|
|
||||||
|
import midpcalc.Real;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@ -85,7 +86,7 @@ public class JsclMathEngineTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testEngineeringNotationWithRounding() throws Exception {
|
public void testEngineeringNotationWithRounding() throws Exception {
|
||||||
final JsclMathEngine me = JsclMathEngine.getInstance();
|
final JsclMathEngine me = JsclMathEngine.getInstance();
|
||||||
me.setScienceNotation(true);
|
me.setNumberFormat(Real.NumberFormat.FSE_ENG);
|
||||||
me.setRoundResult(true);
|
me.setRoundResult(true);
|
||||||
me.setPrecision(5);
|
me.setPrecision(5);
|
||||||
|
|
||||||
@ -141,7 +142,7 @@ public class JsclMathEngineTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testEngineeringNotationWithoutRounding() throws Exception {
|
public void testEngineeringNotationWithoutRounding() throws Exception {
|
||||||
final JsclMathEngine me = JsclMathEngine.getInstance();
|
final JsclMathEngine me = JsclMathEngine.getInstance();
|
||||||
me.setScienceNotation(true);
|
me.setNumberFormat(Real.NumberFormat.FSE_ENG);
|
||||||
me.setRoundResult(false);
|
me.setRoundResult(false);
|
||||||
|
|
||||||
assertEquals("10E6", me.format(10000000d));
|
assertEquals("10E6", me.format(10000000d));
|
||||||
|
@ -3,7 +3,10 @@ package org.solovyev.common;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static java.lang.Math.pow;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.solovyev.common.NumberFormatter.DEFAULT_MAGNITUDE;
|
||||||
|
import static org.solovyev.common.NumberFormatter.NO_ROUNDING;
|
||||||
|
|
||||||
public class NumberFormatterTest {
|
public class NumberFormatterTest {
|
||||||
|
|
||||||
@ -25,12 +28,109 @@ public class NumberFormatterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSimpleFormat() throws Exception {
|
public void testScientificFormatNoRounding() throws Exception {
|
||||||
|
numberFormatter.useScientificFormat(DEFAULT_MAGNITUDE);
|
||||||
|
numberFormatter.setPrecision(NO_ROUNDING);
|
||||||
|
|
||||||
|
assertEquals("1", numberFormatter.format(1d));
|
||||||
|
assertEquals("0.3333333333333333", numberFormatter.format(1d / 3));
|
||||||
|
assertEquals("3.333333333333333E-19", numberFormatter.format(pow(10, -18) / 3));
|
||||||
|
assertEquals("1.23456789E18", numberFormatter.format(123456789 * pow(10, 10)));
|
||||||
|
assertEquals("1E-16", numberFormatter.format(pow(10, -16)));
|
||||||
|
assertEquals("5.9999999999999949E18", numberFormatter.format(5999999999999994999d));
|
||||||
|
|
||||||
|
testScientificFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScientificFormatWithRounding() throws Exception {
|
||||||
|
numberFormatter.useScientificFormat(DEFAULT_MAGNITUDE);
|
||||||
|
numberFormatter.setPrecision(5);
|
||||||
|
|
||||||
|
assertEquals("1", numberFormatter.format(1d));
|
||||||
|
assertEquals("0.33333", numberFormatter.format(1d / 3));
|
||||||
|
assertEquals("3.33333E-19", numberFormatter.format(pow(10, -18) / 3));
|
||||||
|
assertEquals("1.23457E18", numberFormatter.format(123456789 * pow(10, 10)));
|
||||||
|
assertEquals("1E-16", numberFormatter.format(pow(10, -16)));
|
||||||
|
assertEquals("6E18", numberFormatter.format(5999999999999994999d));
|
||||||
|
|
||||||
|
|
||||||
|
testScientificFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleFormatNoRounding() throws Exception {
|
||||||
numberFormatter.useSimpleFormat();
|
numberFormatter.useSimpleFormat();
|
||||||
|
numberFormatter.setPrecision(NO_ROUNDING);
|
||||||
|
|
||||||
|
assertEquals("1", numberFormatter.format(1d));
|
||||||
|
assertEquals("0.000001", numberFormatter.format(pow(10, -6)));
|
||||||
|
assertEquals("0.3333333333333333", numberFormatter.format(1d / 3));
|
||||||
|
assertEquals("3.333333333333333E-19", numberFormatter.format(pow(10, -18) / 3));
|
||||||
|
assertEquals("1234567890000000000", numberFormatter.format(123456789 * pow(10, 10)));
|
||||||
|
assertEquals("0.0000000000000001", numberFormatter.format(pow(10, -16)));
|
||||||
|
|
||||||
|
testSimpleFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleFormatWithRounding() throws Exception {
|
||||||
|
numberFormatter.useSimpleFormat();
|
||||||
|
numberFormatter.setPrecision(5);
|
||||||
|
|
||||||
|
assertEquals("1", numberFormatter.format(1d));
|
||||||
|
assertEquals("0", numberFormatter.format(pow(10, -6)));
|
||||||
|
assertEquals("0.33333", numberFormatter.format(1d / 3));
|
||||||
|
assertEquals("3.33333E-19", numberFormatter.format(pow(10, -18) / 3));
|
||||||
|
assertEquals("1234567890000000000", numberFormatter.format(123456789 * pow(10, 10)));
|
||||||
|
assertEquals("0", numberFormatter.format(pow(10, -16)));
|
||||||
|
|
||||||
|
testSimpleFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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("1000000000000000000", numberFormatter.format(pow(10, 18)));
|
||||||
|
assertEquals("1E19", numberFormatter.format(pow(10, 19)));
|
||||||
|
assertEquals("1E20", numberFormatter.format(pow(10, 20)));
|
||||||
|
assertEquals("1E100", numberFormatter.format(pow(10, 100)));
|
||||||
|
|
||||||
|
assertEquals("0.01", numberFormatter.format(pow(10, -2)));
|
||||||
|
assertEquals("1E-17", numberFormatter.format(pow(10, -17)));
|
||||||
|
assertEquals("1E-18", numberFormatter.format(pow(10, -18)));
|
||||||
|
assertEquals("1.5E-18", numberFormatter.format(1.5 * pow(10, -18)));
|
||||||
|
assertEquals("1E-100", numberFormatter.format(pow(10, -100)));
|
||||||
|
|
||||||
assertEquals("5000000000000000000", numberFormatter.format(5000000000000000000d));
|
assertEquals("5000000000000000000", numberFormatter.format(5000000000000000000d));
|
||||||
assertEquals("5000000000000000000", numberFormatter.format(5000000000000000001d));
|
assertEquals("5000000000000000000", numberFormatter.format(5000000000000000001d));
|
||||||
assertEquals("5999999999999994900", numberFormatter.format(5999999999999994999d));
|
assertEquals("5999999999999994900", numberFormatter.format(5999999999999994999d));
|
||||||
assertEquals("5E19", numberFormatter.format(50000000000000000000d));
|
assertEquals("5E19", numberFormatter.format(50000000000000000000d));
|
||||||
assertEquals("5E40", numberFormatter.format(50000000000000000000000000000000000000000d));
|
assertEquals("5E40", numberFormatter.format(50000000000000000000000000000000000000000d));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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("10000", numberFormatter.format(pow(10, 4)));
|
||||||
|
assertEquals("1E5", numberFormatter.format(pow(10, 5)));
|
||||||
|
assertEquals("1E18", numberFormatter.format(pow(10, 18)));
|
||||||
|
assertEquals("1E19", numberFormatter.format(pow(10, 19)));
|
||||||
|
assertEquals("1E20", numberFormatter.format(pow(10, 20)));
|
||||||
|
assertEquals("1E100", numberFormatter.format(pow(10, 100)));
|
||||||
|
|
||||||
|
assertEquals("0.01", numberFormatter.format(pow(10, -2)));
|
||||||
|
assertEquals("1E-17", numberFormatter.format(pow(10, -17)));
|
||||||
|
assertEquals("1E-18", numberFormatter.format(pow(10, -18)));
|
||||||
|
assertEquals("1.5E-18", numberFormatter.format(1.5 * pow(10, -18)));
|
||||||
|
assertEquals("1E-100", numberFormatter.format(pow(10, -100)));
|
||||||
|
|
||||||
|
assertEquals("5E18", numberFormatter.format(5000000000000000000d));
|
||||||
|
assertEquals("5E18", numberFormatter.format(5000000000000000001d));
|
||||||
|
assertEquals("5E19", numberFormatter.format(50000000000000000000d));
|
||||||
|
assertEquals("5E40", numberFormatter.format(50000000000000000000000000000000000000000d));
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user