JS 中 Math.floor、Math.trunc 与 parseInt 取整区别

作者:异步漫步时间:2026-11-12 16:01:29浏览:444

深入对比 JavaScript 中 Math.floor、Math.trunc 与 parseInt 的取整原理、正负数舍入差异、隐式类型转换机制及工程选型指南。

在 JavaScript 中将数值转为整数时,Math.floor、Math.trunc 与 parseInt 是开发者接触最多的三个工具。虽然在处理正数(如 4.8 都会输出 4)时它们看似等效,但底层逻辑截然不同:Math.floor 严格执行数学意义上的向下舍入(朝负无穷大方向);Math.trunc 则是纯粹的小数截断(朝零方向抹除小数部分);而 parseInt 本质是字符串词法解析器,会先将输入转为文本并逐字符扫描。本文通过数轴映射、执行机制与极端边界值,系统梳理三者的区别与工程选型。
JS 中 Math.floor、Math.trunc 与 parseInt 取整区别
JavaScript 取整机制三者全景与核心定位概览

一、三者的核心定位与基础行为

理解取整区别的前提,是明确 JavaScript 对这三个方法的规范设计定位。它们分属于数学库(Math 内置对象)与全局函数,分别面向不同的运算诉求:

  • Math.floor(x):数学向下取整函数。返回小于或等于指定数值的最大整数(Floor 意为“地板”,即把数字压向数轴下方)。
  • Math.trunc(x):ECMAScript 2015 (ES6) 引入的截断函数。它的行为非常单纯:直接丢弃输入数值的所有小数部分,仅保留整数位(Truncate 意为“截短”,即向零舍入)。
  • parseInt(string, radix):全局字符串解析函数。它的核心任务是从一段字符文本中提取有效整数字符,并按照给定的进制(radix)将其解析为十进制数字。

在基础正数场景下,三者的运行结果完全一致:

// 正数输入场景
console.log(Math.floor(4.9));  // 4
console.log(Math.trunc(4.9));  // 4
console.log(parseInt(4.9, 10)); // 4

console.log(Math.floor(4.1));  // 4
console.log(Math.trunc(4.1));  // 4
console.log(parseInt(4.1, 10)); // 4

正是因为正数场景下的输出完全重合,许多初学者在编写业务代码时容易随意替换它们,从而为后续涉及负数、特殊类型或边界计算时埋下隐患。

二、负数区间的关键分水岭:朝负无穷 vs 向零截断

一旦输入值落入负数区间,Math.floor 与另外两者的逻辑便产生了本质分歧。这是三者在日常开发中最容易被触发的差异点。

假设输入值为 -4.3:

  • Math.floor(-4.3):在数轴上,小于等于 -4.3 的最大邻近整数是 -5。因为 -5 < -4.3,所以它继续朝负无穷方向滑动,结果为 -5。
  • Math.trunc(-4.3):直接将小数点后的 .3 抹除,保留符号与整数位 -4,朝坐标原点 0 的方向靠拢,结果为 -4。
  • parseInt(-4.3, 10):转换为字符串 "-4.3",遇到小数点停止解析,输出 -4。
// 负数输入场景对比
console.log(Math.floor(-4.3));  // -5
console.log(Math.trunc(-4.3));  // -4
console.log(parseInt(-4.3, 10)); // -4

// 靠近 0 的负浮点数
console.log(Math.floor(-0.7));  // -1 (向下跨越至 -1)
console.log(Math.trunc(-0.7));  // -0 (保持在 0 区间)
console.log(parseInt(-0.7, 10)); // -0 (保持在 0 区间)
JS 中 Math.floor、Math.trunc 与 parseInt 取整区别
图 1:数轴映射与舍入方向对比(负数区间 Math.floor 与 Math.trunc/parseInt 分化)
数学关系说明:对于任意实数 $x$,Math.floor(x) <= x 恒成立。但在负数时,Math.trunc(x) >= x(例如 -4 > -4.3)。在实现网格对齐、分页索引、瓦片地图计算时,必须依赖 Math.floor 的单调递增性;而在计算几何距离、坐标切分或纯粹去除精度残余时,通常应选择 Math.trunc。

三、parseInt 的特殊机制:字符串解析与潜在陷阱

很多开发者习惯使用 parseInt(val) 对变量取整,但必须强调:parseInt 绝对不是一个数学取整函数。它的输入参数在规范中定义为字符串 parseInt(string, radix)。

当传入数字时,JavaScript 引擎会悄悄执行隐式类型转换步骤:

  1. 通过规范内置的 ToString(val) 将入参强制序列化为字符串。
  2. 跳过字符串起始的连续空白字符,并检测可选的正负符号位(+ 或 -)。
  3. 依照传入的 radix 逐个字符向后扫描,直到遇到第一个无法识别为该进制有效数字的字符时立即终止。
  4. 将截取到的数字子串解析为整型并返回;若开头无合法字符则返回 NaN。
JS 中 Math.floor、Math.trunc 与 parseInt 取整区别
图 2:parseInt 内部执行流水线与类型转换漏斗

陷阱 1:科学计数法导致的恶性 Bug

JavaScript 引擎在打印或转换极端微小或巨大的浮点数时,会自动启用科学计数法表示。当这种数字被传入 parseInt 时,会导致完全荒谬的解析结果:

// 极小浮点数(小于 10^-6 自动启用科学计数法)
let num1 = 0.0000005;
console.log(String(num1));        // "5e-7"
console.log(Math.floor(num1));    // 0
console.log(Math.trunc(num1));    // 0
console.log(parseInt(num1, 10));  // 5 !!!(扫描到 '5',遇 'e' 截断终止)

