3

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?

Community
  • 1
  • 1
Richard Inglis
  • 5,888
  • 2
  • 33
  • 37
  • Take a look at java.math.MathContext. You can construct a MathContext using a java.math.RoundingMode (HALF_EVEN), and then pass that to the constructor of BigDecimal or to one of the multiply methods: `multiply(BigDecimal multiplicand, MathContext mc)` – mttdbrd Oct 30 '14 at 04:06
  • @mttdbrd - sorry, I should have made clear I need arbitrary precision, based on the pattern (see my edit above). – Richard Inglis Oct 30 '14 at 04:21
  • http://stackoverflow.com/questions/9482889/set-specific-precision-of-a-bigdecimal and http://stackoverflow.com/questions/15352229/rounding-mode-with-bigdecimal-in-java – ComputerFellow Oct 30 '14 at 04:56
  • @ComputerFellow - sorry, those are answers about BigDecimals, my question is about changing the rounding mode used by String.Format() – Richard Inglis Oct 30 '14 at 07:39

3 Answers3

3

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();
}
bestsss
  • 11,796
  • 3
  • 53
  • 63
1

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.

Community
  • 1
  • 1
Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68
  • I like this approach. It could be even better if it was extended a bit to handle the case where a token is used more than once with different scales: getPrice( "Around £%2$.0f annualy (£%2.2f to be exact)" ); – Richard Inglis Nov 09 '14 at 23:23
0

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

Kevin Krumwiede
  • 9,868
  • 4
  • 34
  • 82