Sunday, September 26, 2021

Rounding in JavaScript

Floating point rounding is a task which all developers have to perform at some point. There are actually several types of rounding algorithms, including:

  • round-up
  • round-down
  • round-toward-nearest
  • arithmetic rounding
  • round-half-up
  • round-half-down
  • round-half-even
  • round-half-odd
  • round-toward-zero
  • round-away-from-zero
  • round-ceiling
  • round-floor
  • truncation (chopping)
  • round-alternate
  • round-random (stochastic rounding)

to name but a few. Of these, you are probably most familiar with round-half-up, which is described by the following mnemonic phrase:

5 or more? Raise the Score. 4 or less? Let it Rest.

All of the popular programming languages, JavaScript included, provide a built-in rounding function. Strangely, JavaScript’s round() function is very no-frills, rounding to the nearest integer only. That doesn’t mean that you can’t round to the nearest decimal; just that you’ll have to put in some work to do so. This article will describe a few different approaches to carry out round-half-up rounding to a given number of decimal places.

The Math Object

Math is a built-in object that includes static properties and methods for mathematical constants and functions. Constants include PI (3.14159), LN2 (Natural logarithm of 2), and SQRT1_2 (Square root of ½), while functions include abs() (absolute value), cos() (cosine), and, of course, round(). Since all of the Math’s member properties and methods are static, we don’t need to create an instance of a Math object in order to use them. Instead, we just need to prefix the property or method with the Math object, i.e. Math.round().

In addition to Math.round(), there are a few other functions that can help with rounding operations. These include:

  1. Math.floor(): rounds down to the nearest integer
  2. Math.ceil(): rounds up to the nearest integer
  3. Math.trunc(): returns the integer portion of a number, removing any fractional digits.

You’ll notice that all of these functions have one thing in common: they all round to the nearest integer. Read on to learn how to round to a given decimal place.

Expanding On the Math.round() Function

Looking to display 33.76538 in the standard currency format? How about PI to ten decimal places? Formatting numbers to a specific number of decimal places can still be done using the Math.round() function, but will require a little multiplication and division. Here are a few examples to demonstrate:

var original = 33.76538;

// 1) round to one decimal
var result = Math.round(original * 10) / 10; //returns 33.8

// 2) round to two decimals
result = Math.round(original * 100) / 100;   //returns 33.77
	
// 3) round to three decimals
result = Math.round(original * 1000) / 1000; //returns 33.765

As seen above, the formula to round any number to x decimal points is:

  1. Multiply the original number by 10^x (10 to the power of x).
  2. Apply Math.round() to the result.
  3. Divide result by the same 10^x number that you multiplied by originally.

Floating Point Numbers and Rounding Errors

While the above formula works the vast majority of the time, there are some numbers that will result in downwards rounding, rather than up.

Math.round(0.145 * 100)/100; //returns 0.14!?!

This result is wrong, as it should have rounded up to 0.15. The problem is that JavaScript has limited precision when representing floating point numbers. Some numbers require many decimals of precision:

0.145 * 100 = 14.499999999999998

While others do not:

33.76538 * 100 = 3376.538

To be fair, this is not a JavaScript problem, but one that affects all programming languages. Due to floating-point numbers having a limited number of digits, they are not able to represent all real numbers accurately. Thus, when there are more digits than the format allows, the leftover ones are omitted, and the number is rounded down.

Exponential Notation

Many solutions have been proposed over the years, ranging from simple to highly complex. The simplest I know of is to represent numbers using exponential notation:

Number(Math.round(0.145+'e2')+'e-2') = 0.15 // Yay!

This solution has been incorporated into a function that you can use in your own applications:

function roundHalfUp(value, decimals) {
  return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}

roundHalfUp(1.005, 2); // 1.01

Number.EPSILON

Another solution is to perform round-to-nearest (using “round half away from zero”), in which epsilon correction is applied before calling the rounding function. This works by adding the smallest possible float value to the product before rounding. After adding Number.EPSILON, the value of 0.145 * 100, which is 14.499999999999998, will be corrected to 14.500000000000002. This amount will be rounded up to 15, with the final result being 0.15:

function roundToNearest(num, decimals) {
  decimals = decimals || 0;
  var p = Math.pow(10, decimals);
  var n = (num * p) * (1 + Number.EPSILON);
  return Math.round(n) / p;
}

roundToNearest(1.005, 2); // 1.01

Conclusion

Rounding floating point numbers is not without challenges, whether in JavaScript or any other language. But, as the saying goes, “where there’s a will, there’s a way!” The Math.round() function will get you at least halfway. Once there, you might as well continue on the finish line. That’s what rounding is all about afterall!

Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Popular Articles

Featured