Is there any reliable way in JavaScript to get the number of decimal places of an arbitrary number?

It is important to note that I am not looking for a rounding function. I am looking for a function that returns the number of decimal places in an arbitrary amount, a simplified decimal representation. That is, we have the following:

decimalPlaces(5555.0); //=> 0 decimalPlaces(5555); //=> 0 decimalPlaces(555.5); //=> 1 decimalPlaces(555.50); //=> 1 decimalPlaces(0.0000005); //=> 7 decimalPlaces(5e-7); //=> 7 decimalPlaces(0.00000055); //=> 8 decimalPlaces(5.5e-7); //=> 8 

My first instinct was to use string representations: split by '.' , then to 'e-' and do the math, for example (example - detailed):

 function decimalPlaces(number) { var parts = number.toString().split('.', 2), integerPart = parts[0], decimalPart = parts[1], exponentPart; if (integerPart.charAt(0) === '-') { integerPart = integerPart.substring(1); } if (decimalPart !== undefined) { parts = decimalPart.split('e-', 2); decimalPart = parts[0]; } else { parts = integerPart.split('e-', 2); integerPart = parts[0]; } exponentPart = parts[1]; if (exponentPart !== undefined) { return integerPart.length + (decimalPart !== undefined ? decimalPart.length : 0) - 1 + parseInt(exponentPart); } else { return decimalPart !== undefined ? decimalPart.length : 0; } } 

For my examples above, this function works. However, I am not satisfied until I checked all the possible values, so I threw out Number.MIN_VALUE .

 Number.MIN_VALUE; //=> 5e-324 decimalPlaces(Number.MIN_VALUE); //=> 324 Number.MIN_VALUE * 100; //=> 4.94e-322 decimalPlaces(Number.MIN_VALUE * 100); //=> 324 

At first it looked reasonable, but then on a double take, I realized that 5e-324 * 10 should be 5e-323 ! And then it hit me: I am dealing with the effects of quantizing very small numbers. Before storage, not only are numbers quantized; in addition, some numbers stored in binary format have unnecessarily long decimal representations, so their decimal representations are truncated. This is unfortunate for me because it means that I cannot get their true decimal precision using their string representations.

So, I come to you, the StackOverflow community. Do any of you know a reliable way to get accurate accuracy after a decimal point?

The purpose of this function, if anyone asks, is intended to be used in another function that converts a float into a simplified fraction (i.e. returns a relatively unit numeric numerator and a nonzero natural denominator). The only missing element of this external function is a reliable way to determine the number of decimal places in the float so that I can multiply it by the corresponding power of 10. I hope I overdo it.

+30
javascript decimal floating-point precision fractions
Mar 02 2018-12-12T00:
source share
7 answers

Historical Note: The comment thread below may relate to the first and second implementations. I changed the order in September 2017, as implementation with an implementation error caused confusion.

