2012-03-02 52 views
24

请注意,我并不是在寻找舍入函数。我正在寻找一个函数,返回任意数字的简化十进制表示形式的小数位数。也就是说,我们有以下几点:在JavaScript中是否有可靠的方法来获取任意数字的小数位数?

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 

我的第一反应是用字符串表示:各执'.',然后'e-',和做数学题,像这样(的例子是冗长):

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

对于我上面的例子,这个函数有效。然而,直到我测试了所有可能的价值之后,我才感到满意,于是我剔除了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 

这看起来合理的,但后来就恍然大悟,我意识到,5e-324 * 10应该5e-323!然后它打击我:我正在处理量化非常小的数字的影响。不仅数字在存储之前被量化;另外,以二进制形式存储的一些数字具有不合理的长十进制表示,因此它们的十进制表示被截断。这对我来说是不幸的,因为这意味着我不能使用它们的字符串表示来获得它们的真实小数精度。

所以我来找你,StackOverflow社区。你有没有人知道一个可靠的方法来获得数字的真正的小数点后精度?

任何人都应该问这个函数的目的是用于另一个将float转换为简化分数的函数(也就是说,它返回相对的coprime整数分子和非零自然分母)。在这个外部函数中唯一缺失的部分是确定浮点数的小数位数的可靠方法,所以我可以乘以10的适当幂。希望我可以超越它。

+0

任何浮点数都有无穷多的小数位数。 – 2012-03-02 20:19:07

+2

您可能感兴趣的:http://blog.thatscaptaintoyou.com/introducing-big-js-arbitrary-precision-math-for-javascript/ – 2012-03-02 20:20:59

+0

@LightnessRacesinOrbit:对,我无法包括所有的细节标题!但是,我在问题中指定了我正在寻找简化表示法。 – Milosz 2012-03-02 20:29:44

回答

14

历史注释:下面的评论线程可参考第一和第二的实现。我在2017年9月调换了订单,因为领先的错误执行导致了混淆。

如果你想要的东西映射"0.1e-100"到101,那么你可以尝试像

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 
} 

根据规范的基础上,内建号 - 任何解决办法>的字符串转换只能精确到21位超越指数。

