如何向上、向下或四舍五入数字

控制 JavaScript 在使用不同舍入模式格式化小数时的舍入方式

介绍

在格式化数字以显示时,通常需要对小数值进行舍入。例如,$2.567 需要变为 $2.57,3.891 米的测量值可能显示为 4 米。数字的舍入方式会影响精度、用户预期和业务逻辑。

不同场景需要采用不同的舍入策略。有时需要向上舍入以确保产品收费充足,有时需要向下舍入以控制预算。大多数情况下,为了保持精度,会采用四舍五入到最接近的值。

JavaScript 通过 Intl.NumberFormat API 提供了九种不同的舍入模式。这些模式控制当数字处于两个可能值之间时的舍入方式。本课程将介绍三种最常用的舍入模式,并说明其他模式的适用场景。

JavaScript 默认如何舍入数字

当你在格式化数字时未指定舍入模式,JavaScript 会采用一种称为 halfExpand 的策略。该模式会将数值舍入到最接近的可能值,当数字正好处于两个值的中间时,会向零的反方向舍入。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0
});

console.log(formatter.format(2.4));
// Output: "2"

console.log(formatter.format(2.5));
// Output: "3"

console.log(formatter.format(2.6));
// Output: "3"

数值 2.4 更接近 2,因此会向下舍入为 2。数值 2.6 更接近 3,因此会向上舍入为 3。数值 2.5 正好处于 2 和 3 的中间,因此 halfExpand 模式会将其向零的反方向舍入为 3。

这种默认行为符合大多数人在学校学到的内容,也符合日常计算的预期。它能在多次计算中均匀分布舍入误差,适用于通用数字格式化。

理解“远离零”是什么意思

“远离零”这个短语描述了当数字正好处于两个可能值的中间时的舍入方向。对于正数,远离零意味着向上取整。对于负数,远离零意味着向下取整(绝对值变大)。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0
});

console.log(formatter.format(2.5));
// Output: "3"

console.log(formatter.format(-2.5));
// Output: "-3"

2.5 和 -2.5 都是远离零舍入。对于 2.5,远离零就是向正无穷方向,结果为 3。对于 -2.5,远离零就是向负无穷方向,结果为 -3。两种情况下,绝对值都变大。

使用 ceil 向上取整

ceil 舍入模式总是向正无穷方向取整。对于正数来说,这意味着向上取整。对于负数来说,这意味着向零方向取整。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "ceil"
});

console.log(formatter.format(2.1));
// Output: "3"

console.log(formatter.format(2.9));
// Output: "3"

console.log(formatter.format(-2.1));
// Output: "-2"

console.log(formatter.format(-2.9));
// Output: "-2"

当你需要确保数值绝不会小于所需时,这种模式非常有用。例如,如果你计算出一个箱子可以装 2.3 个物品,你就需要 3 个箱子。如果你计算出一个任务需要 1.1 天,你就需要安排 2 天。

const boxFormatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "ceil"
});

const itemsPerBox = 5;
const totalItems = 12;
const boxesNeeded = totalItems / itemsPerBox;

console.log(boxFormatter.format(boxesNeeded));
// Output: "3"

计算结果为 2.4 个箱子,但你无法订购半个箱子。向上取整可以确保容量足够。

使用 floor 向下取整

floor 舍入模式总是向负无穷方向取整。对于正数来说,这意味着向下取整。对于负数来说,这意味着远离零方向取整。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "floor"
});

console.log(formatter.format(2.1));
// Output: "2"

console.log(formatter.format(2.9));
// Output: "2"

console.log(formatter.format(-2.1));
// Output: "-3"

console.log(formatter.format(-2.9));
// Output: "-3"

当你需要保守估算或希望不超出限制时,这种模式非常有用。例如,如果你的预算是 $100.87,你可能会显示为 $100,以避免意外超支。

const budgetFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0,
  roundingMode: "floor"
});

const availableBudget = 100.87;

console.log(budgetFormatter.format(availableBudget));
// Output: "$100"

向下舍入可确保显示的金额始终在实际预算范围内可实现。

使用 halfExpand 进行最接近值舍入

虽然 halfExpand 是默认模式,但你可以显式指定它,以便在代码中明确表达你的意图。该模式会将数值舍入到最接近的值,并在遇到正好一半的情况时,向远离零的方向舍入。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 1,
  roundingMode: "halfExpand"
});

console.log(formatter.format(2.14));
// Output: "2.1"

console.log(formatter.format(2.15));
// Output: "2.2"

console.log(formatter.format(2.16));
// Output: "2.2"

数值 2.14 更接近 2.1,因此舍入为 2.1。数值 2.16 更接近 2.2,因此舍入为 2.2。数值 2.15 正好处于中间,因此会向远离零的方向舍入为 2.2。

