Closed josdejong closed 10 years ago
Yes, this is intended.
BigNumber.config(10)
is the short form of BigNumber.config({ DECIMAL_PLACES: 10 })
, which specifies that division is to be performed to 10 decimal places.
So, for example, 5e-15 / 3
, which is 0.000000000000001666...
, is 0
when rounded to 10 decimal places (assuming ROUND_HALF_UP
rounding mode).
To do divisions on small values without losing precision just make sure that the DECIMAL_PLACES
is set sufficiently high. For example, if you want 5e-15 / 3
to equal 1.666666667e-15
then it would need to be set to at least 24 (i.e 15 + 9).
Normally, you would just set a higher DECIMAL_PLACES
value then just pass one less than the number of significant digits you want to toExponential
, e.g.
BigNumber.config(30);
new BigNumber('5e-15').div('3').toExponential(9); // '1.666666667e-15'
The ability to perform division to a specified number of significant digits, instead of decimal places, is a feature that will be added soon.
Yes, this is intended.
Ai. I hope you are willing to change your mind on this. The number of decimals should really be independent from the exponent of a number.
I see similar issues with sqrt
and pow
: they seem not to reckon with the configured precision, resulting in numbers loosing digits or having too much digits.
The ability to perform division to a specified number of significant digits, instead of decimal places, is a feature that will be added soon.
Thanks, I'm looking forward to having this issue fixed!
@MikeMcl Thanks for great library.
This whole problem with precision, decimal and exponential points is really tricky. Maybe you can have a look at the lengthy discussion we had with @josdejong here. At the end we found a solution which allowed us to cover all possible cases. (Thanks @josdejong ) Also this is along with standard JS way of doing precisions and exponentials.
Users of our popular calculator at (http://numerics.info) have very different requirements and current settings allow them to meet whole spectrum of needs. That will work absolutely great, once we get bigNumber.js working.
@josdejong
The number of decimals should really be independent from the exponent of a number.
I am not clear what you mean exactly, or why you believe that.
If a particular number of decimals (in exponential notation) is required then it can be specified as shown above. If a particular number of significant digits is required it can be specified by toPrecision
.
I see similar issues with sqrt and pow: they seem not to reckon with the configured precision, resulting in numbers loosing digits or having too much digits.
The problem with statements such as this is that some viewers may mistakenly believe there is some fault with the library and that it is not working correctly. As I explained, and the README and documentation makes clear, the "configured precision" is in terms of decimal places (of a number in normal, non-exponential notation) not significant digits. sqrt
and pow
(negative exponents) always conform to the required precision in that sense, and numbers never lose or have too many digits.
I'm looking forward to having this issue fixed!
I don't think it's an issue that needs fixing, it's a fundamental design decision. Some people find it simpler to think in terms of decimal places, others in terms of significant digits. Maths homework sometimes required answers in terms of the former, sometimes the latter. Generally, perhaps more scientific use cases prefer significant digits and more financial use cases prefer decimal places. There are advantages and disadvantages to each.
The BigDecimal libraries work in terms of significant digits and there are quite complex rules as to the precision of the result of a calculation. I wanted to take a different, perhaps simpler approach: just one global decimal places setting for operations involving division, and other operations always returning full precision.
But, yes, it would be good to be able to specify precision in terms of significant digits and I am working on that at present as a by-product of adding support for non-integer powers.
Thanks for the feedback.
I understand your point. However to me this is a serious flaw, not a feature.
What I would expect from a BigNumber library is to handle (a) very large numbers, (b) very small numbers, and (c) with an arbitrary (configurable) precision. Unfortunately bignumber.js only does (c) so far.
The design decision to handle precision like fixed point means that you effectively can't work with very small numbers, as you loose precision below DECIMAL_PLACES
. And you can't efficiently work with very large numbers as all decimals are stored sort of as a fixed point number (new BigNumber('1e500').div(3)
stores the number with 520 digits (!)). I would like BigNumber to work as a floating point number, storing a limited (configured) number of significant digits, independent from the exponent of the number. I don't think this would harm these "some people", as the can just set DECIMAL_PLACES
very high and use .toFixed(xyz)
to get output in their desired number-of-decimals-behind-the-point.
The following type of calculations should just be no problem for BigNumber with 20 digits, like it is no problem for a regular (floating point) Number with ~16 digits precision.
console.log(5e-30 / 3); // 1.6666666666666666e-30
console.log(new BigNumber('5e-30').div(3).valueOf()); // 0
console.log(Math.sqrt(3e-30)); // 1.7320508075688773e-15
console.log(new BigNumber('3e-30').sqrt().valueOf()); // 0
Those calculations are no problem for this library. It doesn't seem unreasonable to have to set the DP (decimal places) to 30 or 40 (depending on how many SD (significant digits) you want) when working with numbers such as 5e-30
. The numbers are stored in floating point format so it is not inefficient to work with small numbers using a high DP.
What I would expect from a BigNumber library is to handle (a) very large numbers, (b) very small numbers, and (c) with an arbitrary (configurable) precision. Unfortunately bignumber.js only does (c) so far.
Well, it does all three, but clearly not as you would like.
The design decision to handle precision like fixed point means that you effectively can't work with very small numbers, as you loose precision below DECIMAL_PLACES.
Yes, but only in the same way that you can lose precision when performing division to an arbitrary number of SD if you set the value too low
precision = 10 (significant digits)
1e+30 / 3 = 333333333300000000000000000000 // Incorrect by more than 33333333333333333333
decimal places = 10
1e+30 / 3 = 333333333333333333333333333333.3333333333
As shown, it is easier to lose precision with large numbers when it is specified in terms of SD, and as larger numbers are arguably more commonly used than extremely small numbers, it follows that it is easier to avoid losing precision using DP than SD.
And you can't efficiently work with very large numbers as all decimals are stored sort of as a fixed point number (new BigNumber('1e500').div(3) stores the number with 520 digits (!)).
Yes, but 1e500
is not just a big number, it is an enormous number (for example, the estimated number of atoms in the universe is 1e+80
).
And if you want to store and use fewer SD you can do
new BigNumber( BigNumber('1e500').div(3).toPrecision(20) )
although I accept that this is inefficient and that is why I'm adding better support for SD.
As it is, this library is best used for numbers of a few hundred digits and less, and perhaps when decimal places are more important than significant digits (as in, for example, financial calculations). The priority is not to be able to efficiently handle enormous/tiny values that most users of the library will not be using.
This is indeed the best choice for financial calculations and less for scientific calculations. Reading the docs, the setting DECIMAL_PLACES
is only applied by div
, sqrt
, and pow
, so they are the only ones who can suffer loosing precision. Maybe you can introduce a complementary setting like MINIMUM_SIGNIFICANT_DIGITS
to prevent that :D.
Anyway, I agree with you that this is kind of an edge case. I'm looking forward to your improvements for division (and sqrt, pow as well?). Thanks for this excellent and well documented library.
Hi Jos, I have just published decimal.js which handles precision in terms of significant digits. It was going to be an update to bignumber.js, but I made so many changes I thought I better release it as a new library.
That's awesome Michael! I'm going to check it out asap.
@MikeMcl does this explain why new BigNumber('1.1').dividedBy('2').times(new BigNumber(2).dividedBy('1.1')).toString()
returns 1.000000000000000000001
and not 1
?
2 / 1.1
has a non-terminating decimal expansion (an infinite number of digits after the decimal point), which means that the result of BigNumber(2).dividedBy(1.1)
will only be an approximation.
BigNumber.config().DECIMAL_PLACES // 20
BigNumber.config().ROUNDING_MODE // 4
new BigNumber(2).dividedBy(1.1) // 1.81818181818181818182
BigNumbers are stored as floating-point values not ratios.
Ok, so because BigNumbers are stored as floating-point and not ratios, then the lib is unable to cancel the ratios and return exactly 1
, correct? In that case, should the number of DECIMAL_PLACES
be increased in order to cover the necessary precision? Is this what decimal.js does? (edit: reading about it here - https://github.com/MikeMcl/big.js/issues/45)
Infinite precision would be needed in order to return exactly 1
.
decimal.js works using significant digits instead of decimal places, but it also stores numbers in a floating-point format.
Your choices would seem to be to either find a library that stores numbers as ratios or just round the result.
a = new BigNumber(1.1).dividedBy(2)
b = new BigNumber(2).dividedBy(1.1)
a.times(b).round() // 1
a.times(b).round(10) // 1
a.times(b).round(19) // 1
a.times(b).round(20) // 1
a.times(b).round(21) // 1.000000000000000000001
I noticed some unexpected behavior when dividing numbers. In the following example I have set the precision to 10 decimal places. I expected this to be the number of significant digits, but apparently this means the number of significant digits behind the decimal point.
In the following example I would expect each of the results to be in the format
1.666666667e[+-]EXP
: each time having 10 digits, and the value ofEXP
being equal to the exponent of the divided value. Instead, the number of digits varies depending on the values exponent.Is this intended behavior? If so, is there any workaround so I can actually do divisions on small values without loosing precision?