45

What I would like to have is the almost opposite of Number.prototype.toPrecision(), meaning that when i have number, how many decimals does it have? E.g.

(12.3456).getDecimals() // 4
JussiR
  • 2,055
  • 3
  • 21
  • 23
  • 3
    `15 - Math.ceil(Math.log(x) / Math.log(10))` – Ignacio Vazquez-Abrams Mar 04 '12 at 08:12
  • possible dup of: http://stackoverflow.com/questions/1458633/elegant-workaround-for-javascript-floating-point-number-problem – Nir Alfasi Mar 04 '12 at 08:17
  • 1
    `x = 12.3456; var precision = String(x).replace('.', '').length - x.toFixed().length;` – Malitta N Mar 04 '12 at 08:20
  • @alfasin don't think so. In this case the OP doesn't want to restrict to a given precision, they just want to count the number of decimal places – JaredPar Mar 04 '12 at 08:23
  • Duplicate of [Is there a reliable way in JavaScript to obtain the number of decimal places of an arbitrary number?](https://stackoverflow.com/questions/9539513/is-there-a-reliable-way-in-javascript-to-obtain-the-number-of-decimal-places-of) – Dan Dascalescu Jun 14 '17 at 08:22
  • @blackpla9ue your solution will show wrong result, for example, for `x = 0.0000005`, because it's presented in exponential format: `5e-7`. – Boolean_Type Jun 30 '17 at 12:04

12 Answers12

61

For anyone wondering how to do this faster (without converting to string), here's a solution:

function precision(a) {
  var e = 1;
  while (Math.round(a * e) / e !== a) e *= 10;
  return Math.log(e) / Math.LN10;
}

Edit: a more complete solution with edge cases covered:

function precision(a) {
  if (!isFinite(a)) return 0;
  var e = 1, p = 0;
  while (Math.round(a * e) / e !== a) { e *= 10; p++; }
  return p;
}
Mourner
  • 3,171
  • 24
  • 21
  • 7
    If you pass NaN to this, it'll go into an infinite loop. – jarib Jul 10 '15 at 11:13
  • 1
    This is a good answer, but I recommend you check for edge cases, such as NaN or the Infinities, to avoid infinite looping. Also, it might come out better if you returned `Math.round(Math.log(e) / Math.LN10)` instead. – Justin Jul 15 '15 at 20:39
  • Typically the number of digits in a given base is `ceil(log-N(num))` – Eric Lagergren Dec 17 '15 at 06:28
  • 2
    Just for kicks, put this in a jsperf compared to the string answer above. It's about 8x faster: http://jsperf.com/getting-numerical-precision/2 – STRML Jan 21 '16 at 15:55
  • Thanks for feedback everyone — added a faster, more complete solution. – Mourner Jan 25 '16 at 12:35
  • This is a nice algorithm, but unfortunately javascript's questionable Number type is not so nice after all. If you take for example: Number.MIN_VALUE which is indeed a finite and valid number with a precision of 324 and put it into your algorithm, you can wait until next year to get a result. The string representation would be infinitely faster for high precision numbers. Unfortunately, javascript will use the scientific notation for precisions > 20. Which invalidates the string solution mentioned here. So start with this: http://stackoverflow.com/a/1685917/1173210 . Won't be fast! – Seraph Feb 08 '17 at 09:21
  • 1
    Please note that the existing version still causes an endless loop if the input is invalid (e.g. an empty string). – Christian Grün Apr 11 '17 at 09:40
  • It does, since `123.000` is `12`, and JS does not distinguish between the two. – Mourner May 09 '19 at 10:27
  • @Mourner Edited version of your solution is still not handling edge cases, causes infinite loop for null and empty string. – mtx Jun 09 '19 at 06:57
  • Still buggy, causes infinite loops... This can't be the accepted answer if not fixed... – Edouard Menayde Jul 10 '19 at 09:24
  • Maybe its better to add number check. if (typeof a !== "number" || !isFinite(a)) return 0; – özüm Nov 05 '19 at 08:25
  • 2
    Float point error kicks in for case `n = 0.44399999999999995`, `precision(n) == 18` while the face value precision should be 17. – hackape Aug 18 '20 at 06:05
  • Probably edge case: `0.1**12==28`, but `0.1**13==30`! and `0.1**14==31` – Oleg Zarevennyi Mar 15 '21 at 09:55
  • If you add `if (typeof a !== "number") a = parseFloat(a); if (isNaN(a)) return 0;` as the first two lines in the function it will prevent the function call from getting stuck in a while loop if a string is passed. – Benji Apr 21 '22 at 10:31
  • `+value.toExponential().split('e')[1]*-1 || 0` This could also be used. – Pankaj Phartiyal Oct 18 '22 at 10:17
33

One possible solution (depends on the application):

var precision = (12.3456 + "").split(".")[1].length;
Manish
  • 3,472
  • 1
  • 17
  • 16
  • 9
    `(1.0 + "").split(".")[1].length;` gives error because `split` returns one-character so we cannot access it's second (`[1]`) element. – Piotr Aleksander Chmielowski Feb 16 '17 at 13:16
  • 1
    Something like this should work: function getPrecision(number) { var n = number.toString().split("."); return n.length > 1 ? n[1].length : 0; } – Christian Grün Apr 11 '17 at 09:39
  • This "accepted" answer is incorrect for the general case, as Piotr mentioned above. Also, this question is a [duplicate](https://stackoverflow.com/questions/9539513/is-there-a-reliable-way-in-javascript-to-obtain-the-number-of-decimal-places-of). – Dan Dascalescu Jun 14 '17 at 08:22
  • 1
    `(12.3456 + '.').split('.')[1].length;` works for all cases in getting the decimal place count. – Heath Dutton Feb 01 '19 at 18:19
  • 1
    it will fail when you put number without decimals or when you put string with dot (for example `wat.wat`), improved: `function precision(num) { if (isNaN(+num)) return 0; const decimals = (num + '').split('.')[1]; if (decimals) return decimals.length; return 0; }` – mtx Jun 09 '19 at 07:10
4

If by "precision" you mean "decimal places", then that's impossible because floats are binary. They don't have decimal places, and most values that have a small number of decimal places have recurring digits in binary, and when they're translated back to decimal that doesn't necessarily yield the original decimal number.

Any code that works with the "decimal places" of a float is liable to produce unexpected results on some numbers.

Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
  • Ok, good to know. Anyway, i'm only dealing with how decimal numbers are shown, so changing them to string an splitting with '.' seems to be thee answer. – JussiR Mar 04 '12 at 11:00
4

There is no native function to determine the number of decimals. What you can do is convert the number to string and then count the offset off the decimal delimiter .:

Number.prototype.getPrecision = function() {
    var s = this + "",
        d = s.indexOf('.') + 1;

    return !d ? 0 : s.length - d;
};

(123).getPrecision() === 0;
(123.0).getPrecision() === 0;
(123.12345).getPrecision() === 5;
(1e3).getPrecision() === 0;
(1e-3).getPrecision() === 3;

But it's in the nature of floats to fool you. 1 may just as well be represented by 0.00000000989 or something. I'm not sure how well the above actually performs in real life applications.

rodneyrehm
  • 13,442
  • 1
  • 40
  • 56
2

Basing on @blackpla9ue comment and considering numbers exponential format:

function getPrecision (num) {
  var numAsStr = num.toFixed(10); //number can be presented in exponential format, avoid it
  numAsStr = numAsStr.replace(/0+$/g, '');

  var precision = String(numAsStr).replace('.', '').length - num.toFixed().length;
  return precision;  
}

getPrecision(12.3456);         //4
getPrecision(120.30003300000); //6, trailing zeros are truncated
getPrecision(15);              //0
getPrecision(120.000))         //0
getPrecision(0.0000005);       //7
getPrecision(-0.01))           //2
Boolean_Type
  • 1,146
  • 3
  • 13
  • 40
