mssql-jdbc: Arithmetic operations with decimals return incorrect results

Driver version

12.4.0.jre11

SQL Server version

Microsoft SQL Server 2019 (RTM-CU15) (KB5008996) - 15.0.4198.2 (X64) 
Jan 12 2022 22:30:08 
Copyright (C) 2019 Microsoft Corporation
Express Edition (64-bit) on Linux (Ubuntu 20.04.3 LTS) <X64>

Client Operating System

Ubuntu on Windows version 2004.2022.1.0

JAVA/JVM version

1.17

Table schema

CREATE TABLE dbo.df_test_all_types
(
    ID           INT PRIMARY KEY,
    COL_TINYINT  TINYINT,
    COL_SMALLINT SMALLINT,
    COL_INT      INT,
    COL_BIGINT   BIGINT,
    COL_FLOAT    FLOAT,
    COL_NUMERIC  NUMERIC,
    COL_DECIMAL  DECIMAL(19, 8),
    COL_BOOLEAN  BIT,
    COL_DATE     DATE,
    COL_TIME     TIME,
    COL_DATETIME DATETIME,
    COL_VARCHAR  VARCHAR(100),
    COL_NVARCHAR NVARCHAR(100),
    COL_TEXT     TEXT,
    COL_BLOB     VARBINARY(5000)
);


INSERT INTO dbo.df_test_all_types (ID, COL_INT, COL_BIGINT, COL_DECIMAL, COL_BOOLEAN,
                                   COL_VARCHAR, COL_NVARCHAR, COL_TEXT)
VALUES (1, 1, 1000, 48.9, 0, 'Some string', 'Some other string', 'Some Text');
INSERT INTO dbo.df_test_all_types (ID, COL_INT, COL_BIGINT, COL_DECIMAL, COL_BOOLEAN,
                                   COL_TEXT)
VALUES (2, 2, 2000, -48.999, 1, ' TrimText ');
INSERT INTO dbo.df_test_all_types (ID, COL_INT, COL_BIGINT, COL_DECIMAL, COL_BOOLEAN,
                                   COL_VARCHAR, COL_NVARCHAR, COL_TEXT)
VALUES (3, 3, 3000, 25, 1, '1234', '1234', '1234');
INSERT INTO dbo.df_test_all_types (ID, COL_INT, COL_BIGINT, COL_DECIMAL, COL_BOOLEAN,
                                   COL_VARCHAR, COL_NVARCHAR, COL_TEXT)
VALUES (4, 4, 4000, 12.34, 1, '2147483650', '2147483650', '2147483650');
INSERT INTO dbo.df_test_all_types (ID, COL_INT, COL_BIGINT, COL_DECIMAL)
VALUES (5, 5, 10, 2.5);
INSERT INTO dbo.df_test_all_types (ID, COL_TINYINT)
VALUES (6, 19);

Problem description

This new version of the driver introduces some changes on how arithmetic operations with decimals work. For a better understanding consider the follwing examples.

Sum of 3 big decimals with different precision rounds the value to the closest integer:

Query: SELECT ? + ? + ? TABLE_EXAMPLE WHERE "ID" = 4 Dynamic Parameters: new BigDecimal("4"), new BigDecimal("5") and new BigDecimal("3.14") Expected Result: 5.86 Actual Result: 6

Multiplication followed by a Subtraction of 3 big decimals with different precision returns a decimal without precision:

Query: SELECT ? * ? - ? FROM TABLE_EXAMPLE WHERE "ID" = 4 Dynamic Parameters: new BigDecimal("4"), new BigDecimal("2") and new BigDecimal("1.00") Expected Result: 7.00 Actual Result: 7

Subtraction of 2 big decimals rounds the result to the closest integer:

Query: SELECT ? - ? FROM TABLE_EXAMPLE WHERE "ID" = 4 Dynamic Parameters: new BigDecimal("4.0") and new BigDecimal("3.14") Expected Result: 0.86 Actual Result: 1

Subtraction of 2 big decimals round the result to a decimal with the lowest precision:

Example 1

Query: SELECT ? - ? TABLE_EXAMPLE WHERE "ID" = 4 Dynamic Parameters: new BigDecimal("4.0") and new BigDecimal("3.14")
Expected Result: 0.86 Actual Result: 0.9

Example 2

Query: SELECT ? - ? TABLE_EXAMPLE WHERE "ID" = 4] Dynamic Parameters: new BigDecimal("1.23") and new BigDecimal("1.2")
Expected Result: 0.03 Actual Result: 0.0

Incorrect precision when using CAST

Example 1

Query: SELECT ? - CAST("COL_INT" AS DECIMAL(38, 2) FROM TABLE_EXAMPLE WHERE "ID" = 4 Dynamic Parameters: new BigDecimal("0.00")
Expected Result: 0.00 Actual Result: 0

Example 2

Query: SELECT ? - CAST("COL_BIGINT" AS DECIMAL(38, 2) FROM TABLE_EXAMPLE WHERE "ID" = 4 Dynamic Parameters: new BigDecimal("-3996.00")
Expected Result: -3996.00 Actual Result: -3996

Example 3

Query: SELECT ? - "COL_DECIMAL" FROM TABLE_EXAMPLE WHERE "ID" = 4 Dynamic Parameters: new BigDecimal("-3996.00")
Expected Result: -8.34000000 Actual Result: -8

Error message/stack trace

Not applicable. The driver returns no error. The result though is no the expected one.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 18 (10 by maintainers)

Most upvoted comments

Hi @Jeffery-Wasty, @tkyc,

Adding the mentioned property to the connection string works as expected. Thank you for your help.

Hi @OS-veracardoso,

In addition to the testing we did previously with our own driver, we looked into jTDS and the MySQL Java connector.

For jTDS, it behaves the same as the JDBC driver, producing similar arithmetic errors. For the MySQL connector, they are using a similar approach to what we did in 12.2, being able to parse the BigDecimal to fetch precision and scale from there. We’re not sure what kind of performance impact this introduces to the driver, but the same approach in the JDBC driver caused a noticeable performance impact. We tried to find a way that precision could be updated for the parameter type definition without recreating the whole execution plan (the reason for the performance impact) but were unable to do so.

For the time being, we ask that you consider using the 12.2 version of the driver, as that version has the BigDecimal fixes you are interested in. A potential enhancement to the driver could include a connection property to restore previous BigDecimal behavior, at the cost of performance. We’ll keep this issue open while that option is still in consideration/development.