该模式适用于大多数数字格式化任务,因为它能在多次计算中最小化总舍入误差。每个正好一半的值都有相等的概率出现在零的正负两侧,因此舍入方向会随着时间的推移而平衡。

结合小数位数设置使用舍入模式

舍入模式与小数位数设置配合使用,以控制最终输出。maximumFractionDigits 选项决定显示多少位小数,roundingMode 决定如何处理落在可表示数之间的值。

const ceilFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
  roundingMode: "ceil"
});

console.log(ceilFormatter.format(10.001));
// Output: "$10.01"

console.log(ceilFormatter.format(10.999));
// Output: "$11.00"

保留两位小数时,10.001 需要舍入为 10.00 或 10.01。ceil 模式会向上舍入,得到 10.01。数值 10.999 会向上舍入为 11.00。

使用 trunc 向零方向舍入

trunc 舍入模式会向零方向舍入,也就是去除数字的小数部分。对于正数来说,这是向下舍入;对于负数来说,则是向上舍入。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "trunc"
});

console.log(formatter.format(2.1));
// Output: "2"

console.log(formatter.format(2.9));
// Output: "2"

console.log(formatter.format(-2.1));
// Output: "-2"

console.log(formatter.format(-2.9));
// Output: "-2"

该模式会直接截断小数部分。当你只想显示数字的整数部分而不考虑小数值的舍入时,这种方式非常有用。

使用 expand 进行远离零的舍入

expand 舍入模式会远离零方向舍入。对于正数来说,这是向上取整;对于负数来说,则是向下取整(绝对值变大)。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "expand"
});

console.log(formatter.format(2.1));
// Output: "3"

console.log(formatter.format(2.9));
// Output: "3"

console.log(formatter.format(-2.1));
// Output: "-3"

console.log(formatter.format(-2.9));
// Output: "-3"

此模式确保每次舍入都增加数值的绝对值。在金融场景中,如果你希望在保守方向上优先保证精度而不是低估结果,这种方式会很有用。

理解五种四舍五入到最近值的模式

half 开头的五种模式都采用四舍五入到最近值的方式,但它们在处理正好一半的情况时各有不同。这些模式可以让你精确控制当数值正好处于两个可表示数之间时的取舍方式。

halfCeil 模式会将正好一半的数值向正无穷方向舍入。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "halfCeil"
});

console.log(formatter.format(2.5));
// Output: "3"

console.log(formatter.format(-2.5));
// Output: "-2"

halfFloor 模式会将正好一半的数值向负无穷方向舍入。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "halfFloor"
});

console.log(formatter.format(2.5));
// Output: "2"

console.log(formatter.format(-2.5));
// Output: "-3"

halfTrunc 模式会将正好一半的数值向零方向舍入。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "halfTrunc"
});

console.log(formatter.format(2.5));
// Output: "2"

console.log(formatter.format(-2.5));
// Output: "-2"

halfEven 模式会将正好一半的数值舍入到最接近的偶数。这种模式有时被称为银行家舍入,因为它可以减少金融计算中的偏差。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 0,
  roundingMode: "halfEven"
});

console.log(formatter.format(2.5));
// Output: "2"

console.log(formatter.format(3.5));
// Output: "4"

console.log(formatter.format(4.5));
// Output: "4"

数值 2.5 会舍入为 2,因为 2 是偶数。数值 3.5 会舍入为 4,因为 4 是偶数。数值 4.5 也会舍入为 4,因为 4 是偶数。

为你的应用选择合适的舍入模式

你选择的舍入模式取决于业务需求以及所展示数据的特性。不同的场景需要采用不同的策略。

当你需要确保充足时,请使用 ceil。容量规划、库存计数和时间预估通常需要向上舍入,以保证资源充足。

当您需要保持在限制范围内时,请使用 floor。预算显示、配额跟踪和折扣计算通常需要向下舍入,以避免超出可用资源。

对于对准确性有要求但不需要极高精度的通用显示,建议使用 halfExpand。这也是默认选项,适用于大多数数字格式化场景。

在需要最小化累计舍入偏差的金融计算中,请使用 halfEven。该模式确保在大量计算中,舍入误差不会持续偏向某一方向。

当您只想显示数字的整数部分而不对小数部分进行舍入时,请使用 trunc

结合货币格式化使用舍入模式

舍入模式与货币格式化可以自然结合。不同企业对货币数值的舍入有不同规则,roundingMode 选项可帮助您实现这些规则。

const retailPrice = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
  roundingMode: "ceil"
});

const wholesalePrice = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
  roundingMode: "floor"
});

const calculatedPrice = 19.874;

console.log(retailPrice.format(calculatedPrice));
// Output: "$19.88"

console.log(wholesalePrice.format(calculatedPrice));
// Output: "$19.87"

零售商可能会将价格向上舍入以确保盈利,而批发商则可能向下舍入以保持竞争力。相同的计算价格,根据舍入模式中编码的业务规则,显示出的数值会有所不同。