簡體   English   中英

JavaScript 中是否有可靠的方法來獲取任意數字的小數位數?

[英]Is there a reliable way in JavaScript to obtain the number of decimal places of an arbitrary number?

重要的是要注意,我不是在尋找舍入函數。 我正在尋找一個函數,該函數以任意數字的簡化十進制表示形式返回小數位數。 也就是說,我們有以下內容:

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 社區。 你們中有人知道獲得數字真正的小數點后精度的可靠方法嗎?

如果有人問,這個函數的目的是用於另一個將浮點數轉換為簡化分數的函數(即,它返回相對互質的整數分子和非零自然分母)。 此外部函數中唯一缺少的部分是確定浮點數中小數位數的可靠方法,因此我可以將其乘以適當的 10 次冪。希望我多慮了。

歷史記錄:下面的評論線程可能會參考第一個和第二個實現。 我在 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 應用於數字類型

  1. 否則,令 n、k 和 s 為整數,使得 k ≥ 1,10k−1 ≤ s < 10k,s × 10n−k 的數值為 m,k 盡可能小。 請注意,k 是 s 的十進制表示中的位數,s 不能被 10 整除,並且 s 的最低有效位不一定由這些標准唯一確定。
  2. 如果 k ≤ n ≤ 21,則返回由 s 的十進制表示的 k 位數字組成的字符串(按順序,沒有前導零),后跟 n-k 次出現的字符“0”。
  3. 如果 0 < n ≤ 21,則返回由 s 的十進制表示的最高有效 n 位數字組成的字符串,后跟小數點“.”,后跟 s 的十進制表示的其余 k-n 位數字。
  4. 如果-6 < n ≤ 0,則返回由字符“0”組成的字符串,后跟小數點“.”,后跟字符“0”的-n次出現,后跟十進制表示的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 (!)

好吧,我使用的解決方案基於這樣一個事實:如果將浮點數乘以 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));

2017年更新

這是基於 Edwin 答案的簡化版本。 它有一個測試套件,並為極端情況返回正確的小數位數,包括 NaN、無窮大、指數符號和連續分數有問題的數字,例如 0.0029 或 0.0408。 這涵蓋了絕大多數金融應用程序,其中0.0408有 4 位小數(不是 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.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));

權衡是該方法僅限於最多 10 個保證小數。 它可能會正確返回更多小數,但不要依賴於此。 小於 1e-10 的數字可能被認為是零,並且函數將返回 0。選擇該特定值是為了正確解決 11.6894 的極端情況,對於這種情況,乘以 10 的冪的簡單方法失敗(它返回 5 而不是 4 )。

然而,這是我發現的第 5 個極端情況,在 0.0029、0.0408、0.1584 和 4.3573 之后。 在每次之后,我不得不將精度降低一位小數。 我不知道是否還有其他小於 10 位小數的數字,此函數可能會返回錯誤的小數位數。 為了安全起見,請尋找任意精度的庫

請注意,轉換為字符串並按. 只是最多 7 位小數的解決方案。 String(0.0000007) === "7e-7" 或者甚至更少? 浮點表示不直觀。

這適用於小於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;
}

這對我有用

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

此方法期望該值是一個標准數字。

2021 更新

處理科學和非科學表示的 Mike Samuel 的優化版本。

 // Helper function to extract the number of decimal assuming the // input is a number (either as a number of a stringified number) // Note: if a stringified number has an exponent, it will always be // '<x>e+123' or '<x>e-123' or '<x.dd...d>e+123' or '<x.dd...d>e-123'. // No need to check for '<x>e123', '<x>E+123', '<x>E-123' etc. const _numDecimals = v => { const [i, p, d, e, n] = v.toString().split(/(\\.|e[\\-+])/g); const f = e === 'e-'; return ((p === '.' && (!e || f) && d.length) + (f && parseInt(n))) || (p === 'e-' && parseInt(d)) || 0; } // But if you want to be extra safe...you can replace _numDecimals // with this: const _numSafeDecimals = v => { let [i, p, d, e, n] = v.toString().split(/(\\.|[eE][\\-+])/g); e = e.toLowerCase(); const f = e === 'e-'; return ((p === '.' && (!e || f) && d.length) + (f && parseInt(n))) || (p.toLowerCase() === 'e-' && parseInt(d)) || 0; } // Augmenting Number proto. Number.prototype.numDecimals = function () { return (this % 1 !== 0 && _numDecimals(this)) || 0; } // Independent function. const numDecimals = num => ( (!isNaN(num) && num % 1 !== 0 && _numDecimals(num)) || 0 ); // Tests: const test = n => ( console.log('Number of decimals of', n, '=', n.numDecimals()) ); test(1.234e+2); // --> 1 test(0.123); // ---> 3 test(123.123); // ---> 3 test(0.000123); // ---> 6 test(1e-20); // --> 20 test(1.2e-20); // --> 21 test(1.23E-20); // --> 22 test(1.23456789E-20); // --> 28 test(10); // --> 0 test(1.2e20); // --> 0 test(1.2e+20); // --> 0 test(1.2E100); // --> 0 test(Infinity); // --> 0 test(-1.234e+2); // --> 1 test(-0.123); // ---> 3 test(-123.123); // ---> 3 test(-0.000123); // ---> 6 test(-1e-20); // --> 20 test(-1.2e-20); // --> 21 test(-1.23E-20); // --> 22 test(-1.23456789E-20); // --> 28 test(-10); // --> 0 test(-1.2e20); // --> 0 test(-1.2e+20); // --> 0 test(-1.2E100); // --> 0 test(-Infinity); // --> 0

不僅數字在存儲之前被量化; 此外,一些以二進制存儲的數字具有不合理的十進制表示形式,因此它們的十進制表示形式被截斷。

JavaScript 使用IEEE-754雙精度(64 位)格式表示數字。 據我了解,這為您提供了 53 位精度,或十五到十六位十進制數字。

因此,對於具有更多位數的任何數字,您只會得到一個近似值。 有一些庫可以更精確地處理大數字,包括本線程中提到的那些。

昵稱答案的優化版本。

該函數要求 n 是一個字符串。 即使全部為 0,此函數也會獲取小數,例如 1.00 -> 2 位小數。

function getDecimalPlaces(n) {
    var i = n.indexOf($DecimalSeparator)
    return i > 0 ? n.length - i - 1 : 0
}

console.log(getDecimalPlaces("5555.0"));  // 1
console.log(getDecimalPlaces("5555"));  // 0
console.log(getDecimalPlaces("555.5"));  // 1
console.log(getDecimalPlaces("555.50"));  // 2
console.log(getDecimalPlaces("0.0000005"));  // 7
console.log(getDecimalPlaces("0.00000055"));  // 8
console.log(getDecimalPlaces("0.00005500"));  // 8

基於 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。 據我所知,這沒有錯誤。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM