diff --git a/src/main/java/io/r2dbc/postgresql/codec/NumericDecodeUtils.java b/src/main/java/io/r2dbc/postgresql/codec/NumericDecodeUtils.java index cd04d68e..158ccf02 100755 --- a/src/main/java/io/r2dbc/postgresql/codec/NumericDecodeUtils.java +++ b/src/main/java/io/r2dbc/postgresql/codec/NumericDecodeUtils.java @@ -23,6 +23,7 @@ import reactor.util.annotation.Nullable; import java.math.BigDecimal; +import java.math.RoundingMode; import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY; @@ -110,40 +111,18 @@ public static BigDecimal decodeBinary(ByteBuf byteBuf) { digits[i] = byteBuf.readShort(); } - StringBuilder builder = new StringBuilder(); - // whole part - builder.append(digits[0]); - for (short i = 0; i < weight * 4; i++) { - builder.append(0); + StringBuilder sb = new StringBuilder(); + if (sign != 0) { + sb.append("-"); } - // decimal part - if (scale > 0) { - builder.append('.'); - for (short i = 0; i < scale; i++) { - builder.append(0); - } + sb.append("0."); + for (short digit : digits) { + sb.append(String.format("%04d", digit)); } - int expectedLength = builder.length(); - int baseOffset = Short.toString(digits[0]).length(); - - for (short i = 1; i < numOfDigits; i++) { - weight--; - String temp = Short.toString(digits[i]); - int offset = baseOffset + 4 * i - temp.length(); - if (weight < 0) { - offset++; // dot between whole and decimal parts - } - builder.replace(offset, offset + temp.length(), temp); - } - - builder.setLength(expectedLength); // remove zeros from the end - - if (sign == 0) { - return new BigDecimal(builder.toString()); - } else { - return new BigDecimal("-" + builder.toString()); - } + return new BigDecimal(sb.toString()) + .movePointRight((weight + 1) * 4) + .setScale(scale, RoundingMode.DOWN); } } diff --git a/src/test/java/io/r2dbc/postgresql/codec/NumericDecodeUtilsTest.java b/src/test/java/io/r2dbc/postgresql/codec/NumericDecodeUtilsTest.java new file mode 100644 index 00000000..7610c4a9 --- /dev/null +++ b/src/test/java/io/r2dbc/postgresql/codec/NumericDecodeUtilsTest.java @@ -0,0 +1,87 @@ +package io.r2dbc.postgresql.codec; + +import io.netty.buffer.ByteBuf; +import io.r2dbc.postgresql.util.TestByteBufAllocator; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.postgresql.util.ByteConverter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class NumericDecodeUtilsTest { + + static Stream bigDecimalValues() { + return Stream.of( + new Object[]{new BigDecimal("0.1")}, + new Object[]{new BigDecimal("0.10")}, + new Object[]{new BigDecimal("0.01")}, + new Object[]{new BigDecimal("0.001")}, + new Object[]{new BigDecimal("0.0001")}, + new Object[]{new BigDecimal("0.00001")}, + new Object[]{new BigDecimal("-0.1")}, + new Object[]{new BigDecimal("-0.10")}, + new Object[]{new BigDecimal("-0.01")}, + new Object[]{new BigDecimal("-0.002")}, + new Object[]{new BigDecimal("-0.0033")}, + new Object[]{new BigDecimal("-0.004343")}, + new Object[]{new BigDecimal("1.0")}, + new Object[]{new BigDecimal("0.000000000000000000000000000000000000000000000000000")}, + new Object[]{new BigDecimal("0.100000000000000000000000000000000000000000000009900")}, + new Object[]{new BigDecimal("-1.0")}, + new Object[]{new BigDecimal("-1")}, + new Object[]{new BigDecimal("1.2")}, + new Object[]{new BigDecimal("-2.05")}, + new Object[]{new BigDecimal("0.000000000000000000000000000990")}, + new Object[]{new BigDecimal("-0.000000000000000000000000000990")}, + new Object[]{new BigDecimal("10.0000000000099")}, + new Object[]{new BigDecimal(".10000000000000")}, + new Object[]{new BigDecimal("1.10000000000000")}, + new Object[]{new BigDecimal("99999.2")}, + new Object[]{new BigDecimal("99999")}, + new Object[]{new BigDecimal("-99999.2")}, + new Object[]{new BigDecimal("-99999")}, + new Object[]{new BigDecimal("2147483647")}, + new Object[]{new BigDecimal("-2147483648")}, + new Object[]{new BigDecimal("2147483648")}, + new Object[]{new BigDecimal("-2147483649")}, + new Object[]{new BigDecimal("9223372036854775807")}, + new Object[]{new BigDecimal("-9223372036854775808")}, + new Object[]{new BigDecimal("9223372036854775808")}, + new Object[]{new BigDecimal("-9223372036854775809")}, + new Object[]{new BigDecimal("10223372036850000000")}, + new Object[]{new BigDecimal("19223372036854775807")}, + new Object[]{new BigDecimal("19223372036854775807.300")}, + new Object[]{new BigDecimal("-19223372036854775807.300")}, + new Object[]{new BigDecimal(BigInteger.valueOf(1234567890987654321L), -1)}, + new Object[]{new BigDecimal(BigInteger.valueOf(1234567890987654321L), -5)}, + new Object[]{new BigDecimal(BigInteger.valueOf(-1234567890987654321L), -3)}, + new Object[]{new BigDecimal(BigInteger.valueOf(6), -8)}, + new Object[]{new BigDecimal("30000")}, + new Object[]{new BigDecimal("40000").setScale(15, RoundingMode.UNNECESSARY)}, + new Object[]{new BigDecimal("20000.000000000000000000")}, + new Object[]{new BigDecimal("9990000").setScale(8, RoundingMode.UNNECESSARY)}, + new Object[]{new BigDecimal("1000000").setScale(31, RoundingMode.UNNECESSARY)}, + new Object[]{new BigDecimal("10000000000000000000000000000000000000").setScale(14, RoundingMode.UNNECESSARY)}, + new Object[]{new BigDecimal("90000000000000000000000000000000000000")}, + new Object[]{new BigDecimal("1234567890.12")}, + new Object[]{new BigDecimal("-3.141592653590")}, + new Object[]{new BigDecimal("3.141592653590")}, + new Object[]{new BigDecimal("-0.141592653590")}, + new Object[]{new BigDecimal("0.141592653590")} + ); + } + + @MethodSource("bigDecimalValues") + @ParameterizedTest + void decodeBinary2(BigDecimal value) { + ByteBuf byteBuf = TestByteBufAllocator.TEST.buffer(); + byteBuf.writeBytes(ByteConverter.numeric(value)); + assertThat(NumericDecodeUtils.decodeBinary(byteBuf)).isEqualByComparingTo(value); + } + +} \ No newline at end of file