Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NumericDecodeUtils.decodeBinary(byteBuf) returns wrong result for BigDecimal between 1 and -1 #556

Closed
alphaville76 opened this issue Nov 4, 2022 · 5 comments
Labels
type: bug A general bug
Milestone

Comments

@alphaville76
Copy link

Bug Report

Versions

  • Driver: master
  • Database:
  • Java: 11
  • OS: Linux

Current Behavior

The BigDecimal 0.99 is decoded as 9900.00. The problem occurs for each big decimal between -1 and 1.
In the other cases works correctly.

Steps to reproduce

To reproduce, please run
short[] array = new short[]{1, -1, 0, 2, 9900}

        ByteBuf byteBuf = TestByteBufAllocator.TEST.buffer();
        //BigDecimal 0.99
        short[] array = new short[]{1, -1, 0, 2, 9900};
        for (int i = 0; i < array.length; i++) {
            byteBuf.writeShort(array[i]);
        }

        BigDecimal bigDecimal = NumericDecodeUtils.decodeBinary(byteBuf);

        System.out.println(bigDecimal);

Expected behavior/code

0.99 istead of 9900.00

Possible Solution

The problem is that a negative weight is not handled.
As stated in https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/numeric.c#L263

Note: the first digit of a NumericVar's value is assumed to be multiplied
by NBASE ** weight. Another way to say it is that there are weight+1
digits before the decimal point. It is possible to have weight < 0.

@alphaville76 alphaville76 added the status: waiting-for-triage An issue we've not yet triaged label Nov 4, 2022
@alphaville76
Copy link
Author

One can give a look to the jdbc version to understand how to handle a negative weight:
https://github.com/pgjdbc/pgjdbc/blob/a29767eaa27b97a68c21dee04e8483872783f2e2/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java#L184

I forgot to say that the bug occurs only with FORMAT_BINARY

@alphaville76
Copy link
Author

possible fix, not sure, not tested, if it works when scale > 4

issue_556.patch.txt

@alphaville76
Copy link
Author

Based on my understanding of the algorithm descripted in this paper, the same used by postgres as documented in numeric.c, I implemented decodeBinary from scratch as follows:

    private BigDecimal decodeBinary(ByteBuf byteBuf) {
        short numOfDigits = byteBuf.readShort();
        if (numOfDigits == 0) {
            return BigDecimal.ZERO;
        }
        short weight = byteBuf.readShort();
        short sign = byteBuf.readShort();
        short scale = byteBuf.readShort();
        short[] digits = new short[numOfDigits];
        for (short i = 0; i < numOfDigits; i++) {
            digits[i] = byteBuf.readShort();
        }

        StringBuilder sb = new StringBuilder();
        if (sign != 0) {
            sb.append("-");
        }
        sb.append("0.");
        for (short digit: digits) {
            sb.append(String.format("%04d", digit));
        }

        return new BigDecimal(sb.toString())
            .movePointRight((weight + 1) * 4)
            .setScale(scale, RoundingMode.DOWN);
    }

The unit-tests below runs sucessfully:

    @Test
    void decodeBinary2() {
        assertThat(decodeBinary(fromArray(new short[]{1, -1, 0, 2, 700})))
            .isEqualTo(new BigDecimal("0.07"));

        assertThat(decodeBinary(fromArray(new short[]{2, 0, 0, 2, 123, 4500})))
            .isEqualTo(new BigDecimal("123.45"));

        assertThat(decodeBinary(fromArray(new short[]{4, 2, 0, 2, 12, 3456, 7890, 1200})))
            .isEqualTo(new BigDecimal("1234567890.12"));

        assertThat(decodeBinary(fromArray(new short[]{1, -1, 0, 2, 9900})))
            .isEqualTo(new BigDecimal("0.99"));

        assertThat(decodeBinary(fromArray(new short[]{4, 0, -1, 12, 3, 1415, 9265, 3590})))
            .isEqualTo(new BigDecimal("-3.141592653590"));
    }

    private ByteBuf fromArray(short[] array) {
        ByteBuf byteBuf = TestByteBufAllocator.TEST.buffer();
        for (int i = 0; i < array.length; i++) {
            byteBuf.writeShort(array[i]);
        }
        return byteBuf;
    }

Looking forward for you feedback and hope my suggestion can help fixing the bug.

@mp911de mp911de added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Nov 7, 2022
@mp911de mp911de changed the title NumericDecodeUtils.decodeBinary(byteBuf) return wrong result for BigDecimal < 0 NumericDecodeUtils.decodeBinary(byteBuf) returns wrong result for BigDecimal between 1 and -1 Nov 8, 2022
@mp911de
Copy link
Collaborator

mp911de commented Nov 8, 2022

Thanks for reporting the issue and your effort to provide a fix. I confirm that this is a bug that we haven't caught because we didn't had it tested with the appropriate data. Are you interested in submitting a pull request? If so, we have release week this week so we can include a fix.

@mp911de mp911de added this to the 0.9.3.RELEASE milestone Nov 8, 2022
alphaville76 pushed a commit to alphaville76/r2dbc-postgresql that referenced this issue Nov 8, 2022
…wrong result for BigDecimal between 1 and -1
@alphaville76
Copy link
Author

Yes, I've just submitted the pull request.
Hopefully it can be included in this week release!

@mp911de mp911de linked a pull request Nov 9, 2022 that will close this issue
4 tasks
mp911de pushed a commit that referenced this issue Nov 9, 2022
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]
mp911de added a commit that referenced this issue Nov 9, 2022
Replace String.format(…) with our own digit rendering and padding to reduce computation on hot code paths.

[resolves #558][#556]

Signed-off-by: Mark Paluch <[email protected]>
@mp911de mp911de closed this as completed in 2ad7d71 Nov 9, 2022
mp911de added a commit that referenced this issue Nov 9, 2022
Replace String.format(…) with our own digit rendering and padding to reduce computation on hot code paths.

[resolves #558][#556]

Signed-off-by: Mark Paluch <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants