2

If i use org.javamoney.moneta and run the following program, i got some contrary results for some operations on org.javamoney.moneta.RoundedMoney. Sometimes the resulted value is rounded some times it is not.

Am I using the class wrong or is that a bug?

import java.math.BigDecimal;
import javax.money.CurrencyUnit;
import javax.money.Monetary;
import org.javamoney.moneta.RoundedMoney;

public final class RoundedMoneyRounding
{
    private RoundedMoneyRounding()
    {
    }


    public static void main(final String... args)
    {
        final CurrencyUnit usd = Monetary.getCurrency("USD");
        final RoundedMoney halfcent = RoundedMoney.of(new BigDecimal("0.005"), usd);
        final RoundedMoney zero = RoundedMoney.of(BigDecimal.ZERO, usd);

        System.out.append("A1. 0.005 + 0 = ").println(//
                                                      halfcent.add(zero) //
                                                                      .getNumber().numberValue(BigDecimal.class).toPlainString());

        System.out.append("A2. 0 + 0.005 = ").println(//
                                                      zero.add(halfcent) //
                                                                      .getNumber().numberValue(BigDecimal.class).toPlainString());

        System.out.println("----");

        System.out.append("B1: -0.005 = ").println(//
                                                   halfcent.negate() //
                                                                   .getNumber().numberValue(BigDecimal.class).toPlainString());

        System.out.append("B2: 0.005 * -1 = ").println(//
                                                       halfcent.multiply(new BigDecimal("-1")) //
                                                                       .getNumber().numberValue(BigDecimal.class).toPlainString());

        System.out.println("----");

        System.out.append("C1: 0.005 * 1 = ").println(//
                                                      halfcent.multiply(BigDecimal.ONE) //
                                                                      .getNumber().numberValue(BigDecimal.class).toPlainString());

        System.out.append("C2: 0.005 * 1.1 = ").println(//
                                                        halfcent.multiply(new BigDecimal("1.1")) //
                                                                        .getNumber().numberValue(BigDecimal.class).toPlainString());

        System.out.println("----");

        System.out.append("D1: 0.005 * 2 = ").println(//
                                                      halfcent.multiply(new BigDecimal("2")) //
                                                                      .getNumber().numberValue(BigDecimal.class).toPlainString());

        System.out.append("D2: (0.005 * 2) / 2 = ").println(//
                                                            halfcent.multiply(new BigDecimal("2")).divide(new BigDecimal("2")) //
                                                                            .getNumber().numberValue(BigDecimal.class).toPlainString());
    }
}

Output:

A1. 0.005 + 0 = 0.005
A2. 0 + 0.005 = 0
----
B1: -0.005 = -0.005
B2: 0.005 * -1 = 0
----
C1: 0.005 * 1 = 0.005
C2: 0.005 * 1.1 = 0.01
----
D1: 0.005 * 2 = 0.01
D2: (0.005 * 2) / 2 = 0

The used maven dependency is:

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.3</version>
    <type>pom</type>
</dependency>
rfcmbs
  • 21
  • 3

1 Answers1

0

(Just found the corresponding issue on GitHub )

This is probably due to the implementation-specific assumption that RoundedMoney instances will always contain rounded values, but, apparently, this is not enforced in the factory methods and constructors of that class. You can happily construct it with unrounded values.

When doing math with that class, some arithmetic optimizations apply: Examples A1 and C1 use the identity elements of addition and multiplication on the right hand side, hence they are effectively no-ops, and this will be returned, representing the initial unrounded value. Examples A2 and C2 could theoretically return the right hand side operator directly, but that optimization is missing, so RoundedMoney actually starts to calculate (and round) the result.

Example B1 just flips the sign of the number value. Here, the same assumptions apply: If x is a correctly rounded value, then -x is also correctly rounded[1]. So, RoundedMoney won't bother to apply the rounding to the new numeric value. In contrast, example B2 isn't optimized, but calculated and rounded.

So, I think the actual culprits are the factory methods, which won't apply the rounding to user-supplied values:

public static RoundedMoney of(BigDecimal number, CurrencyUnit currency) {
    return new RoundedMoney(number, currency, Monetary.getDefaultRounding());
}

public static RoundedMoney of(BigDecimal number, CurrencyUnit currency, MonetaryOperator rounding) {
    return new RoundedMoney(number, currency, rounding);
}

should most probably be

public static RoundedMoney of(BigDecimal number, CurrencyUnit currency) {
    return of(number, currency, Monetary.getDefaultRounding());
}

public static RoundedMoney of(BigDecimal number, CurrencyUnit currency, MonetaryOperator rounding) {
    return new RoundedMoney(number, currency, rounding).with(rounding);
}

I'd argue this is a bug, but since there's not much (no?) documentation about how that class is supposed to be used – or not used, for that matter – it might even be intended behavior?


[1]: This is interesting. Is there any rounding which won't satisfy that assumption? In fact, since RoundedMoney leverages a MoentaryOperator to apply the rounding, one could easily pass in some arbitrary "non-symmetric" operator.

Tom
  • 515
  • 7
  • 9