I'd like to format a BigDecimal in Java to 8 Characters (including the seperator), rounding HALF-UP. Some examples:
12345.6789 -> "12345.68"
123 -> " 123.0"
0.12345678 -> "0.123457"
1.5 -> " 1.5"
0.0045 -> " 0.0045"
12 -> " 12.0"
The result must have leading spaces to fill up to 8 characters. What's the easiest way?
I'm pretty sure this is not the best way, but it's one way.
First note that if your numbers have more than six digits before the period, or more generally more than width - 2
digits before the period, your format with .0
will not work anymore.
String.format()
uses half up as a rounding method, so you can use it as a first step to get your output to eight characters.
BigDecimal b = new BigDecimal(0.0045);
String format = "%8f";
String str = String.format(format, b);
Output:
12345.678900
123.000000
0.123457
1.500000
0.004500
12.000000
123456.000000
By default String.format()
uses a precision of 6 for BigDecimal
. To get your custom precision, you need to know how many digits are before the period and substract this number (+ 1 for the period itself) from the total width.
width - digits - 1
To get the number of digits you can simply check if (number % radix) == number
applies for radix = 10, 100, 1000, ... As soon as it fits you know the number of digits.
public static int digitsBeforePeriod(BigDecimal b) {
int number = b.intValue();
int radix = 10;
int digits = 1;
while((number % radix) != number) {
radix *= 10;
++digits;
}
return digits;
}
So the next step is to modify the format:
BigDecimal b = new BigDecimal(0.0045);
int digits = digitsBeforePeriod(b);
String format = "%8." + (8 - digits - 1) + "f";
String str = String.format(format, b);
Output:
12345.68
123.0000
0.123457
1.500000
0.004500
12.00000
123456.0
Still there are lots of 0s, but at least the rounding is correct now. Now you specify that if a number is round to an integer, you want to print it with a .0
suffix, otherwise without.
To achieve this there might also exist clever formats, I didn't think any further though. The naive way to do this is simply:
while(str.endsWith("0") && !str.endsWith(".0")) {
str = str.substring(0, str.length()-1);
}
This removes the last character of the string until it either doesn't end on 0
at all or ends with .0
.
Now the numbers will have the correct format, but are not aligned correctly:
12345.68
123.0
0.123457
1.5
0.0045
12.0
123456.0
To align them, just use the String.format()
again.
str = String.format("%" + width + "s", str);
Output:
12345.68
123.0
0.123457
1.5
0.0045
12.0
123456.0
In the context this looks like the following. Note that I included a check wether or not the number can be formatted that way - if not, it will print Invalid. You can of course also just print the number, I just wanted to show the limitations of that format.
public static String trimToWidth(BigDecimal b, int width) {
String str = "Invalid";
int digits = digitsBeforePeriod(b);
// -2 for period and 0
if(digits <= width - 2) {
// use width and (width - digits - 1) as precision (-1 for period)
String format = "%" + width + "." + (width - digits - 1) + "f";
// rounds half-up
str = String.format(format, b);
// trim trailing 0s, unless it's .0
while(str.endsWith("0") && !str.endsWith(".0")) {
str = str.substring(0, str.length()-1);
}
}
// add spaces in front
str = String.format("%" + width + "s", str);
return str;
}
public static int digitsBeforePeriod(BigDecimal b) {
int number = b.intValue();
int radix = 10;
int d = 1;
while((number % radix) != number) {
radix *= 10;
++d;
}
return d;
}
public static void main(String[] args) {
double[] values = new double[] {
12345.6789, 123, 0.12345678,
1.5, 0.0045, 12, 123456, 1234567
};
BigDecimal[] bigs = new BigDecimal[values.length];
for(int i = 0; i < bigs.length; ++i) {
bigs[i] = new BigDecimal(values[i]);
System.out.println(trimToWidth(bigs[i], 8));
}
}