引言
在编程的世界里,数字无处不在。从简单的计数到复杂的金融交易,精确处理数字的能力是每个开发者都应掌握的技能之一。然而,在JavaScript中,由于其Number类型的设计限制,处理特别大的数字或需要极高精度的计算时会遇到一些挑战。本文将深入探讨如何使用JavaScript进行大数相加,并介绍一种有趣且实用的应用——红包算法。
JavaScript与大数问题
数字存储的本质
首先,让我们快速了解一下为什么JavaScript在处理大数时会出现不精确的问题。根据IEEE754标准,JavaScript中的Number类型实际上是以双精度浮点格式存储的,这意味着它只能精确表示一定范围内的整数和小数。超出这个范围(例如超过2^53-1),就会出现精度丢失的情况。
我们来看下面的代码,大家猜一下会打印出什么呢,我也不卖关子了之间告诉大家:0.30000000000000004
console.log(0.1 + 0.2);
啊?!这还叫“计算机”?连 0.1 + 0.2 都算不准了?!
是的,这就是 JavaScript 的“痛”。它只有一种 Number 类型,而这个类型背后用的是 IEEE754 浮点数标准。简单来说,就是计算机用二进制来表示十进制小数时,有些数是无限循环的,比如 0.1 在二进制中就变成了:
0.0001100110011001100110011001100110011001100110011001101...
是不是很像你每次想减肥但总忍不住吃宵夜的状态——无限循环、永远无法结束。
我们只需要(0.110 + 0.210)/10 就可以得到0.3
我们再来看以下代码
let num = 1215165165416516516516541651561456556
console.log(num);//1.2151651654165165e+36
并不能完全的打印出这个大数,那我们该如何解决这个问题呢
所以,当我们要处理非常大的数字(比如比特币交易、宇宙飞船轨道计算)或者需要极高精度的场景(比如金融系统),JavaScript 就显得有点力不从心了。
那怎么办呢?别急,我们今天就要一起解决两个非常经典的问题:
- 大数相加
- 微信红包算法
解决方案:BigInt
幸运的是,ES6为我们带来了BigInt,这是一种专门用来表示极大整数的数据类型。通过在数字后添加n或者使用BigInt()构造函数,我们可以轻松创建一个BigInt类型的变量。这使得处理超大数字成为可能,而不用担心溢出或精度丢失的问题。让我们的JS也可能成为企业级开发的大型语言
const bigNum = 123456789012345678901234567890123456789n
// BigInt 声明方式 函数声明
// bigint 简单数据类型, 不是对象,不是构造函数
const theNum = BigInt("123456789012345678901234567890123456789")
console.log(bigNum, theNum, typeof bigNum);
不能使用new BigInt(),BigInt 声明方式 函数声明 ,bigint 简单数据类型, 不是对象,不是构造函数,并且要用""来包裹我们需要声明的bigint数据类型
第一部分:大数相加 —— 让字符串也能做加法!
为什么不能直接相加?
大数相加的实现
如果你尝试运行以下代码
const a = "9999999999999999999999999999999999999999";
const b = "8888888888888888888888888888888888888888";
console.log(a + b); // 不对哦,这是字符串拼接
console.log(Number(a) + Number(b)); // 还是不对,会丢失精度
基于BigInt,我们可以编写一个函数来安全地对两个以字符串形式表示的大数进行相加。以下是该函数的一个简单实现:
实现思路:手动模拟竖式加法
想象一下小学三年级数学课上老师教你的竖式加法:
浅色版本
9999999999999999999999999999999999999999
+ 8888888888888888888888888888888888888888
---------------------------------------------
18888888888888888888888888888888888888887
我们只需要从右往左一位一位地加,记住进位就行啦!
我们可以利用字符串来实现两个大数的相加,我们从末位相加,大于10我们就可以进1,以这种思路,我们来写一个大数算法的函数,我们看到以下代码,其实这也是力扣415题的原题
/**
*
* @param {string} num1
* @param {string} num2
* @returns {string}
*/
function addLargeNumbers(num1, num2) {
let result = '' // 存储结果的字符串
let carry = 0 // 存储进位的数字
let i = num1.length - 1 // 从字符串的最后一位开始遍历
let j = num2.length - 1 // 从字符串的最后一位开始遍历
while (i >= 0 || j >= 0 || carry > 0) {
// 边界,parseInt 会把字符串转换成数字
const digit1 = i >= 0 ? parseInt(num1[i]) : 0
const digit2 = j >= 0 ? parseInt(num2[j]) : 0
const sum = digit1 + digit2 + carry // 计算当前位的和
result = (sum % 10) + result // 将当前位的和添加到结果字符串的前面
carry = Math.floor(sum / 10) // 计算进位
i-- // 移动到下一位
j-- // 移动到下一位
}
return result
}
来测试一下吧
console.log(addLargeNumbers("123", "456")); // 输出 "579"
console.log(addLargeNumbers("999", "1")); // 输出 "1000"
console.log(addLargeNumbers("0", "0")); // 输出 "0"
console.log(addLargeNumbers(
"9999999999999999999999999999999999999999",
"8888888888888888888888888888888888888888"
));
// 输出 "18888888888888888888888888888888888888887"
那我们学了bigint,我们是不是可以直接相加呢,其实不是的,bigint和number不能直接相加,需要将1转换为bigint,再进行相加
let a = 123456789012345678901234567890123456789n
console.log(a + 1);//TypeError: Cannot mix BigInt and other types, use explicit conversions
我们把1转换为大数后,就可以实现相加,得到我们想要的结果
let a = 123456789012345678901234567890123456789n
console.log(a + 1n);//123456789012345678901234567890123456790n
第二部分:微信红包算法 —— 如何让每个人都觉得“我运气不错”
微信红包一夜之间让用户养成了用微信支付的习惯,而它的核心功能之一,就是“抢红包”。
那么问题来了:如何在一个红包里平均分 N 份,每一份都不同且总和等于总金额?
比如用户发了一个 31 元的红包,分成 31 个包,每个人能拿到多少钱?要保证公平,又要保留一点点“惊喜”。
红包算法的核心思想
我们可以把这个问题看作是一个“带约束的随机分配问题”:
- 总金额固定
- 每个红包金额必须大于 0
- 所有红包金额加起来等于总金额
- 要有一定的随机性,让每个人觉得“我运气不错”
基础实现:平均 + 随机
这里介绍一种常见的实现方式:
- 对于前 N - 1 个红包,我们使用随机数生成一个金额,范围控制在
[最小单位, 平均值 * 2]。 - 最后一个红包直接补上剩余金额即可。
代码如下:
/**
* 发红包啦!
* @param {number} total 总金额
* @param {number} num 红包数量
* @returns {string[]} 返回每个红包的金额数组
*/
function hongbao(total, num) {
const arr = [];
let restAmount = total;
let restNum = num;
for (let i = 0; i < num - 1; i++) {
// 随机生成金额,最多不超过当前平均值的两倍
let amount = (Math.random() * (restAmount / restNum * 2)).toFixed(2);
restAmount -= amount;
restNum--;
arr.push(amount);
}
// 最后一个红包补上剩余金额
arr.push(restAmount.toFixed(2));
return arr;
}
console.log(hongbao(31, 31));
示例结果
看到没?有的人才 0.03,有的人拿了 3 块多,但总共加起来还是 31 元!这就叫做“公平又刺激”。
我们还可以来简单模拟一下领红包的流程
我们从数组里面抽出一个值,就是模拟我们领红包的过程
const chai = hongbao(100, 20)
console.log(chai[Math.floor(Math.random() * 21)]);
这就是作者领的几个红包数额,手气不是怎么好,哈哈哈哈
改进建议(可选)
当然,上面的算法只是一个基础版本,实际微信内部肯定做了很多优化,比如:
- 设置最小金额(如 0.01 元) 比如我们的测试结果就存在0.00的结果
- 控制最大金额(防止某人拿走一半)
- 加入时间戳或用户 ID 影响随机种子
- 使用更复杂的概率分布(如正态分布)
这些都可以作为后续扩展的方向。
结语:技术 + 产品思维 = 更好的用户体验
通过今天的讲解,我们学习了两个实用的算法:
- 大数相加:用字符串模拟加法,避免精度丢失
- 红包算法:结合随机性和公平性,设计有趣的用户互动
这些看似简单的功能背后,其实蕴含着丰富的数学原理、工程思维以及产品设计逻辑。
📌 技术不是冷冰冰的代码,它是解决问题的艺术,更是创造体验的魔法。
下次再有人问你:“JavaScript 怎么处理大数?”、“微信红包怎么做的?”你就可以自信满满地说:
“我会写大数加法,还会发红包!我就是新时代的‘财神爷’!”
总结一句话
JS 不擅长计算,但只要方法得当,照样玩转大数和红包算法!
🎯 作者寄语:
希望你在编程的路上越走越远,既能写出严谨的算法,也能设计出有趣的交互。毕竟,优秀的开发者,既懂技术,也懂人心。