How can I deal with floating point number precision in JavaScript? [duplicate]
Asked Answered
M

46

831

I have the following dummy test script:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

This will print the result 0.020000000000000004 while it should just print 0.02 (if you use your calculator). As far as I understood this is due to errors in the floating point multiplication precision.

Does anyone have a good solution so that in such case I get the correct result 0.02? I know there are functions like toFixed or rounding would be another possibility, but I'd like to really have the whole number printed without any cutting and rounding. Just wanted to know if one of you has some nice, elegant solution.

Of course, otherwise I'll round to some 10 digits or so.

Mcclung answered 22/9, 2009 at 7:34 Comment(15)
Actually, the error is because there is no way to map 0.1 to a finite binary floating point number.Kasiekask
Most fractions can't be converted to a decimal with exact precision. A good explanation is here: docs.python.org/release/2.5.1/tut/node16.htmlSihon
@AaronDigulla: (new Number(0.1)).valueOf() is 0.1.Overglaze
@SalmanA: That your JavaScript runtime hides this problem from you doesn't mean I'm wrong.Kasiekask
Disagree with Aaron, there are ways to code 0.1 perfectly and completely in binary. But IEEE 754 does not necessarily defines this. Imagine a representation where you would code the integer part in binary on the one hand, the decimal part on the other hand, up to n decimals, in binary too, like a normal integer > 0, and finally, the position of the decimal point. Well, you would represent 0.1 perfectly, with no error. Btw, since JS uses a finite number of decimals internally, they devs might as well coded the guts to not make that mistake on the last decimals.Alva
@epascarello That question concerns why that happens, this one is about how to fix it. The answers are different, so the questions are different.Harder
@AaronDigulla why JS is doing this and C# does not? I noticed JS does it also on sum, makes no sense. Why can't add 1.12345678 + 1.12345678 correctly. No multiplying no division, no overflowValeric
I wrote two equations/formulas to do the increase and decrease reverse percentage calculation. you can find it here:https://mcmap.net/q/36894/-excel-formula-for-reverse-percentage-calculation and here https://mcmap.net/q/36895/-reverse-percentage-lookupLumbar
@PawelCioch Did you test with the exact same numbers? Did you use the binary pattern to initialize the variables (otherwise, you'll get rounding errors from the float->string converter)? Most languages use the CPU for floating point math (so they behave the same), only the code to parse and format the numbers is different.Kasiekask
So funny and more weird with 0.1 + 0.2 _Reception
Binary fractions can only handle 1/2, 1/4, 1/8, 1/16, 1/32... correctly. And because 0.3 and 0.2 isn't a fraction that can be built with the above ones there is this 'weird' behavior. This one helped me a lot: youtube.com/watch?v=PZRI1IfStY0Espadrille
@FabienHaddadi (mostly commenting this to solidify my understanding) I guess it's a matter of definitions. To you "encoding a number in binary" means "having a way to represent that number using 0's and 1's". To Aaron, it means "writing it as a finite (pseudo) geometric sum with 2 as the common ratio and weights of 0 or 1."Brachium
@user56202: yes it must be a matter of defs. I learnt binary on the now old HP48 scientific calculator. It used to represent a real number by two components: a signed mantissa, and a signed exponent of ten. Now, decimal number 0.1 is equal to 1 factor of ten to the power of minus one. This is trivial to store for HP48.Alva
This question has been closed, however the supposed duplicate is asking a very general question about floating point math (language agnostic), while this one is specifically asking about a JavaScript solution to the problem. I vote to reopen it.Ginetteginevra
I also asked on the Meta board about improving and reopening this question, but was heavily downvoted (see here). If the community prefers to keep it closed, then that's fine, but if anyone has a differing opinion, please chime in.Ginetteginevra
D
604

From the Floating-Point Guide:

What can I do to avoid this problem?

That depends on what kind of calculations you’re doing.

  • If you really need your results to add up exactly, especially when you work with money: use a special decimal datatype.
  • If you just don’t want to see all those extra decimal places: simply format your result rounded to a fixed number of decimal places when displaying it.
  • If you have no decimal datatype available, an alternative is to work with integers, e.g. do money calculations entirely in cents. But this is more work and has some drawbacks.

Note that the first point only applies if you really need specific precise decimal behaviour. Most people don't need that, they're just irritated that their programs don't work correctly with numbers like 1/10 without realizing that they wouldn't even blink at the same error if it occurred with 1/3.

If the first point really applies to you, use BigDecimal for JavaScript or DecimalJS, which actually solves the problem rather than providing an imperfect workaround.

Dispeople answered 9/8, 2010 at 12:30 Comment(22)
I noticed your dead link for BigDecimal and while looking for a mirror, I found an alternative called BigNumber: jsfromhell.com/classes/bignumberKenogenesis
@Jackson: Thanks, I'll take a look at that. I've also fixed the link to the BigDecimal library, which can now be found on GitHubDispeople
I'm not sure if solution 3 (work with integers) is valid, if JavaScript doesn't have an internal integer representation. AFAIK the universal number type is float at the end of the day.Attested
@bass-t: Yes, but floats can exactly represent integers up to the length of the significand, and as per ECMA standard it's a 64bit float. So it can exactly represent integers up to 2^52Dispeople
What are drawbacks of using integers?Montagnard
@Jus12: integer overflow leads to wraparound - really nasty if it ever happens in production. And you can't handle currencies with different splits (some have 1000 subunits rather than 100).Dispeople
@MichaelBorgwardt: Up to 2^53, actually, because the leading '1' bit is not explicitly represented.Sacring
Working in cents seemed to be a good quick solution, but indeed, it is not perfect. I'm working with currency and, for example, 9830.45 * 100 = 983045.0000000001.Plantar
Why so many up votes on the comment commenting on "without realizing that they wouldn't even blink at the same error if it occurred with 1/3."? The fraction, 1/10 can be represented as a finite decimal in base 10 while the fraction 1/3 cannot. Not the same error nor the same problem.Equine
@Karl: The decimal fraction 1/10 cannot be represented as a finite binary fraction in base 2, and that's what Javascript numbers are. So it is in fact exactly the same problem.Dispeople
Ok, I see your point, and it's a good one. As I mentioned, I was looking at it from the base 10 perspective.Equine
I learned today that even integers have precision problems in javascript. Consider that console.log(9332654729891549) actually prints 9332654729891548 (ie off by one!)Cancellation
@mlathe: Doh.. ;P... Between 2⁵²=4,503,599,627,370,496 and 2⁵³=9,007,199,254,740,992 the representable numbers are exactly the integers. For the next range, from 2⁵³ to 2⁵⁴, everything is multiplied by 2, so the representable numbers are the even ones, etc. Conversely, for the previous range from 2⁵¹ to 2⁵², the spacing is 0.5, etc. This is due to simply increasing|decreasing the base|radix 2|binary exponent in/of the 64-bit float value (which in turn explains the rarely documented 'unexpected' behavior of toPrecision() for values between 0 and 1).Defalcate
We can use a mathematical way to reduce the error. (It only works for certain values such as 0.1 * 0.1.) Since mathematically A * B = C^(log_C(AB)) = C^(log_C(A) + log_C(B)), in order to calculate A * B, we can use 10 ** (Math.log10(A) + Math.log10(B)) instead, and it gives a mathematically expected value for certain values. For example: 0.1 * 0.1 is 0.010000000000000002, whereas 10 ** (Math.log10(0.1) + Math.log10(0.1)) is 0.01. – Taking logs and adding versus multiplyingMoujik
@MichaelBorgwardt, At the very moment the result needs to be displayed to a human, it becomes wrong, not just "irritating".Bedraggled
I am also facing this type issue, When i multiply 17.955*100. it returns 1795.4999999999998 instead of 1795.5Spinneret
I have recently been using decimal.js for this purpose. In my opinion, it is a better version of BigNumber with really good documentation and API.Ailyn
@K._ 10 ** (Math.log10(0.55) + Math.log10(0.1)) => 0.05500000000000001 - what's wrong?Doscher
@gumkins “We can use a mathematical way to reduce the error.” Unfortunately, this does not completely get rid of the error and is not a universal method as well. Just a way possible. Nothing more, nothing less.Moujik
@MichaelBorgwardt on the first point you mention "special decimal datatype" do you mean BigInt? will be better if you revise your first point to be like "especially when you work with money: use a BigInt datatype."Engender
@Engender I wrote this answer about 8 years before BigInt was added to Javascript, and it's not a solution to the problem because it does not represent fractions.Dispeople
decimalJS doesn't seem to do the job. Rounding 3.14459 to 2 decimal places returns 3.14 instead of 3.15. I've not found a single library that can perfom this correctlyGourmand
A
194

I like Pedro Ladaria's solution and use something similar.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

Unlike Pedros solution this will round up 0.999...repeating and is accurate to plus/minus one on the least significant digit.

Note: When dealing with 32 or 64 bit floats, you should use toPrecision(7) and toPrecision(15) for best results. See this question for info as to why.

Aile answered 4/9, 2010 at 23:0 Comment(12)
Any reason why you picked 12?Tetrarch
toPrecision returns a string instead of a number. This might not always be desirable.Fredella
parseFloat(1.005).toPrecision(3) => 1.00Maecenas
@Maecenas the number before the decimal point is counted as well.Racism
@user2428118, I know, I meant to show the rounding error, The outcome is 1.00 instead of 1.01Maecenas
What @Racism said may not be obvious enough: (9.99*5).toPrecision(2) = 50 instead of 49.95 because toPrecision counts the whole number, not just decimals. You can then use toPrecision(4), but if your result is >100 then you're out of luck again, because it'll allow the first three numbers and one decimal, that way shifting the dot, and rendering this more or less unusable. I ended up using toFixed(2) insteadClabo
Using StackOverflow this is the answer I came for.Trait
Using @aexl answer, parseFloat() your toFixed(2) will return float. Very helpful, thanks!Bricklaying
@aexl toFixed() does not always work. For example Number((8.04 * 25).toFixed(16)) <- 200.99999999999997 while Number((8.04 * 25).toPrecision(16)) <- 201Olympiaolympiad
If you want a number instead of a string, you could do the toPrecision first and then parseFloat. Like this: parseFloat((number).toPrecision(12))Kindhearted
If you are working with more than 10 decimal places we can't use parseFloat as it rounds it.Trixi
DO NOT USE .toPrecision, it is really bad practice !!! parseFloat(1.005).toPrecision(4) RETURNS => '1.005' parseFloat(11.005).toPrecision(4) RETURNS => '11.01'Hexa
L
95

For the mathematically inclined: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

The recommended approach is to use correction factors (multiply by a suitable power of 10 so that the arithmetic happens between integers). For example, in the case of 0.1 * 0.2, the correction factor is 10, and you are performing the calculation:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

A (very quick) solution looks something like:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

In this case:

> Math.m(0.1, 0.2)
0.02

I definitely recommend using a tested library like SinfulJS

Lamellicorn answered 20/9, 2013 at 2:55 Comment(7)
Il love this elegant workaround but seems not to be perfect: jsfiddle.net/Dm6F5/1 Math.a(76.65, 38.45) returns 115.10000000000002Schwann
Math.m(10,2332226616) is giving me "-19627406800" which is a negative value... I hope there must be a upper limit - might be that is causing this issue. Please suggestEgyptian
This all looks great, but seems to have a mistake or two in there somewhere.Harriot
Very quick solution he said...broken fix no one ever said.Precontract
Doesn't work var test = 1.8; (test * 100) / 10000 returns 0.018000000000000002Shive
While the provided code doesn't work in all use cases (and should probably have a sizable warning about accuracy), the concept of using a correction factor actually does work - just make sure to establish an accurate factor for the inputs. Note that the bounds here will also be constrained by whether the corrected values are integers which can be represented as floats.Profiterole
this is really bad for performanceHexa
S
57

Are you only performing multiplication? If so then you can use to your advantage a neat secret about decimal arithmetic. That is that NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. That is to say that if we have 0.123 * 0.12 then we know that there will be 5 decimal places because 0.123 has 3 decimal places and 0.12 has two. Thus if JavaScript gave us a number like 0.014760000002 we can safely round to the 5th decimal place without fear of losing precision.

Sihon answered 11/8, 2010 at 14:33 Comment(5)
... and how to get the exact amount of decimal places.Rockabilly
0.5 * 0.2 = 0.10; You can still truncate at 2 decimal places (or less). But there will never be a number with any mathematical significance beyond this law.Sihon
Do you have a citation for this? Also note that the same is not true for division.Cicerone
Griffin: a citation (and more importantly, an easy to understand explanation): mathsisfun.com/multiplying-decimals.html and math.com/school/subject1/lessons/S1U1L5DP.html In essence: "Because when you (my addition: manually on paper) multiply without the decimal point, you are really shifting the decimal point to the right to get it out of the way (my addition: for each number)" so, # shifts for x plus # shifts for y.Defalcate
@NateZaugg you can't truncate the the overflowing decimals, you have to round the amount, because 2090.5 * 8.61 is 17999.205 but in float it's 17999.204999999998Manipulator
N
56

Surprisingly, this function has not been posted yet although others have similar variations of it. It is from the MDN web docs for Math.round(). It's concise and allows for varying precision.

function precisionRound(number, precision) {
    var factor = Math.pow(10, precision);
    return Math.round(number * factor) / factor;
}

console.log(precisionRound(1234.5678, 1));
// expected output: 1234.6

console.log(precisionRound(1234.5678, -1));
// expected output: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

UPDATE: Aug/20/2019

Just noticed this error. I believe it's due to a floating point precision error with Math.round().

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

These conditions work correctly:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

Fix:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

This just adds a digit to the right when rounding decimals. MDN has updated the Math.round() page so maybe someone could provide a better solution.

Neoimpressionism answered 9/4, 2018 at 9:30 Comment(7)
wrong answer. 10.2 will always return 10.19. jsbin.com/tozogiwide/edit?html,js,console,outputMonogenic
@Žilvinas The JSBin link you posted does not use the MDN function listed above. I think your comment is directed at the wrong person.Neoimpressionism
would Math.ceil not account for that 0.01 in the same way (it's making it an integer and then casting back down to a float afaik)Millstream
wow, thanks, this works great for what I needed, using a precision of about 12 with precisionRoundMod does the trick for my use cases!Gorrian
You can replace Math.pow(10, precision) to 10 ** precision.Hassanhassell
Surprise: 1 / 10 ** 23 = 1.0000000000000001e-23Caliphate
This answer is a bit confusing in the sense that your precisionRound() function parameter is expecting the number of digits after the decimal point, whereas the built-in JavaScript function Number.prototype.toPrecision() expects the number of significant digits, which may be before or after the decimal point. Perhaps this should be made clear.Ginetteginevra
P
44

I'm finding BigNumber.js meets my needs.

A JavaScript library for arbitrary-precision decimal and non-decimal arithmetic.

It has good documentation and the author is very diligent responding to feedback.

The same author has 2 other similar libraries:

Big.js

A small, fast JavaScript library for arbitrary-precision decimal arithmetic. The little sister to bignumber.js.

and Decimal.js

An arbitrary-precision Decimal type for JavaScript.

Here's some code using BigNumber:

$(function(){      
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>
Padget answered 2/12, 2014 at 14:35 Comment(2)
Using a library is definitely the best choice in my opinion.Smoothie
From this link github.com/MikeMcl/big.js/issues/45 bignumber.js -> financial decimal.js -> scientific big.js -> ???Stroboscope
D
31

You are looking for an sprintf implementation for JavaScript, so that you can write out floats with small errors in them (since they are stored in binary format) in a format that you expect.

Try javascript-sprintf, you would call it like this:

var yourString = sprintf("%.2f", yourNumber);

to print out your number as a float with two decimal places.

You may also use Number.toFixed() for display purposes, if you'd rather not include more files merely for floating point rounding to a given precision.

Deemster answered 6/8, 2010 at 8:38 Comment(3)
I think this is the cleanest solution. Unless you really really need the result to be 0.02, the small error is negligible. It sounds like what's important is that your number is displayed nicely, not that you have arbitrary precision.Logway
For display this is indeed the best option, for complicated calculations, check Borgwardt's answer.Peccary
But then again this will return exactly the same string as yourNumber.toFixed(2).Psychogenesis
W
25
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---or---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---also---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- as in ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2
Wivina answered 8/8, 2010 at 3:16 Comment(1)
I think that would give the same problem as a result. You return a floating point so a big chance the return value will also be "incorrect".Tyishatyke
B
22

You can use parseFloat() and toFixed() if you want to bypass this issue for a small operation:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;
Bede answered 4/3, 2015 at 13:49 Comment(1)
Perhaps this code could use some comments, as it's a bit confusing to follow as is. Also, .toFixed() only works if you know exactly how many decimal places you need at the time. Sometimes you might want to keep a certain amount of significant digits without knowing the exact number of decimal places. In that case, use .toPrecision() instead of .toFixed().Ginetteginevra
E
15

You just have to make up your mind on how many decimal digits you actually want - can't have the cake and eat it too :-)

Numerical errors accumulate with every further operation and if you don't cut it off early it's just going to grow. Numerical libraries which present results that look clean simply cut off the last 2 digits at every step, numerical co-processors also have a "normal" and "full" lenght for the same reason. Cuf-offs are cheap for a processor but very expensive for you in a script (multiplying and dividing and using pov(...)). Good math lib would provide floor(x,n) to do the cut-off for you.

So at the very least you should make global var/constant with pov(10,n) - meaning that you decided on the precision you need :-) Then do:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

You could also keep doing math and only cut-off at the end - assuming that you are only displaying and not doing if-s with results. If you can do that, then .toFixed(...) might be more efficient.

If you are doing if-s/comparisons and don't want to cut of then you also need a small constant, usually called eps, which is one decimal place higher than max expected error. Say that your cut-off is last two decimals - then your eps has 1 at the 3rd place from the last (3rd least significant) and you can use it to compare whether the result is within eps range of expected (0.02 -eps < 0.1*0.2 < 0.02 +eps).

Europa answered 6/8, 2010 at 8:30 Comment(3)
You can also add 0.5 in order to do a poor man's rounding: Math.floor(x*PREC_LIM + 0.5)/PREC_LIMFachanan
Note though, that e.g. Math.floor(-2.1) is -3. So perhaps use e.g. Math[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIMGeraldine
Why floor instead of round?Virago
M
12

Notice that for the general purpose use, this behavior is likely to be acceptable.
The problem arises when comparing those floating points values to determine an appropriate action.
With the advent of ES6, a new constant Number.EPSILON is defined to determine the acceptable error margin :
So instead of performing the comparison like this

0.1 + 0.2 === 0.3 // which returns false

you can define a custom compare function, like this :

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

Source : http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon

Musket answered 15/9, 2017 at 23:44 Comment(2)
In my case Number.EPSILON was too small, which resulted in e.g. 0.9 !== 0.8999999761581421Ganley
Number.EPSILON is useless as that value changes with the number. It works if the number is small enough. In a very large floating point number epsilon could get even above 1.Commingle
A
11

The result you've got is correct and fairly consistent across floating point implementations in different languages, processors and operating systems - the only thing that changes is the level of the inaccuracy when the float is actually a double (or higher).

0.1 in binary floating points is like 1/3 in decimal (i.e. 0.3333333333333... forever), there's just no accurate way to handle it.

If you're dealing with floats always expect small rounding errors, so you'll also always have to round the displayed result to something sensible. In return you get very very fast and powerful arithmetic because all the computations are in the native binary of the processor.

Most of the time the solution is not to switch to fixed-point arithmetic, mainly because it's much slower and 99% of the time you just don't need the accuracy. If you're dealing with stuff that does need that level of accuracy (for instance financial transactions) Javascript probably isn't the best tool to use anyway (as you've want to enforce the fixed-point types a static language is probably better).

You're looking for the elegant solution then I'm afraid this is it: floats are quick but have small rounding errors - always round to something sensible when displaying their results.

Archaic answered 9/8, 2010 at 8:57 Comment(0)
G
11

The round() function at phpjs.org works nicely: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
Ganley answered 1/3, 2012 at 19:50 Comment(1)
@jrg By convention, numbers ending with a "5" are rounded to the nearest even (because always rounding up or down would introduce a bias to your results). Therefore, 4.725 rounded to two decimal places should indeed be 4.72.Zhukov
R
11

decimal.js, big.js or bignumber.js can be used to avoid floating-point manipulation problems in Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: minimalist; easy-to-use; precision specified in decimal places; precision applied to division only.

bignumber.js: bases 2-64; configuration options; NaN; Infinity; precision specified in decimal places; precision applied to division only; base prefixes.

decimal.js: bases 2-64; configuration options; NaN; Infinity; non-integer powers, exp, ln, log; precision specified in significant digits; precision always applied; random numbers.

link to detailed comparisons

Racine answered 6/8, 2019 at 22:26 Comment(1)
how are "non-integer powers" a specific feature? it seems native Math.powi.e ** already handles that?Whithersoever
I
9

0.6 * 3 works fine:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}
Interplanetary answered 12/2, 2015 at 9:14 Comment(3)
Would this work though with something like 8.22e-8 * 1.3 ?Hooker
0.6 x 3 = 1.8, the code you give results to 2... so not good.Substituent
Interesting. You can swap the multiplication and division operators in this and it also works.Tantrum
M
8

not elegant but does the job (removes trailing zeros)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
Maecenas answered 22/9, 2009 at 12:43 Comment(1)
toFixed doesn't always work: #662062Eldoria
T
8

To avoid this you should work with integer values instead of floating points. So when you want to have 2 positions precision work with the values * 100, for 3 positions use 1000. When displaying you use a formatter to put in the separator.

Many systems omit working with decimals this way. That is the reason why many systems work with cents (as integer) instead of dollars/euro's (as floating point).

Tyishatyke answered 9/8, 2010 at 12:12 Comment(0)
M
8

I solved it by first making both numbers integers, executing the expression and afterwards dividing the result to get the decimal places back:

function evalMathematicalExpression(a, b, op) {
    const smallest = String(a < b ? a : b);
    const factor = smallest.length - smallest.indexOf('.');

    for (let i = 0; i < factor; i++) {
        b *= 10;
        a *= 10;
    }

    a = Math.round(a);
    b = Math.round(b);
    const m = 10 ** factor;
    switch (op) {
        case '+':
            return (a + b) / m;
        case '-':
            return (a - b) / m;
        case '*':
            return (a * b) / (m ** 2);
        case '/':
            return a / b;
    }

    throw `Unknown operator ${op}`;
}

Results for several operations (the excluded numbers are results from eval):

0.1 + 0.002   = 0.102 (0.10200000000000001)
53 + 1000     = 1053 (1053)
0.1 - 0.3     = -0.2 (-0.19999999999999998)
53 - -1000    = 1053 (1053)
0.3 * 0.0003  = 0.00009 (0.00008999999999999999)
100 * 25      = 2500 (2500)
0.9 / 0.03    = 30 (30.000000000000004)
100 / 50      = 2 (2)
Milewski answered 29/9, 2019 at 17:53 Comment(3)
works for me, fixed tradingview lightweight chart with this function when AMBUSDT pair is not working correctly because of 0.00001*10000000 returns 100.00000000000001 , thanks SimonMoor
Do you know the theory of why integers work fine but only float have this imprecision issue? I'd be keen to understand this better.Ginetteginevra
UPDATE: After some reading up on IEEE-754 (my brain now hurts), I discovered that not all integers are represented exactly, just those lower than 9007199254740993 (since JS uses 64 bit precision). JS will round that number down by 1. Going higher, you can only increment by 2. But that's more than enough for 99% of use cases.Ginetteginevra
S
7

Problem

Floating point can't store all decimal values exactly. So when using floating point formats there will always be rounding errors on the input values. The errors on the inputs of course results on errors on the output. In case of a discrete function or operator there can be big differences on the output around the point where the function or operator is discrete.

Input and output for floating point values

So, when using floating point variables, you should always be aware of this. And whatever output you want from a calculation with floating points should always be formatted/conditioned before displaying with this in mind.
When only continuous functions and operators are used, rounding to the desired precision often will do (don't truncate). Standard formatting features used to convert floats to string will usually do this for you.
Because the rounding adds an error which can cause the total error to be more then half of the desired precision, the output should be corrected based on expected precision of inputs and desired precision of output. You should

  • Round inputs to the expected precision or make sure no values can be entered with higher precision.
  • Add a small value to the outputs before rounding/formatting them which is smaller than or equal to 1/4 of the desired precision and bigger than the maximum expected error caused by rounding errors on input and during calculation. If that is not possible the combination of the precision of the used data type isn't enough to deliver the desired output precision for your calculation.

These 2 things are usually not done and in most cases the differences caused by not doing them are too small to be important for most users, but I already had a project where output wasn't accepted by the users without those corrections.

Discrete functions or operators (like modula)

When discrete operators or functions are involved, extra corrections might be required to make sure the output is as expected. Rounding and adding small corrections before rounding can't solve the problem.
A special check/correction on intermediate calculation results, immediately after applying the discrete function or operator might be required. For a specific case (modula operator), see my answer on question: Why does modulus operator return fractional number in javascript?

Better avoid having the problem

It is often more efficient to avoid these problems by using data types (integer or fixed point formats) for calculations like this which can store the expected input without rounding errors. An example of that is that you should never use floating point values for financial calculations.

Seiden answered 27/7, 2017 at 14:4 Comment(0)
T
6

Elegant, Predictable, and Reusable

Let's deal with the problem in an elegant way reusable way. The following seven lines will let you access the floating point precision you desire on any number simply by appending .decimal to the end of the number, formula, or built-in Math function.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0
Titian answered 12/9, 2019 at 6:41 Comment(7)
If you choose to downvote, at least provide a reason.Titian
I didn't downvote, but while this is elegant and reusable, a monkey patch of a JavaScript primitive type object isn't likely to be predictable. Some of these concerns would appear to apply.Albertinealbertite
Try: ((0.1*3)*1e14).decimalInsured
@Albertinealbertite I totally agree it is a monkey patch and it isn't suitable for some projects for the linked reasons. But for many, this solution is the ideal lesser of two evils.Titian
@Insured Agreed. There is a definitely a limit to how far it can goTitian
@Titian It's the bigger of two evils, exactly for the specified reason. When any script on the page was written by another dev who thought it's a good idea to use common property names like decimal and precision for their own needs, the problem appears. It's strange to even consider this option in the age of modular JS. decimal could be a helper function and imported where it's needed, and this approach would be correct and receive no downvotes. The solution itself looks pretty solid, besides the fact that it's floating and not fixed point precision and not tested on bigger numbers.Inset
Agreed, there's no good reason to modify native objects' shapes. Just create your own helper function. ponyfill instead of polyfill so you don't affect the environment. Let's be safe programmers.Estriol
F
5

You can't represent most decimal fractions exactly with binary floating point types (which is what ECMAScript uses to represent floating point values). So there isn't an elegant solution unless you use arbitrary precision arithmetic types or a decimal based floating point type. For example, the Calculator app that ships with Windows now uses arbitrary precision arithmetic to solve this problem.

Forzando answered 5/8, 2010 at 17:44 Comment(0)
A
4

Have a look at Fixed-point arithmetic. It will probably solve your problem, if the range of numbers you want to operate on is small (eg, currency). I would round it off to a few decimal values, which is the simplest solution.

Anachronous answered 22/9, 2009 at 7:39 Comment(1)
The problem is not floating point vs. fixed point, the problem is binary vs. decimal.Dispeople
S
4

From my point of view, the idea here is to round the floating point number in order to have a nice/short default string representation.

The 53-bit significand precision gives from 15 to 17 significant decimal digits precision (2−53 ≈ 1.11 × 10−16). If a decimal string with at most 15 significant digits is converted to IEEE 754 double-precision representation, and then converted back to a decimal string with the same number of digits, the final result should match the original string. If an IEEE 754 double-precision number is converted to a decimal string with at least 17 significant digits, and then converted back to double-precision representation, the final result must match the original number. ... With the 52 bits of the fraction (F) significand appearing in the memory format, the total precision is therefore 53 bits (approximately 16 decimal digits, 53 log10(2) ≈ 15.955). The bits are laid out as follows ... Wikipedia

(0.1).toPrecision(100) ->
0.1000000000000000055511151231257827021181583404541015625000000000000000000000000000000000000000000000

(0.1+0.2).toPrecision(100) ->
0.3000000000000000444089209850062616169452667236328125000000000000000000000000000000000000000000000000

Then, as far as I understand, we can round the value up to 15 digits to keep a nice string representation.

10**Math.floor(53 * Math.log10(2)) // 1e15

eg.

Math.round((0.2+0.1) * 1e15 ) / 1e15
0.3
(Math.round((0.2+0.1) * 1e15 ) / 1e15).toPrecision(100)
0.2999999999999999888977697537484345957636833190917968750000000000000000000000000000000000000000000000

The function would be:

function roundNumberToHaveANiceDefaultStringRepresentation(num) {

    const integerDigits = Math.floor(Math.log10(Math.abs(num))+1);
    const mult = 10**(15-integerDigits); // also consider integer digits
    return Math.round(num * mult) / mult;
}
Strong answered 29/11, 2019 at 10:55 Comment(2)
This answer in underrated. PS: I think it's 52 * Math.log10(2) because it's a signed double? Result would still be 1e15Cavorelievo
Why not just do Math.round(num * 1e15) / 1e15 ?Riddell
A
4

I could not find a solution using the built in Number.EPSILON that's meant to help with this kind of problem, so here is my solution:

function round(value, precision) {
  const power = Math.pow(10, precision)
  return Math.round((value*power)+(Number.EPSILON*power)) / power
}

This uses the known smallest difference between 1 and the smallest floating point number greater than one to fix the EPSILON rounding error ending up just one EPSILON below the rounding up threshold.

Maximum precision is 15 for 64-bit floating point and 6 for 32-bit floating point. Your JavaScript is likely 64 bit.

Alvaalvan answered 15/6, 2021 at 13:15 Comment(1)
what an elegant solution thank you.Murmurous
B
3

You are right, the reason for that is limited precision of floating point numbers. Store your rational numbers as a division of two integer numbers and in most situations you'll be able to store numbers without any precision loss. When it comes to printing, you may want to display the result as fraction. With representation I proposed, it becomes trivial.

Of course that won't help much with irrational numbers. But you may want to optimize your computations in the way they will cause the least problem (e.g. detecting situations like sqrt(3)^2).

Backspace answered 7/8, 2010 at 11:21 Comment(1)
You are right, the reason for that is limited precision of floating point numbers<pedant> actually, the OP put it down to imprecise floating point operations, which is wrong </pedant>Jerryjerrybuild
D
3

Use Number(1.234443).toFixed(2); it will print 1.23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();
Dayflower answered 8/10, 2014 at 10:46 Comment(0)
A
3

I had a rounding error problem with mod 3. Sometimes when I should get 0 I would get .000...01. Just test for <= .01. But sometimes I would get 2.99999999999998.

BigNumbers solved the problem, but introduced another problem. When trying to load 8.5 into BigNumbers it was really 8.4999… and had more than 15 significant digits. BigNumbers could not accept it.

Solution:

x = Math.round(x*100);
// I only need 2 decimal places, if I needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);
Aperient answered 14/12, 2014 at 17:34 Comment(0)
T
3

enter image description here

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985
Tessatessellate answered 8/5, 2015 at 11:22 Comment(0)
K
3

Avoid dealing with floating points during the operation using Integers

As stated on the most voted answer until now, you can work with integers, that would mean to multiply all your factors by 10 for each decimal you are working with, and divide the result by the same number used.

For example, if you are working with 2 decimals, you multiply all your factors by 100 before doing the operation, and then divide the result by 100.

Here's an example, Result1 is the usual result, Result2 uses the solution:

var Factor1="1110.7";
var Factor2="2220.2";
var Result1=Number(Factor1)+Number(Factor2);
var Result2=((Number(Factor1)*100)+(Number(Factor2)*100))/100;
var Result3=(Number(parseFloat(Number(Factor1))+parseFloat(Number(Factor2))).toPrecision(2));
document.write("Result1: "+Result1+"<br>Result2: "+Result2+"<br>Result3: "+Result3);

The third result is to show what happens when using parseFloat instead, which created a conflict in our case.

Katinka answered 21/9, 2019 at 18:34 Comment(1)
I like this because it's simple. but, you still have to worry about any big number. "1120003000600.126" * 1 still comes out to 1120003000600.126 "11200030006000.126" * 1 still comes out to 1120003000600.127 which makes any solution painful, anything over 13 digits gets brokenGreedy
A
3

Here is my solution:

function float(equation, precision = 9) {
    return Math.floor(equation * (10 ** precision)) / (10 ** precision);
}

console.log(float(0.1 * 0.2)); // => 0.02
console.log(float(0.2 + 0.4)); // => 0.6
console.log(float(1 / 3));     // => 0.333333333
console.log(float(1 / 3, 2));  // => 0.33
Amniocentesis answered 21/1, 2022 at 12:24 Comment(3)
good answer. but isn't it opinionated to use Math.floor over Math.round here? Just wondering, or am I missing something?Whithersoever
I think Math.round is the correct answer, because float(606.67-100, 2) should be 506.67 but it is returning the wrong value 506.66. The code needs a fix.Hassanhassell
An explanation would be in order. E.g., what is the idea/gist? From the Help Center: "...always explain why the solution you're presenting is appropriate and how it works". Please respond by editing (changing) your answer, not here in comments (*** *** *** *** *** without *** *** *** *** *** "Edit:", "Update:", or similar - the answer should appear as if it was written today).Key
B
3

The lack of precision in programming for floating point values

The lack of precision in programming for floating point values is a known issue in different programming languages. JavaScript is one of those that have problems doing math with floating point values. Such an issue is due substantially to the in-memory binary representation of floating point numbers done by those programming languages.

Let's hope that programming languages and compilers and hardware developers and engineers will deal with such issues solving them once and for all at least to the 1024 floating point digit... probably this will be enough to have peace of mind for 99.999% of all the other developers for the next century of programming...

A possible solution: convert to integer, calculate, than back

A possible solution could be just to convert the floating point numbers into integers, execute the calculations and then transform the result back into a floating point number. It seems to work, most of the time. There are many ways to proceed. In principle:

function calculate()
{
  var x = 0.1 * 0.2; // Original approach
  var y = ((0.1*10) * (0.2*10))/100; // The working possible solution
  document.write(x + '<br>' + y);
}
calculate();

Of course, in case of need, the function can be adapted accordingly. For instance:

function calculate()
  {
    var x = 0.12 * 0.23; // Original approach
    var y = ((0.12*100) * (0.23*100))/10000; // The working possible solution
    document.write(x + '<br>' + y);
  }
calculate();

It can also be automated:

function toInt(v)
{
  let i = 1;
  let r;
  while(r % 1 != 0)
  {
    i = i * 10;
    r = v * i;
  }
  return [r, i];
}

function calculate()
{
  let x = 0.11111111;
  let y = 0.22222222;
  let a = x * y; // Original approach

  [x, i] = toInt(x);
  [y, j] = toInt(y);
  let d = i * j;

  // Another working possible solution
  let b = x * y / d;
  console.log(a + '\n' + b)
  document.write(a + '<br>' + b);
}
calculate();

With that in mind, we can do, for example:

function toInt(v)
{
  let i = 1;
  let r;
  while(r % 1 != 0)
  {
    i = i * 10;
    r = v * i;
  }
  return [r, i];
}

function calculate()
{
  let x = 14898
  let y = 10.8
  let z = 100
  let a = x * y / z ; // Original approach

  // Another working possible solution
  [y, i] = toInt(y);
  let b = (x * y) / (z * i);
  console.log(a + '\n' + b)
  document.write(a + '<br>' + b);
}
calculate();

Of course, with some code refactoring, we can also remove the while loop. So finally we have:

function toInt(v)
{
  let d = v.toString().split('.')[1].length;
  let p = Math.pow(10, d);
  let r = v * p;
  return [r, p];
}

function calculate()
{
  let x = 14898
  let y = 10.8
  let z = 100
  let a = x * y / z ; // Original approach

  // Another working possible solution
  [y, i] = toInt(y);
  let b = (x * y) / (z * i);
  console.log(a + '\n' + b)
  document.write(a + '<br>' + b);
}
calculate();
Babarababassu answered 30/9, 2022 at 19:27 Comment(2)
This would break on floats with more then 1 decimal like 0.11Runofthemill
@0stone0, you're right: I've just shown the principle, but I supposed there was not need to say that if needed the function could be adapted and even generalized... thanks to your suggestion I have improved the answer now. Does it brakes now on floats with more then 1 decimal like 0.11 as you rightfully observed?Babarababassu
N
1

Use

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
Negligee answered 22/9, 2009 at 8:15 Comment(2)
Hmm...but note, this always rounds to 2 decimals. That would of course be an option, but what about the calculation 0.55*0.55 (since I don't know the exact numbers in advance. That would give 0.3 instead of 0.3025. Of course I could then use Math.round(x*Math.pow(10,4))/Math.pow(10,4);. Rounding is always an option, but I just wanted to know whether there is some better solutionMcclung
10.2 always returns 10.19 jsbin.com/tozogiwide/edit?html,js,console,outputMonogenic
T
1
function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54
Teplitz answered 3/10, 2013 at 19:29 Comment(1)
unfortunately, round_up(4.15,2) => 4.16.Platinous
L
1

Output using the following function:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

Pay attention to the output toFixedCurrency(x).

Lake answered 19/6, 2014 at 23:51 Comment(0)
A
1

while adding two float value its never give the precise values so we need to fixed this to certain number that will help us to compare.

console.log((parseFloat(0.1) + parseFloat(0.2)).toFixed(1) == parseFloat(0.3).toFixed(1));
Alenealenson answered 14/7, 2016 at 7:6 Comment(0)
P
1

Without using any libraries or scripts:

var toAlgebraic = function(f1, f2) {
    let f1_base = Math.pow(10, f1.split('.')[1].length);
    let f2_base = Math.pow(10, f2.split('.')[1].length);
    f1 = parseInt(f1.replace('.', ''));
    f2 = parseInt(f2.replace('.', ''));

    let dif, base;
    if (f1_base > f2_base) {
        dif = f1_base / f2_base;
        base = f1_base;
        f2 = f2 * dif;
    } else {
        dif = f2_base / f1_base;
        base = f2_base;
        f1 = f1 * dif;
    }

    return (f1 * f2) / base;
};

console.log(0.1 * 0.2);
console.log(toAlgebraic("0.1", "0.2"));
Papyrology answered 10/7, 2017 at 11:13 Comment(0)
H
1

I usually use something like this.

function pf(n) {
    return Math.round(n * 1e15) / 1e15;
}

I make no claim that this is optimal in any way, but I like it for its simplicity. It rounds the number off to 15 decimal places or so. I have not witnessed it returning inaccurate floats, though what is odd is that it has done so when I use * 1e-15 at the end, but not with this method.

This solution may be better suited for casual use -- and not precise mathematical use -- where precision errors are messing up your code.

Hood answered 21/4, 2021 at 16:37 Comment(0)
M
0

If you don't want to think about having to call functions each time, you can create a Class that handles conversion for you.

class Decimal {
  constructor(value = 0, scale = 4) {
    this.intervalValue = value;
    this.scale = scale;
  }

  get value() {
    return this.intervalValue;
  }

  set value(value) {
    this.intervalValue = Decimal.toDecimal(value, this.scale);
  }

  static toDecimal(val, scale) {
    const factor = 10 ** scale;
    return Math.round(val * factor) / factor;
  }
}

Usage:

const d = new Decimal(0, 4);
d.value = 0.1 + 0.2;              // 0.3
d.value = 0.3 - 0.2;              // 0.1
d.value = 0.1 + 0.2 - 0.3;        // 0
d.value = 5.551115123125783e-17;  // 0
d.value = 1 / 9;                  // 0.1111

Of course, when dealing with Decimal there are caveats:

d.value = 1/3 + 1/3 + 1/3;   // 1
d.value -= 1/3;              // 0.6667
d.value -= 1/3;              // 0.3334
d.value -= 1/3;              // 0.0001

You'd ideally want to use a high scale (like 12), and then convert it down when you need to present it or store it somewhere. Personally, I did experiment with creating a UInt8Array and trying to create a precision value (much like the SQL Decimal type), but since Javascript doesn't let you overload operators, it just gets a bit tedious not being able to use basic math operators (+, -, /, *) and using functions instead like add(), substract(), mult(). For my needs, it's not worth it.

But if you do need that level of precision and are willing to endure the use of functions for math, then I recommend the decimal.js library.

Maxinemaxiskirt answered 6/2, 2019 at 16:24 Comment(0)
S
0

I was looking the same fix and I worked out that if you add a whole number in like 1 and evaluate that console.log(0.1 * 0.2 + 1);. Which results in 1.02. This can be used to round the original x variable to the correct amount.

Once the length of the decimal places 2 is retrieved in your example we can then use it with the toFixed() function to round the original x variable correctly.

See inside the code as to what this function does in the commented sections.

var myX= 0.2 * 0.1;
var myX= 42.5-42.65;
var myX= 123+333+3.33+33333.3+333+333;

console.log(myX);
// Outputs (example 1): 0.020000000000000004
// Outputs (example 2): -0.14999999999999858
// Outputs (example 3): 34458.630000000005
// Wrong

function fixRoundingError(x) {
// 1. Rounds to the nearest 10
//    Also adds 1 to round of the value in some other cases, original x variable will be used later on to get the corrected result.
var xRound = eval(x.toFixed(10)) + 1;
// 2. Using regular expression, remove all digits up until the decimal place of the corrected equation is evaluated..
var xDec = xRound.toString().replace(/\d+\.+/gm,'');
// 3. Gets the length of the decimal places.
var xDecLen = xDec.length;
// 4. Uses the original x variable along with the decimal length to fix the rounding issue.
var x = eval(x).toFixed(xDecLen);
// 5. Evaluate the new x variable to remove any unwanted trailing 0's should there be any.
return eval(x);
}

console.log(fixRoundingError(myX));
// Outputs (example 1): 0.02
// Outputs (example 2): -0.15
// Outputs (example 3): 34458.63
// Correct

It returns the same value as the calculator in windows in every case I've tried and also rounds of the result should there be any trailing 0's automatically.

Showboat answered 5/11, 2019 at 17:22 Comment(1)
Do not use evals.Custommade
T
0

I like the approach with the correction factor, and here it is my shortened decisions for both the ES6 and ES5 standards. Its advantage compared to the toFixed method is that it does not leave unnecessary zeros in the end of the number, if we want to round to hundreds, but the result number is some tenth number:

ES6 variant:

// .1 + .2
((a, b, crr) => (a*crr + b*crr)/crr)(.1, .2, 100 /* Correction factor */ ); // 0.3
// .1 * .2
((a, b, crr) => a*crr*b/crr)(.1, .2, 100); // 0.02

ES5 variant:

// .1 + .2
(function(a, b, crr){ return (a*crr + b*crr)/crr; })(.1, .2, 100 /* Correction factor */ ); // 0.3
// .1 * .2
(function(a, b, crr){ return a*crr*b/crr; })(.1, .2, 100); // 0.02
Torrez answered 8/4, 2021 at 7:4 Comment(0)
L
0

If you need to make arbitrary-precision floating-point computations, you can use my NPM library called gmp-wasm, which is based on GMP + MPFR libraries. You can easily set any precision you want, and return the result with fixed precision.

<script src="https://cdn.jsdelivr.net/npm/gmp-wasm"></script>
<script>
  gmp.init().then(({ getContext }) => {
    const ctx = getContext({ precisionBits: 100 });
    const result = ctx.Float('0.1').mul(ctx.Float('0.2'));
    document.write(`0.1 * 0.2 = ` + result.toFixed(2));
    ctx.destroy();
  });
</script>
Liebknecht answered 5/12, 2021 at 11:52 Comment(0)
D
0

This npm library is built to resolve this, from my own use case, and has been deployed in large scale production.

npm i jsbi-calculator

It is based on the GoogleChromeLabs/jsbi project and uses stringified expressions to perform arbitrary rational computation, Internet Explorer 11-compatible.

Usage for a browser:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Jsbi-calculator Test</title>
    <script src="https://cdn.jsdelivr.net/npm/jsbi-calculator/dist/jsbi-calculator-umd.js"></script>
  </head>
  <body></body>
  <script type="text/javascript">
    const expressionOne = "((10 * (24 / ((9 + 3) * (-2)))) + 17) + 5.2";
    const resultOne = JBC.calculator(expressionOne);
    console.log(resultOne);
    // -> '12.2'

    const userAgent = navigator.userAgent;
    const isIE11 =
      userAgent.indexOf("Trident") > -1 && userAgent.indexOf("rv:11.0") > -1;
    let max;
    // MAX_SAFE_INTEGER not available in IE11
    max = isIE11 ? "9007199254740991" : String(Number.MAX_SAFE_INTEGER);

    console.log(max);
    // -> '9007199254740991'
    const expressionTwo = max + " + 2.2";
    const resultTwo = JBC.calculator(expressionTwo);
    console.log(resultTwo);
    // -> '9007199254740993.2'
  </script>
</html>
Douglass answered 21/6, 2022 at 2:26 Comment(0)
G
-1

A workaround: Just multiplying with 10E^x doesn't work with 1.1.

function sum(a,b){
    var tabA = (a + "").split(".");
    var tabB = (b + "").split(".");
    decA = tabA.length>1?tabA[1].length:0;
    decB = tabB.length>1?tabB[1].length:0;
    a = (tabA[0]+tabA[1])*1.0;
    b = (tabB[0]+tabB[1])*1.0;
    var diff = decA-decB;
    if(diff >0){
        //a has more decimals than b
        b=b*Math.pow(10,diff);
        return (a+b)/Math.pow(10,decA);
    }else if (diff<0){
        //a has more decimals than b
        a=a*Math.pow(10,-diff);
                return (a+b)/Math.pow(10,decB);
    }else{
        return (a+b)/Math.pow(10,decA);
    }       
}
Giraudoux answered 14/9, 2012 at 16:54 Comment(0)
K
-1

I threw this together based on SheetJS's answer, which I liked:

  getCorrectionFactor(numberToCheck: number): number {
    var correctionFactor: number = 1;

    if (!Number.isInteger(numberToCheck)) {
      while (!Number.isInteger(numberToCheck)) {
        correctionFactor *= 10;
        numberToCheck *= correctionFactor;
      }
    }

    return correctionFactor;
  }
Kapor answered 27/2, 2020 at 5:40 Comment(0)
M
-4

To handle arbitrary floating number:

function shorten(num) {
    num += 0.000000001;// to deal with "12.03999999997" form
    num += '';
    return num.replace(/(\.\d*?)0{5,}\d+$/, '$1') * 1;
}

console.log(1.2+1.9===1.3+1.8);// false
console.log(shorten(1.2+1.9)===shorten(1.3+1.8));// true
Multifoil answered 11/12, 2017 at 16:16 Comment(0)
A
-9

You could use a regex to check if the number ends with a long string of 0s followed by a small remainder:

// using max number of 0s = 8, maximum remainder = 4 digits
x = 0.1048000000000051
parseFloat(x.toString().replace(/(\.[\d]+[1-9])0{8,}[1-9]{0,4}/, '$1'), 10)
// = 0.1048
Augment answered 27/6, 2013 at 15:23 Comment(4)
Oh god please no. The set of decimal representations of numbers arbitrarily close to some threshold is not a regular language.Coffey
Sorry, but that's just plain ridiculous. At least try to understand why you're getting the error and address it properly. My first downvote... 4 years in, I guess it had to happen at some point.Accredit
You have a problem. You decide to fix it using Regular Expressions. Now you have two problems.Immedicable
HahahahahahahahaTantrum

© 2022 - 2024 — McMap. All rights reserved.