1

Based on @boolean_Type's method of handling exponents, but avoiding the regex:

function getPrecision (value) {
    if (!isFinite(value)) { return 0; }

    const [int, float = ''] = Number(value).toFixed(12).split('.');

    let precision = float.length;
    while (float[precision - 1] === '0' && precision >= 0) precision--;

    return precision;
}
Sc0ttyD
  • 1,576
  • 1
  • 14
  • 22
1

Here are a couple of examples, one that uses a library (BigNumber.js), and another that doesn't use a library. Assume you want to check that a given input number (inputNumber) has an amount of decimal places that is less than or equal to a maximum amount of decimal places (tokenDecimals).

With BigNumber.js

import BigNumber from 'bignumber.js'; // ES6
// const BigNumber = require('bignumber.js').default; // CommonJS

const tokenDecimals = 18;
const inputNumber = 0.000000000000000001;
// Convert to BigNumber
const inputNumberBn = new BigNumber(inputNumber);

// BigNumber.js API Docs: http://mikemcl.github.io/bignumber.js/#dp
console.log(`Invalid?: ${inputNumberBn.dp() > tokenDecimals}`);

Without BigNumber.js

function getPrecision(numberAsString) {
  var n = numberAsString.toString().split('.');
  return n.length > 1
    ? n[1].length
    : 0;
}

