JavaScript中有一种可靠的方法来获取任意数字的小数位数吗?

Mil*_*osz 30 javascript floating-point precision decimal fractions

重要的是要注意我不是在寻找舍入函数.我正在寻找一个函数,它返回任意数字的简化十进制表示中的小数位数.也就是说,我们有以下内容:

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
Run Code Online (Sandbox Code Playgroud)

我的第一直觉是使用字符串表示:拆分'.',然后打开'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;
  }
}
Run Code Online (Sandbox Code Playgroud)

对于上面的示例,此功能有效.但是,在我测试了所有可能的值之前,我并不满意,所以我被淘汰了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
Run Code Online (Sandbox Code Playgroud)

这一开始看起来很合理,但是接下来我意识到5e-324 * 10应该是这样5e-323!然后它击中了我:我正在处理非常小数量的量化效应.数字不仅在存储之前被量化; 另外,存储在二进制中的一些数字具有不合理的长十进制表示,因此它们的十进制表示被截断.这对我来说是不幸的,因为这意味着我无法使用它们的字符串表示来获得真正的小数精度.

所以我来找你,StackOverflow社区.你们中间是否有人知道一种可靠的方法来获得一个真正的小数点后精度?

如果有人问的话,这个函数的目的是用于将float转换为简化分数的另一个函数(也就是说,它返回相对互质的整数分子和非零自然分母).这个外部函数中唯一缺少的部分是一个可靠的方法来确定浮点数中的小数位数,所以我可以乘以10的适当幂.希望我过度思考它.

Mik*_*uel 18

历史记录:下面的评论帖子可以指第一和第二实施方式.我在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
}
Run Code Online (Sandbox Code Playgroud)

根据规范,任何基于内置数字 - >字符串转换的解决方案只能精确到指数之外的21个位置.

9.8.1 ToString应用于数字类型

  1. 否则,令n,k和s为整数,使得k≥1,10k-1≤s<10k,s×10n-k的Number值为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'组成的字符串,后跟小数点'.',后跟-n出现的字符'0',后跟小数表示的k位数字秒.

历史记录:下面的实施是有问题的.我将它留在这里作为评论主题的上下文.

根据定义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 (!)
Run Code Online (Sandbox Code Playgroud)

  • 这在Google Chrome版本30中对我不起作用.输入555.50会导致输出为1,但555.20会导致输出为20. (7认同)
  • 我做了一些测试((+ number).toFixed(20)),数字= 0.2正在返回"0.20000000000000001110",这搞砸了你的方法.我测试了铬. (4认同)
  • 不幸的是,这种精确度的损失使得`toFixed(20)`解决方案变得非常不可靠,因为对于像`0.123`,`0.121`,`0.1215`等数字我得到一个相当一致的`20`虽然我只是在Chrome中测试过 (4认同)
  • 人们会认为这会更容易.另一方面,世界上没有问题,足够大的正则表达式无法解决.+1使用MDN而不是w3schools (2认同)

Edw*_*rzo 12

好吧,我使用一个解决方案,基于这样一个事实:如果你将浮点数乘以10的右幂,你会得到一个整数.

例如,如果乘以3.14*10 ^ 2,则得到314(整数).然后,exponent表示浮点数具有的小数位数.

所以,我认为如果我通过增加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));
Run Code Online (Sandbox Code Playgroud)

  • 它失败为0.0029,返回5而不是4.浮点数在JavaScript中是一个雷区. (3认同)
  • 不使用reqular表达式的+1和不限于任何特定小数的解决方案.我看到的唯一问题是具有无限小数位数的数字,如1/3. (2认同)

Dan*_*scu 5

2017年更新

这是基于埃德温答案的简化版本。它有一个测试套件,可以为极端情况返回正确的小数位数,包括 NaN、无穷大、指数符号以及连续分数表示有问题的数字,例如 0.0029 或 0.0408。这涵盖了绝大多数金融应用,其中0.04084 位小数(而不是 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));
Run Code Online (Sandbox Code Playgroud)

代价是该方法仅限于最多 10 个保证小数。它可能会正确返回更多小数,但不要依赖于此。小于 1e-10 的数字可能被视为零,并且该函数将返回 0。选择该特定值是为了正确解决 11.6894 的极端情况,对于这种情况,乘以 10 的幂的简单方法会失败(它返回 5 而不是 4) )。

然而,这是继 0.0029、0.0408、0.1584 和 4.3573 之后我发现的第五个极端情况。每次之后,我都必须将精度降低一位小数。我不知道是否还有其他小于 10 位小数的数字,该函数可能会返回不正确的小数位数。为了安全起见,请寻找任意精度的库

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

  • +1虽然这个函数有已知的局限性,但它可以满足我的需要(供人类使用的任何合理数量的小数,即我的应用程序中的0 - 4) (2认同)

ash*_*awg 5

简单的“单行”

\n

如果您正在做的事情需要超过16 位精度,那么这不适合您。

\n

这个“一行字”在其他时候也能很好地工作99.99999999999999%(是的,甚至是这个数字。)

\n
function numDec(n){return n%1==0?0:(""+n).length-(""+n).lastIndexOf(".")-1}\n
Run Code Online (Sandbox Code Playgroud)\n

片段中的演示:

\n

\r\n
\r\n
function numDec(n){return n%1==0?0:(""+n).length-(""+n).lastIndexOf(".")-1}\n
Run Code Online (Sandbox Code Playgroud)\r\n
function numDec(n){return n%1==0?0:(""+n).length-(""+n).lastIndexOf(".")-1}\n\n\nsetInterval(function(){\n  n=Math.random()*10000000000;\n  document.body.innerHTML=n+\' \xe2\x86\x90 \'+numDec(n)+\' decimal places\';\n},777);
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n


\n

更多信息:

\n\n