Truncate (not round off) decimal numbers in javascript

175,009

Solution 1

upd:

So, after all it turned out, rounding bugs will always haunt you, no matter how hard you try to compensate them. Hence the problem should be attacked by representing numbers exactly in decimal notation.

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

[   5.467.toFixedDown(2),
    985.943.toFixedDown(2),
    17.56.toFixedDown(2),
    (0).toFixedDown(1),
    1.11.toFixedDown(1) + 22];

// [5.46, 985.94, 17.56, 0, 23.1]

Old error-prone solution based on compilation of others':

Number.prototype.toFixedDown = function(digits) {
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

Solution 2

Dogbert's answer is good, but if your code might have to deal with negative numbers, Math.floor by itself may give unexpected results.

E.g. Math.floor(4.3) = 4, but Math.floor(-4.3) = -5

Use a helper function like this one instead to get consistent results:

truncateDecimals = function (number) {
    return Math[number < 0 ? 'ceil' : 'floor'](number);
};

// Applied to Dogbert's answer:
var a = 5.467;
var truncated = truncateDecimals(a * 100) / 100; // = 5.46

Here's a more convenient version of this function:

truncateDecimals = function (number, digits) {
    var multiplier = Math.pow(10, digits),
        adjustedNum = number * multiplier,
        truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);

    return truncatedNum / multiplier;
};

// Usage:
var a = 5.467;
var truncated = truncateDecimals(a, 2); // = 5.46

// Negative digits:
var b = 4235.24;
var truncated = truncateDecimals(b, -2); // = 4200

If that isn't desired behaviour, insert a call to Math.abs on the first line:

var multiplier = Math.pow(10, Math.abs(digits)),

EDIT: shendz correctly points out that using this solution with a = 17.56 will incorrectly produce 17.55. For more about why this happens, read What Every Computer Scientist Should Know About Floating-Point Arithmetic. Unfortunately, writing a solution that eliminates all sources of floating-point error is pretty tricky with javascript. In another language you'd use integers or maybe a Decimal type, but with javascript...

This solution should be 100% accurate, but it will also be slower:

function truncateDecimals (num, digits) {
    var numS = num.toString(),
        decPos = numS.indexOf('.'),
        substrLength = decPos == -1 ? numS.length : 1 + decPos + digits,
        trimmedResult = numS.substr(0, substrLength),
        finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    return parseFloat(finalResult);
}

For those who need speed but also want to avoid floating-point errors, try something like BigDecimal.js. You can find other javascript BigDecimal libraries in this SO question: "Is there a good Javascript BigDecimal library?" and here's a good blog post about math libraries for Javascript

Solution 3

var a = 5.467;
var truncated = Math.floor(a * 100) / 100; // = 5.46

Solution 4

You can fix the rounding by subtracting 0.5 for toFixed, e.g.

(f - 0.005).toFixed(2)

Solution 5

Consider taking advantage of the double tilde: ~~.

Take in the number. Multiply by significant digits after the decimal so that you can truncate to zero places with ~~. Divide that multiplier back out. Profit.

function truncator(numToTruncate, intDecimalPlaces) {    
    var numPower = Math.pow(10, intDecimalPlaces); // "numPowerConverter" might be better
    return ~~(numToTruncate * numPower)/numPower;
}

I'm trying to resist wrapping the ~~ call in parens; order of operations should make that work correctly, I believe.

alert(truncator(5.1231231, 1)); // is 5.1

alert(truncator(-5.73, 1)); // is -5.7

alert(truncator(-5.73, 0)); // is -5

JSFiddle link.

EDIT: Looking back over, I've unintentionally also handled cases to round off left of the decimal as well.

alert(truncator(4343.123, -2)); // gives 4300.

The logic's a little wacky looking for that usage, and may benefit from a quick refactor. But it still works. Better lucky than good.

Share:
175,009

Related videos on Youtube

kcssm
Author by

kcssm

wordpresser :)

Updated on July 08, 2022

