截断(不舍入)JavaScript中的十进制数字


92

我试图将十进制数字截断到小数位。像这样:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)所做的只是正确的事情,但是却舍入了价值。我不需要四舍五入的价值。希望这是可能的JavaScript。


6
jQuery只是一个框架,您的问题与jQuery不相关。它更多地是关于使用JavaScript进行一些基本计算。我希望您也对非jQuery解决方案感到满意。
Felix Kling

我发现使用Javascript返回仅返回2个小数的计算量太大。我可以轻松地在数据库视图中完成此操作。我意识到这种方法并不适合所有情况,但我想将其放在这里,因为它可以节省很多时间。
MsTapp '16

Answers:


51

更新

因此,无论如何,四舍五入的bug总是会困扰着您,无论您如何努力补偿它们。因此,应该通过精确地用十进制表示法来解决该问题。

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

4
是的,原型无法跨浏览器可靠地工作。与其通过类型系统定义此(有限用途)函数,而是以一种无法可靠工作的方式,为什么不将其放入库中。
Thomas W

3
这不能正常工作。尝试数字17.56和数字=2。应为17.56,但此函数返回17.55。
shendz

2
此函数有两个不一致之处:该函数返回一个字符串,因此1.11.toFixedDown(1) + 221.122代替23.1。也0.toFixedDown(1)应该产生,0但是它产生-0.1
Nick Knowlson 2014年

5
请注意,此功能将消除负号。例如:(-10.2131).toFixedDown(2) // ==> 10.21
rgajrawala 2014年

4
另外,(1e-7).toFixedDown(0) // ==> 1e-7。这是否为1e-(>=7)(例如:1e-81e-9,...)。
rgajrawala 2014年

60

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。有关为什么会发生这种情况的更多信息,请阅读每个计算机科学家应该了解的有关浮点算法的知识。。不幸的是,使用javascript编写消除所有浮点错误源的解决方案非常棘手。在另一种语言中,您可能会使用整数或十进制类型,但使用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之类的方法。您可以在此问题中找到其他javascript BigDecimal库:“是否有一个好的Javascript BigDecimal库?” 这是关于Java语言数学库的一篇不错的博客文章


为什么出乎意料?低于0时更改舍入方向会导致各种算术伪像和糟糕的数学运算。例如,四舍五入到0的数字是任何其他整数的两倍。对于图形,会计和许多其他用途,您将获得糟糕的结果。说句实话,这将会是很难说你的建议是什么如说什么这是不是
Thomas W

准确地说出它的好处-当您想截断小数而不是四舍五入时。
Nick Knowlson 2012年

1
无法使用17.56,因为浏览器给出的是17.56 * 100 = 1755.9999999999998而不是1756
shendz

好点桑兹。我已经用一种解决方案更新了我的答案,该解决方案消除了所有需要此功能的人的浮点错误。
Nick Knowlson

