从数字中删除不重要的尾随零?

266

我是否错过了一个标准的API调用,可以将数字末尾无关的零去掉?

var x = 1.234000; // to become 1.234
var y = 1.234001; // stays 1.234001

Number.toFixed()Number.toPrecision()并不完全符合我的需求。


6
嗯,1.234000 === 1.234的意思是“1.234000等于1.234”。 - Gumbo
5
如果您执行alert(x),它会弹出没有尾随零的值。 - JKirchartz
78
经过与多个客户的合作,我可以证明即使 1.234000 === 1.234,如果没有必要,客户也不想看到那些多余的零。 - contactmatt
32
使用 parseFloat(n) ? - Mr. Alien
我的问题是:前导零无关紧要吗?从数学角度来看,1.2300表示:小数点后面的4位数字是精确的。而1.23表示:只有2位数字是精确的,可能会四舍五入。例如:比较12m和12.00m。 - Wiimm
显示剩余2条评论
24个回答

6

当Django在文本字段中显示十进制类型值时,我也需要解决这个问题。例如,当值为“1”时,它将显示“1.00000000”。如果值为“1.23”,则会显示“1.23000000”(在“decimal_places”设置为8的情况下)。

对我来说,使用parseFloat不是一个选项,因为它可能不会返回完全相同的值。而toFixed也不是一个选项,因为我不想四舍五入任何内容,所以我创建了一个函数:

function removeTrailingZeros(value) {
    value = value.toString();

    # if not containing a dot, we do not need to do anything
    if (value.indexOf('.') === -1) {
        return value;
    }

    # as long as the last character is a 0 or a dot, remove it
    while((value.slice(-1) === '0' || value.slice(-1) === '.') && value.indexOf('.') !== -1) {
        value = value.substr(0, value.length - 1);
    }
    return value;
}

这个方法可以运行,但效率相当低下,因为每次执行都会创建多个字符串。想象一下在表格中运行此操作,其中许多单元格的值都是.00000。更好的选择是确定有多少个尾随零,然后一次性进行切片。确定字符是什么,在很大程度上是高效的,而拆分字符串则不太高效。 - Derek Nigel Bartram
我做了类似于你的建议 Derek 的事情:https://github.com/rek/remove-trailing-zeros/blob/master/remove-trailing-zeros.js - rekarnar
性能测试在这里:https://jsperf.com/remove-trailing-zeros/1,你是对的,找到零并将其删除要快得多! - rekarnar

5
如果我们有一个表示数字的字符串 s,可以使用.toFixed(digits) Number方法(或任何其他方法)来获取它。为了去除s字符串中无关紧要的尾随零,我们可以使用:
s.replace(/(\.0*|(?<=(\..*))0*)$/, '')

/**********************************
 * Results for various values of s:
 **********************************
 *
 * "0" => 0
 * "0.000" => 0
 * 
 * "10" => 10
 * "100" => 100
 * 
 * "0.100" => 0.1
 * "0.010" => 0.01
 * 
 * "1.101" => 1.101
 * "1.100" => 1.1
 * "1.100010" => 1.10001
 * 
 * "100.11" => 100.11
 * "100.10" => 100.1
 */


在上述replace()中使用的正则表达式如下所解释:
  • 请首先注意正则表达式中的|运算符,表示“或”,因此replace()方法将从s中移除两种可能的子字符串,分别由(\.0*)$部分或((?<=(\..*))0*)$部分匹配。
  • 正则表达式的(\.0*)$部分匹配点符号后面的所有零和其他字符,直到s字符串的末尾。例如,这可能是0.0(匹配并移除.0)、1.0(匹配并移除.0)、0.000(匹配并移除.000)或任何类似的字符串,因此如果此部分正则表达式匹配,则所有结尾的零以及点号本身都将被删除。
  • ((?<=(\..*))0*)$部分仅匹配尾随的零(这些零位于点符号后面,其前面有任意数量的任何字符,直到连续尾随零的开头)。例如,这可能是0.100(尾随的00被匹配并删除)、0.010(最后一个0被匹配并删除,注意(?<=(\..*)),“正向后查找断言”在0*的前面,这部分正则表达式根本不会被匹配),1.100010等。
  • 如果两个部分中的任何一个都不匹配,则不会删除任何内容。例如,这可能是100100.11等。因此,如果输入没有任何尾随零,则其保持不变。

