如何在Javascript中获取浮点数的小数位?

45

我想要的是与Number.prototype.toPrecision()几乎相反的功能,也就是说,当我有一个数字时,它有多少位小数?例如:

(12.3456).getDecimals() // 4

3
15 - Math.ceil(Math.log(x) / Math.log(10)) - Ignacio Vazquez-Abrams
可能是重复的问题: https://dev59.com/tXM_5IYBdhLWcg3wQQpd - Nir Alfasi
1
x = 12.3456; var precision = String(x).replace('.', '').length - x.toFixed().length; - Malitta N
@alfasin 不这么认为。在这种情况下,OP并不想限制精度,他们只想计算小数点的数量。 - JaredPar
@blackpla9ue你的解决方案将显示错误的结果, 比如对于 x = 0.0000005,因为它是以指数格式表示的: 5e-7. - Boolean_Type
12个回答

61

如果有人想知道如何更快地做到这一点(而不需要转换为字符串),这里是一个解决方案:

function precision(a) {
  var e = 1;
  while (Math.round(a * e) / e !== a) e *= 10;
  return Math.log(e) / Math.LN10;
}

编辑:一个覆盖边界情况的更完整的解决方案:

function precision(a) {
  if (!isFinite(a)) return 0;
  var e = 1, p = 0;
  while (Math.round(a * e) / e !== a) { e *= 10; p++; }
  return p;
}

7
如果你将NaN传入此函数,它会进入无限循环。 - jarib
1
这是一个不错的答案,但我建议您检查边缘情况,例如NaN或无穷大,以避免无限循环。此外,如果您返回Math.round(Math.log(e) / Math.LN10),可能会更好。 - Justin
2
只是为了好玩,可以将此代码与上面的字符串答案放在 jsperf 中进行比较。它大约快了8倍:http://jsperf.com/getting-numerical-precision/2 - STRML
1
请注意,现有版本如果输入无效(例如空字符串),仍会导致无限循环。 - Christian Grün
2
浮点错误在n = 0.44399999999999995precision(n) == 18的情况下出现,而面值精度应为17。 - hackape
显示剩余10条评论

33

一个可能的解决方案(取决于应用程序):

var precision = (12.3456 + "").split(".")[1].length;

9
split 方法返回的是一个字符数组,因为它将字符串拆分成了单个字符。所以 (1.0 + "").split(".")[1].length; 报错了,因为我们无法访问单个字符数组的第二个元素([1])。 - Piotr Aleksander Chmielowski
1
应该可以这样实现:function getPrecision(number) { var n = number.toString().split("."); return n.length > 1 ? n[1].length : 0; } - Christian Grün
这个“被接受”的答案对于一般情况是不正确的,正如Piotr在上面提到的。此外,这个问题是一个重复 - Dan Dascalescu
1
(12.3456 + '.').split('.')[1].length; 可以适用于所有情况,用来获取小数位数。 - Heath Dutton
1
当您输入没有小数的数字或带有点号的字符串(例如wat.wat)时,它将失败。改进后的代码如下:function precision(num) { if (isNaN(+num)) return 0; const decimals = (num + '').split('.')[1]; if (decimals) return decimals.length; return 0; } - mtx

4

没有原生函数可以确定小数点位数。您可以将数字转换为字符串,然后计算小数点分隔符.的偏移量来确定小数点位数:

Number.prototype.getPrecision = function() {
    var s = this + "",
        d = s.indexOf('.') + 1;

    return !d ? 0 : s.length - d;
};

(123).getPrecision() === 0;
(123.0).getPrecision() === 0;
(123.12345).getPrecision() === 5;
(1e3).getPrecision() === 0;
(1e-3).getPrecision() === 3;

但是浮点数的特性容易让人感到困惑。例如,1 可能会被表示为 0.00000000989 或者其他值。 我不确定上述方法在实际应用中的表现如何。


4
如果你所说的“精度”是指“小数位数”,那是不可能的,因为浮点数是二进制的。它们没有小数位数,大多数具有少量小数位数的值在二进制中具有循环数字,当它们被转换回十进制时,并不能得到原始的十进制数。
任何处理浮点数“小数位数”的代码都可能在某些数字上产生意外结果。