const tokenDecimals = 18;
const inputNumber = 0.000000000000000001;

// Conversion of number to string returns scientific conversion
// So obtain the decimal places from the scientific notation value
const inputNumberDecimalPlaces = inputNumber.toString().split('-')[1];

// Use `toFixed` to convert the number to a string without it being
// in scientific notation and with the correct number decimal places
const inputNumberAsString = inputNumber.toFixed(inputNumberDecimalPlaces);

// Check if inputNumber is invalid due to having more decimal places
// than the permitted decimal places of the token
console.log(`Invalid?: ${getPrecision(inputNumberAsString) > tokenDecimals}`);
Luke Schoen
  • 4,129
  • 2
  • 27
  • 25
1

Assuming number is valid.

let number = 0.999; 
let noOfPlaces = number.includes(".") //includes or contains
                        ? number.toString().split(".").pop().length
                        : 0;  
Umar Farooq Awan
  • 161
  • 1
  • 3
  • 6
1

5.3M ops/s (81.82 % slower):

function precision (n) {
  return (n.toString().split('.')[1] || '').length;
}
precision(1.0123456789)

29M ops/s (fastest):

function precision (n) {
  let e = 1;
  let p = 0;
  while (Math.round(n * e) / e !== n) {
    e *= 10;
    p++;
  }
  return p;
}
precision(1.0123456789);
Ilya Ordin
  • 57
  • 5
  • correctness should be valued over speed, fastest version gives precision(4.3573) == 16 – Lauri Jul 23 '23 at 11:38
  • @Lauri Thank you for spotting a mistake! I totally agree with you and have removed the last 2 implementations. – Ilya Ordin Jul 24 '23 at 16:09
1

Try the following

function countDecimalPlaces(number) { 
  var str = "" + number;
  var index = str.indexOf('.');
  if (index >= 0) {
    return str.length - index - 1;
  } else {
    return 0;
  }
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
0

Here is a simple solution

First of all, if you pass a simple float value as 12.1234 then most of the below/above logics may work but if you pass a value as 12.12340, then it may exclude a count of 0. For e.g, if the value is 12.12340 then it may give you a result of 4 instead of 5. As per your problem statement, if you ask javascript to split and count your float value into 2 integers then it won't include trailing 0s of it.

Let's satisfy our requirement here with a trick ;)

In the below function you need to pass a value in string format and it will do your work

function getPrecision(value){
a = value.toString()
console.log('a ->',a)
b = a.split('.')
console.log('b->',b)
return b[1].length

getPrecision('12.12340') // Call a function

For an example, run the below logic

value = '12.12340'
a = value.toString()
b = a.split('.')
console.log('count of trailing decimals->',b[1].length)

That's it! It will give you the exact count for normal float values as well as the float values with trailing 0s!

Thank you!

Omkar Hande
  • 71
  • 1
  • 4
0

This answer adds to Mourner's accepted solution by making the function more robust. As noted by many, floating point precision makes such a function unreliable. For example, precision(0.1+0.2) yields 17 rather than 1 (this might be computer specific, but for this example see https://jsfiddle.net/s0v17jby/5/).

IMHO, there are two ways around this: 1. either properly define a decimal type, using e.g. https://github.com/MikeMcl/decimal.js/, or 2. define an acceptable precision level which is both OK for your use case and not a problem for the js Number representation (8 bytes can safely represent a total of 16 digits AFAICT). For the latter workaround, one can write a more robust variant of the proposed function:

const MAX_DECIMAL_PRECISION = 9; /* must be <= 15 */
const maxDecimalPrecisionFloat = 10**MAX_DECIMAL_PRECISION;

function precisionRobust(a) {
  if (!isFinite(a)) return 0;
  var e = 1, p = 0;
  while ( ++p<=MAX_DECIMAL_PRECISION && Math.round( ( Math.round(a * e) / e - a ) * maxDecimalPrecisionFloat ) !== 0) e *= 10;
  return p-1;
}

In the above example, the maximum precision of 9 means this accepts up to 6 digits before the decimal point and 9 after (so this would work for numbers less than one million and with a maximum of 9 decimal points). If your use-case numbers are smaller then you can choose to make this precision even greater (but with a maximum of 15). It turns out that, for calculating precision, this function seems to do OK on larger numbers as well (though that would no longer be the case if we were, say, adding two rounded numbers within the precisionRobust function).

Finally, since we now know the maximum useable precision, we can further avoid infinite loops (which I have not been able to replicate but which still seem to cause problems for some).

sg1234
  • 600
  • 4
  • 19