Skip to content

Commit 4842084

Browse files
authored
PgClient Money does not preserve sign if integral part is zero (#1362)
Fixes #1360 Signed-off-by: Thomas Segismont <[email protected]>
1 parent f9e17ed commit 4842084

File tree

3 files changed

+98
-59
lines changed

3 files changed

+98
-59
lines changed

vertx-pg-client/src/main/java/io/vertx/pgclient/data/Money.java

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,92 +11,79 @@
1111
package io.vertx.pgclient.data;
1212

1313
import java.math.BigDecimal;
14-
import java.math.BigInteger;
14+
import java.text.DecimalFormat;
15+
import java.util.Objects;
1516

1617
/**
1718
* The PostgreSQL <a href="https://www.postgresql.org/docs/9.1/datatype-money.html">MONEY</> type.
18-
*
19-
* This has the {@link #getIntegerPart() integer part} and {@link #getDecimalPart() decimal part} of the value without loss of information.
20-
*
21-
* {@link #bigDecimalValue()} returns the value without loss of information
22-
* {@link #doubleValue()} ()} returns the value possible loss of information
19+
* <p>
20+
* {@link #bigDecimalValue()} returns the value without loss of information.
21+
* {@link #doubleValue()} returns the value possible loss of information.
2322
*/
2423
public class Money {
2524

26-
private long integerPart;
27-
private int decimalPart;
25+
private BigDecimal value;
2826

27+
/**
28+
* @deprecated as of 4.5, use {@link #Money(Number)} instead
29+
*/
30+
@Deprecated
2931
public Money(long integerPart, int decimalPart) {
30-
setIntegerPart(integerPart);
31-
setDecimalPart(decimalPart);
32+
this(new BigDecimal(integerPart + "." + new DecimalFormat("00").format(decimalPart)));
3233
}
3334

3435
public Money(Number value) {
35-
if (value instanceof Double || value instanceof Float) {
36-
value = BigDecimal.valueOf((double) value);
36+
this.value = (value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(String.valueOf(value))).stripTrailingZeros();
37+
if (this.value.toBigInteger().abs().longValue() > Long.MAX_VALUE / 100) {
38+
throw new IllegalArgumentException("Value is too big: " + value);
3739
}
38-
if (value instanceof BigDecimal) {
39-
BigInteger bd = ((BigDecimal) value).multiply(new BigDecimal(100)).toBigInteger();
40-
setIntegerPart(bd.divide(BigInteger.valueOf(100)).longValueExact());
41-
setDecimalPart(bd.remainder(BigInteger.valueOf(100)).abs().intValueExact());
42-
} else {
43-
setIntegerPart(value.longValue());
40+
if (this.value.scale() > 2) {
41+
throw new IllegalArgumentException("Value has more than two decimal digits: " + value);
4442
}
4543
}
4644

4745
public Money() {
46+
value = BigDecimal.ZERO;
4847
}
4948

49+
/**
50+
* @deprecated as of 4.5, use {@link #bigDecimalValue()} instead
51+
*/
52+
@Deprecated
5053
public long getIntegerPart() {
51-
return integerPart;
54+
return value.toBigInteger().longValue();
5255
}
5356

57+
/**
58+
* @deprecated as of 4.5, use {@link #bigDecimalValue()} instead
59+
*/
60+
@Deprecated
5461
public int getDecimalPart() {
55-
return decimalPart;
62+
return value.remainder(BigDecimal.ONE).movePointRight(value.scale()).abs().intValue();
5663
}
5764

5865
/**
59-
* Set the integer part of the monetary value.
60-
*
61-
* <p> This value must belong to the range {@code ]Long.MAX_VALUE / 100, Long.MIN_VALUE / 100[}
62-
*
63-
* @param part the integer part of the value
64-
* @return this object
66+
* @deprecated as of 4.5, create another instance instead
6567
*/
68+
@Deprecated
6669
public Money setIntegerPart(long part) {
67-
if (part > Long.MAX_VALUE / 100 || part < Long.MIN_VALUE / 100) {
68-
throw new IllegalArgumentException();
69-
}
70-
integerPart = part;
70+
value = new Money(part, value.remainder(BigDecimal.ONE).abs().intValue()).bigDecimalValue();
7171
return this;
7272
}
7373

7474
/**
75-
* Set the decimal part of the monetary value.
76-
*
77-
* <p> This value must belong to the range {@code [0, 100]}
78-
*
79-
* @param part decimal part
80-
* @return this object
75+
* @deprecated as of 4.5, create another instance instead
8176
*/
77+
@Deprecated
8278
public Money setDecimalPart(int part) {
83-
if (part > 99 || part < 0) {
84-
throw new IllegalArgumentException();
85-
}
86-
decimalPart = part;
79+
value = new Money(value.longValue(), part).bigDecimalValue();
8780
return this;
8881
}
8982

9083
/**
9184
* @return the monetary amount as a big decimal without loss of information
9285
*/
9386
public BigDecimal bigDecimalValue() {
94-
BigDecimal value = new BigDecimal(integerPart).multiply(BigDecimal.valueOf(100));
95-
if (integerPart >= 0) {
96-
value = value.add(BigDecimal.valueOf(decimalPart));
97-
} else {
98-
value = value.subtract(BigDecimal.valueOf(decimalPart));
99-
}
10087
return value;
10188
}
10289

@@ -111,17 +98,17 @@ public double doubleValue() {
11198
public boolean equals(Object o) {
11299
if (this == o) return true;
113100
if (o == null || getClass() != o.getClass()) return false;
114-
Money that = (Money) o;
115-
return decimalPart == that.decimalPart && integerPart == that.integerPart;
101+
Money money = (Money) o;
102+
return Objects.equals(value, money.value);
116103
}
117104

118105
@Override
119106
public int hashCode() {
120-
return ((Long)integerPart).hashCode() ^ ((Integer)decimalPart).hashCode();
107+
return Objects.hash(value);
121108
}
122109

123110
@Override
124111
public String toString() {
125-
return "Money(" + integerPart + "." + decimalPart + ")";
112+
return "Money(" + new DecimalFormat("#0.##").format(value) + ")";
126113
}
127114
}

vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.vertx.sqlclient.data.Numeric;
3434
import io.vertx.sqlclient.impl.codec.CommonCodec;
3535

36+
import java.math.BigDecimal;
3637
import java.net.Inet4Address;
3738
import java.net.Inet6Address;
3839
import java.net.InetAddress;
@@ -1455,19 +1456,12 @@ private static void binaryEncodeInet(Inet value, ByteBuf buff) {
14551456
}
14561457

14571458
private static void binaryEncodeMoney(Money money, ByteBuf buff) {
1458-
long integerPart = money.getIntegerPart();
1459-
long value;
1460-
if (integerPart >= 0) {
1461-
value = money.getIntegerPart() * 100 + money.getDecimalPart();
1462-
} else {
1463-
value = money.getIntegerPart() * 100 - money.getDecimalPart();
1464-
}
1465-
binaryEncodeINT8(value, buff);
1459+
binaryEncodeINT8(money.bigDecimalValue().movePointRight(2).longValue(), buff);
14661460
}
14671461

14681462
private static Money binaryDecodeMoney(int index, int len, ByteBuf buff) {
14691463
long value = binaryDecodeINT8(index, len, buff);
1470-
return new Money(value / 100, Math.abs(((int)value % 100)));
1464+
return new Money(BigDecimal.valueOf(value, 2));
14711465
}
14721466

14731467
private static String binaryDecodeTsQuery(int index, int len, ByteBuf buff) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.vertx.pgclient.data;
2+
3+
import org.junit.Before;
4+
import org.junit.Test;
5+
import org.junit.runner.RunWith;
6+
import org.junit.runners.Parameterized;
7+
import org.junit.runners.Parameterized.Parameter;
8+
import org.junit.runners.Parameterized.Parameters;
9+
10+
import java.math.BigDecimal;
11+
12+
import static org.junit.Assert.assertEquals;
13+
14+
@RunWith(Parameterized.class)
15+
public class MoneyTest {
16+
17+
@Parameters
18+
public static Object[][] data() {
19+
return new Object[][]{{"-1234.56", -1234L, 56}, {"-0.12", 0L, 12}, {"0.12", 0L, 12}, {"1234.56", 1234L, 56}};
20+
}
21+
22+
@Parameter
23+
public String strValue;
24+
@Parameter(1)
25+
public long integralPart;
26+
@Parameter(2)
27+
public int fractionalPart;
28+
private BigDecimal value;
29+
private Money money;
30+
31+
@Before
32+
public void setUp() {
33+
value = new BigDecimal(strValue);
34+
money = new Money(value);
35+
}
36+
37+
@Test
38+
public void testBigDecimalValue() {
39+
assertEquals(value, money.bigDecimalValue());
40+
}
41+
42+
@Test
43+
public void testIntegerPart() {
44+
assertEquals(integralPart, money.getIntegerPart());
45+
}
46+
47+
@Test
48+
public void testDecimalPart() {
49+
assertEquals(fractionalPart, money.getDecimalPart());
50+
}
51+
52+
@Test
53+
@SuppressWarnings("deprecation")
54+
public void setParts() {
55+
assertEquals(new Money(3.24D), money.setIntegerPart(3).setDecimalPart(24));
56+
assertEquals(new Money(-173.01D), money.setIntegerPart(-173).setDecimalPart(1));
57+
}
58+
}

0 commit comments

Comments
 (0)