48

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 
6

15 Answers 15

66

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; } 
Sign up to request clarification or add additional context in comments.

15 Comments

If you pass NaN to this, it'll go into an infinite loop.
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.
Just for kicks, put this in a jsperf compared to the string answer above. It's about 8x faster: jsperf.com/getting-numerical-precision/2
Please note that the existing version still causes an endless loop if the input is invalid (e.g. an empty string).
Float point error kicks in for case n = 0.44399999999999995, precision(n) == 18 while the face value precision should be 17.
|
34

One possible solution (depends on the application):

var precision = (12.3456 + "").split(".")[1].length; 

5 Comments

(1.0 + "").split(".")[1].length; gives error because split returns one-character so we cannot access it's second ([1]) element.
Something like this should work: function getPrecision(number) { var n = number.toString().split("."); return n.length > 1 ? n[1].length : 0; }
This "accepted" answer is incorrect for the general case, as Piotr mentioned above. Also, this question is a duplicate.
(12.3456 + '.').split('.')[1].length; works for all cases in getting the decimal place count.
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; }
5

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.

1 Comment

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.
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.

Comments

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 

Comments

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; } } 

Comments

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; } 

Comments

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}`); 

Comments

1

Assuming number is valid.

let number = 0.999; let noOfPlaces = number.includes(".") //includes or contains ? number.toString().split(".").pop().length : 0; 

Comments

1

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!

Comments

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); 

2 Comments

correctness should be valued over speed, fastest version gives precision(4.3573) == 16
@Lauri Thank you for spotting a mistake! I totally agree with you and have removed the last 2 implementations.
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).

Comments

0

decimal.js-light package provides .dp() ⇒ number method, it will return the number of decimal places.

x = new Decimal(1.234) x.decimalPlaces() // '3' y = new Decimal(987.654321) y.dp() // '6' 

runkit playground

Comments

0

I'm trying to fix an issue with @Mourner's solution, which is by far the best one I've seen. However, some numbers like 0.44399999999999995 give incorrect results.

Additionally, Number.MIN_VALUE results in an "infinite" loop (may happen with some other inputs).

To address these issues, I added a string processing method inside the function to handle these edge cases.

Of course, both of our solutions are still subject to rounding "errors", but that's inherent to how numbers are represented in floating-point arithmetic.

/** * Finds the decimals places of the given number. * * Results are still subject to [IEEE 754 rounding "errors"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding). * * @param {Number|String} num May be a **number**, or a **String** formatted as one. * @author [Volodymyr Agafonkin (aka Mourner)](https://stackoverflow.com/a/27865285), [xvr.pop](https://github.com/xvrpop) * @returns {Number} number of decimals places of the given number */ function findPrecision(num) { let t = num.constructor.name; if(Number.isInteger(num)) return 0; if(t == "Number"){ if(num!==num) throw new Error("passed value is NaN"); if(num<0) num=-num; //or use Math.abs() :) const LIMIT = 1000000000000000; //the point after which numbers could be different from our expectations (IEEE 754 explains why) let fixed = Math.round(num*LIMIT)/LIMIT; if(num-fixed==0){ //code here by Volodymyr Agafonkin (aka Mourner) let e = 1, decP = 0; while (Math.round(num * e) / e !== num) { e *= 10; decP++; } return decP; } num = num.toString(); } else if(t != "String") throw new Error("`num` must be a number or a String formatted like a number") let precision = 0; num = num.split(''); let i=num.indexOf('.'), len = num.length; if(i==-1){ i = num.indexOf('e'); if(i<0) return 0; } else{ while(i+1 < len && num[i+1]!='e'){ // i+1 cause otherwise the precision will be +1 the actual value precision++; ++i; } i++; // if we found an 'e' we need skip 1, otherwise incrementing further will not be an issue } if(i<len){ let expStr = "" i++; // here we skip a character, cause both here `num[i+1]!='e'` and here `i = num.indexOf('e');` num[i] will be 'e' while(i<len){ expStr += num[i]; i++; } expStr = +expStr; precision += (expStr < 0 ? -expStr : expStr); } return precision; } 

Comments

0

How do I get the decimal places of a floating point number?

It seems so simple, right? A fractional number like 123.456 consists of three integer digits 123, and a decimal point ., and then three more fractional digits 456. How hard can it be to extract the fractional digits 456, or even simpler, just find out how many of them there are?

And as the answers here show, the answer to "How hard can it be?" is "Pretty hard". There are lots of techniques presented here, but they're all complicated, and some of them involve unnecessary-seeming conversions back to strings. Some of these techniques are error-prone, or don't work properly on all inputs.

And it turns out that the reason the problem is hard is that the premise is flawed. Internally to a computer, the decimal fraction 123.456 is not represented as three integer digits 123, and a decimal point ., and then three fractional digits 456. Nothing like that.

As you probably know, computers use binary, or base 2, for just about everything. They don't use decimal (base 10). So how do computers represent fractions, if not in the obvious way?

Let's look first at a slightly different decimal fraction, 123.625. That's equal to 123⅝, which is going to make it easier to see in base two. 123.625 is represented internally as the binary fraction 1.111011101, times a scaling factor of 26, or 64. Let's check that: 1.111011101 is 1.931640625, and 1.931640625 × 64 = 123.625. Check.

But what about 123.456? Since the fractional part .456 is not representable as a binary fraction made up of halves and quarters and eighths and sixteenths, it's not going to work so well.

In the IEEE 754 double-precision floating point format used by most JavaScript implementations, the number 123.456 is represented by the binary number 1.1110110111010010111100011010100111111011111001110111, again multiplied by 26. But if you do the math, this works out to about 123.45600000000000307. It is not exactly equal to 123.456.

This is a big surprise the first time you encounter it. Binary floating-point representations such as are used by JavaScript (and in fact most computers and programming languages) can not represent decimal fractions like 123.456 exactly.

And since the internal, binary representation of 123.456 does not involve the digits 123 or 456, it is not so easy to extract them in that form, after all.

And, not only is it not so easy to directly extract the fractional digits 456, it's problematic to even ask how many of them there are. As I said, once you've read in a number like 123.456 into a JavaScript program, it's represented internally by the binary fraction 1.1110110111010010111100011010100111111011111001110111 × 26, which works out to about 123.45600000000000307. So should we say that this number has 17 digits past the decimal? No, and it's even worse than that: I said that the internal binary representation works out to the equivalent of "about 123.45600000000000307", but it works out to exactly 123.4560000000000030695446184836328029632568359375, which has 46 digits past the decimal.

The answers presented here use various tricks, such as rounding or string conversion, to get around this problem. The answers presented here tend to give the answer 3 for the alleged number of places past the decimal for the input number 123.456. But since that input number 123.456 isn't really 123.456, these answers are, to some extent, cheating. And, indeed, if you take a function that can "correctly" give the answer 3 for the precision of the input 123.456, it will also give the answer 3 for input values of 123.45600000000000307 or 123.4560000000000030695446184836328029632568359375. (Try it!)

So how do you get around this problem? How do you find the true precision of the input number 123.456, if by the time you're working with it it's really a number like 123.45600000000000307? There are ways, but before choosing one, you have to ask: why do you need the precision, actually?

If it's an academic exercise, with little or no practical value, the techniques presented in the answers here may be acceptable, as long as you understand the underlying meaninglessness of the exercise.

But if you're trying to perform operations on the user's input in some way, operations which should reflect the user's actual input precision, you may want to initially take the user's input as a string, and count the number of places past the decimal in that representation, before converting the input string to a number and performing the rest of your operations. In that way, not only will you trivially get the right answer for an input of "123.456", but you will also be able to correctly determine that user input of, say, "123.456000" should be interpreted as having six places past the decimal.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.