1
如果您不希望小数,则对于小于1的数字将不起作用-除非添加检查,否则truncateDecimals(.12345,0)的结果为NaN,这 if(isNAN(result) result = 0; 取决于您想要的行为。
杰里米·威特默

35
var a = 5.467;
var truncated = Math.floor(a * 100) / 100; // = 5.46

5
这种方法效果很好,但是如果他(或其他稍后查看此答案的人)必须处理负数,则得出的结果可能是不希望的。参见stackoverflow.com/a/9232092/224354
Nick Knowlson 2012年

3
为什么不受欢迎?低于0时更改舍入方向会导致各种算术伪像。
Thomas W

8
舍入和截断有区别。截断显然是此问题寻求的行为。如果我打电话truncate(-3.14)和接收-4回来,我肯定会称之为不可取。
Nick Knowlson

我同意托马斯的观点。无论是平时截断显示还是进行计算,透视图的不同都可能带来。从计算角度来看,这避免了“算术伪像”

3
var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 因此,这不是一个正确的解决方案
Sanyam Jain

22

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

(f - 0.005).toFixed(2)

1
注意:因为这对很小的数字,小数点后3位以上的数字或负数不起作用。试试.0045、5.4678和-5.467
Nick

只要将要减去的值与希望的长度相匹配,就可以使用。您传递给toFixed()的任何内容都必须是小数点后的0。
dmarra

18

考虑利用双波浪号:~~

输入号码。乘以小数点后的有效数字,以便您可以使用截断至零位~~。再除以该乘数。利润。

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链接

编辑:回头看,我也无意间还处理了一些情况,也将小数点后的四舍五入。

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

逻辑在寻找这种用法时有些古怪,可能会受益于快速重构。但它仍然有效。幸运总比不好好。


这是最好的答案。如果您Math以此扩展原型并在执行前检查NaN-s,那将是完美的。
巴特洛梅耶扎莱夫斯基

truncator((10 * 2.9) / 100, 2)返回0.28而不是0.29 ... jsfiddle.net/25tgrzq1
Alex


11

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

truncate = function(number, places) {
  var shift = Math.pow(10, places);

  return ((number * shift) | 0) / shift;
};

1
好决定。使用按位运算符可将值强制转换为int,并or加上0表示“只要保留我已经得到的内容”即可。执行我的~~答案,但只执行一次按位操作。尽管它也具有与书写相同的限制:我们不能超过2 ^ 31
鲁芬,2015年

1
truncate((10 * 2.9) / 100);此代码返回0.28而不是0.29时不正确jsfiddle.net/9pf0732d
Alex

@Alex我猜你意识到...欢迎使用JavaScript!。有修复程序。也许您想分享一个?:D
ruffin

@ruffin我知道这个问题=)我以为这个答案就是解决这个问题的方法。不幸的是,到处都存在这样的问题,我还没有找到确切的解决方案。
亚历克斯


7

@Dogbert的答案可以通过改进Math.trunc,该方法会截断而不是舍入。

舍入和截断有区别。截断显然是此问题寻求的行为。如果我调用truncate(-3.14)并收到-4,那肯定是不受欢迎的。– @NickKnowlson

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

1
并非在所有情况下都有效,即console.log(Math.trunc(9.28 * 100)/ 100); // 9.27
Mike Makuch,

@MikeMakuch,这不是一个问题Math.trunc,但不是9.28 * 100就是927.9999不是928。您可能需要阅读浮点的危险
zurfyx

5

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

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

这是我期望使用的方法
taxilian

4
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

我喜欢将其与字符串一起使用,从而消除了浮点数的细微差别。谢谢!
iCode

4

我认为此功能可能是一个简单的解决方案:

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等找出最佳方法时偶然发现了这一点。我真的很喜欢这种解决方案。死了很简单,但是效果很好(无论如何对于我的用例而言)。
giles123 '18年

但是不要忘记考虑n = 0。
giles123 '18年

3

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

如果您想始终使用3个小数,2个或3个小数,该怎么办,因此,您必须在右边填写前导零

// 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
不符

3
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

3

@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数字四舍五入

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


2

这是一个简单但有效的功能,可将数字截断最多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;
            }

2

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

/* 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);

2

Lodash有一些Math实用程序方法,可以对圆形地板天花板一些给定的小数精度。这留下了尾随零。

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

(注:funcMath.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));

链接到源代码


1

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

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)

1

标记为解决方案的是直到今天我发现的更好的解决方案,但是存在严重的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);
}

1

这是我用的:

var t = 1;
for (var i = 0; i < decimalPrecision; i++)
    t = t * 10;

var f = parseFloat(value);
return (Math.floor(f * t)) / t;

1
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精度)

0

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

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

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])

0

这是您想要做的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


0
Number.prototype.truncate = function(places) {
  var shift = Math.pow(10, places);

  return Math.trunc(this * shift) / shift;
};

0

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

截断(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
    }
 }

0

我对为什么这么简单的问题有这么多不同的答案感到困惑。我发现只有两种方法值得研究。我做了一个快速基准测试,看看使用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%”,因此第二种肯定是我建议使用的方法。

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

基准屏幕截图

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.