在JavaScript中截取(而非四舍五入)小数。

131

我正在尝试将小数截断到指定的位数。类似这样:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)可以实现四舍五入,但我不需要进行四舍五入。希望在JavaScript中有这种可能。


7
jQuery只是一个框架,你的问题与jQuery无关,更多地涉及JavaScript中的一些基本计算。我希望你也能接受一个非jQuery的解决方案。 - Felix Kling
我发现使用Javascript让我的计算结果仅保留两位小数太费力了。相比之下,在我的数据库视图中很容易实现。我知道这种方法并不适用于所有情况,但我想分享出来,因为它可能会为某些人节省大量时间。 - MsTapp
32个回答

78

狗伯特的回答不错,但是如果你的代码可能涉及负数,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编写消除所有浮点误差的解决方案相当棘手。在另一种语言中,您可以使用整数或 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库:“有没有一个好的JavaScript BigDecimal库?”这里有一篇关于JavaScript数学库的好博客文章


1
无法使用17.56,因为浏览器会将17.56 * 100 = 1755.9999999999998而不是1756。 - shendz
1
如果你不想保留小数,对于小于1的数字使用truncateDecimals(.12345, 0)将会无法正常工作并返回NaN值,除非你添加一个检查:if(isNAN(result)) result = 0;这取决于你所需要的行为。 - Jeremy Witmer
if(isNan( 缺少一个闭合括号 - lukas.pukenis
+1. 这个函数无法处理整数(没有小数点的数字)。检查 decPos 是否为 -1 可以解决这个问题。像这样:result = decPos === -1 ? num : numS.substr(0, 1 + decPos + digits); - Hari Pachuveetil
啊哈,原来是这样。小整数能正常工作,这可能就是我第一次编写时没注意到的原因。现在已经修复了,谢谢! - Nick Knowlson
显示剩余4条评论

60

更新:

所以,最后发现,无论你如何努力弥补它们,四舍五入的错误总会困扰着你。因此,该问题应该通过在十进制符号中精确表示数字来解决。

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) + 22 的结果为 1.122 而不是 23.1。另外 0.toFixedDown(1) 应该产生 0,但实际上它产生了 -0.1 - Nick Knowlson
5
请注意,此函数会移除负号。例如:(-10.2131).toFixedDown(2) // ==> 10.21 - rgajrawala
4
另外,(1e-7).toFixedDown(0) // ==> 1e-7。对于 1e-(>=7)(例如:1e-81e-9等),也是这样处理的。 - rgajrawala
显示剩余6条评论

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

5
如果他(或稍后查看此答案的其他人)需要处理负数,那么这个方法虽然有效,但可能会产生不希望看到的结果。请参考https://dev59.com/x2445IYBdhLWcg3wWI2L#9232092。 - Nick Knowlson
3
为什么会不理想?当数字进入负数范围时,改变四舍五入方向会导致各种算术错误。 - Thomas W
9
四舍五入和截断是有区别的。这个问题明显是在寻求截断行为。如果我调用 truncate(-3.14) 并收到 -4 的返回值,我肯定会认为这是不可取的。 - Nick Knowlson
我同意Thomas的观点。不同的观点可能源于您通常是为显示还是为计算而截断。从计算的角度来看,这可以避免“算术错误”。 - user334911
3
因此,这不是一个正确的解决方案:var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 - Sanyam Jain

21

您可以通过减去0.5来修正toFixed的四舍五入,例如:

(f - 0.005).toFixed(2)

1
注意:这段代码无法处理非常小的数字、超过三位小数或负数。请尝试输入 0.0045、5.4678 和 -5.467。 - Nick Knowlson
只要你将要减去的值与所需长度匹配,这个方法就能够正常工作。无论你传递给 toFixed() 的是什么,它都需要是小数点后的 0 的数量。 - dmarra

21

简洁的一行解决方案:

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

2
我喜欢这个解决方案,但请记住它并不被所有浏览器完全支持。(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc#Browser_compatibility) - Gene Parcellano
希望它能百分之百地工作,但是Math.trunc(0.29 * Math.pow(10, 2)) / Math.pow(10, 2)会给你0.28。说实话,到目前为止我看到的唯一可靠的方法是通过拆分/切割字符串来实现。 - HelpfulPanda
@HelpfulPanda 哎呀,真是的 :|我目前在解决这个问题上遇到了困难,所以我想我会按照你提出的方法回退到字符串 :) - stackoverflow

19

考虑利用双波浪号:~~

取数字。将小数点后的有效数字乘以一个倍数,这样你就可以使用~~截断到零位。然后再除以该倍数。获利。

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

我试图避免将~~调用包含在括号中; 我相信操作顺序应该可以正确地工作。

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

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

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

JSFiddle链接

编辑:回顾一下,我无意中还处理了小数点左侧的四舍五入。

alert(truncator(4343.123, -2)); // 得到4300。

这种用法的逻辑看起来有点奇怪,可能会受益于快速重构。但它仍然有效。比好运气更好。


这是最好的答案。如果您使用此扩展Math原型并在执行之前检查NaN,它将是完美的。 - Liglo App
truncator((10 * 2.9) / 100, 2) 返回的结果是0.28而不是0.29... https://jsfiddle.net/25tgrzq1/ - Alex
对我不起作用,truncator(1000.12345678, 7) 返回141.1299975。 - HelpfulPanda
@Alex 如果您想避免浮点数错误,请使用十进制类型。否则,我们可以重写使用字符串操作,但那似乎太疯狂了。你会发现类似的答案也有相同的问题。 - ruffin
@HelpfulPanda 这是因为JavaScript在位运算符中使用32位整数最大的32位整数是2,147,483,647,而1000123456782147483647要大得多。如果您确实有超过32位的数字(更确切地说,如果您需要那么多有效数字),那么这不是您要寻找的答案。对于可能只需要4-5个有效数字的快速且简单的答案,可以使用此方法。对于可能过度完美的解决方案,请尝试接受的答案。;^D - ruffin

11

我想使用|来回答问题,因为它既简单又有效。

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

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

1
好的调用。使用位运算符将值强制转换为int,并且与0进行“或”操作意味着“只保留我已经得到的”。执行与我的~~答案相同的操作,但只需进行一次位运算。尽管它也有与写入相同的限制:我们不能超过2 ^ 31 - ruffin
1
当运行代码 truncate((10 * 2.9) / 100); 时,返回的结果是0.28而不是0.29。https://jsfiddle.net/9pf0732d/ - Alex
@Alex,我猜你已经意识到了...欢迎来到JavaScript!。这个问题是可以解决的。也许你想分享一下解决方法? :D - ruffin
@ruffin 我知道这个问题 =) 我以为这个答案是解决这个问题的方法。不幸的是,我还没有找到确切的解决方案,到处都是这样的问题。 - Alex

9

使用位运算符进行截断:

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

这是如何工作的参考:https://dev59.com/Lms05IYBdhLWcg3wCNlG - jperelli
如何将数字截断为两位小数? - Salman A

9

@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
1
@MikeMakuch,这不是Math.trunc的问题,而是9.28 * 100得到的结果是927.9999而不是928。你可能需要阅读一下《浮点数的危险》(The Perils of Floating Point)的内容。 - zurfyx

7

我用一种更简短的方法写了一个答案。以下是我得到的结果:

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
和其他人指出的一样,truncate(0.29, 2)会给你返回0.28。 - HelpfulPanda

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