MikeMcl / decimal.js

An arbitrary-precision Decimal type for JavaScript
http://mikemcl.github.io/decimal.js
MIT License
6.36k stars 480 forks source link

Addition is sometimes not associative #157

Closed tom-sherman closed 4 years ago

tom-sherman commented 4 years ago
const a = new Decimal('-13.77');
const b = new Decimal('1.5612555115940468637');
const c = new Decimal('5');

const result1 = a.add(b).add(c);
const result2 = a.add(b.add(c));

console.log(result1.toString()); // "-7.208744488405953136"
console.log(result2.toString()); // "-7.2087444884059531363"

Fiddle: https://jsfiddle.net/es9pun76/3/

I originally found this bug while using decimal.js-light, I've raised it there too: https://github.com/MikeMcl/decimal.js-light/issues/13

tom-sherman commented 4 years ago

This does not seem to be a problem with big.js.

See https://jsfiddle.net/q6ujk8co/

i-fail commented 4 years ago

Probably similar bug with subtraction:

    const result1 = a.sub(b).sub(c);
    const result2 = a.sub(b.add(c));

Gives different results:

61151.88334586265491493
39165.25929059911
15132.518521452032
=
6854.105533811512915
6854.1055338115129149
----------------
46749.4651970984415455
96478.89913780178
89024.39114386331
=
-138753.82508456664846
-138753.82508456664845
----------------
54475.5665251096347428
2185.5827202641453
57825.85036833445
=
-5535.866563488960557
-5535.8665634889605572
----------------
58992.9583753479768395
33080.30873264703
73094.27562413673
=
-47181.62598143578316
-47181.625981435783161
----------------
64844.5226497100850571
11926.844436415584
55366.61233115836
=
-2448.934117863858943
-2448.9341178638589429
----------------
78633.3759187694437864
61349.60896677592
24037.334341935868
=
-6753.567389942344214
-6753.5673899423442136
----------------
23262.6238587170458838
9113.905513593034
7502.578484250666
=
6646.139860873345884
6646.1398608733458838
i-fail commented 4 years ago

And the same bug with multiplication:

    const result1 = a.mul(b).mul(c);
    const result2 = a.mul(b.mul(c));

Sometimes very different results:

​ 54589.4651442717259757
​ 14531.661735889224
​ 87000.43426094622
​ =
​ 69015325326942.290179
​ 69015325326942.290177
​ ----------------
​ 52048.32741142384580627
​ 53639.76666863257
​ 23092.74059529074
​ =
​ 64471701941503.353832
64471701941503.353829
----------------
33399.89694640863159
30781.520873727917
24155.741512409313
=
24834508791380.318002
24834508791380.318
----------------
3451.11469792955847387
34499.40876089024
74401.5767897021
=
8858357133173.036061
8858357133173.0360608
i-fail commented 4 years ago

And division is affected as well:

    const result1 = a.div(b).div(c);
    const result2 = a.div(b.mul(c));

Results:

49071.7151697405872294
19651.555584921844
56327.54692030375
=
0.000044331606098322916993
0.00004433160609832291699
----------------
34230.9480468589585105
16920.466962595303
34158.96473380873
=
0.00005922456548766828347
0.000059224565487668283468
----------------
75805.3619716876658181
14663.844716427076
78686.83897169911
=
0.000065697670970978308659
0.000065697670970978308656
----------------
61983.5809415613374257
16601.813314653402
7057.458466598976
=
0.00052902087064047293051
0.00052902087064047293053
----------------
59362.35693026017511214
29587.34934146463
46701.80233764018
=
0.000042960708493808559056
0.000042960708493808559058
----------------
64187.7243637776484876
16905.705182181995
72720.60253411996
=
0.000052210904779655426607
0.000052210904779655426609
----------------
91187.7781421704394917
40380.80558308221
41165.87623742567
=
0.000054856019542917490185
0.000054856019542917490183
----------------
57233.88444065540565098
80262.92293133863
1279.4662545472236
=
0.00055732614240645836888
0.0005573261424064583689
tom-sherman commented 4 years ago

@i-fail Subtraction and division are not associative so I think you would expect different results with those examples. Not entirely sure of your test cases though.

Multiplication is associcative though so I would assume those are bugs.

i-fail commented 4 years ago

Subtraction is not associative

Well, a-b-c should have the same result as a-(b+c). This is an arbitrary-precision library, isn't it?

This argument would be correct for division, which can require infinite precision.

tom-sherman commented 4 years ago

Ah, I didn't read your examples correctly, I thought you were doing a-b-c == a-(b-c)

MikeMcl commented 4 years ago

There's no bug here, just wasted time and effort, and quite a few people seemingly not understanding the basics of how the library works.

Operations are rounded to the number of significant digits specified by the Decimal.precision setting, which is 20 by default.

The mathematical result of a.add(b) is "-12.2087444884059531363" which is 21 digits long, so it gets rounded to "-12.208744488405953136".

Just try putting, for example,

Decimal.precison = 25;

after the import, and the results will be as expected.

It is best to always specify the precision explicitly even if you are using the default value.

josdejong commented 4 years ago

This is an arbitrary-precision library, isn't it?

I guess the misunderstanding is that "arbitrary precision" does not mean "infinite precision". Though you can specify how many digits you want to use with calculations in decimal.js, you're still working with floating point and limited precision, and hence have round-off errors when for example doing divisions such as 1/3 (which cannot be represented with a limited number of digits).

In order to understand what bignumber.js can and can't do, it's important to understand the limitations of floating point calculations.