【问题标题】:Truncate (not round off) decimal numbers in javascript在javascript中截断(不四舍五入)十进制数字
【发布时间】:2011-06-22 05:13:47
【问题描述】:

我正在尝试将十进制数字截断为小数位。像这样的:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2) 做了正确的事情,但它使价值四舍五入。我不需要四舍五入的值。希望这在 javascript 中是可能的。

【问题讨论】:

  • jQuery 只是一个框架,您的问题与 jQuery 无关。它更多的是在 JavaScript 中进行一些基本的计算。我希望您也对非 jQuery 解决方案感到满意。
  • 我发现使用 Javascript 让我的计算只返回 2 位小数的工作量太大。我可以在我的数据库视图中轻松地做到这一点。我意识到这种方法并不适合所有情况,但我想把它放在这里,因为它可能会节省很多时间。

标签: javascript floating-point floating-accuracy


【解决方案1】:
var a = 5.467;
var truncated = Math.floor(a * 100) / 100; // = 5.46

【讨论】:

  • 这很好用,但如果他(或稍后查看此答案的其他人)必须处理负数,则会给出可能不受欢迎的结果。见*.com/a/9232092/224354
  • 为什么不受欢迎?低于 0 时更改舍入方向会导致各种算术伪影。
  • 舍入和截断是有区别的。截断显然是这个问题所寻求的行为。如果我打电话给truncate(-3.14) 并收到-4 回复,我肯定会认为这是不可取的。
  • 我同意托马斯的观点。透视的差异可能与您是否通常截断显示或计算有关。从计算的角度来看,这避免了“算术伪像”
  • var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 因此这不是一个正确的解决方案
【解决方案2】:

您可以通过为 toFixed 减去 0.5 来修复舍入,例如

(f - 0.005).toFixed(2)

【讨论】:

  • 注意:因为它不适用于非常小的数字、超过 3 位小数的数字或负数。尝试 .0045、5.4678 和 -5.467
  • 只要您将减去的值与您希望的长度相匹配,这将起作用。传递给 toFixed() 的任何内容都必须是小数点后 0 的数量。
【解决方案3】:

更新

所以,事实证明,无论你多么努力地弥补它们,舍入错误都会一直困扰着你。因此,应该通过精确地用十进制表示数字来解决这个问题。

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

[   5.467.toFixedDown(2),
    985.943.toFixedDown(2),
    17.56.toFixedDown(2),
    (0).toFixedDown(1),
    1.11.toFixedDown(1) + 22];

// [5.46, 985.94, 17.56, 0, 23.1]

基于别人编译的老容易出错的解决方案:

Number.prototype.toFixedDown = function(digits) {
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

【讨论】:

  • 是的,原型不能可靠地跨浏览器工作。与其通过类型系统以一种无法可靠工作的方式定义此(有限用途)函数,不如将其放入库中。
  • 这不起作用。试试数字 17.56 和 digits = 2。应该是 17.56,但这个函数返回 17.55。
  • 此函数的两个不一致之处:此函数返回一个字符串,因此1.11.toFixedDown(1) + 221.122 而不是23.1 结尾。同样0.toFixedDown(1) 应该产生0 但它会产生-0.1
  • 请注意,此函数会删除负号。例如:(-10.2131).toFixedDown(2) // ==> 10.21.
  • 另外,(1e-7).toFixedDown(0) // ==> 1e-7。对1e-(>=7)(例如:1e-81e-9、...)是否如此。
【解决方案4】:

Dogbert 的回答很好,但如果您的代码可能不得不处理负数,Math.floor 本身可能会产生意想不到的结果。

例如Math.floor(4.3) = 4,但是Math.floor(-4.3) = -5

使用类似这样的辅助函数来获得一致的结果:

truncateDecimals = function (number) {
    return Math[number < 0 ? 'ceil' : 'floor'](number);
};

// Applied to Dogbert's answer:
var a = 5.467;
var truncated = truncateDecimals(a * 100) / 100; // = 5.46

这里是这个函数的一个更方便的版本:

truncateDecimals = function (number, digits) {
    var multiplier = Math.pow(10, digits),
        adjustedNum = number * multiplier,
        truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);

    return truncatedNum / multiplier;
};

