Search code examples
freemarker

Freemarker: How to write a BigDecimal's value that can be used in a BigDecimal constructor


I would like to use freemarker to generate java code that instantiates a BigDecimal.

The value of the BigDecimal is present at generation time.

the BigDecimal API would work like this:

BigDecimal copy = new BigDecimal(original.toString());

Alas, the freemarker magic uses numeric conversion on my value of original, so this does not work (in a freemarkter template):

BigDecimal copy = new BigDecimal("${original?c}");

None of the numeric conversions (percent, number, computer, ...) works - c/computer most interestingly, because it outputs 0 if the value becomes too big.

With considerable pain, I might be able to wrap the BigDecimal in another Object that just gives me a toString and is not a number, so freemarker might leave its value untouched and my generated BigDecimal is correct, but that's only a last resort.

Maybe there is a way to invoke the toString() method and print the result into the target document?

ERRATUM: because it outputs 0 if the value becomes too big should read because it outputs 0 if the value becomes too small (see @ddkany's answer)


Solution

  • Update: FreeMarker 2.3.32 now supports lossless formatting with ?c, which is not based on DecimalFormat, but mostly on toString. To use that, you have to set the c_format configuration setting to any other value than its backward compatible default, "legacy". Like setting it to "JavaScript or JSON" is fine for most projects (that's also the default if you just set the incompatible_improvements configuration setting to 2.3.32). See the fine details of how numers are formatted in the documentation of the c built-in: https://freemarker.apache.org/docs/ref_builtins_number.html#ref_builtin_c

    Old answer, for 2.3.31 and before:

    What FreeMarke does by default is whatever Java's DecimalFormat does (for the localized medium format by default, again defined by the Java platform).

    ?c uses new DecimalFormat("0.################") with fixed US locale (and some more symbol adjustments for INF and NaN). So I don't know how that gives 0 for a huge BigDecimal number. Are you sure about that? I guess it was actually a very small number, so it was rounded down. Well, switching to "scientific" format would make more sense then, though.

    To have whatever formatting logic you need, you can register your own number formatter like, in this case, configuration.setCustomNumberFormats(Map.of("toString", new MyToStringTemplateNumberFormatFactory()), and then you can use ${original?string.@toString}. Or rather, you can set the number_format to @toString, and then just use ${original}.