Historical note: the comment thread below may refer to first and second implementations. I swapped the order in September 2017 since leading with a buggy implementation caused confusion.
If you want something that maps "0.1e-100"
to 101, then 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 spec, any solution based on the builtin number->string conversion can only be accurate to 21 places beyond the exponent.
9.8.1 ToString Applied to the Number Type
- Otherwise, let n, k, and s be integers such that k ≥ 1, 10k−1 ≤ s < 10k, the Number value for s × 10n−k is m, and k is as small as possible. Note that k is the number of digits in the decimal representation of s, that s is not divisible by 10, and that the least significant digit of s is not necessarily uniquely determined by these criteria.
- If k ≤ n ≤ 21, return the String consisting of the k digits of the decimal representation of s (in order, with no leading zeroes), followed by n−k occurrences of the character ‘0’.
- If 0 < n ≤ 21, return the String consisting of the most significant n digits of the decimal representation of s, followed by a decimal point ‘.’, followed by the remaining k−n digits of the decimal representation of s.
- If −6 < n ≤ 0, return the String consisting of the character ‘0’, followed by a decimal point ‘.’, followed by −n occurrences of the character ‘0’, followed by the k digits of the decimal representation of s.
Historical note: The implementation below is problematic. I leave it here as context for the comment thread.
Based on the definition of Number.prototype.toFixed
, it seems like the following should work but due to the IEEE-754 representation of double values, certain numbers will produce 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's 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 (!)