Java String.format() with HALF_EVEN rounding - java

I'd like to use String.format() to format some BigDecimals as part of a string:
// Example:
String getPrice( String pattern )
{
BigDecimal price = basePrice.multiply( BigDecimal.ONE.add( vatRate ) );
BigDecimal priceInPence = price.multiply( new BigDecimal( "100" ) );
BigDecimal annualPrice = price.multiply( new BigDecimal( "365" ) );
return String.format( pattern, priceInPence, annualPrice );
}
String myPrice1 = getPrice( "Your price is %1$.3fp/day (£2$.2f/year) including VAT" );
// --> "Your price is 32.100p/day (£117.16/year) including VAT"
String myPrice2 = getPrice( "Around £%2$.0f annualy" );
// --> "Around £117 annually"
However the docs for String.format() say that any rounding of BigDecimals will be done with HALF_UP rounding, whereas I need HALF_EVEN.
I know how to manually set the scale of BigDecimals (Set specific precision of a BigDecimal) - but in this case I want to be able to use an arbitrary pattern string (including non-numeric pattern elements), so I won't know in advance what scale to use.
My question is therefore:
can I set the rounding mode used by String.format()? OR
is there another formatter or library that would format the numbers as in my example?

can I set the rounding mode used by String.format()?
Short answer: no.
is there another formatter or library that would format the numbers as in my example?
The BigDecimal is converted internally via new MathContext(compPrec) or plain HALF_UP.
You can take the code of java.util.Formatter of the latest (or your preferred) version Java and modify the creation of the MathContext to use HALF_EVEN. It should be 10-15minutes work. But then you need a custom method to mimic String.format:
public static String format(String format, Object... args) {
return new FormatterHALF_EVEN().format(format, args).toString();
}

Really "solid" advice to add 5000 lines of dead-weight code to your project! From what I see, the Formatter will not set scale unless it is already set to what is needed. So help it out, parse the format string and set your scale:
public static String getPrice(String pattern) {
BigDecimal basePrice = new BigDecimal("23");
BigDecimal vatRate = new BigDecimal("0.5");
BigDecimal price = basePrice.multiply(BigDecimal.ONE.add(vatRate));
BigDecimal priceInPence = price.multiply(new BigDecimal("100"));
BigDecimal annualPrice = price.multiply(new BigDecimal("365"));
Matcher matcher = Pattern.compile("%(\\d+)\\$.(\\d+)f").matcher(pattern);
while (matcher.find()) {
String index = matcher.group(1);
int scale = Integer.parseInt(matcher.group(2));
if (index.equals("1"))
priceInPence = priceInPence.setScale(scale, RoundingMode.HALF_EVEN);
else if (index.equals("2"))
annualPrice = annualPrice.setScale(scale, RoundingMode.HALF_EVEN);
}
return String.format(pattern, priceInPence, annualPrice);
}
with these numbers I get this output:
Your price is 3450.000p/day (£12592.50/year) including VAT
Around £12592 annualy
So it applies correct rounding.

Set the scale with the rounding mode you like, and include the values in the format string as strings, using BigDecimal#toString().

Related

Convert string to double without scientific notation

