rounding to an arbitrary number of significant digits in javascript is not working

You can try javascript inbuilt method-

Number( my_number.toPrecision(3) )

For Your case try

Number( 24730790.0.toPrecision(5) )

For your refrence and working example you can see link


First of all thanks to everybody, it would be a hard task without these snippets shared.

My value added, is the following snippet (see below for complete implementation)

parseFloat(number.toPrecision(precision))

Please note that if number is, for instance, 10000 and precision is 2, then number.toPrecision(precision) will be '1.0e+4' but parseFloat understands exponential notation.

It is also worth to say that, believe it or not, the algorithm using Math.pow and logarithms posted above, when run on test case formatNumber(5, 123456789) was giving a success on Mac (node v12) but rising and error on Windows (node v10). It was weird so we arrived at the solution above.

At the end I found this as the definitive implementation, taking advantage of all feedbacks provided in this post. Assuming we have a formatNumber.js file with the following content

/**
 * Format number to significant digits.
 *
 * @param {Number} precision
 * @param {Number} number
 *
 * @return {String} formattedValue
 */

export default function formatNumber (precision, number) {
  if (typeof number === 'undefined' || number === null) return ''

  if (number === 0) return '0'

  const roundedValue = round(precision, number)
  const floorValue = Math.floor(roundedValue)

  const isInteger = Math.abs(floorValue - roundedValue) < Number.EPSILON

  const numberOfFloorDigits = String(floorValue).length
  const numberOfDigits = String(roundedValue).length

  if (numberOfFloorDigits > precision) {
    return String(floorValue)
  } else {
    const padding = isInteger ? precision - numberOfFloorDigits : precision - numberOfDigits + 1

    if (padding > 0) {
      if (isInteger) {
        return `${String(floorValue)}.${'0'.repeat(padding)}`
      } else {
        return `${String(roundedValue)}${'0'.repeat(padding)}`
      }
    } else {
      return String(roundedValue)
    }
  }
}

function round (precision, number) {
  return parseFloat(number.toPrecision(precision))
}

If you use tape for tests, here there are some basic tests

import test from 'tape'

import formatNumber from '..path/to/formatNumber.js'

test('formatNumber', (t) => {
  t.equal(formatNumber(4, undefined), '', 'undefined number returns an empty string')
  t.equal(formatNumber(4, null), '', 'null number return an empty string')

  t.equal(formatNumber(4, 0), '0')
  t.equal(formatNumber(4, 1.23456789), '1.235')
  t.equal(formatNumber(4, 1.23), '1.230')
  t.equal(formatNumber(4, 123456789), '123500000')
  t.equal(formatNumber(4, 1234567.890123), '1235000')
  t.equal(formatNumber(4, 123.4567890123), '123.5')
  t.equal(formatNumber(4, 12), '12.00')
  t.equal(formatNumber(4, 1.2), '1.200')
  t.equal(formatNumber(4, 1.234567890123), '1.235')
  t.equal(formatNumber(4, 0.001234567890), '0.001235')

  t.equal(formatNumber(5, 123456789), '123460000')

  t.end()
})

Unfortunately the inbuilt method will give you silly results when the number is > 10, like exponent notation etc.

I made a function, which should solve the issue (maybe not the most elegant way of writing it but here it goes):

function(value, precision) {
  if (value < 10) {
    value = parseFloat(value).toPrecision(precision)
  } else {
    value = parseInt(value)
    let significantValue = value
    for (let i = value.toString().length; i > precision; i--) {
      significantValue = Math.round(significantValue / 10)
    }
    for (let i = 0; significantValue.toString().length < value.toString().length; i++ ) {
      significantValue = significantValue * 10
    }
    value = significantValue
  }
  return value
}

If you prefer having exponent notation for the higher numbers, feel free to use toPrecision() method.