问题
JavaScript 中数字类型 Number 的最大的安全整数 Number.MAX_SAFE_INTEGER
为 ,既 9_007_199_254_740_991
,当使用 Number 存储的数字大于最大的安全整数以及对其计算都有可能造成精度丢失,从而造成错误的结果.比如下面这些错误的结果:
const a = 2 ** 53, b = 2 ** 53 + 1
console.log(a === b) // true
console.log(2 ** 54 === 2 ** 54 + 1) // true
在刷题时,经常会有需要处理数字大于最大安全整数的情况,这个时候我们可以使用 BigInt 来进行存储,只是 BigInt 相对于 Number 来说,性能会差很多(可以查看后面的基准测试).像 C++ 中的 long long
则要快很多,所以如果是极限情况,C++ 用 long long
能过,但 JavaScript 中用 BigInt 却会超时(TLE).
如果只是在力扣刷题的话,一般不会遇到这种情况,力扣大多数题的数据对 JavaScript 还是很友好的.主要是去做一些别的平台比如像洛谷或者 Codeforces 上的题,其数据都比较极限,就很容易出现几个比较大的数据 TLE 的情况,当然如果只是单纯为了学习算法,可以不用管那几个 TLE 的数据,能过前面的数据,一般来说算法逻辑应该是没问题的.
不过如果像我一样,刷题总想的能全部 AC 的好,有几个 TLE 在,心里总感觉像有根刺,必须要拔掉才舒服,这时候就需要想办法去解决它了
这个问题最开始是学倍增算法时,在 CodeForcess 上做 D. For Gamers. By Gamers. 这题时遇到的
其数据范围导致最后有些数字会达到最大 ,这时就需要使用 BigInt 不然会得到错误的结果,但使用 BigInt 对于一些大的数据又会超时,这就很难受了
解决思路
经过断断续续数天的尝试,突然想到,其实并不需要所有的数字都使用 BigInt 去存,只有当数字超过最大安全整数的时候使用 BigInt,而超过最大安全整数的数字使用 Number 去存,这样能大大减少使用 BigInt 类型的数量,从而减少运行时间.
可以选定一个数作为分界点,对大于这个数的数字用 BigInt 存储,而小于这个数的数字用 Number 去存储.我一般用 ,极限一点的话,用
Number.MAX_SAFE_INTEGER
应该也可以
只是 BigInt 不能和 Number 混合操作,否则会报 TypeError
类型错误,如果是需要进行混合数字之前进行加减乘除这些计算,就需要自定义这些运算的函数
其实我想过另外一种方案,就是用两个 Number 来表示一个数字,比如还是以 为分界点,其中一个数字表示小于 的部分,另外一个数字表示大于等于 的部分,这样两个数就能完整的表达一个数的信息,然后再去实现对应的各种操作.只是这样会更麻烦一些,所以在当前这种方案能解决的情况下,也就没有研究下去了,如果有兴趣的同学,可以尝试一下.
像这题 D. For Gamers. By Gamers 只用去比较两个数的大小,所以不用那么麻烦,直接进行比较即可
在比较的操作符中,如果是要对 Number 和 BigInt 进行相等的判断,需要使用
==
,使用===
则会判断为不相等,比如1n===1
会返回flase
,需要用1n==1
实现代码
D. For Gamers. By Gamers
这题可以查看在线的提交页面中的代码 D. For Gamers. By Gamers. - submissions
另外昨天学线段树,在写 P3372 【模板】线段树 1 这题时,其数据会超过最大安全整数,如果完全用 BigInt 会超时,于是换成这种混合的方式存储,最终通过了全部样例
其中需要用到乘法和加法,所以需要自定义这两种运算符,其中主要是对不同的类型进行分类讨论,如果至少有一个数是 BigInt 或者计算的结果会大于分界点时,则改用 BigInt 去计算,否则直接用 Number 去计算
function add(a, b) {
if (typeof a === 'bigint' || typeof b === 'bigint' || a + b >= MAX) return BigInt(a) + BigInt(b)
return a + b
}
function mul(a, b) {
if (typeof a === 'bigint' || typeof b === 'bigint' || a * b >= MAX) return BigInt(a) * BigInt(b)
return a * b
}
完整的实现:
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
// output: process.stdout,
})
let i = 0,
m = 0,
n = 0,
nums = [],
sum = [],
v = []
let res = [],
insert,
query
rl.on('line', line => {
const input = line.split(' ')
switch (i++) {
case 0:
;[n, m] = input.map(Number)
break
case 1:
for (let i = 0; i < n; i++) nums.push(toNum(input[i]))
;[insert, query] = segTree(nums)
break
default:
const [a, b, c] = input.slice(0, 3).map(Number)
if (a === 1) {
const d = toNum(input[3])
insert(b - 1, c - 1, d)
} else {
res.push(query(b - 1, c - 1))
}
break
}
})
rl.on('close', () => process.stdout.write(res.join('\n')))
const MAX = 10 ** 15
function add(a, b) {
if (typeof a === 'bigint' || typeof b === 'bigint' || a + b >= MAX) return BigInt(a) + BigInt(b)
return a + b
}
function mul(a, b) {
if (typeof a === 'bigint' || typeof b === 'bigint' || a * b >= MAX) return BigInt(a) * BigInt(b)
return a * b
}
function toNum(str) {
if (str.length > 15) return BigInt(str)
else return Number(str)
}
function segTree(nums, n = nums.length) {
const arr = [],
v = []
const build = (i, l, r) => {
if (l === r) {
arr[i] = nums[l]
return
}
const mid = Math.floor((l + r) / 2)
build(2 * i + 1, l, mid)
build(2 * i + 2, mid + 1, r)
arr[i] = add(arr[2 * i + 1] ?? 0, arr[2 * i + 2] ?? 0)
}
const insert = (i, l, r, x, y, z) => {
if (l === x && r === y) {
v[i] = add(v[i] ?? 0, z)
return
}
arr[i] = add(arr[i] ?? 0, mul(y - x + 1, z))
const mid = Math.floor((l + r) / 2)
if (y <= mid) insert(2 * i + 1, l, mid, x, y, z)
else if (x > mid) insert(2 * i + 2, mid + 1, r, x, y, z)
else insert(2 * i + 1, l, mid, x, mid, z), insert(2 * i + 2, mid + 1, r, mid + 1, y, z)
}
const query = (i, l, r, x, y) => {
if (y < x) return 0
let res = 0
if (l === x && r === y) return add(mul(v[i] ?? 0, y - x + 1), arr[i] ?? 0)
const mid = Math.floor((l + r) / 2)
if (y <= mid) res = query(2 * i + 1, l, mid, x, y)
else if (x > mid) res = query(2 * i + 2, mid + 1, r, x, y)
else res = add(query(2 * i + 1, l, mid, x, mid), query(2 * i + 2, mid + 1, r, mid + 1, y))
return add(res, mul(v[i] ?? 0, y - x + 1))
}
build(0, 0, n)
return [insert.bind(null, 0, 0, n), query.bind(null, 0, 0, n)]
// return { add: add.bind(null, 0, 0, n), query: query.bind(null, 0, 0, n) }
}
基准测试
// Setup
const intArr = [],
bigintArr = []
for (let i = 0; i < 10000; i++) {
const num = Math.floor(Math.random() * 10000)
intArr.push(num)
bigintArr.push(BigInt(num))
}
比较操作
// BigInt
let res = 0
for (let i = 0; i < bigintArr.length; i++) {
if (bigintArr[i] > 5000n) res++
}
// Number
let res = 0
for (let i = 0; i < intArr.length; i++) {
if (intArr[i] > 5000) res++
}
求和操作
// BigInt
let res = 0n
for (let i = 0; i < bigintArr.length; i++) {
res += bigintArr[i]
}
// Number
let res = 0
for (let i = 0; i < intArr.length; i++) {
res += intArr[i]
}