Comments

  • kcssm
    kcssm almost 2 years

    I am trying to truncate decimal numbers to decimal places. Something like this:

    5.467   -> 5.46  
    985.943 -> 985.94
    

    toFixed(2) does just about the right thing but it rounds off the value. I don't need the value rounded off. Hope this is possible in javascript.

    • Felix Kling
      Felix Kling over 13 years
      jQuery is just a framework and your problem is not jQuery related. It is more about doing some basic computation in JavaScript. I hope you are also satisfied with a non-jQuery solution.
    • MsTapp
      MsTapp over 7 years
      I found it to be too much work to get my calculations to return just 2 decimals using Javascript. I was able to do it easily in my database view instead. I realize that this method won't fit every situation, but I want to put it out here because it might save somebody a lot of time.
  • Nick Knowlson
    Nick Knowlson over 12 years
    This works well but will give results that are probably undesirable if he (or someone else looking at this answer later) has to deal with negative numbers. See stackoverflow.com/a/9232092/224354
  • Thomas W
    Thomas W almost 12 years
    Yeah, Prototypes don't work reliably cross-browser. Instead of defining this (limited purpose) function through the type system, in a way that doesn't work reliably, why not just put it in a library.
  • Thomas W
    Thomas W almost 12 years
    Why undesirable? Changing the direction of rounding when you go below 0, causes all sorts of arithmetical artefacts.
  • Thomas W
    Thomas W almost 12 years
    Why unexpected? Changing the direction of rounding when you go below 0, causes all sorts of arithmetical artefacts & crappy math. For example, twice as many numbers will round to 0, as any other integer. For graphics, accounting & many other uses, you'll get crappy results. To tell you the truth, it'd be harder to say what your suggestion is good for as to say what it's not.
  • Nick Knowlson
    Nick Knowlson almost 12 years
    It's good for exactly what it says - when you want to truncate decimals rather than rounding.
  • shendz
    shendz over 11 years
    This does not work as excepted. Try the number 17.56 and digits = 2. It should be 17.56, but this function returns 17.55.
  • shendz
    shendz over 11 years
    Not going to work with 17.56 because browser gives 17.56 * 100 = 1755.9999999999998 not 1756
  • kirilloid
    kirilloid over 11 years
    Thanks for report, this is an important notice. Unfortunately 17.56 != 17.55 - 0.01. I updated the code.
  • Nick Knowlson
    Nick Knowlson about 11 years
    Good point shendz. I've updated my answer with a solution that eliminates all floating-point error for those who need that.
  • Jeremy Witmer
    Jeremy Witmer about 11 years
    This won't work for numbers that are less than 1 if you want no decimals - truncateDecimals(.12345, 0) results in NaN unless you add a check: if(isNAN(result) result = 0; Depends on the behavior you want.
  • Nick Knowlson
    Nick Knowlson about 11 years
    I didn't know what you were talking about at first, until I realized that ".1".toString doesn't always return "0.1", maybe only in Firefox's developer tools. I'll fix it, thanks.
  • Nick Knowlson
    Nick Knowlson almost 11 years
    There is a difference between rounding and truncating. Truncating is clearly the behaviour this question is seeking. If I call truncate(-3.14) and receive -4 back, I would definitely call that undesirable.
  • lukas.pukenis
    lukas.pukenis over 10 years
    if(isNan( is missing a closing bracket
  • Nick Knowlson
    Nick Knowlson over 10 years
    Heads up: as it is this doesn't work for very small numbers, numbers with more than 3 decimal places, or negative numbers. Try .0045, 5.4678 and -5.467
  • Hari Pachuveetil
    Hari Pachuveetil over 10 years
    +1. This does not handle integers (with no decimal point in them). Checking for decPos to be -1 will fix it. Like this: result = decPos === -1 ? num : numS.substr(0, 1 + decPos + digits);
  • Nick Knowlson
    Nick Knowlson about 10 years
    Aha, so it doesn't. Small integers end up working fine, that's probably why I didn't notice back when I first wrote it. Fixed now, thanks!
  • Nick Knowlson
    Nick Knowlson about 10 years
    Two inconsistencies with this function: This function returns a string so 1.11.toFixedDown(1) + 22 ends up as 1.122 instead of 23.1. Also 0.toFixedDown(1) should produce 0 but instead it produces -0.1.
  • rgajrawala
    rgajrawala almost 10 years
    Note that this function removes the negative sign. Ex: (-10.2131).toFixedDown(2) // ==> 10.21.
  • rgajrawala
    rgajrawala almost 10 years
    Also, (1e-7).toFixedDown(0) // ==> 1e-7. Does that for 1e-(>=7) (ex: 1e-8, 1e-9, ...).
  • Liglo App
    Liglo App over 9 years
    This is the best answer. If you extend the Math prototype with this and check for NaN-s before executing, it would be just perfect.
  • Alex Pi
    Alex Pi over 9 years
    This is solution is good enough in my case, I only changed the regExp a little: "(-*\\d+\\.\\d{" + digits + "})(\\d)"
  • kirilloid
    kirilloid over 9 years
    I'd write "([-+]?\\d+\\.\\d{" + digits + "})(\\d)" then.
  • Admin
    Admin almost 9 years
    I agree with Thomas. The difference in perspective may come with whether you are usually truncating for display, or for computation. From a computational perspective, this avoids "arithmetical artifacts"
  • ruffin
    ruffin almost 9 years
    Good call. Using a bitwise operator coerces the value into an int, and oring with 0 means "just keep what I've already got". Does what my ~~ answer does, but with a single bitwise operation. Though it has the same limitation as written too: We can't go over 2^31.
  • Gene Parcellano
    Gene Parcellano over 7 years
    I like this solution, but just keep in mind that it's not fully supported by all browsers. (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…‌​)
  • jperelli
    jperelli about 7 years
    How does this work reference: stackoverflow.com/questions/7487977/…
  • Sanyam Jain
    Sanyam Jain over 6 years
    var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 Hence this is not a correct solution
  • Salman A
    Salman A over 6 years
    How does it truncate something to, say, 2 decimal places?
  • dmarra
    dmarra about 6 years
    This will work so long as you match the value you are subtracting with the length you wish to have. whatever you pass to toFixed() needs to be the number of 0's after the decimal.
  • Mike Makuch
    Mike Makuch about 6 years
    That doesn't work in all cases i.e. console.log(Math.trunc(9.28 * 100) / 100); // 9.27
  • zurfyx
    zurfyx about 6 years
    @MikeMakuch that's not a problem with Math.trunc, but rather than 9.28 * 100 is 927.9999 rather than 928. You might want to read over The Perils of Floating Point
  • giles123
    giles123 almost 6 years
    Two years after this was posted but stumbled across this when I was trying to work out the best way using Math.trunc, regex, etc. I really like this solution. Dead simple but works perfectly (for my use case anyway).
  • giles123
    giles123 almost 6 years
    Don't forget to account for n=0 though.
  • jStaff
    jStaff over 5 years
    This answer is obsurd. You shouldn't be editting the javascript Number class to remove decimal points. It complicates the issue a lot more than it has to be.
  • Ling Loeng
    Ling Loeng over 5 years
    x = 0.0000 test truncateDecimals (x, 2) fails. returns 0. not as expected 0.00
  • Konstantin Smolyanin
    Konstantin Smolyanin about 5 years
    It seems that function works correctly but author's statement: "rounding bugs will always haunt you" is incorrect. There are also correct and more simple (and fast) solutions are provided in some other answers here.
  • Alex
    Alex almost 5 years
    not correct when truncate((10 * 2.9) / 100); this code return 0.28 instead of 0.29 jsfiddle.net/9pf0732d
  • Alex
    Alex almost 5 years
    truncator((10 * 2.9) / 100, 2) return 0.28 instead of 0.29 ... jsfiddle.net/25tgrzq1
  • ruffin
    ruffin almost 5 years
    @Alex As I'm guessing you realize... welcome to JavaScript!. There are fixes. Perhaps you'd like to share one? :D
  • Alex
    Alex almost 5 years
    @ruffin I know about this issue =) I thought this answer was the solution to this problem. Unfortunately, I haven’t found an exact solution yet, everywhere there is such a problem.
  • Esteban
    Esteban over 4 years
    I'd write "([-+]?\\d*\\.\\d{" + digits + "})(\\d)" => Ex: .00 , .00009, .999999
  • iCode
    iCode almost 4 years
    I like that this works with strings, thus eliminating the nuances of floating point numbers. Thanks!
  • taxilian
    taxilian over 3 years
    this is the method that I would have expected to use
  • HelpfulPanda
    HelpfulPanda over 3 years
    Not working for me, truncator(1000.12345678, 7) returns 141.1299975
  • HelpfulPanda
    HelpfulPanda over 3 years
    Would love if that worked 100%, but Math.trunc(0.29 * Math.pow(10, 2)) / Math.pow(10, 2) gives you 0.28. To be honest the only reliable way I've seen so far is by splitting/cutting strings.
  • HelpfulPanda
    HelpfulPanda over 3 years
    Same issue as others pointed out, truncate(0.29, 2) gives you 0.28.
  • HelpfulPanda
    HelpfulPanda over 3 years
    console.log((-5).trim(2)); throws Uncaught TypeError: d[1] is undefined
  • ruffin
    ruffin over 3 years
    @Alex If you want to avoid floating point errors, use a decimal type. Or I guess we could rewrite to use string manipulation, but that seems insane. You'll notice similar answers have the same issue.
  • ruffin
    ruffin over 3 years
    @HelpfulPanda That's because JavaScript uses 32-bit ints for bitwise operators. The max 32-bit int is 2,147,483,647, and 100012345678 is significantly bigger than 2147483647. If you really have numbers larger than 32-bit (rather, if you need that many significant digits), this isn't the droid you're looking for. This is a quick and dirty (and fast) answer for, say, 4-5 sig digits. For potentially overkill-ish perfection, try the accepted answer. ;^D
  • Ankur144
    Ankur144 almost 3 years
    This code will add padding "0" is places after decimal is than what is required.
  • Ankur144
    Ankur144 almost 3 years
    eg- for 3 decimal places (with padding"0") 5.3 --->5.300 0.01 ---->0.010 0.00001 --->0.000 5.32195--->5.321 -3.66696---> -3.666
  • stackoverflow
    stackoverflow about 2 years
    @HelpfulPanda pff yeah, wtf :| I'm currently having hard times solving this issue, so I think I'll fallback as you proposed to strings :)