If you need something that displays "0.1e-100" at 101, you can try something like

 function decimalPlaces(n) { // Make sure it is a number and use the builtin number -> string. var s = "" + (+n); // Pull out the fraction and the exponent. var match = /(?:\.(\d+))?(?:[eE]([+\-]?\d+))?$/.exec(s); // NaN or Infinity or integer. // We arbitrarily decide that Infinity is integral. if (!match) { return 0; } // Count the number of digits in the fraction and subtract the // exponent to simulate moving the decimal point left by exponent places. // 1.234e+2 has 1 fraction digit and '234'.length - 2 == 1 // 1.234e-2 has 5 fraction digit and '234'.length - -2 == 5 return Math.max( 0, // lower limit. (match[1] == '0' ? 0 : (match[1] || '').length) // fraction length - (match[2] || 0)); // exponent } 

According to the specification, any solution based on the built-in conversion from number-> string can be accurate only in 21 places outside the exponent.

9.8.1 ToString Applies to Number Type

  1. Otherwise, let n, k and s be integers such that k ≥ 1, 10k-1 ≤ s <10k, the numerical value for s × 10n-k is m, and k is as small as possible. Note that k is the number of digits in decimal s, that s is not divisible by 10, and that the least significant digit s is not necessarily uniquely determined by these criteria.
  2. If k ≤ n ≤ 21, return a String consisting of k digits of the decimal representation of s (in order, without leading zeros), and then nk occurrences of the character '0.
  3. If 0 <n ≤ 21, return a String consisting of the most significant n digits of the decimal representation s, followed by the decimal point '., Followed by the remaining kn digits of the decimal representation s.
  4. If -6 <n ≤ 0, return a string consisting of the character '0 followed by a decimal point'., And then -n occurrences of the character '0, followed by k decimal digits s.



Historical note. The implementation below is problematic. I leave it here as a context for the comment stream.

Based on the definition of Number.prototype.toFixed , it seems that the following should work, but due to the representation of IEEE-754 double values, certain numbers will give false results. For example, decimalPlaces(0.123) will return 20 .

 function decimalPlaces(number) { // toFixed produces a fixed representation accurate to 20 decimal places // without an exponent. // The ^-?\d*\. strips off any sign, integer portion, and decimal point // leaving only the decimal fraction. // The 0+$ strips off any trailing zeroes. return ((+number).toFixed(20)).replace(/^-?\d*\.?|0+$/g, '').length; } // The OP examples: console.log(decimalPlaces(5555.0)); // 0 console.log(decimalPlaces(5555)); // 0 console.log(decimalPlaces(555.5)); // 1 console.log(decimalPlaces(555.50)); // 1 console.log(decimalPlaces(0.0000005)); // 7 console.log(decimalPlaces(5e-7)); // 7 console.log(decimalPlaces(0.00000055)); // 8 console.log(decimalPlaces(5e-8)); // 8 console.log(decimalPlaces(0.123)); // 20 (!) 
+17
Mar 02 '12 at 20:13
source share

Well, I use a solution based on the fact that if you multiply a floating point number by the correct power of 10, you will get an integer.

For example, if you multiply 3.14 * 10 ^ 2, you get 314 (integer). The exponent is the number of decimal places, a floating point number.

So, I thought that if I gradually multiply the floating point by increasing the power of 10, you will eventually come to a solution.

 let decimalPlaces = function () { function isInt(n) { return typeof n === 'number' && parseFloat(n) == parseInt(n, 10) && !isNaN(n); } return function (n) { const a = Math.abs(n); let c = a, count = 1; while (!isInt(c) && isFinite(c)) { c = a * Math.pow(10, count++); } return count - 1; }; }(); for (const x of [ 0.0028, 0.0029, 0.0408, 0, 1.0, 1.00, 0.123, 1e-3, 3.14, 2.e-3, 2.e-14, -3.14e-21, 5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8, 0.000006, 0.0000007, 0.123, 0.121, 0.1215 ]) console.log(x, '->', decimalPlaces(x)); 
+12
Dec 02 '13 at
source share

this works for numbers smaller than e-17 :

 function decimalPlaces(n){ var a; return (a=(n.toString().charAt(0)=='-'?n-1:n+1).toString().replace(/^-?[0-9]+\.?([0-9]+)$/,'$1').length)>=1?a:0; } 
+3
Mar 02 2018-12-12T00:
source share

Update 2017

Here's a simplified version based on Edwin's answer. It has a set of tests and returns the correct number of decimal places for angular cases, including NaN, Infinity, exponent readings and numbers with problematic representations of their consecutive fractions, for example 0.0029 or 0.0408. This covers the vast majority of financial applications, where 0.0408 with 4 decimal places (not 6) is more important than 3.14e-21, which has 23.

 function decimalPlaces(n) { function hasFraction(n) { return Math.abs(Math.round(n) - n) > 1e-10; } let count = 0; // multiply by increasing powers of 10 until the fractional part is ~ 0 while (hasFraction(n * (10 ** count)) && isFinite(10 ** count)) count++; return count; } for (const x of [ 0.0028, 0.0029, 0.0408, 0.1584, 4.3573, // corner cases against Edwin answer 11.6894, 0, 1.0, 1.00, 0.123, 1e-3, -1e2, -1e-2, -0.1, NaN, 1E500, Infinity, Math.PI, 1/3, 3.14, 2.e-3, 2.e-14, 1e-9, // 9 1e-10, // should be 10, but is below the precision limit -3.14e-13, // 15 3.e-13, // 13 3.e-14, // should be 14, but is below the precision limit 123.12345678901234567890, // 14, the precision limit 5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8, 0.000006, 0.0000007, 0.123, 0.121, 0.1215 ]) console.log(x, '->', decimalPlaces(x)); 

The tradeoff is that the method is limited to a maximum of 10 guaranteed decimal places. It may return more decimals correctly, but do not rely on it. Numbers smaller than 1e-10 can be considered zero, and the function will return 0. This particular value was chosen to correctly solve the angular case 11.6894, for which failure is a simple way of multiplying by a power of 10 (it returns 5 instead of 4).

However, this is the fifth I discovered after 0.0029, 0.0408, 0.1584 and 4.3573. After each of them I had to reduce the accuracy by one decimal place. I do not know if there are other numbers with less than 10 decimal places for which this function may return the wrong number of decimal places. To be safe, find an arbitrary precision library .

Note that converting to a string and splitting into . is only a solution for up to seven decimal places. String(0.0000007) === "7e-7" . Or maybe even less? The floating-point representation is not intuitive.

+2
Jun 29 '17 at 3:14
source share

By storing data, not only are numbers quantized; in addition, some numbers stored in binary format have unnecessarily long decimal representations, so their decimal representations are truncated.

JavaScript presents numbers using the IEEE-754 double-precision format (64 bits). As far as I understand, this gives you an accuracy of 53 bits or from fifteen to sixteen decimal digits.

So, for any number with a lot of digits, you just get an approximate value. There are several libraries for processing large numbers with greater accuracy, including those mentioned in this thread .

+1
Mar 02 2018-12-12T00:
source share

It works for me

const decimalPlaces = value.substring(value.indexOf('.') + 1).length;

This method expects the value to be a standard number.

0
Jul 19 '19 at 1:54
source share

Based on gion_13's answer, I came up with the following:

 function decimalPlaces(n){ let result= /^-?[0-9]+\.([0-9]+)$/.exec(n); return result === null ? 0 : result[1].length; } for (const x of [ 0, 1.0, 1.00, 0.123, 1e-3, 3.14, 2.e-3, -3.14e-21, 5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8, 0.000006, 0.0000007, 0.123, 0.121, 0.1215 ]) console.log(x, '->', decimalPlaces(x)); 

It captures a return of 1 when there are no decimals. As far as I can tell, this works without errors.

-one
Dec 26 '13 at 15:45
source share



All Articles