Given 0.01835132889570552
, the digits 1835
are significant (and might need to round up), and the digits 132889570552
are insignificant, so we don't want to put them into our user's input systems.
Any digits to the left of the decimal place are significant, and the minimum number of numerals to the right of the decimal place is 4.
We need to pass this test:
assertEquals("1,835.1329", formatInsignificantDigits(1835.13288957055));
assertEquals("183.5133", formatInsignificantDigits(183.513288957055));
assertEquals("18.3513", formatInsignificantDigits(18.3513288957055));
assertEquals("1.8351", formatInsignificantDigits(1.83513288957055));
assertEquals("0.1835", formatInsignificantDigits(0.183513288957055));
assertEquals("0.01835", formatInsignificantDigits(0.0183513288957055));
assertEquals("0.001835", formatInsignificantDigits(0.00183513288957055));
assertEquals("0.0001835", formatInsignificantDigits(0.000183513288957055));
Note that 183.5133
rounded up by 0.0001
.
So the question is: How to write formatInsignificantDigits()
without resorting to brute force or string surgery? I will answer my own question with a little brute force, and then we can see what others come up with.
CodePudding user response:
The math function to answer the question "How significant are my decimal digits?" is Math.log10()
. So we convert our fractions into negative logarithms, and turn them into a positive number indicating how many zeros we need to pad. Then we add 4 to get all the digits, and we set that amount of precision:
public static @NonNull String formatInsignificantDigits(double input) {
DecimalFormat df = new DecimalFormat();
//noinspection NumericCastThatLosesPrecision
int digits = 4 (int) -Math.log10(input);
df.setMaximumFractionDigits(Math.max(digits, 4));
return df.format(input);
}
Does anyone have a cleaner way to do this? Or a function of DecimalFormat
that we overlooked?
And it turns out this algorithm converts -0.03997394115
to "0.04"
, because rounding up cascaded to the left, and then DecimalFormat
lost the trailing zeros. That might not be acceptable in some circumstances...
CodePudding user response:
Not sure where this fits on the clean / brute-force continuum, but you could also make use of BigDecimal
and MathContext
to manage precision:
public static String formatInsignificantDigits(double value) {
DecimalFormat format = DecimalFormat.getInstance();
format.setMaximumFractionDigits(Integer.MAX_VALUE);
BigDecimal bigDecimal = new BigDecimal(value);
int wholeDigits = Math.max(bigDecimal.precision() - bigDecimal.scale(), 0);
return format.format(bigDecimal.round(new MathContext(wholeDigits 4)));
}