.toFixed(digits) 的一些更多示例(以下示例使用文本值“1000.1010”,但我们可以假定使用变量):

let digits = 0; // Get `digits` from somewhere, for example: user input, some sort of config, etc.

(+"1000.1010").toFixed(digits).replace(/(\.0*|(?<=(\..*))0*)$/, '');
// Result: '1000'

(+"1000.1010").toFixed(digits = 1).replace(/(\.0*|(?<=(\..*))0*)$/, '');
// Result: '1000.1'


(+"1000.1010").toFixed(digits = 2).replace(/(\.0*|(?<=(\..*))0*)$/, '');
// Result: '1000.1'


(+"1000.1010").toFixed(digits = 3).replace(/(\.0*|(?<=(\..*))0*)$/, '');
// Result: '1000.101'


(+"1000.1010").toFixed(digits = 4).replace(/(\.0*|(?<=(\..*))0*)$/, '');
// Result: '1000.101'


(+"1000.1010").toFixed(digits = 5).replace(/(\.0*|(?<=(\..*))0*)$/, '');
// Result: '1000.101'

(+"1000.1010").toFixed(digits = 10).replace(/(\.0*|(?<=(\..*))0*)$/, '');
// Result: '1000.101'

为了玩弄上述在replace()中使用的正则表达式,我们可以访问:https://regex101.com/r/owj9fz/1


1
请注意,正向预查是 JavaScript 的较新添加项,在本评论发布时,Safari 仍不支持它:https://caniuse.com/js-regexp-lookbehind - Jonathan Tran

4

这些解决方案对于非常小的数字并不适用。 http://numeraljs.com/ 为我解决了这个问题。

parseFloat(0.00000001.toFixed(8));
// 1e-8

numeral(0.00000001).format('0[.][00000000]');
// "0.00000001"

它无法处理超过6位精度。如果这个库是为小数而设计的,我会说它是无用的。绝对不推荐使用。 - Daniel Kmak

3

我需要去除所有的尾随零,但至少保留2位小数,包括任何零。
我正在处理由.toFixed(6)生成的6位小数字符串。

预期结果:

var numstra = 12345.000010 // should return 12345.00001
var numstrb = 12345.100000 // should return 12345.10
var numstrc = 12345.000000 // should return 12345.00
var numstrd = 12345.123000 // should return 12345.123

解决方案:
var numstr = 12345.100000

while (numstr[numstr.length-1] === "0") {           
    numstr = numstr.slice(0, -1)
    if (numstr[numstr.length-1] !== "0") {break;}
    if (numstr[numstr.length-3] === ".") {break;}
}

console.log(numstr) // 12345.10

逻辑:

如果字符串的最后一个字符是零,则运行循环函数。
删除最后一个字符并更新字符串变量。
如果更新后的字符串的最后一个字符不是零,则结束循环。
如果更新后的字符串倒数第三个字符是浮点数,则结束循环。


这对我的情况效果最好。必须从二进制中删除尾随的0。 - Robby Lebotha

2
这是我的做法:
parseFloat(number.toString());

这也是解决TypeScript bug的好方法。该bug在某些情况下会将数字更改为字符串。


2

如果由于涉及货币浮点数等原因无法使用浮点数,并且您已经从表示正确数字的字符串开始,那么您可能会发现这个解决方案很方便。它将表示数字的字符串转换为表示无尾随零的数字的字符串。

function removeTrailingZeroes( strAmount ) {
    // remove all trailing zeroes in the decimal part
    var strDecSepCd = '.'; // decimal separator
    var iDSPosition = strAmount.indexOf( strDecSepCd ); // decimal separator positions
    if ( iDSPosition !== -1 ) {
        var strDecPart = strAmount.substr( iDSPosition ); // including the decimal separator

        var i = strDecPart.length - 1;
        for ( ; i >= 0 ; i-- ) {
            if ( strDecPart.charAt(i) !== '0') {
                break;
            }
        }

        if ( i=== 0 ) {
            return strAmount.substring(0, iDSPosition);
        } else {
            // return INTPART and DS + DECPART including the rightmost significant number
            return strAmount.substring(0, iDSPosition) + strDecPart.substring(0,i + 1);
        }
    }

    return strAmount;
}

