Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions moneta-core/src/main/java/org/javamoney/moneta/FastMoney.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public MonetaryContext getContext() {
}

private long getInternalNumber(Number number) {
BigDecimal bd = MoneyUtils.getBigDecimal(number);
BigDecimal bd = getStrippedBigDecimal(number);
if (bd.scale() > SCALE) {
throw new ArithmeticException(number + " can not be represented by this class, scale > " + SCALE);
}
Expand All @@ -206,6 +206,9 @@ private long getInternalNumber(Number number) {
return bd.movePointRight(SCALE).longValue();
}

private static BigDecimal getStrippedBigDecimal(Number number) {
return MoneyUtils.stripScalingZeroes(MoneyUtils.getBigDecimal(number));
}

/**
* Static factory method for creating a new instance of {@link FastMoney}.
Expand Down Expand Up @@ -333,11 +336,12 @@ public FastMoney add(MonetaryAmount amount) {

private void checkAmountParameter(MonetaryAmount amount) {
MoneyUtils.checkAmountParameter(amount, this.currency);
// numeric check for overflow...
if (amount.getNumber().getScale() > SCALE) {
// numeric check for overflow, based on the effective (trailing-zero stripped) value
BigDecimal bd = MoneyUtils.stripScalingZeroes(amount.getNumber().numberValue(BigDecimal.class));
if (bd.scale() > SCALE) {
throw new ArithmeticException("Parameter exceeds maximal scale: " + SCALE);
}
if (amount.getNumber().getPrecision() > MAX_BD.precision()) {
if (bd.precision() > MAX_BD.precision()) {
throw new ArithmeticException("Parameter exceeds maximal precision: " + SCALE);
}
}
Expand Down Expand Up @@ -427,7 +431,7 @@ public FastMoney remainder(Number divisor) {
}

private boolean isOne(Number number) {
BigDecimal bd = MoneyUtils.getBigDecimal(number);
BigDecimal bd = getStrippedBigDecimal(number);
try {
return bd.scale() == 0 && bd.longValueExact() == 1L;
} catch (Exception e) {
Expand Down Expand Up @@ -566,7 +570,7 @@ public boolean hasSameNumberAs(Number number) {
*/
@Override
public NumberValue getNumber() {
return new DefaultNumberValue(getBigDecimal());
return new DefaultNumberValue(MoneyUtils.stripScalingZeroes(getBigDecimal()));
}

@Override
Expand Down Expand Up @@ -594,7 +598,7 @@ protected void checkNumber(Number number) {
if (number.longValue() > MAX_BD.longValue()) {
throw new ArithmeticException("Value exceeds maximal value: " + MAX_BD);
}
BigDecimal bd = MoneyUtils.getBigDecimal(number);
BigDecimal bd = getStrippedBigDecimal(number);
if (bd.precision() > MAX_BD.precision()) {
throw new ArithmeticException("Precision exceeds maximal precision: " + MAX_BD.precision());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,21 @@ BigDecimal getDecimal(Number num) {
NUMBERVALUE {
@Override
BigDecimal getDecimal(Number num) {
BigDecimal result = ((NumberValue)num).numberValue(BigDecimal.class);
return stripScalingZeroes(result);
return ((NumberValue)num).numberValue(BigDecimal.class);
}
},
/** Conversion from BigDecimal. */
BIGDECIMAL {
@Override
BigDecimal getDecimal(Number num) {
BigDecimal result = ((BigDecimal)num);
return stripScalingZeroes(result);
return (BigDecimal) num;
}
},
/** Conversion from BigDecimal, extended. */
BIGDECIMAL_EXTENDS {
@Override
BigDecimal getDecimal(Number num) {
BigDecimal result = ((BigDecimal)num).stripTrailingZeros();
return stripScalingZeroes(result);
return (BigDecimal) num;
}
},
/** Default conversion based on String, if everything else failed. */
Expand All @@ -89,9 +86,8 @@ BigDecimal getDecimal(Number num) {
result = new BigDecimal(num.toString());
} catch (NumberFormatException ignored) {
}
result = Optional.ofNullable(result).orElse(
return Optional.ofNullable(result).orElse(
BigDecimal.valueOf(num.doubleValue()));
return stripScalingZeroes(result);
}
};

Expand Down Expand Up @@ -131,14 +127,4 @@ private static ConvertBigDecimal factory(Number num) {

private static final List<Class<? extends Number>> FLOATINGS = Arrays.asList(
Float.class, Double.class);

private static BigDecimal stripScalingZeroes(BigDecimal result) {
if (result.signum() == 0) {
return BigDecimal.ZERO;
}
if (result.scale() > 0) {
return result.stripTrailingZeros();
}
return result;
}
}
17 changes: 17 additions & 0 deletions moneta-core/src/main/java/org/javamoney/moneta/spi/MoneyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,23 @@ public static BigDecimal getBigDecimal(Number num, MonetaryContext moneyContext)
return bd;
}

/**
* Returns the numeric value with any non-significant trailing zeros removed, mapping any
* zero value (including {@code -0}) to {@link BigDecimal#ZERO}.
*
* @param value the value to normalize, not null.
* @return the value with non-significant trailing zeros stripped.
*/
public static BigDecimal stripScalingZeroes(BigDecimal value) {
if (value.signum() == 0) {
return BigDecimal.ZERO;
}
if (value.scale() > 0) {
return value.stripTrailingZeros();
}
return value;
}

/**
* Evaluates the {@link MathContext} from the given {@link MonetaryContext}.
*
Expand Down
14 changes: 7 additions & 7 deletions moneta-core/src/test/java/org/javamoney/moneta/MoneyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void testDivideAndRemainder_BigDecimal() {
Money[] divideAndRemainder = money1.divideAndRemainder(new BigDecimal("0.50000000000000000001"));
assertEquals(divideAndRemainder[0].getNumber().numberValue(BigDecimal.class), BigDecimal.ONE);
assertEquals(divideAndRemainder[1].getNumber().numberValue(BigDecimal.class),
new BigDecimal("0.5"));
new BigDecimal("0.5000000000000000"));
}

@Test
Expand Down Expand Up @@ -1084,10 +1084,10 @@ public void testToString_ReverseOrder() {
System.setProperty("org.javamoney.toStringFormatOrder", "ca");
assertEquals(Money.of(new BigDecimal("1.23455645"), "XXX").toString(), "XXX 1.23455645");
assertEquals("CHF 1234", Money.of(1234, "CHF").toString());
assertEquals("CHF 1234", Money.of(new BigDecimal("1234.0"), "CHF").toString());
assertEquals("CHF 1234.0", Money.of(new BigDecimal("1234.0"), "CHF").toString());
assertEquals("CHF 1234.1", Money.of(new BigDecimal("1234.1"), "CHF").toString());
assertEquals("CHF 0.01", Money.of(new BigDecimal("0.0100"), "CHF").toString());
assertEquals("CHF 50",
assertEquals("CHF 0.0100", Money.of(new BigDecimal("0.0100"), "CHF").toString());
assertEquals("CHF 50.0",
Money.of(new BigDecimal("500").multiply(new BigDecimal(".1")), "CHF").toString());
} finally {
System.clearProperty("org.javamoney.toStringFormatOrder");
Expand All @@ -1105,13 +1105,13 @@ public void testToString() {
assertEquals(Money.of(1234, "CHF").toString(), "CHF 1234");
assertEquals(Money.of(new BigDecimal("1234"), "CHF",
MonetaryContextBuilder.of().setMaxScale(2).setFixedScale(true).build()).toString(), "CHF 1234.00");
assertEquals(Money.of(new BigDecimal("1234.0"), "CHF").toString(), "CHF 1234");
assertEquals(Money.of(new BigDecimal("1234.0"), "CHF").toString(), "CHF 1234.0");
assertEquals("CHF 1234.1", Money.of(new BigDecimal("1234.1"), "CHF").toString());
assertEquals(Money.of(new BigDecimal("1234.1"), "CHF",
MonetaryContextBuilder.of().setMaxScale(2).build()).toString(), "CHF 1234.1");
assertEquals(Money.of(new BigDecimal("1234.1"), "CHF", twoDigitFixedContext).toString(), "CHF 1234.10");
assertEquals("CHF 0.01", Money.of(new BigDecimal("0.0100"), "CHF").toString());
assertEquals("CHF 50",
assertEquals("CHF 0.0100", Money.of(new BigDecimal("0.0100"), "CHF").toString());
assertEquals("CHF 50.0",
Money.of(new BigDecimal("500").multiply(new BigDecimal(".1")), "CHF").toString());
Money money1 = Money.of(1234567.3456, "EUR");
assertEquals(money1.toString(), "EUR 1234567.3456");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
Copyright (c) 2012, 2026, Werner Keil and others by the @author tag.

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/
package org.javamoney.moneta;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

import java.math.BigDecimal;

import javax.money.*;

import org.javamoney.moneta.spi.DefaultNumberValue;
import org.testng.annotations.Test;

public class MoneyTrailingZerosTest {

private static final CurrencyUnit USD = Monetary.getCurrency("USD");

@Test
public void preservesScaleFromBigDecimal() {
Money m = Money.of(new BigDecimal("1.420"), USD);
assertEquals(m.getScale(), 3);
assertEquals(m.getPrecision(), 4);
assertEquals(m.getNumber().getScale(), 3);
assertEquals(m.getNumber().getPrecision(), 4);
assertEquals(m.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
assertEquals(m.getNumber().numberValueExact(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleThroughParseRoundTrip() {
Money m = Money.of(new BigDecimal("1.420"), USD);
Money parsed = Money.parse(m.toString());
assertEquals(parsed.getScale(), 3);
assertEquals(parsed.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleViaNumberOverload() {
Number number = new BigDecimal("1.420");
Money m = Money.of(number, USD);
assertEquals(m.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleFromNumberValueInput() {
Money m = Money.of(DefaultNumberValue.of(new BigDecimal("1.420")), USD);
assertEquals(m.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleViaCurrencyCode() {
Money byCode = Money.of(new BigDecimal("1.420"), "USD");
assertEquals(byCode.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleViaExplicitContext() {
MonetaryContext ctx = MonetaryContextBuilder.of(Money.class).setPrecision(64).build();
Money withCtx = Money.of(new BigDecimal("1.420"), "USD", ctx);
assertEquals(withCtx.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleViaTypedFactory() {
MonetaryAmountFactory<Money> factory = Monetary.getAmountFactory(Money.class);
Money m = factory.setCurrency(USD).setNumber(new BigDecimal("1.420")).create();
assertEquals(m.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleThroughGetFactoryRoundTrip() {
Money m = Money.of(new BigDecimal("1.420"), USD);
Money rebuilt = m.getFactory().create();
assertEquals(rebuilt.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleFromOtherMonetaryAmount() {
RoundedMoney source = RoundedMoney.of(new BigDecimal("1.420"), USD);
Money m = Money.from(source);
assertEquals(m.getNumber().numberValue(BigDecimal.class), new BigDecimal("1.420"));
}

@Test
public void preservesScaleFromOfMinor() {
Money m = Money.ofMinor(USD, 1200, 2);
assertEquals(m.getScale(), 2);
assertEquals(m.getNumber().numberValue(BigDecimal.class), new BigDecimal("12.00"));
assertEquals(m.toString(), "USD 12.00");
}

@Test
public void preservesScaleOfArithmeticResult() {
Money product = Money.of(new BigDecimal("2.50"), USD).multiply(new BigDecimal("2"));
assertEquals(product.getNumber().numberValue(BigDecimal.class), new BigDecimal("5.00"));
}

@Test
public void fractionPartsReflectPreservedScale() {
NumberValue value = Money.of(new BigDecimal("1.420"), USD).getNumber();
assertEquals(value.getAmountFractionNumerator(), 420L);
assertEquals(value.getAmountFractionDenominator(), 1000L);
}

@Test
public void equalityAndComparisonIgnoreTrailingZeros() {
Money withZeros = Money.of(new BigDecimal("1.420"), USD);
Money withoutZeros = Money.of(new BigDecimal("1.42"), USD);

assertEquals(withZeros, withoutZeros);
assertEquals(withZeros.hashCode(), withoutZeros.hashCode());
assertTrue(withZeros.isEqualTo(withoutZeros));
assertEquals(withZeros.compareTo(withoutZeros), 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void testDivideAndRemainder_BigDecimal() {
RoundedMoney money1 = RoundedMoney.of(BigDecimal.ONE, EURO);
RoundedMoney[] divideAndRemainder = money1.divideAndRemainder(new BigDecimal("0.50000001"));
assertEquals(divideAndRemainder[0].getNumber().numberValue(BigDecimal.class), BigDecimal.ONE);
assertEquals(divideAndRemainder[1].getNumber().numberValue(BigDecimal.class), new BigDecimal("0.5"));
assertEquals(divideAndRemainder[1].getNumber().numberValue(BigDecimal.class), new BigDecimal("0.50"));
}

@Test
Expand Down Expand Up @@ -974,15 +974,15 @@ public void testToString() {
Locale.setDefault(Locale.ENGLISH);
assertEquals(RoundedMoney.of(new BigDecimal("1.23455645"), "XXX").toString(), "XXX 1.23455645");
assertEquals("CHF 1234", RoundedMoney.of(1234, "CHF").toString());
assertEquals("CHF 1234", RoundedMoney.of(new BigDecimal("1234.0"), "CHF").toString());
assertEquals("CHF 1234.0", RoundedMoney.of(new BigDecimal("1234.0"), "CHF").toString());
assertEquals("CHF 1234.1", RoundedMoney.of(new BigDecimal("1234.1"), "CHF").toString());
assertEquals("CHF 0.01", RoundedMoney.of(new BigDecimal("0.0100"), "CHF").toString());
assertEquals("CHF 0.0100", RoundedMoney.of(new BigDecimal("0.0100"), "CHF").toString());

assertEquals(RoundedMoney.of(new BigDecimal("1.23455645"), "XXX").toString(), "XXX 1.23455645");
assertEquals("CHF 1234", RoundedMoney.of(1234, "CHF").toString());
assertEquals("CHF 1234", RoundedMoney.of(new BigDecimal("1234.0"), "CHF").toString());
assertEquals("CHF 1234.0", RoundedMoney.of(new BigDecimal("1234.0"), "CHF").toString());
assertEquals("CHF 1234.1", RoundedMoney.of(new BigDecimal("1234.1"), "CHF").toString());
assertEquals("CHF 0.01", RoundedMoney.of(new BigDecimal("0.0100"), "CHF").toString());
assertEquals("CHF 0.0100", RoundedMoney.of(new BigDecimal("0.0100"), "CHF").toString());
} finally {
Locale.setDefault(defaultLocale);
}
Expand Down
Loading