Skip to content

Commit

Permalink
Fix NumericDecodeUtils.decodeBinary(byteBuf) decoding.
Browse files Browse the repository at this point in the history
Previously, decodeBinary returned a wrong result for BigDecimal between 1 and -1. Now the decoding is implemented as per the Postgres spec.

[#558][resolves #556]
  • Loading branch information
gebit-costantino authored and mp911de committed Nov 9, 2022
1 parent 754d7a0 commit 2ad7d71
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 31 deletions.
41 changes: 10 additions & 31 deletions src/main/java/io/r2dbc/postgresql/codec/NumericDecodeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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<Object[]> 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);
}

}

0 comments on commit 2ad7d71

Please sign in to comment.