I have searched the internet but I could not find any solution (maybe, I've searched badly).
I want to convert the String "108595000.5" to a double and I have used these methods:
Double.parseDouble("108595000.5");
Double.valueOf("108595000.5");
Unfortunately, both of them return 1.08595E8.
How can I convert this String to double without problem?
The methods you have used do not return 1.08595E8, instead, they return the number and what you are complaining about is the representation of that number in the console (or as a String).
However, you can specify how to output a doubleyourself with a specified formatting, see this example:
public static void main(String[] args) {
String value = "108595000.5";
// use a BigDecimal to parse the value
BigDecimal bd = new BigDecimal(value);
// choose your desired output:
// either the String representation of a double (undesired)
System.out.println("double:\t\t\t\t\t" + bd.doubleValue());
// or an engineering String
System.out.println("engineering:\t\t\t\t" + bd.toEngineeringString());
// or a plain String (might look equal to the engineering String)
System.out.println("plain:\t\t\t\t\t" + bd.toPlainString());
// or you specify an amount of decimals plus a rounding mode yourself
System.out.println("rounded with fix decimal places:\t"
+ bd.setScale(2, BigDecimal.ROUND_HALF_UP));
}
double: 1.085950005E8
engineering: 108595000.5
plain: 108595000.5
rounded with fix decimal places: 108595000.50
try using
value = new BigDecimal(yourString);
doubleValue = value.doubleValue();
if you want the exact value.
If you want 2 numbers after ","
double a = yourDouble;
System.out.printf("%.2f",a)

Converting a BigDecimal Number to the format of ###.#

I am trying to make a number in a BigDecimal variable match the format of ###.# where no matter what number is passed it will come out as ###.#
For example, if I was passed the number 1 in a BigDecimal variable the method would return 001.0
If I was passed 11.1 as a BigDecimal variable the method would return 011.1
I already have a bit of the code to make the decimal places match
BigDecimal x = new BigDecimal(1);
DecimalFormat df = new DecimalFormat("#.0");
String formatted = df.format(x);
So this would return 1.0 however I cannot figure out how to make the leading zeros appear before converting back to BigDecimal.
Can someone help me out or point me in the right direction?
Thanks everyone
Use a NumberFormat instance, with a Locale which uses the dot as decimal separator.
final BigDecimal x = new BigDecimal("11.1");
final NumberFormat nf = NumberFormat.getNumberInstance(Locale.ENGLISH);
nf.setMinimumFractionDigits(1);
nf.setMaximumFractionDigits(1);
nf.setMinimumIntegerDigits(3);
final String formatted = nf.format(x);
NumberFormat is basically a Factory, so for example in this case the underlying formatter returned by getNumberInstance is a DecimalFormat. I always prefer being abstracted off the real implementation.
You can also decide if you want to display the grouping comma or not.
nf.setGroupingUsed(true); // 123,456.0 - default value is true
nf.setGroupingUsed(false); // 123456.0
Use new DecimalFormat("000.0")
Examples
DecimalFormat df = new DecimalFormat("000.0", DecimalFormatSymbols.getInstance(Locale.US));
System.out.println(df.format(new BigDecimal("1"))); // prints: 001.0
System.out.println(df.format(new BigDecimal("11.1"))); // prints: 011.1
System.out.println(df.format(new BigDecimal("123.456"))); // prints: 123.5
System.out.println(df.format(new BigDecimal(".07"))); // prints: 000.1
System.out.println(df.format(new BigDecimal("123456"))); // prints: 123456.0
You can use the StringUtils of apache.
StringUtils.leftPad(number.toString(), 3, '0');
Where the first argument is the String to format, second argument is the number of to left places to validate, and the last argument is the char for complete.
Reggards.

Format received long number as a decimal one with custom format

I'm receiving a number formatted as long. Although i'm receiving this number as a long I now that the last 3 digits correspond to a decimal part so I want to show the number formatted with grouping and a decimal separator.
Example: if I receive the number 11111111111 I want it to be shown like 11 111 111.111
I have this code:
DecimalFormat formatter = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
formatter.setGroupingUsed(true);
symbols.setDecimalSeparator('.');
symbols.setGroupingSeparator(' ');
formatter.setDecimalFormatSymbols(symbols);
long valueAsLong = 11111111111L;
double value = (double) valueAsLong / 1000;
System.out.println(formatter.format(valueAsLong));
System.out.println(formatter.format(value));
I want to know if I can achieve this without that cast, that is, setting a formatter that receives a long and format the number the way I want.
No DecimalFormat doesn't support this, as it's purpose is to format a number as a String without changing it's value.
format(longValue / 1000.0) is the easiest solution, note however that it will not work for very large longs:
public class Test {
public static void main(String[] args) {
DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance(Locale.US);
char decimalSeparator = decimalFormat.getDecimalFormatSymbols().getDecimalSeparator();
// prints 123.456
System.out.println(decimalFormat.format(123456 / 1000.0));
// 9,223,372,036,854,775,807
System.out.println(decimalFormat.format(Long.MAX_VALUE));
// 9,223,372,036,854,776, not 9,223,372,036,854,776.807, as double's resolution is not sufficient
System.out.println(decimalFormat.format(Long.MAX_VALUE / 1000.0));
// 9,223,372,036,854,775.807
BigInteger[] divAndRem = new BigInteger(Long.toString(Long.MAX_VALUE))
.divideAndRemainder(new BigInteger("1000"));
System.out.println(decimalFormat.format(divAndRem[0])
+ decimalSeparator + divAndRem[1]);
// using String manipulation
String longString = decimalFormat.format(Long.MAX_VALUE);
System.out.println(new StringBuilder(longString).replace(
longString.length() - 4,
longString.length() - 3,
Character.toString(decimalSeparator)));
}
}
You can actually do it without casting, but it will need parsing with java.text.DecimalFormat#parse, with "#\u2030" pattern, where \u2030 is the ‰ (per-mille) character.
long myLong = 123456L;
String asPerMille =
new StringBuffer().append(myLong).append('\u2030').toString();
DecimalFormat perMilleFormat = new DecimalFormat("#\u2030");
Number myLongAsDec = perMilleFormat.parse(asPerMille);
System.out.println(
String.format("%d can be parsed with pattern %s as a per-mille and gives %f",
myLong,
perMilleFormat.toPattern(),
myLongAsDec.doubleValue()));
The output is :
123456 can be parsed with pattern #‰ as a per-mille and gives 123.456000
Note that performance wise, your method is certainly better than building and parsing a String and then reformatting it with the different groupings.

String format BigDecimal to show either zero or two decimal places, never one

I'm trying to format some BigDecimals to a currency format. I need no decimal places to show if there are none in the BigDecimal, but two decimal places to show if there are some (even if it's only 10s)
e.g.
12334 -> 12,334
12334.99 -> 12,334.99
12334.90 -> 12,334.90
not
12334.90 -> 12,334.9
Currently using NumberFormat.getInstance() which fails only for the final example above
int precision = val.toString().indexOf('.') != -1 ? 2:0;
NumberFormat formatter = NumberFormat.getInstance();
formatter.setMinimumFractionDigits(precision);
String result = formatter.format(val);
Try this:
NumberFormat formatter = NumberFormat.getInstance();
formatter.setMinimumFractionDigits(2);
String formatted = formatter.format(number);
if (formatted.endsWith(".00")) {
formatted = formatted.substring(0, formatted.length()-3);
}

How to format numbers with no grouping separator

I'm trying to format a BigDecimal value by using methods of DecimalFormat.format().
My problem, is I don't know how to set DecimalFormats DecimalFormatSymbol to format text without any grouping separators.
I'd like to know how to set a grouping symbol and use the format methods. I know how to do it differently by using replace or others methods but it's not what I want.
So I need to know how to set an empty character as grouping operator.
Example:
DecimalFormat dec = new DecimalFormat();
DecimalFormatSymbols decFS = new DecimalFormatSymbols();
decFS.setGroupingSeparator( '\0' );
dec.setDecimalFormatSymbols( decFS );
BigDecimal number = new BigDecimal(1000);
String result = dec.format(number);
I want to get "1000" as string with no other characters. Please help
note(react to post): I want to formate the number only, without grouping.
Simply:
DecimalFormat dec = new DecimalFormat();
dec.setGroupingUsed(false);
If you dont want any formatting marks in the number you should just call toString on the BigDecimal Object.
import java.math.*;
public class TestBigDecimal
{
public static void main(String[] args)
{
BigDecimal number = new BigDecimal(1000);
String result = number.toString();
}
}
String result = String.valueOf(number);
returns your number as a string with no formatting.

Categories

Resources