JavaScript 中是否有可靠的方法来获取任意数字的小数位数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9539513/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a reliable way in JavaScript to obtain the number of decimal places of an arbitrary number?
提问by Milosz
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):
我的第一直觉是使用字符串表示:拆分 on '.'
,然后 on '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;
}
}
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
.
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.
起初这看起来很合理,但后来我意识到5e-324 * 10
应该是5e-323
!然后它击中了我:我正在处理非常小的数字量化的影响。不仅数字在存储之前被量化;此外,一些以二进制存储的数字具有不合理的十进制表示形式,因此它们的十进制表示形式被截断。这对我来说很不幸,因为这意味着我无法使用它们的字符串表示来获得它们真正的十进制精度。
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?
所以我来找你,StackOverflow 社区。你们当中有没有人知道一种可靠的方法来获得一个数字的真实小数点后精度?
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.
如果有人问,这个函数的目的是用于另一个将浮点数转换为简化分数的函数(即,它返回相对互质的整数分子和非零自然分母)。此外部函数中唯一缺少的部分是确定浮点数中小数位数的可靠方法,因此我可以将其乘以适当的 10 次幂。希望我多虑了。
采纳答案by Mike Samuel
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.
历史记录:下面的评论线程可能会参考第一个和第二个实现。我在 2017 年 9 月交换了订单,因为以错误的实施领先导致了混乱。
If you want something that maps "0.1e-100"
to 101, then you can try something like
如果你想要映射"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
}
According to the spec, any solution based on the builtin number->string conversion can only be accurate to 21 places beyond the exponent.
根据规范,任何基于内置数字->字符串转换的解决方案只能精确到指数以外的 21 位。
9.8.1 ToString Applied to the Number Type
- 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.
- 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'.
- 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.
- 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.
- 否则,令 n、k 和 s 为整数,使得 k ≥ 1,10k?1 ≤ s < 10k,s × 10n?k 的数值为 m,k 尽可能小。请注意,k 是 s 的十进制表示中的位数,s 不能被 10 整除,并且 s 的最低有效数字不一定由这些标准唯一确定。
- 如果 k ≤ n ≤ 21,则返回由 s 的十进制表示的 k 位数字组成的字符串(按顺序,没有前导零),后跟 n?k 次出现的字符“0”。
- 如果 0 < n ≤ 21,则返回由 s 的十进制表示的最高有效 n 位数字组成的 String,后跟小数点“.”,后跟 s 的十进制表示的剩余 k?n 位数字。
- 如果?6 < n ≤ 0,则返回由字符“0”组成的字符串,后跟小数点“.”,后跟字符“0”的?n次出现,然后是十进制表示的k位数字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
.
根据 的定义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 (!)
回答by Edwin Dalorzo
Well, I use a solution based on the fact that if you multiply a floating-point number by the right power of 10, you get an integer.
好吧,我使用的解决方案基于以下事实:如果将浮点数乘以 10 的正确幂,则会得到一个整数。
For instance, if you multiply 3.14 * 10 ^ 2, you get 314 (an integer). The exponent represents then the number of decimals the floating-point number has.
例如,如果你乘以 3.14 * 10 ^ 2,你会得到 314(一个整数)。指数表示浮点数的小数位数。
So, I thought that if I gradually multiply a floating-point by increasing powers of 10, you eventually arrive to the solution.
所以,我认为如果我逐渐通过增加 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));
回答by Dan Dascalescu
2017 Update
2017年更新
Here's a simplified version based on Edwin's answer. It has a test suite and returns the correct number of decimals for corner cases including NaN, Infinity, exponent notations, and numbers with problematic representations of their successive fractions, such as 0.0029 or 0.0408. This covers the vast majority of financial applications, where 0.0408
having 4 decimals (not 6) is more important than 3.14e-21 having 23.
这是基于 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));
The tradeoff is that the method is limited to maximum 10 guaranteed decimals. It may return more decimals correctly, but don't rely on that. Numbers smaller than 1e-10 may be considered zero, and the function will return 0. That particular value was chosen to solve correctly the 11.6894 corner case, for which the simple method of multiplying by powers of 10 fails (it returns 5 instead of 4).
权衡是该方法仅限于最多 10 个保证小数。它可能会正确返回更多小数,但不要依赖于此。小于 1e-10 的数字可能被认为是零,并且函数将返回 0。选择该特定值是为了正确解决 11.6894 的极端情况,对于这种情况,乘以 10 的幂的简单方法失败(它返回 5 而不是 4 )。
However, this is the 5thcorner case I've discovered, after 0.0029, 0.0408, 0.1584 and 4.3573. After each, I had to reduce the precision by one decimal. I don't know if there are other numbers with less than 10 decimals for which this function may return an incorrect number of decimals. To be on the safe side, look for an arbitrary precision library.
然而,这是我发现的第 5 个极端情况,在 0.0029、0.0408、0.1584 和 4.3573 之后。在每次之后,我不得不将精度降低一位小数。我不知道是否还有其他小于 10 位小数的数字,此函数可能会返回错误的小数位数。为了安全起见,请寻找任意精度的库。
Note that converting to string and splitting by .
is only a solution for up to 7 decimals. String(0.0000007) === "7e-7"
. Or maybe even less? Floating point representation isn't intuitive.
请注意,转换为字符串并拆分.
为最多 7 位小数的解决方案。String(0.0000007) === "7e-7"
. 或者甚至更少?浮点表示不直观。
回答by gion_13
this works for numbers smaller than e-17
:
这适用于小于 的数字e-17
:
function decimalPlaces(n){
var a;
return (a=(n.toString().charAt(0)=='-'?n-1:n+1).toString().replace(/^-?[0-9]+\.?([0-9]+)$/,'').length)>=1?a:0;
}
回答by nnnnnn
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.
不仅数字在存储之前被量化;此外,一些以二进制存储的数字具有不合理的十进制表示形式,因此它们的十进制表示形式被截断。
JavaScript represents numbers using IEEE-754double-precision (64 bit) format. As I understand it this gives you 53 bits precision, or fifteen to sixteen decimal digits.
JavaScript 使用IEEE-754双精度(64 位)格式表示数字。据我了解,这为您提供了 53 位精度,或 15 到 16 位十进制数字。
So for any number with more digits you just get an approximation. There are some libraries around to handle large numbers with more precision, including those mentioned in this thread.
因此,对于具有更多位数的任何数字,您只会得到一个近似值。周围有一些库可以更精确地处理大数字,包括本线程中提到的那些。
回答by nick
This works for me
这对我有用
const decimalPlaces = value.substring(value.indexOf('.') + 1).length;
const decimalPlaces = value.substring(value.indexOf('.') + 1).length;
This method expects the value to be a standard number.
此方法期望该值是一个标准数字。
回答by Hoffmann
Based on gion_13 answer I came up with this:
基于 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));
It fixes the returning 1 when there are no decimal places. As far as I can tell this works without errors.
当没有小数位时,它修复了返回 1。据我所知,这没有错误。