// Usage:
var a = 5.467;
var truncated = truncateDecimals(a, 2); // = 5.46

// Negative digits:
var b = 4235.24;
var truncated = truncateDecimals(b, -2); // = 4200

如果这不是所需的行为,请在第一行插入对 Math.abs 的调用:

var multiplier = Math.pow(10, Math.abs(digits)),

编辑: shendz 正确地指出,将这个解决方案与a = 17.56 一起使用会错误地产生17.55。有关为什么会发生这种情况的更多信息,请阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic。不幸的是,使用 javascript 编写一个消除所有浮点错误来源的解决方案非常棘手。在另一种语言中,您会使用整数或 Decimal 类型,但使用 javascript...

这个解决方案应该是 100% 准确的,但它也会更慢:

function truncateDecimals (num, digits) {
    var numS = num.toString(),
        decPos = numS.indexOf('.'),
        substrLength = decPos == -1 ? numS.length : 1 + decPos + digits,
        trimmedResult = numS.substr(0, substrLength),
        finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    return parseFloat(finalResult);
}

对于那些需要速度但又想避免浮点错误的人,可以试试BigDecimal.js。您可以在这个 SO 问题中找到其他 javascript BigDecimal 库:"Is there a good Javascript BigDecimal library?",这是一篇关于 math libraries for Javascript 的好博文

