Use Real all the time
This commit is contained in:
@@ -9,7 +9,7 @@ import jscl.math.operator.Percent;
|
||||
import jscl.math.operator.Rand;
|
||||
import jscl.math.operator.matrix.OperatorsRegistry;
|
||||
import jscl.text.ParseException;
|
||||
import midpcalc.Real;
|
||||
import org.solovyev.common.NumberFormatter;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
import org.solovyev.common.msg.MessageRegistry;
|
||||
import org.solovyev.common.msg.Messages;
|
||||
@@ -17,7 +17,6 @@ import org.solovyev.common.msg.Messages;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -33,17 +32,10 @@ public class JsclMathEngine implements MathEngine {
|
||||
@Nonnull
|
||||
private final ConstantsRegistry constantsRegistry = new ConstantsRegistry();
|
||||
@Nonnull
|
||||
private final ThreadLocal<DecimalFormat> defaultNumbersFormat = new ThreadLocal<DecimalFormat>() {
|
||||
private final ThreadLocal<NumberFormatter> numberFormatter = new ThreadLocal<NumberFormatter>() {
|
||||
@Override
|
||||
protected DecimalFormat initialValue() {
|
||||
return new DecimalFormat();
|
||||
}
|
||||
};
|
||||
@Nonnull
|
||||
private final ThreadLocal<DecimalFormat> smallNumbersFormat = new ThreadLocal<DecimalFormat>() {
|
||||
@Override
|
||||
protected DecimalFormat initialValue() {
|
||||
return new DecimalFormat("##0.#####E0");
|
||||
protected NumberFormatter initialValue() {
|
||||
return new NumberFormatter();
|
||||
}
|
||||
};
|
||||
@Nonnull
|
||||
@@ -165,107 +157,30 @@ public class JsclMathEngine implements MathEngine {
|
||||
public String format(@Nonnull Double value, @Nonnull NumeralBase nb) throws NumeralBaseException {
|
||||
if (value.isInfinite()) {
|
||||
return formatInfinity(value);
|
||||
} else if (value.isNaN()) {
|
||||
}
|
||||
if (value.isNaN()) {
|
||||
// return "NaN"
|
||||
return String.valueOf(value);
|
||||
} else if (nb == NumeralBase.dec) {
|
||||
return formatDec(value);
|
||||
} else {
|
||||
return convert(value, nb);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String formatDec(@Nonnull Double value) {
|
||||
if (value == 0d) {
|
||||
return "0";
|
||||
if (nb == NumeralBase.dec) {
|
||||
if (value == 0d) {
|
||||
return "0";
|
||||
}
|
||||
// detect if current number is precisely equals to constant in constants' registry (NOTE: ONLY FOR SYSTEM CONSTANTS)
|
||||
final IConstant constant = findConstant(value);
|
||||
if (constant != null) {
|
||||
return constant.getName();
|
||||
}
|
||||
}
|
||||
// detect if current number is precisely equals to constant in constants' registry (NOTE: ONLY FOR SYSTEM CONSTANTS)
|
||||
final IConstant constant = findConstant(value);
|
||||
if (constant != null) {
|
||||
return constant.getName();
|
||||
}
|
||||
|
||||
final NumberFormatter nf = numberFormatter.get();
|
||||
nf.setGroupingSeparator(useGroupingSeparator ? decimalGroupSymbols.getGroupingSeparator() : NumberFormatter.NO_GROUPING);
|
||||
nf.setPrecision(roundResult ? precision : NumberFormatter.DEFAULT_PRECISION);
|
||||
if (scienceNotation) {
|
||||
return formatDecEngineering(value);
|
||||
}
|
||||
|
||||
return formatDecDefault(value);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String formatDecEngineering(@Nonnull Double value) {
|
||||
final double absValue = Math.abs(value);
|
||||
final boolean smallNumber = absValue < 1 && absValue >= 0.001;
|
||||
final Real.NumberFormat nf = new Real.NumberFormat();
|
||||
nf.fse = smallNumber ? Real.NumberFormat.FSE_FIX : Real.NumberFormat.FSE_ENG;
|
||||
if (useGroupingSeparator) {
|
||||
nf.thousand = decimalGroupSymbols.getGroupingSeparator();
|
||||
}
|
||||
if (roundResult) {
|
||||
nf.precision = precision;
|
||||
}
|
||||
final Real real = new Real(Double.toString(value));
|
||||
return stripTrailingZeros(real.toString(nf));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String stripTrailingZeros(@Nonnull String s) {
|
||||
final int dot = s.indexOf('.');
|
||||
if (dot < 0) {
|
||||
// no dot - no trailing zeros
|
||||
return s;
|
||||
}
|
||||
final int e = s.lastIndexOf('E');
|
||||
final int start;
|
||||
String exponent = "";
|
||||
if (e > 0) {
|
||||
exponent = s.substring(e);
|
||||
if (exponent.length() == 2 && exponent.charAt(1) == '0') {
|
||||
exponent = "";
|
||||
}
|
||||
start = e - 1;
|
||||
nf.useEngineeringFormat(NumberFormatter.DEFAULT_MAGNITUDE);
|
||||
} else {
|
||||
start = s.length() - 1;
|
||||
nf.useSimpleFormat();
|
||||
}
|
||||
final int i = findLastNonZero(s, start);
|
||||
return s.substring(0, i == dot ? i : i + 1) + exponent;
|
||||
}
|
||||
|
||||
private int findLastNonZero(String s, int start) {
|
||||
int i = start;
|
||||
for (; i >= 0; i--) {
|
||||
if (s.charAt(i) != '0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String formatDecDefault(@Nonnull Double value) {
|
||||
final BigDecimal bd;
|
||||
if (roundResult) {
|
||||
bd = BigDecimal.valueOf(value).setScale(precision, BigDecimal.ROUND_HALF_UP);
|
||||
} else {
|
||||
bd = BigDecimal.valueOf(value);
|
||||
}
|
||||
value = bd.doubleValue();
|
||||
if (value == 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
final DecimalFormat df = Math.abs(value) < Math.pow(10, -5) ? smallNumbersFormat.get() : defaultNumbersFormat.get();
|
||||
df.setDecimalFormatSymbols(decimalGroupSymbols);
|
||||
df.setGroupingUsed(useGroupingSeparator);
|
||||
df.setGroupingSize(NumeralBase.dec.getGroupingSize());
|
||||
if (roundResult) {
|
||||
df.setMaximumFractionDigits(precision);
|
||||
} else {
|
||||
// set maximum fraction digits high enough to show all fraction digits in case of no rounding
|
||||
df.setMaximumFractionDigits(MAX_FRACTION_DIGITS);
|
||||
}
|
||||
return df.format(value);
|
||||
return nf.format(value, nb.radix).toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
File diff suppressed because it is too large
Load Diff
136
jscl/src/main/java/org/solovyev/common/NumberFormatter.java
Normal file
136
jscl/src/main/java/org/solovyev/common/NumberFormatter.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package org.solovyev.common;
|
||||
|
||||
import midpcalc.Real;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static midpcalc.Real.NumberFormat.*;
|
||||
|
||||
public class NumberFormatter {
|
||||
|
||||
public static final int NO_GROUPING = 0;
|
||||
public static final int DEFAULT_PRECISION = 16;
|
||||
public static final int DEFAULT_MAGNITUDE = 5;
|
||||
|
||||
private final Real.NumberFormat numberFormat = new Real.NumberFormat();
|
||||
private final Real real = new Real();
|
||||
private int format = FSE_NONE;
|
||||
private int simpleFormatMagnitude = DEFAULT_MAGNITUDE;
|
||||
private int precision = DEFAULT_PRECISION;
|
||||
private char groupingSeparator;
|
||||
|
||||
public void useEngineeringFormat(int simpleFormatMagnitude) {
|
||||
this.format = FSE_ENG;
|
||||
this.simpleFormatMagnitude = simpleFormatMagnitude;
|
||||
}
|
||||
|
||||
public void useSimpleFormat() {
|
||||
this.format = FSE_NONE;
|
||||
this.simpleFormatMagnitude = DEFAULT_MAGNITUDE;
|
||||
}
|
||||
|
||||
public void setPrecision(int precision) {
|
||||
this.precision = precision;
|
||||
}
|
||||
|
||||
public void setGroupingSeparator(char groupingSeparator) {
|
||||
this.groupingSeparator = groupingSeparator;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public CharSequence format(double 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);
|
||||
}
|
||||
double absValue = Math.abs(value);
|
||||
final boolean dec = radix == 10;
|
||||
final boolean fixedFormat = !dec || format == FSE_NONE || Math.pow(10, -simpleFormatMagnitude) <= absValue && absValue < Math.pow(10, simpleFormatMagnitude);
|
||||
|
||||
if (fixedFormat) {
|
||||
final int newScale = (int) (precision * Math.max(1, radix / 10f));
|
||||
value = BigDecimal.valueOf(value).setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue();
|
||||
absValue = Math.abs(value);
|
||||
}
|
||||
numberFormat.fse = fixedFormat ? FSE_FIX : FSE_ENG;
|
||||
numberFormat.thousand = groupingSeparator;
|
||||
numberFormat.precision = precision;
|
||||
numberFormat.base = radix;
|
||||
numberFormat.maxwidth = fixedFormat ? 100 : 30;
|
||||
|
||||
if (radix == 2 && value < 0) {
|
||||
return "-" + prepare(absValue);
|
||||
}
|
||||
return prepare(value);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private CharSequence prepare(double 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 stripZeros(@Nonnull String s) {
|
||||
int dot = -1;
|
||||
int firstNonZero = -1;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
final char c = s.charAt(i);
|
||||
if (c != '0' && c != groupingSeparator && firstNonZero == -1) {
|
||||
firstNonZero = i;
|
||||
}
|
||||
if (c == '.') {
|
||||
dot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstNonZero == -1) {
|
||||
// all zeros
|
||||
return "";
|
||||
}
|
||||
if (dot < 0) {
|
||||
// no dot - no trailing zeros
|
||||
return s.substring(firstNonZero);
|
||||
}
|
||||
if (firstNonZero == dot) {
|
||||
// one zero before dot must be kept
|
||||
firstNonZero--;
|
||||
}
|
||||
final int e = s.lastIndexOf('e');
|
||||
final int i = findLastNonZero(s, e);
|
||||
final int end = i == dot ? i : i + 1;
|
||||
return s.substring(firstNonZero, end) + getExponent(s, e);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String getExponent(@Nonnull String s, int e) {
|
||||
String exponent = "";
|
||||
if (e > 0) {
|
||||
exponent = s.substring(e);
|
||||
if (exponent.length() == 2 && exponent.charAt(1) == '0') {
|
||||
exponent = "";
|
||||
}
|
||||
}
|
||||
return exponent;
|
||||
}
|
||||
|
||||
private int findLastNonZero(@Nonnull String s, int e) {
|
||||
int i = e > 0 ? e - 1 : s.length() - 1;
|
||||
for (; i >= 0; i--) {
|
||||
if (s.charAt(i) != '0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user