繁体   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