[FIX] web: rounding error

In JavaScript, the following computation introduces numerical errors:
```
Math.pow(10, -4) = 0.00009999999999999999
Math.pow(10, -5) = 0.000009999999999999999
```

In the context of `round_decimals`, such errors propagate to
`round_precision`, ultimately leading to inconsistencies between Python
and JS (see #47300 for detailed use case).

Instead of computing `Math.pow(10, decimals)`, we parse the scientific
notation to prevent such inaccuracies.

We also introduce tests in line with the Python tests.

opw-2205092

closes odoo/odoo#47694

X-original-commit: cb471897bfa08d1fee6a9f49e90d9c120e1733fc
Signed-off-by: Nicolas Martinelli (nim) <nim@odoo.com>
This commit is contained in:
Nicolas Martinelli
2020-03-16 12:01:57 +00:00
parent 0067aec0dd
commit a12479aaa8
2 changed files with 69 additions and 1 deletions
+9 -1
View File
@@ -571,7 +571,15 @@ var utils = {
* @param {Number} decimals the number of decimals. eg: round_decimals(3.141592,2) -> 3.14
*/
round_decimals: function (value, decimals) {
return utils.round_precision(value, Math.pow(10,-decimals));
/**
* The following decimals introduce numerical errors:
* Math.pow(10, -4) = 0.00009999999999999999
* Math.pow(10, -5) = 0.000009999999999999999
*
* Such errors will propagate in round_precision and lead to inconsistencies between Python
* and JavaScript. To avoid this, we parse the scientific notation.
*/
return utils.round_precision(value, parseFloat('1e' + -decimals));
},
/**
* performs a half up rounding with arbitrary precision, correcting for float loss of precision
@@ -136,6 +136,66 @@ QUnit.module('core', {}, function () {
assert.strictEqual(child.bar(), 'Child bar');
})
QUnit.test('round_decimals', function (assert) {
assert.expect(21);
var round_di = utils.round_decimals;
assert.strictEqual(String(round_di(1.0, 0)), '1');
assert.strictEqual(String(round_di(1.0, 1)), '1');
assert.strictEqual(String(round_di(1.0, 2)), '1');
assert.strictEqual(String(round_di(1.0, 3)), '1');
assert.strictEqual(String(round_di(1.0, 4)), '1');
assert.strictEqual(String(round_di(1.0, 5)), '1');
assert.strictEqual(String(round_di(1.0, 6)), '1');
assert.strictEqual(String(round_di(1.0, 7)), '1');
assert.strictEqual(String(round_di(1.0, 8)), '1');
assert.strictEqual(String(round_di(0.5, 0)), '1');
assert.strictEqual(String(round_di(-0.5, 0)), '-1');
assert.strictEqual(String(round_di(2.6745, 3)), '2.6750000000000003');
assert.strictEqual(String(round_di(-2.6745, 3)), '-2.6750000000000003');
assert.strictEqual(String(round_di(2.6744, 3)), '2.674');
assert.strictEqual(String(round_di(-2.6744, 3)), '-2.674');
assert.strictEqual(String(round_di(0.0004, 3)), '0');
assert.strictEqual(String(round_di(-0.0004, 3)), '0');
assert.strictEqual(String(round_di(357.4555, 3)), '357.456');
assert.strictEqual(String(round_di(-357.4555, 3)), '-357.456');
assert.strictEqual(String(round_di(457.4554, 3)), '457.455');
assert.strictEqual(String(round_di(-457.4554, 3)), '-457.455');
});
QUnit.test('round_precision', function (assert) {
assert.expect(26);
var round_pr = utils.round_precision;
assert.strictEqual(String(round_pr(1.0, 1)), '1');
assert.strictEqual(String(round_pr(1.0, 0.1)), '1');
assert.strictEqual(String(round_pr(1.0, 0.01)), '1');
assert.strictEqual(String(round_pr(1.0, 0.001)), '1');
assert.strictEqual(String(round_pr(1.0, 0.0001)), '1');
assert.strictEqual(String(round_pr(1.0, 0.00001)), '1');
assert.strictEqual(String(round_pr(1.0, 0.000001)), '1');
assert.strictEqual(String(round_pr(1.0, 0.0000001)), '1');
assert.strictEqual(String(round_pr(1.0, 0.00000001)), '1');
assert.strictEqual(String(round_pr(0.5, 1)), '1');
assert.strictEqual(String(round_pr(-0.5, 1)), '-1');
assert.strictEqual(String(round_pr(2.6745, 0.001)), '2.6750000000000003');
assert.strictEqual(String(round_pr(-2.6745, 0.001)), '-2.6750000000000003');
assert.strictEqual(String(round_pr(2.6744, 0.001)), '2.674');
assert.strictEqual(String(round_pr(-2.6744, 0.001)), '-2.674');
assert.strictEqual(String(round_pr(0.0004, 0.001)), '0');
assert.strictEqual(String(round_pr(-0.0004, 0.001)), '0');
assert.strictEqual(String(round_pr(357.4555, 0.001)), '357.456');
assert.strictEqual(String(round_pr(-357.4555, 0.001)), '-357.456');
assert.strictEqual(String(round_pr(457.4554, 0.001)), '457.455');
assert.strictEqual(String(round_pr(-457.4554, 0.001)), '-457.455');
assert.strictEqual(String(round_pr(-457.4554, 0.05)), '-457.45000000000005');
assert.strictEqual(String(round_pr(457.444, 0.5)), '457.5');
assert.strictEqual(String(round_pr(457.3, 5)), '455');
assert.strictEqual(String(round_pr(457.5, 5)), '460');
assert.strictEqual(String(round_pr(457.1, 3)), '456');
});
});
});