9.8.1 ToString Applied to the Number Type

  • 否则,设N,K,和s是整数,使得ķ≥1,10K-1≤小号< 10K,数对于s值×10n-k是m,并且k尽可能小。请注意,k是s的十进制表示中的数字位数,s不能被10整除,并且s的最低位数不一定由这些条件唯一确定。
  • 如果k≤n≤21,则返回包含s的十进制表示形式的k个数字的字符串(按顺序,无前导零),然后返回n-k个字符“0”。
  • 如果0 < n≤21,则返回由s的十进制表示形式的最高有效n位数字组成的字符串,后跟一个小数点'。',随后是s的十进制表示形式的其余k-n数字。
  • 如果-6 < n≤0,则返回由字符'0'组成的字符串,后跟小数点'。',后跟-n个字符'0',后跟k个数字s的十进制表示。

  • 历史注释:下面的实现是有问题的。我把它留在这里作为评论主题的上下文。

    根据Number.prototype.toFixed的定义,似乎下面应该可以工作,但由于IEEE-754表示双值,某些数字会产生错误的结果。例如,decimalPlaces(0.123)将返回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 (!)

    +0

    非常丰富,谢谢。为了简单起见,我想我最终可能会使用toFixed(20),并忽略真正的小数字。量化线必须在某个地方绘制,并且20对于大多数目的而言不够低。 – Milosz 2012-03-02 20:53:04

    +0

    @Milosz,是的。如果我正在写图书馆,我会采用第二种方法,但对于特定的应用程序,第一种可能就足够了。有一点需要注意的是,第一个和NaN和Infinity不一样。 – 2012-03-02 22:19:13

    +7

    这不适用于Google Chrome版本30. 555.50的输入结果输出为1,但555.20输出为20. – user1 2013-10-09 20:20:21

    3

    这个作品比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; 
    } 
    
    +0

    这让我想起,我忘了在数字的开始处包括检查减号。谢谢!我也很喜欢这个功能的简洁。推动绝对值超过1的数字以避免负指数是聪明的(虽然我不明白为什么JavaScript为了正指数而切换到指数符号)。如果没有任何东西能够在e-17上出现,那么这可能就是我要做的。 – Milosz 2012-03-02 20:22:23

    +0

    错误地返回'1.2'的'0' – serg 2013-04-01 00:12:33

    +1

    截至上次编辑时(2013年4月1日),此答案将返回(正确)1为1.2。它为howerver返回1(而不是零)没有小数位数 – Hoffmann 2013-12-26 13:41:41

    1

    不仅号码被存储之前被量化;另外,以二进制形式存储的一些数字具有不合理的长十进制表示,因此它们的十进制表示被截断。

    JavaScript代表使用IEEE-754双精度(64位)格式的数字。据我了解,这给你53位精度,或十五至十六位十进制数字。

    因此,对于任何数字更多的数字,你只是得到一个近似值。有一些库可以更精确地处理大量数据,其中包括this thread中提到的那些库。

    11

    那么,我使用的是一个解决方案,基于这样的事实:如果用10的右幂乘以浮点数,就会得到一个整数。例如,如果乘以3.14 * 10^2,则会得到314(整数)。指数表示浮点数所具有的小数位数。因此,我认为如果我通过增加10的幂数来逐渐增加浮点数,那么你最终会到达解决方案。

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

    +2

    +1不适用于指令表达式和不限于任何特定小数位数的解决方案。唯一的问题是我看到的数字有无限小数点,比如1/3。 – 2014-01-16 08:57:33

    +0

    @DaniëlTulp具有无限小数符号的偶数必须在计算机存储器中用有限数量的小数表示。我想这种方法会告诉你有限符号有多少小数。我想,要准确地表示1/3,我们将不得不使用分数,因为小数表示法不起作用。 – 2014-06-20 14:23:56

    +1

    这对我来说是最一致的方法。我觉得使用数学更舒服,然后转换为字符串,道具! – SuckerForMayhem 2015-05-19 14:36:35

    -1

    基于gion_13答案,我想出了这个:

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

    它修复返回1时,有没有小数位。据我可以告诉这个工程没有错误。

    +0

    它只适用于你已经包含的极少数测试用例而没有错误:) – 2017-06-29 02:32:57

    1

    2017更新

    这里是一个基于埃德温答案的简化版本。它有一个测试套件,并返回包含NaN,无穷大,指数符号和其连续分数的问题表示的数字(例如0.0029或0.0408)的小数的正确小数位数。这覆盖绝大多数的金融应用中,其中具有4位小数0.0408(未6)比3.14e-21具有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's 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.123456789, // 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));

    的折衷,该方法是有限的,最多保证10位小数。它可能会正确返回更多的小数,但不要依赖它。小于1e-10的数字可以被认为是0,并且函数将返回0.该特定值被选择来正确求解11.6894拐角的情况,对此,乘以10的幂的简单方法失败(它返回5而不是4 )。

    但是,这是the 5th我发现,在0.0029,0.0408,0.1584和4.3573之后发现的拐角情况。每次之后,我必须将精度降低一位小数。我不知道是否还有其他小于10位小数的数字,该函数可能会返回不正确的小数位数。为了安全起见,请查找arbitrary precision library

    请注意,转换为字符串并按.拆分只能解决多达7位小数的​​问题。 String(0.0000007) === "7e-7"。或者甚至更少?浮点表示不直观。

    +1

    +1虽然这个函数有已知的局限性,但它适用于我所需要的(任何合理数量的小数用于人类消费,即0 - 4 in我的应用) – Johannes 2017-11-13 17:25:18

    相关问题