好的,知道了。无论如何,我只处理十进制数是如何显示的,所以将它们更改为字符串并用“.”分割似乎是答案。 - JussiR

2
基于 @blackpla9ue 的评论并考虑数字指数格式:
function getPrecision (num) {
  var numAsStr = num.toFixed(10); //number can be presented in exponential format, avoid it
  numAsStr = numAsStr.replace(/0+$/g, '');

  var precision = String(numAsStr).replace('.', '').length - num.toFixed().length;
  return precision;  
}

getPrecision(12.3456);         //4
getPrecision(120.30003300000); //6, trailing zeros are truncated
getPrecision(15);              //0
getPrecision(120.000))         //0
getPrecision(0.0000005);       //7
getPrecision(-0.01))           //2

1
尝试以下内容。
function countDecimalPlaces(number) { 
  var str = "" + number;
  var index = str.indexOf('.');
  if (index >= 0) {
    return str.length - index - 1;
  } else {
    return 0;
  }
}

1

基于@boolean_Type处理指数的方法,但避免使用正则表达式:

function getPrecision (value) {
    if (!isFinite(value)) { return 0; }

    const [int, float = ''] = Number(value).toFixed(12).split('.');

    let precision = float.length;
    while (float[precision - 1] === '0' && precision >= 0) precision--;

    return precision;
}

1

5622890.31 次/秒(比原来慢了 91.58%):

function precision (n) {
    return (n.toString().split('.')[1] || '').length
}
precision(1.0123456789)

33004904.53次操作/秒(慢50.58%):

function precision (n) {
    let e = 1
    let p = 0
    while(Math.round(n * e) / e !== n) {
        e *= 10
        p++
    }
    return p
}
precision(1.0123456789)

每秒执行62610550.04个操作(比原来慢6.25%):

function precision (n) {
    let cur = n
    let p = 0
    while(!Number.isInteger(cur)) {
        cur *= 10
        p++
    }
    return p
}
precision(1.0123456789)

66786361.47 ops/s(最快):

function precision (n) {
    let cur = n
    let p = 0
    while(Math.floor(cur) !== cur) {
        cur *= 10
        p++
    }
    return p
}
precision(1.0123456789)

正确性应该比速度更重要,最快的版本提供了精确度(4.3573)== 16。 - Lauri
@Lauri 谢谢你发现了一个错误!我完全同意你的观点,并已删除了最后两个实施方案。 - Ilya Ordin

1
假设数字是有效的。
let number = 0.999; 
let noOfPlaces = number.includes(".") //includes or contains
                        ? number.toString().split(".").pop().length
                        : 0;  

1

这里有两个例子,一个使用库(BigNumber.js),另一个不使用库。假设您想检查给定的输入数字(inputNumber)的小数位数是否小于或等于最大小数位数(tokenDecimals)。

使用 BigNumber.js

import BigNumber from 'bignumber.js'; // ES6
// const BigNumber = require('bignumber.js').default; // CommonJS

const tokenDecimals = 18;
const inputNumber = 0.000000000000000001;
// Convert to BigNumber
const inputNumberBn = new BigNumber(inputNumber);

// BigNumber.js API Docs: http://mikemcl.github.io/bignumber.js/#dp
console.log(`Invalid?: ${inputNumberBn.dp() > tokenDecimals}`);

没有BigNumber.js
function getPrecision(numberAsString) {
  var n = numberAsString.toString().split('.');
  return n.length > 1
    ? n[1].length
    : 0;
}

const tokenDecimals = 18;
const inputNumber = 0.000000000000000001;

// Conversion of number to string returns scientific conversion
// So obtain the decimal places from the scientific notation value
const inputNumberDecimalPlaces = inputNumber.toString().split('-')[1];

// Use `toFixed` to convert the number to a string without it being
// in scientific notation and with the correct number decimal places
const inputNumberAsString = inputNumber.toFixed(inputNumberDecimalPlaces);

// Check if inputNumber is invalid due to having more decimal places
// than the permitted decimal places of the token
console.log(`Invalid?: ${getPrecision(inputNumberAsString) > tokenDecimals}`);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接