TypeScript与Rust跨平台位移运算结果不一致问题排查
问题概述
我用Rust实现了一个decompose函数,对i64类型进行位移操作(输入非负),同时用TypeScript基于bigint实现了相同逻辑,但输入整数844562369609728时两段代码结果不一致,TypeScript返回错误结果。按AssemblyScript文档,i64对应JS的bigint,理论上应该兼容,请问原因是什么?
Rust代码:
pub const fn decompose(result: i64) -> (bool, u64, u64) { let ptr = (result >> 32) as u64; let len = ((result << 32) >> 48) as u64; let success = ((result << 63) >> 63) == 0; (success, ptr, len) }
TypeScript代码:
interface CallResult { ptr: bigint; len: bigint; status: boolean; } function decompose(result: bigint): CallResult { let ptr = result >> 32n; let len = (result << 32n) >> 48n; let success = (result << 63n) >> 63n == 0n; return { ptr: ptr, len: len, status: success, }; }
核心原因:整数精度与溢出行为差异
两段代码的本质差异在于整数的存储范围和溢出处理逻辑:
- Rust的
i64是固定64位的有符号整数,所有位移操作都会自动截断到64位范围(溢出时仅保留低64位),且位移遵循有符号整数的算术规则。 - TypeScript的
bigint是任意精度整数,不会自动截断,位移操作会完整保留所有高位数据,不存在溢出截断行为。
以输入844562369609728(即3 * 2^48)为例:
len计算差异:- Rust中:
result << 32会将3 * 2^48左移32位得到3 * 2^80,超出i64的最大值(2^63 - 1),因此自动截断为低64位(全0),再右移48位后结果仍为0。 - TypeScript中:
bigint会完整保留3 * 2^80,右移48位后得到3 * 2^32,与Rust结果完全不同。
- Rust中:
success计算差异:- Rust中:
result << 63得到3 * 2^111,截断到64位后为0,算术右移63位仍为0,因此success为true。 - TypeScript中:
result << 63n完整保留3 * 2^111,右移63位后得到原输入值3 * 2^48,不等于0n,因此success为false。
- Rust中:
解决方案:在TypeScript中模拟Rust的i64行为
要让TypeScript代码与Rust逻辑一致,需要手动模拟i64的64位截断和有符号位移规则:
interface CallResult { ptr: bigint; len: bigint; status: boolean; } function decompose(result: bigint): CallResult { const I64_MAX = 0x7FFFFFFFFFFFFFFFn; const I64_MASK = 0xFFFFFFFFFFFFFFFFn; const U32_MASK = 0xFFFFFFFFn; const U16_MASK = 0xFFFFn; // 将输入截断为i64范围的有符号bigint let truncated = result & I64_MASK; if (truncated > I64_MAX) { truncated -= 0x10000000000000000n; } // 计算ptr:模拟i64右移32位后转u64 let ptr = truncated >> 32n; ptr = ptr < 0n ? ptr + U32_MASK + 1n : ptr; // 计算len:模拟i64左移32位截断后,再右移48位转u64 let shiftedLeft = (truncated << 32n) & I64_MASK; shiftedLeft = shiftedLeft > I64_MAX ? shiftedLeft - 0x10000000000000000n : shiftedLeft; let len = shiftedLeft >> 48n; len = len < 0n ? len + U16_MASK + 1n : len; // 计算success:模拟i64左移63位截断后右移63位 let shiftedLeft63 = (truncated << 63n) & I64_MASK; shiftedLeft63 = shiftedLeft63 > I64_MAX ? shiftedLeft63 - 0x10000000000000000n : shiftedLeft63; let success = shiftedLeft63 >> 63n === 0n; return { ptr, len, status: success }; }
这段代码通过手动截断到64位、处理有符号数的符号位,完全复刻了Rust中i64的位移和类型转换逻辑,与原Rust代码结果一致。
内容的提问来源于stack exchange,提问作者weegee
相关产品推荐
相关产品推荐

