JavaScript 浮点数
最后修改日期:2025 年 4 月 1 日
JavaScript 使用基于 IEEE 754 标准的单一浮点类型,实现为 64 位数字。本指南探讨其属性、精度挑战以及有效处理数值运算的最佳实践。
JavaScript 数字概述
本节概述了 JavaScript 唯一的数字类型:64 位数字。它基于 IEEE 754 双精度,提供广泛的范围和特定的精度。了解其限制是避免计算中常见陷阱的关键。以下示例显示了其范围和典型使用场景。掌握此类型对于有效的 JavaScript 编程至关重要。
// Number declarations
const singleNumber = 1.2345678901234567;
const smallNumber = 5.0e-324;
const largeNumber = 1.7976931348623157e308;
console.log(`Number: ${singleNumber}`);
console.log(`Smallest positive: ${smallNumber}`);
console.log(`Largest: ${largeNumber}`);
console.log(`\nNumber.MIN_VALUE: ${Number.MIN_VALUE}`);
console.log(`Number.MAX_VALUE: ${Number.MAX_VALUE}`);
console.log(`Size in bytes: ${8}`); // Always 64-bit
// Additional example: Scientific notation
const sciNotation = 1.23e-10;
console.log(`\nScientific notation: ${sciNotation}`);
JavaScript 的 Number 类型是根据 IEEE 754 双精度标准定义的 64 位浮点值,处理所有数字需求,无需单独的 float 或 decimal 类型。它提供 15-17 位有效十进制数字,范围从 ±5.0×10⁻³²⁴ (Number.MIN_VALUE) 到 ±1.7976931348623157×10³⁰⁸ (Number.MAX_VALUE),以 8 个字节存储。
这种一致性简化了编码,但需要谨慎,因为十进制小数(如 0.1)会出现精度问题。与具有多种数字类型的语言不同,JavaScript 的单一方法用途广泛,但在特定情况下容易出现舍入错误。对于非常小或非常大的值,请有效地使用科学记数法(例如,1.23e-10)。
精度与舍入
由于 JavaScript 的 Number 类型的二进制特性,精度可能很棘手。本节探讨了十进制小数如何导致舍入误差,以及如何缓解这些误差。示例演示了内置的舍入方法及其特点。正确处理可确保数值运算的准确结果。这是 JavaScript 开发人员需要学习的关键技能。
// Precision demonstration
const a = 0.1;
const b = 0.2;
const sum = a + b;
console.log(`0.1 + 0.2 = ${sum}`);
console.log(`0.1 + 0.2 === 0.3? ${sum === 0.3}`);
console.log(`Full precision: ${sum.toString()}`);
const value = 2.34567;
console.log(`\nRounding examples:`);
console.log(`Math.round(${value}): ${Math.round(value)}`);
console.log(`Math.floor(${value}): ${Math.floor(value)}`);
console.log(`Math.ceil(${value}): ${Math.ceil(value)}`);
console.log(`toFixed(2): ${value.toFixed(2)}`);
// Additional example: Pi rounding
const pi = 3.14159;
console.log(`\nPi to 3 digits: ${pi.toFixed(3)}`);
JavaScript 的二进制浮点系统难以处理 0.1 + 0.2 等十进制小数,由于基数 2 的限制,结果为 0.30000000000000004 而不是 0.3。这些小错误会在重复计算中增长,因此精度管理对于可靠性至关重要。
Math.round(四舍五入到最接近的整数)、Math.floor(向下取整)和 Math.ceil(向上取整)等方法可以调整数字,而 toFixed(2) 格式化为两位小数,但返回一个字符串。例如,2.34567 使用 toFixed(2) 变为 2.35,尽管底层值保留其全部精度。明智地使用这些工具,注意 toFixed() 的字符串输出需要解析才能进行进一步的数学运算。
比较浮点值
由于精度问题,在 JavaScript 中比较浮点数需要特殊技巧。直接相等性检查通常会失败,因此本节提供了一种可靠的替代方法。它还涵盖了处理 NaN 和 Infinity 等特殊值。示例代码阐明了这些挑战以及实用的解决方案。掌握这些知识可以防止比较逻辑中出现微妙的错误。
function nearlyEqual(a, b, epsilon = 1e-10) {
const absA = Math.abs(a);
const absB = Math.abs(b);
const diff = Math.abs(a - b);
if (a === b) return true;
if (a === 0 || b === 0 || diff < Number.EPSILON) {
return diff < (epsilon * Number.EPSILON);
}
return diff / (absA + absB) < epsilon;
}
const x = 0.1 + 0.2;
const y = 0.3;
console.log(`Direct equality: ${x === y}`);
console.log(`NearlyEqual: ${nearlyEqual(x, y)}`);
console.log(`Difference: ${x - y}`);
const nan = NaN;
const inf = Infinity;
console.log(`\nNaN === NaN: ${nan === nan}`);
console.log(`Number.isNaN(nan): ${Number.isNaN(nan)}`);
console.log(`inf === inf: ${inf === inf}`);
// Additional example: Small number comparison
const small = 1e-15;
console.log(`\nNearlyEqual(${small}, 0): ${nearlyEqual(small, 0)}`);
在 JavaScript 中使用 === 进行浮点比较具有风险,因为由于二进制表示的微小精度误差,0.1 + 0.2 !== 0.3。 nearlyEqual 函数使用一个 epsilon(例如,1e-10)来测试数字是否足够接近,并根据相对大小或接近零的绝对差值(使用 Number.EPSILON)进行调整。
像 NaN 这样的特殊情况需要 Number.isNaN,因为 NaN !== NaN,而 Infinity === Infinity 为真,但需要上下文相关的处理。对于 1e-15 与 0 这样的小值,nearlyEqual 提供了直接检查失败时的可靠结果。这种方法确保了比较在实际代码中既准确又实用。
用于财务计算的浮点数
JavaScript 的 Number 类型对于需要精确小数的金融任务来说并不理想。本节展示了它的局限性以及使用整数来提高精度的一种解决方法。示例包括利息计算和准确舍入货币值。这些技术对于 JavaScript 中可靠的金融应用程序至关重要。对于超出这些基本知识的复杂需求,请考虑使用库。
const principal = 1000.00;
const interestRate = 0.05; // 5%
const years = 10;
// Using floating-point
let futureValueFloat = principal * Math.pow(1 + interestRate, years);
console.log(`Future value (floating-point): $${futureValueFloat.toFixed(2)}`);
// Workaround with integer cents
const principalCents = 100000; // $1000.00 in cents
const futureValueCents = Math.round(principalCents * Math.pow(1 + interestRate, years));
const futureValue = futureValueCents / 100;
console.log(`Accurate future value: $${futureValue.toFixed(2)}`);
const payment = 123.456789;
console.log(`\nPayment to cents: ${payment.toFixed(2)}`);
console.log(`Payment rounded up: ${Math.ceil(payment * 100) / 100}`);
console.log(`Payment rounded down: ${Math.floor(payment * 100) / 100}`);
// Additional example: Tax calculation
const price = 19.99;
const taxRate = 0.08;
const tax = Math.round(price * taxRate * 100) / 100;
console.log(`\nTax on $${price}: $${tax}`);
JavaScript 的浮点 Number 类型在金融计算中会出错,因为在复利等计算中(例如,1000 美元以 5% 的利率计算 10 年)可能会略微歪曲结果。使用整数(例如,美分)可以避免这种情况,将 1000 美元转换为 100000 美分,进行计算,然后除以 100 以获得准确的 1628.89 美元。
在美分上使用 toFixed(2) 或 Math.ceil()/Math.floor() 进行舍入(例如,将 12345 美分舍入到 123.45 美元)可确保货币精度,这对于会计至关重要。例如,这种方法对 19.99 美元的 8% 税额计算结果为 1.60 美元,避免了浮点误差。虽然有效,但严肃的金融应用程序可能仍然更喜欢 Big.js 等库的稳健性。
最佳实践
对于一般数学运算,请坚持使用 Number,但对于 JavaScript 中的金融精度,请切换到基于整数的方法或库。避免使用 === 进行浮点检查,而选择像 nearlyEqual 这样的 epsilon 比较。显式使用 Math.round、toFixed 或类似的方法,注意 toFixed 返回需要解析的字符串。始终使用 Number.isNaN 测试 NaN,并使用 Number.isFinite 测试 Infinity 以安全地处理边缘情况。这些习惯可以确保您的数值代码既可靠又易于维护。
资料来源
从这些资源中了解更多信息:MDN Number 文档、MDN Math 文档 和 ECMAScript Number 类型。
作者
我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。