Skip to content

Commit 7b531ff

Browse files
committed
BUG#35707417: Return decimal value losing precision as string
Some values stored in DECIMAL and NUMERIC columns are losing precision when returned back to the client after executing a table query. Although some unsafe values are already returned as a raw string, others are not. This is due to an issue in the decision-making process for when a decimal value should be converted to JavaScript number or not. This patch introduces the changes to address the shortcomings of that decision-making process and ensures all unsafe values (integers or decimals) are not converted to a JavaScript number. Change-Id: Ica4016014a0f437b87e1cc797965eeba82ddfac1
1 parent 12daae7 commit 7b531ff

File tree

3 files changed

+44
-5
lines changed

3 files changed

+44
-5
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ v8.0.35
1313

1414
- Rows can now be inserted in a table using CRUD with an X DevAPI expression
1515
- Date and time operators are now allowed in X DevAPI expressions
16+
- All unsafe decimal values are now being returned as a raw string
1617

1718
v8.0.33
1819
=======

lib/Protocol/Wrappers/Messages/Resultset/Row.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2023, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -362,9 +362,23 @@ function formatSafeNumber (value) {
362362
const int = matcher[1];
363363
const decimal = matcher[2];
364364

365-
const isUnsafe = Number.parseFloat(int) < Number.MIN_SAFE_INTEGER ||
366-
Number.parseFloat(int) > Number.MAX_SAFE_INTEGER ||
367-
Number.parseFloat(decimal) > Number.MAX_SAFE_INTEGER;
365+
// JavaScript number is represented in 64-bit format IEEE-754, so there
366+
// are exactly 64 bits to store a number: 52 of them are used to store the
367+
// digits, 11 of them store the position of the decimal point, and 1 bit
368+
// is for the sign.
369+
let isUnsafe = false;
370+
371+
if (!decimal) {
372+
// If the decimal part does not exist, we only need to worry about
373+
// integer precision.
374+
isUnsafe = BigInt(int) > Number.MAX_SAFE_INTEGER || BigInt(int) < Number.MIN_SAFE_INTEGER;
375+
} else {
376+
// If the decimal part exists, we can check if the number is safe is by
377+
// comparing the original raw string with the string resulting from
378+
// calling "toFixed()" using the number of decimal digits.
379+
isUnsafe = `${int}.${decimal}` !== Number.parseFloat(`${int}.${decimal}`)
380+
.toFixed(decimal.length);
381+
}
368382

369383
// If the value is unsafe, we should not convert it to a JavaScript number
370384
// because we might be loosing precision.

test/functional/default/relational-tables/table-select.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2023, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -669,6 +669,30 @@ describe('selecting rows from a table using CRUD', () => {
669669
});
670670
});
671671

672+
context('BUG#35707417', () => {
673+
beforeEach('add relevant columns to the existing table', () => {
674+
return session.sql(`ALTER TABLE \`${schema.getName()}\`.\`${table.getName()}\`
675+
ADD COLUMN de1 DECIMAL(18, 17),
676+
ADD COLUMN de2 DECIMAL(18, 13)`)
677+
.execute();
678+
});
679+
680+
beforeEach('populate the table', async () => {
681+
return table.insert('de1', 'de2')
682+
.values('1.23456789012345678', '12345.6789012345678')
683+
.execute();
684+
});
685+
686+
it('does not loose precision when retrieving values from fixed-point decimal columns', async () => {
687+
const want = ['1.23456789012345678', '12345.6789012345678'];
688+
const res = await table.select('de1', 'de2')
689+
.execute();
690+
691+
const got = res.fetchOne();
692+
expect(got).to.deep.equal(want);
693+
});
694+
});
695+
672696
context('when debug mode is enabled', () => {
673697
beforeEach('populate table', () => {
674698
return table.insert(['id', 'name', 'age'])

0 commit comments

Comments
 (0)