// 极大数值(大于等于 10^21 自动启用科学计数法)
let num2 = 1000000000000000000000; // 1e+21
console.log(String(num2));        // "1e+21"
console.log(Math.floor(num2));    // 1e+21
console.log(Math.trunc(num2));    // 1e+21
console.log(parseInt(num2, 10));  // 1 !!!(扫描到 '1',遇 'e' 截断终止)
生产警示:永远不要使用 parseInt 对可能产生微小值(如缩放系数、权重分配、经纬度偏移)或超大统计数值进行取整!此时 Math.trunc 是唯一安全且语义明确的原生选择。

陷阱 2:必须显式传入 radix 进制基数

parseInt 的第二个参数 radix 表示基数(范围 2 到 36)。在历史旧版本规范中,以 "0" 开头的字符串有时会被引擎误当作八进制解析。虽然现代 ES5+ 规范规定未指定 radix 时默认按十进制解析(除非以 "0x" 或 "0X" 开头识别为十六进制),但始终显式传入 10 是前端工程代码规范的基本要求:

// 规范推荐:显式传入 radix
parseInt("08", 10);   // 8
parseInt("1010", 2);  // 10 (按二进制解析)
parseInt("0x1A", 16); // 26 (按十六进制解析)

陷阱 3:带单位文本的提取优势

parseInt 唯一的正向适用场景,正是利用其“遇到非数字字符即终止”的特性,从 CSS 样式字符串或带单位的数据中提取有效整数:

// 提取单位字符串
let widthStr = "120.5px";

console.log(Math.floor(widthStr));    // NaN (无法直接转为合法数字)
console.log(Math.trunc(widthStr));    // NaN (无法直接转为合法数字)
console.log(parseInt(widthStr, 10));  // 120 (遇到 '.' 或 'p' 停止,成功提取数字)

四、非数字类型与极端边界值对比

在实际生产中,由于接口返回值缺失或未做严格入参校验,传入取整函数的变量可能是 null、undefined、布尔值或空字符串。此时 Math 系列与 parseInt 的转换分支完全不同。

Math.floor 和 Math.trunc 底层调用的是抽象操作 ToNumber(),而 parseInt 底层调用的是抽象操作 ToString()。

输入值 (input) ToNumber(input) ToString(input) Math.floor Math.trunc parseInt(input, 10)
"42" 42 "42" 42 42 42
"42.8px" NaN "42.8px" NaN NaN 42
"" (空字符串) 0 "" 0 0 NaN
null 0 "null" 0 0 NaN
undefined NaN "undefined" NaN NaN NaN
true / false 1 / 0 "true" / "false" 1 / 0 1 / 0 NaN
[] (空数组) 0 "" 0 0 NaN
Infinity Infinity "Infinity" Infinity Infinity NaN
JS 中 Math.floor、Math.trunc 与 parseInt 取整区别
图 3:极端边界值与特殊数据类型处理矩阵

从对比矩阵可以看出:如果你期望对非法输入(如 null 或空字符串)严格报错或识别为非数字,parseInt 反而会输出 NaN;而 Math.floor(null) 会直接输出 0,这可能会悄然掩盖前端数据传递中的空指针 Bug。

五、性能表现、位运算取整与工程选型建议

1. 性能考量

在执行速度上,Math.trunc 与 Math.floor 由 V8 等 JavaScript 引擎通过底层 C++ 内联指令或汇编级浮点指令直接实现,执行性能远高于 parseInt。

parseInt 涉及强制转字符串、申请字符串缓冲区、按字符扫描文本等繁琐环节,基准测试中处理数值的吞吐量通常只有 Math.trunc 的 1/5 到 1/10。因此,在循环或高频动画帧中对数值取整,绝不应该使用 parseInt。

2. 位运算取整(| 0, ~~)的陷阱

很多代码库中常见使用按位或 x | 0 或双按位非 ~~x 进行极速取整。位运算在行为上也是“向零截断”,其底层执行逻辑是将浮点数转换为 32 位有符号整数(ToInt32)。

这带来了极其致命的边界问题:一旦数值超出 32 位整型范围(大于 2147483647 或小于 -2147483648),位运算会直接溢出翻转为负数或错误值!

// 32 位整型溢出演示
let bigNum = 3000000000.5; // 三十亿点五

console.log(Math.trunc(bigNum)); // 3000000000 (正确)
console.log(bigNum | 0);          // -1294967296 (严重溢出!直接变成负数)
console.log(~~bigNum);            // -1294967296 (同样溢出)

因此,除非在绝对确定范围(如像素坐标在 0-4096 之内)的高性能物理引擎中,否则现代 JavaScript 规范下应统一使用语义清晰且无溢出风险的 Math.trunc。

JS 中 Math.floor、Math.trunc 与 parseInt 取整区别
图 4:取整方法工程选型决策树与避坑备忘录

3. 综合选型落地法则

在实际开发中,可以按照以下三个判断准则快速定型:

  1. 纯数字的小数抹除(丢弃小数):一律首选 Math.trunc(x)。它是 ES6 官方推荐的向零截断标准实现,语义精准、执行高效,且无论正负数都不改变数字向 0 靠拢的直觉。
  2. 数学坐标、分页或向下网格对齐:使用 Math.floor(x)。当算法需要严格满足“返回值不超过输入值”这一定理时(例如 floor(x) <= x),无论正负数都必须坚持使用 Math.floor。
  3. 带单位字符串中提取整数:使用 parseInt(str, 10),并务必显式传入基数 10。
作者最新文章
相关文章
更多