Monday, May 20, 2024
6
rated 0 times [  10] [ 4]  / answers: 1 / hits: 21535  / 13 Years ago, fri, march 2, 2012, 12:00:00

It's important to note that I'm not looking for a rounding function. I am looking for a function that returns the number of decimal places in an arbitrary number's 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 the string representations: split on '.', then on 'e-', and do the math, like so (the example is verbose):



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'm not satisfied until I've tested every possible value, so I busted 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


This looked reasonable at first, but then on a double take I realized that 5e-324 * 10 should be 5e-323! And then it hit me: I'm dealing with the effects of quantization of very small numbers. Not only are numbers being quantized before storage; additionally, some numbers stored in binary have unreasonably long decimal representations, so their decimal representations are being truncated. This is unfortunate for me, because it means that I can't get at their true decimal precision using their string representations.



So I come to you, StackOverflow community. Does anyone among you know a reliable way to get at a number's true post-decimal-point precision?



The purpose of this function, should anyone ask, is for use in another function that converts a float into a simplified fraction (that is, it returns the relatively coprime integer numerator and nonzero natural denominator). The only missing piece in this outer function is a reliable way to determine the number of decimal places in the float so I can multiply it by the appropriate power of 10. Hopefully I'm overthinking it.


More From » floating-point

 Answers
21

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





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

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

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

  4. 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 (!)




[#87077] Thursday, March 1, 2012, 13 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
myakylas

Total Points: 66
Total Questions: 85
Total Answers: 95

Location: Guadeloupe
Member since Sat, Aug 22, 2020
4 Years ago
myakylas questions
Thu, Apr 28, 22, 00:00, 2 Years ago
Thu, Apr 8, 21, 00:00, 3 Years ago
Sat, Sep 19, 20, 00:00, 4 Years ago
;