1
如果您也想处理数字错误,可以使用 Intl.NumberFormatNumber.toLocaleString()
new Intl.NumberFormat().format(0.0100) // "0.01"
new Intl.NumberFormat().format(0.010000000000001) // "0.01"
new Intl.NumberFormat().format(0.009999999999999) // "0.01"

console.log((0.0100).toLocaleString()) // "0.01"
console.log((0.010000000000001).toLocaleString()) // "0.01"
console.log((0.009999999999999).toLocaleString()) // "0.01"

1

所以你想要

var x = 1.234000; // to become 1.234
var y = 1.234001; // stays 1.234001

没有任何附加条件,只需尝试使用Number()

var x  = 1.234000, // to become 1.234
    y  = 1.234001, // stays 1.234001
    x_ = Number(x),
    y_ = Number(y);

console.log(x_,y_);


在 x=0.000001 上失败。 - Matthew Cornelisse
@MatthewCornelisse 你能详细说明一下吗?你期望得到什么结果,实际得到了什么呢? - Redu
对于小值,返回的值使用科学计数法,我认为这并不是所期望的结果。我已添加了一个答案,其中包含了我在类似情况下使用过的函数。 - Matthew Cornelisse
不是针对 0.000001,而是针对 Number(0.0000001),你会得到 1e-7,这是完全正常的 JS 术语,也与本问题无关。然而,如果你仍然想将其转换为十进制字符串,那么只需捕获科学计数法中的最后一位数字(在本例中为 7),并执行 1e-7.toFixed(7) - Redu
我理解科学计数法,但这种方法对于0.0000000001这样的数字是无效的,最后一位将会是0。我只是指出你的答案不能提供任何小值所需的输出。 - Matthew Cornelisse

1
我编写了这个正则表达式来从包含数字的字符串的开头和结尾删除不重要的内容:零、小数点和空格

const rxInsignificant = /^[\s0]+|(?<=\..*)[\s0.]+$|\.0+$|\.$/gm;

let ary = [
"001.230",
"2.",
"3.00",
"1000",
" 0000000000000010000.10000000000000000000000  "];

ary.forEach((str)=>
{
  console.log(`"${str}" becomes "${str.replace(rxInsignificant,'')}"`);
});

不幸的是,Safari仍然不支持2018年正则表达式中的后顾断言规范。自2017年7月28日以来,已经有一个开放的错误报告

好消息是,后顾断言在Firefox和所有Chromium衍生产品中都有效。希望Safari能收到更多的请求,并尽快实现这个标准。

与此同时,我编写了这个函数,以完成相同的任务,而不需要后顾断言:

function createRemoveInsignificantFunction()
{
  const rxLeadingZeros = /^[\s0]+/;
  const rxEndingZeros = /[\s0]+$/;
  function removeInsignificant(str)
  {
    str = str.replace(rxLeadingZeros,'');
    let ary = str.split('.');
    if (ary.length > 1)
    {
      ary[1] = ary[1].replace(rxEndingZeros,'');
      if (ary[1].length === 0)
      {
        return ary[0];
      }
      else
      {
        return ary[0] + '.' + ary[1];
      }         
    }
    return str;
  }
  return removeInsignificant;
}
let removeInsignificant = createRemoveInsignificantFunction();
let ary = [
"001.230",
"2.",
"3.00",
"1000",
" 0000000000000010000.10000000000000000000000  "];
ary.forEach((str)=>
{
  console.log(`"${str}" becomes "${removeInsignificant(str)}"`);
});

当我有更多时间时,我想弄清楚如何用一个不带回顾的正则表达式来完成这个任务。欢迎在下面的评论中超越我。


0

我认为下面的函数可能接近您想要的。我为我的一个应用程序编写了它。它总是以标准表示法输出,没有尾随零。有一些您可能不需要但可以编辑删除。它始终返回至少一个小数位(例如5=>“5.0”)。它还限制为10个小数位。将其用作指南。

const toDecimalStr(value)=>{
  let str=value.toFixed(10).replace(/([0]+)$/,"");
  try {
    if (str.endsWith(".")) str+='0';
  } catch (e) {
    str+='0';
  }
  return str;
}

try catch 是因为不是所有的东西都支持 endsWith,而我懒得去处理。


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