【讨论】:

  • 为什么出乎意料?当您低于 0 时更改舍入方向会导致各种算术伪像和蹩脚的数学。例如,两倍于任何其他整数的数字将四舍五入为 0。对于图形、会计和许多其他用途,你会得到糟糕的结果。说实话,很难说你的建议是好的,而不是说它不是
  • 正如它所说的那样很好 - 当你想要截断小数而不是四舍五入时。
  • 不适用于 17.56,因为浏览器给出 17.56 * 100 = 1755.9999999999998 而不是 1756
  • 好点 shendz。我已经用一个解决方案更新了我的答案,为那些需要它的人消除了所有浮点错误。
  • 如果您不想要小数,这不适用于小于 1 的数字 - truncateDecimals(.12345, 0) 会导致 NaN 除非您添加检查:if(isNAN(result) result = 0; 取决于您的行为想要。
【解决方案5】:

这是我对这个主题的看法:

convert.truncate = function(value, decimals) {
  decimals = (decimals === undefined ? 0 : decimals);
  return parseFloat((value-(0.5/Math.pow(10, decimals))).toFixed(decimals),10);
};

这只是一个稍微复杂的版本

(f - 0.005).toFixed(2)

【讨论】:

    【解决方案6】:

    考虑taking advantage of the double tilde: ~~

    输入数字。乘以小数点后的有效数字,这样您就可以将truncate~~ 匹配到零位。将该乘数除以。利润。

    function truncator(numToTruncate, intDecimalPlaces) {    
        var numPower = Math.pow(10, intDecimalPlaces); // "numPowerConverter" might be better
        return ~~(numToTruncate * numPower)/numPower;
    }
    

    我试图拒绝将 ~~ 调用包含在括号中;我相信,操作顺序应该可以正常工作。

    alert(truncator(5.1231231, 1)); // is 5.1

    alert(truncator(-5.73, 1)); // is -5.7

    alert(truncator(-5.73, 0)); // is -5

    JSFiddle link.

    编辑:回首往事,我无意中也处理了四舍五入的情况。

    alert(truncator(4343.123, -2)); // gives 4300.

    寻找这种用法的逻辑有点古怪,并且可能会受益于快速重构。但它仍然有效。好运总比好运好。

    【讨论】:

    • 这是最好的答案。如果你用这个扩展 Math 原型并在执行前检查 NaN-s,那就完美了。
    • truncator((10 * 2.9) / 100, 2) 返回 0.28 而不是 0.29 ... jsfiddle.net/25tgrzq1
    • 不适合我,truncator(1000.12345678, 7) 返回 141.1299975
    • @Alex 如果你想避免浮点错误,use a decimal type。或者我想我们可以重写以使用字符串操作,但这似乎很疯狂。你会注意到similar answers 有同样的问题。
    • @HelpfulPanda 那是因为JavaScript uses 32-bit ints for bitwise operatorsmax 32-bit int is 2,147,483,647100012345678 明显大于 2147483647。如果您确实有大于 32 位的数字(相反,如果您需要那么多有效数字),那么这不是您要寻找的机器人。这是一个快速而肮脏(和快速)的答案,例如 4-5 sig 数字。对于潜在的矫枉过正的完美,try the accepted answer。 ;^D
    【解决方案7】:

    只是指出一个对我有用的简单解决方案

    将其转换为字符串,然后对其进行正则表达式...

    var number = 123.45678;
    var number_s = '' + number;
    var number_truncated_s = number_s.match(/\d*\.\d{4}/)[0]
    var number_truncated = parseFloat(number_truncated_s)
    

    可以简写成

    var number_truncated = parseFloat(('' + 123.4568908).match(/\d*\.\d{4}/)[0])
    

    【讨论】:

      【解决方案8】:

      我想我会使用 | 来回答,因为它很简单而且效果很好。

      truncate = function(number, places) {
        var shift = Math.pow(10, places);
      
        return ((number * shift) | 0) / shift;
      };
      

      【讨论】:

      • 好电话。使用按位运算符将值强制转换为 int,而 oring 与 0 表示“只保留我已经拥有的”。执行我的~~ 答案,但使用单个位运算。虽然它也有与写的相同的限制:We can't go over 2^31.
      • truncate((10 * 2.9) / 100);这个代码返回0.28而不是0.29时不正确jsfiddle.net/9pf0732d
      • @Alex 我猜你意识到了...welcome to JavaScript!。有修复。也许你想分享一个? :D
      • @ruffin 我知道这个问题=)我认为这个答案是解决这个问题的方法。不幸的是,我还没有找到确切的解决方案,到处都是这样的问题。
      【解决方案9】:

      标记为解决方案的解决方案是我直到今天找到的更好的解决方案,但是 0 存在严重问题(例如,0.toFixedDown(2) 给出 -0.01)。所以我建议使用这个:

      Number.prototype.toFixedDown = function(digits) {
        if(this == 0) {
          return 0;
        }
        var n = this - Math.pow(10, -digits)/2;
        n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
        return n.toFixed(digits);
      }
      

      【讨论】:

        【解决方案10】:

        这是我使用的:

        var t = 1;
        for (var i = 0; i < decimalPrecision; i++)
            t = t * 10;
        
        var f = parseFloat(value);
        return (Math.floor(f * t)) / t;
        

        【讨论】:

          【解决方案11】:

          这是一个简单但有效的功能,可将数字截断至小数点后 2 位。

                     function truncateNumber(num) {
                          var num1 = "";
                          var num2 = "";
                          var num1 = num.split('.')[0];
                          num2 = num.split('.')[1];
                          var decimalNum = num2.substring(0, 2);
                          var strNum = num1 +"."+ decimalNum;
                          var finalNum = parseFloat(strNum);
                          return finalNum;
                      }
          

          【讨论】:

            【解决方案12】:
            Number.prototype.trim = function(decimals) {
                var s = this.toString();
                var d = s.split(".");
                d[1] = d[1].substring(0, decimals);
                return parseFloat(d.join("."));
            }
            
            console.log((5.676).trim(2)); //logs 5.67
            

            【讨论】:

            • 我喜欢这适用于字符串,从而消除了浮点数的细微差别。谢谢!
            • console.log((-5).trim(2));抛出 Uncaught TypeError: d[1] is undefined
            【解决方案13】:

            我发现了一个问题:考虑下一种情况:2.1 or 1.2 or -6.4

            如果您总是想要 3 位小数或两位或 wharever 怎么办,所以,您必须完成右边的前导零

            // 3 decimals numbers
            0.5 => 0.500
            
            // 6 decimals
            0.1 => 0.10000
            
            // 4 decimales
            -2.1 => -2.1000
            
            // truncate to 3 decimals
            3.11568 => 3.115
            

            这是 Nick Knowlson 的固定函数

            function truncateDecimals (num, digits) 
            {
                var numS = num.toString();
                var decPos = numS.indexOf('.');
                var substrLength = decPos == -1 ? numS.length : 1 + decPos + digits;
                var trimmedResult = numS.substr(0, substrLength);
                var finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;
            
                // adds leading zeros to the right
                if (decPos != -1){
                    var s = trimmedResult+"";
                    decPos = s.indexOf('.');
                    var decLength = s.length - decPos;
            
                        while (decLength <= digits){
                            s = s + "0";
                            decPos = s.indexOf('.');
                            decLength = s.length - decPos;
                            substrLength = decPos == -1 ? s.length : 1 + decPos + digits;
                        };
                    finalResult = s;
                }
                return finalResult;
            };
            

            https://jsfiddle.net/huttn155/7/

            【讨论】:

            • x = 0.0000 测试 truncateDecimals (x, 2) 失败。返回0。不如预期0.00
            【解决方案14】:

            结果类型仍然是一个数字...

            /* Return the truncation of n wrt base */
            var trunc = function(n, base) {
                n = (n / base) | 0;
                return base * n;
            };
            var t = trunc(5.467, 0.01);
            

            【讨论】:

              【解决方案15】:

              不错的单行解决方案:

              function truncate (num, places) {
                return Math.trunc(num * Math.pow(10, places)) / Math.pow(10, places);
              }
              

              然后调用它:

              truncate(3.5636232, 2); // returns 3.56
              truncate(5.4332312, 3); // returns 5.433
              truncate(25.463214, 4); // returns 25.4632
              

              【讨论】:

              • 我喜欢这个解决方案,但请记住,并非所有浏览器都完全支持它。 (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…)
              • 希望 100% 有效,但 Math.trunc(0.29 * Math.pow(10, 2)) / Math.pow(10, 2) 给你 0.28。老实说,到目前为止我看到的唯一可靠的方法是拆分/切割字符串。
              【解决方案16】:

              这是一个 ES6 代码,可以满足您的需求

              const truncateTo = (unRouned, nrOfDecimals = 2) => {
                    const parts = String(unRouned).split(".");
              
                    if (parts.length !== 2) {
                        // without any decimal part
                      return unRouned;
                    }
              
                    const newDecimals = parts[1].slice(0, nrOfDecimals),
                      newString = `${parts[0]}.${newDecimals}`;
              
                    return Number(newString);
                  };
              
              // your examples 
              
               console.log(truncateTo(5.467)); // ---> 5.46
              
               console.log(truncateTo(985.943)); // ---> 985.94
              
              // other examples 
              
               console.log(truncateTo(5)); // ---> 5
              
               console.log(truncateTo(-5)); // ---> -5
              
               console.log(truncateTo(-985.943)); // ---> -985.94

              【讨论】:

                【解决方案17】:

                使用位运算符截断:

                ~~0.5 === 0
                ~~(-0.5) === 0
                ~~14.32794823 === 14
                ~~(-439.93) === -439
                

                【讨论】:

                【解决方案18】:
                Number.prototype.truncate = function(places) {
                  var shift = Math.pow(10, places);
                
                  return Math.trunc(this * shift) / shift;
                };
                

                【讨论】:

                  【解决方案19】:

                  @Dogbert 的答案可以用Math.trunc 改进,它会截断而不是四舍五入。

                  舍入和截断是有区别的。截断是 显然这个问题正在寻求的行为。如果我打电话 truncate(-3.14) 并收到 -4 回来,我肯定会这样称呼 不可取。 ——@尼克诺森

                  var a = 5.467;
                  var truncated = Math.trunc(a * 100) / 100; // = 5.46
                  
                  var a = -5.467;
                  var truncated = Math.trunc(a * 100) / 100; // = -5.46
                  

                  【讨论】:

                  • 这并不适用于所有情况,即 console.log(Math.trunc(9.28 * 100) / 100); // 9.27
                  • @MikeMakuch 这不是Math.trunc 的问题,而是9.28 * 100927.9999 而不是928。您可能需要阅读The Perils of Floating Point
                  【解决方案20】:

                  @kirilloid 的答案似乎是正确的答案,但是,主要代码需要更新。他的解决方案没有处理负数(有人在评论部分提到过,但在主代码中没有更新)。

                  将其更新为完整的最终测试解决方案:

                  Number.prototype.toFixedDown = function(digits) {
                      var re = new RegExp("([-]*\\d+\\.\\d{" + digits + "})(\\d)"),
                      m = this.toString().match(re);
                      return m ? parseFloat(m[1]) : this.valueOf();
                  };
                  

                  示例用法:

                  var x = 3.1415629;
                  Logger.log(x.toFixedDown(2)); //or use whatever you use to log
                  

                  小提琴:JS Number Round down

                  PS:没有足够的 repo 来评论该解决方案。

                  【讨论】:

                    【解决方案21】:
                    function toFixed(number, digits) {
                        var reg_ex = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)")
                        var array = number.toString().match(reg_ex);
                        return array ? parseFloat(array[1]) : number.valueOf()
                    }
                    
                    var test = 10.123456789
                    var __fixed = toFixed(test, 6)
                    console.log(__fixed)
                    // => 10.123456
                    

                    【讨论】:

                      【解决方案22】:

                      我认为这个函数可能是一个简单的解决方案:

                      function trunc(decimal,n=2){
                        let x = decimal + ''; // string 
                        return x.lastIndexOf('.')>=0?parseFloat(x.substr(0,x.lastIndexOf('.')+(n+1))):decimal; // You can use indexOf() instead of lastIndexOf()
                      }
                      
                      console.log(trunc(-241.31234,2));
                      console.log(trunc(241.312,5));
                      console.log(trunc(-241.233));
                      console.log(trunc(241.2,0));  
                      console.log(trunc(241));

                      【讨论】:

                      • 这篇文章发布两年后,但当我试图找出使用 Math.trunc、regex 等的最佳方法时偶然发现了这一点。我真的很喜欢这个解决方案。死的简单,但工作完美(无论如何对于我的用例)。
                      • 不要忘记考虑 n=0。
                      【解决方案23】:

                      Lodash 有一些数学实用方法,它们可以将 roundfloorceil 数字精确到给定的十进制精度。这会去掉尾随的零。

                      他们采用了一种有趣的方法,使用数字的指数。显然这避免了舍入问题。

                      (注意:func 在下面的代码中是Math.roundceilfloor

                      // Shift with exponential notation to avoid floating-point issues.
                      var pair = (toString(number) + 'e').split('e'),
                          value = func(pair[0] + 'e' + (+pair[1] + precision));
                      
                      pair = (toString(value) + 'e').split('e');
                      return +(pair[0] + 'e' + (+pair[1] - precision));
                      

                      Link to the source code

                      【讨论】:

                        【解决方案24】:

                        我用更短的方法写了一个答案。这是我想出的

                        function truncate(value, precision) {
                            var step = Math.pow(10, precision || 0);
                            var temp = Math.trunc(step * value);
                        
                            return temp / step;
                        }
                        

                        方法可以这样使用

                        truncate(132456.25456789, 5)); // Output: 132456.25456
                        truncate(132456.25456789, 3)); // Output: 132456.254
                        truncate(132456.25456789, 1)); // Output: 132456.2   
                        truncate(132456.25456789));    // Output: 132456
                        

                        或者,如果你想要更短的语法,那么你就去吧

                        function truncate(v, p) {
                            var s = Math.pow(10, p || 0);
                            return Math.trunc(s * v) / s;
                        }
                        

                        【讨论】:

                        • 这是我本来希望使用的方法
                        • 与其他人指出的问题相同,truncate(0.29, 2) 给你 0.28。
                        【解决方案25】:
                        const TO_FIXED_MAX = 100;
                        
                        function truncate(number, decimalsPrecison) {
                          // make it a string with precision 1e-100
                          number = number.toFixed(TO_FIXED_MAX);
                        
                          // chop off uneccessary digits
                          const dotIndex = number.indexOf('.');
                          number = number.substring(0, dotIndex + decimalsPrecison + 1);
                        
                          // back to a number data type (app specific)
                          return Number.parseFloat(number);
                        }
                        
                        // example
                        truncate(0.00000001999, 8);
                        0.00000001
                        

                        适用于:

                        • 负数
                        • 非常小的数字(Number.EPSILON 精度)

                        【讨论】:

                          【解决方案26】:

                          您可以使用字符串。 它检查是否“。”存在,然后删除部分字符串。

                          截断 (7.88, 1) --> 7.8

                          截断 (7.889, 2) --> 7.89

                          截断 (-7.88, 1 ) --> -7.88

                          function  truncate(number, decimals) {
                              const tmp = number + '';
                              if (tmp.indexOf('.') > -1) {
                                  return +tmp.substr(0 , tmp.indexOf('.') + decimals+1 );
                              } else {
                                  return +number
                              }
                           }
                          

                          【讨论】:

                            【解决方案27】:

                            我有点困惑,为什么这么简单的问题会有这么多不同的答案;我看到的只有两种方法似乎值得一看。我使用https://jsbench.me/ 做了一个快速基准测试以查看速度差异。

                            这是目前(2020 年 9 月 26 日)标记为答案的解决方案:

                            function truncate(n, digits) {
                                var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
                                    m = n.toString().match(re);
                                return m ? parseFloat(m[1]) : n.valueOf();
                            };
                            
                            [   truncate(5.467,2),
                                truncate(985.943,2),
                                truncate(17.56,2),
                                truncate(0, 1),
                                truncate(1.11, 1) + 22];

                            但是,这是在做字符串和正则表达式的东西,这通常不是很有效,并且有一个 Math.trunc 函数可以完全完成 OP 想要的,没有小数。因此,您可以轻松地使用它加上一点额外的算术来获得相同的结果。

                            这是我在此线程上找到的另一种解决方案,我会使用它:

                            function truncate(n, digits) {
                                var step = Math.pow(10, digits || 0);
                                var temp = Math.trunc(step * n);
                            
                                return temp / step;
                            }
                            
                            [   truncate(5.467,2),
                                truncate(985.943,2),
                                truncate(17.56,2),
                                truncate(0, 1),
                                truncate(1.11, 1) + 22];
                                

                            第一种方法比第二种方法“慢 99.92%”,所以第二种方法绝对是我推荐使用的方法。

                            好的,回到寻找其他避免工作的方法......

                            【讨论】:

                              【解决方案28】:

                              假设您要将数字 x 截断为 n 位。

                              Math.trunc(x * pow(10,n))/pow(10,n);

                              【讨论】:

                                【解决方案29】:
                                enter code here
                                

                                 let number=5.467;// we want to truncate into 5.46
                                     let result=+number.toString().split("").splice(0,4).join('');
                                     console.log(result);

                                【讨论】:

                                  【解决方案30】:
                                    function toFix(num,n)
                                      {
                                      beforeDecimal=num.toString().split(".",[2])[0];
                                      afterDecimal=num.toString().split(".",[2])[1];
                                      updateAfterDecimal="";
                                      if(afterDecimal != undefined && afterDecimal.length >= n )
                                          updateAfterDecimal =afterDecimal.slice(0,n);
                                      if(afterDecimal != undefined && afterDecimal.length < n )
                                          updateAfterDecimal= afterDecimal.padEnd(n,"0");
                                      if(afterDecimal== undefined)
                                          updateAfterDecimal=updateAfterDecimal.padEnd(n,"0");
                                      console.log(`${beforeDecimal}.${updateAfterDecimal}`);
                                      }
                                      toFix(5.12365889,5); 
                                  

                                  【讨论】:

                                  • 此代码将添加填充“0”是小数点后的位数比所需的位数。
                                  • 例如- 3 位小数(带填充“0”) 5.3 --->5.300 0.01 ---->0.010 0.00001 --->0.000 5.32195--->5.321 -3.66696-- -> -3.666