Java String.format() with HALF_EVEN rounding

2019-07-17 09:52发布

问题:

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?

回答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.



回答2:

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();
}


回答3:

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