JavaScript中有一个可靠的方法来获得任意数字的小数位数吗? [英] Is there a reliable way in JavaScript to obtain the number of decimal places of an arbitrary number?

查看:152
本文介绍了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

我的第一本能是使用字符串表示法:split '。 ',然后在'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];
$ b $ if(exponentPart!== undefined){
return integerPart.length +
(decimalPart!== undefined?decimalPart.length:0) - 1 +
parseInt函数(exponentPart);
}
else {
return decimalPart!== undefined? decimalPart.length:0;




$ b $ p
$ b

对于我上面的例子,这个函数起作用。然而,直到我测试了每一个可能的值,我才感到满意,所以我把它们分解为 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的适当幂。希望我超过了它。


<历史笔记:下面的评论线程可能指的是第一个和第二个实现。我在2017年9月交换了订单,因为领先一个错误的执行导致混乱。



如果您想要映射0.1e-100 / code>到101,那么你可以尝试像

 函数decimalPlaces(n){
/ /确保它是一个数字,并使用内置号码 - >串。
var s =+(+ n);
//拉出分数和指数。
var match = /(?:\.(\d+))?(?:[eE]([+\-]?\d+))?$/.exec(s);
// NaN或Infinity或整数。
//我们任意决定Infinity是不可或缺的。
if(!match){return 0; }
//计算分数中的位数,并减去
//指数来模拟移动指数位置左边的小数点。
// 1.234e + 2有1个小数位和'234'.length - 2 == 1
// 1.234e-2有5个小数位和'234'.length - -2 == 5
返回Math.max(
0,//下限。
(match [1] =='0')0:(match [1] ||'').length )//分数长度
- (match [2] || 0)); //指数
}

根据规范,任何基于内建的数字 - >字符串转换只能精确到超出指数的21位。



9.8.1 ToString应用于数字类型



    否则,令n,k和s是整数,使得k≥1,10k-1≤s<1。 10k,s×10n-k的数值为m,k越小越好。请注意,k是s的十进制表示中的数字位数,s不能被10整除,并且s的最低有效位数不一定是由这些条件唯一确定的。
  1. 如果k≤n≤21,则返回由s的十进制表示形式的k个数字组成的字符串(按顺序,无前导零),然后返回n个字符为'0'的字符。 b $ b
  2. 如果0 < n≤21,返回包含s的十进制表示形式中最重要的n个数字的字符串,后跟一个小数点'。',后面跟着s的十进制表示形式的其余k-n个数字。 b $ b
  3. 如果-6 < n≤0,返回由字符'0'组成的字符串,后跟小数点'。',后跟-n出现字符'0',接着是s的十进制表示的k个数字。 li>







历史记录:下面的实现是有问题的。我把它留在这里作为评论线程的上下文。



基于 Number.prototype.toFixed ,似乎下面应该可以工作,但是由于IEEE -754表示双值,某些数字会产生错误的结果。例如, decimalPlaces(0.123)会返回 20

<

 函数decimalPlaces(number){// toFixed产生一个精确到20位小数的固定表达式//没有指数。 // ^  - ?\ d * \。剥去任何符号,整数部分和小数点//只留下小数部分。 // 0 + $去掉任何尾随的零。 ((+)).toFixed(20))。 (小数位数(5555.0)); // 0console.log(decimalPlaces(5555)); // 0console.log(decimalPlaces(555.5)); // 1console.log(decimalPlaces(555.50)); // 1console.log(decimalPlaces(0.0000005)); // 7console.log(decimalPlaces(5e-7)); // 7console.log(decimalPlaces(0.00000055)); // 8console.log(decimalPlaces(5e-8)); // 8console.log(decimalPlaces(0.123)); // 20(!) 


It's important to note that I'm not looking for a rounding function. I am looking for a function that returns the number of decimal places in an arbitrary number's simplified decimal representation. That is, we have the following:

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

My first instinct was to use the string representations: split on '.', then on 'e-', and do the math, like so (the example is verbose):

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

For my examples above, this function works. However, I'm not satisfied until I've tested every possible value, so I busted out 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

This looked reasonable at first, but then on a double take I realized that 5e-324 * 10 should be 5e-323! And then it hit me: I'm dealing with the effects of quantization of very small numbers. Not only are numbers being quantized before storage; additionally, some numbers stored in binary have unreasonably long decimal representations, so their decimal representations are being truncated. This is unfortunate for me, because it means that I can't get at their true decimal precision using their string representations.

So I come to you, StackOverflow community. Does anyone among you know a reliable way to get at a number's true post-decimal-point precision?

The purpose of this function, should anyone ask, is for use in another function that converts a float into a simplified fraction (that is, it returns the relatively coprime integer numerator and nonzero natural denominator). The only missing piece in this outer function is a reliable way to determine the number of decimal places in the float so I can multiply it by the appropriate power of 10. Hopefully I'm overthinking it.

解决方案

Historical note: the comment thread below may refer to first and second implementations. I swapped the order in September 2017 since leading with a buggy implementation caused confusion.

If you want something that maps "0.1e-100" to 101, then you can try something like

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
}

According to the spec, any solution based on the builtin number->string conversion can only be accurate to 21 places beyond the exponent.

9.8.1 ToString Applied to the Number Type

  1. Otherwise, let n, k, and s be integers such that k ≥ 1, 10k−1 ≤ s < 10k, the Number value for s × 10n−k is m, and k is as small as possible. Note that k is the number of digits in the decimal representation of s, that s is not divisible by 10, and that the least significant digit of s is not necessarily uniquely determined by these criteria.
  2. If k ≤ n ≤ 21, return the String consisting of the k digits of the decimal representation of s (in order, with no leading zeroes), followed by n−k occurrences of the character ‘0’.
  3. If 0 < n ≤ 21, return the String consisting of the most significant n digits of the decimal representation of s, followed by a decimal point ‘.’, followed by the remaining k−n digits of the decimal representation of s.
  4. If −6 < n ≤ 0, return the String consisting of the character ‘0’, followed by a decimal point ‘.’, followed by −n occurrences of the character ‘0’, followed by the k digits of the decimal representation of s.


Historical note: The implementation below is problematic. I leave it here as context for the comment thread.

Based on the definition of Number.prototype.toFixed, it seems like the following should work but due to the IEEE-754 representation of double values, certain numbers will produce false results. For example, decimalPlaces(0.123) will return 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 (!)

这篇关于JavaScript中有一个可靠的方法来获得任意数字的小数位数吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