2012-03-02 42 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

    相關問題