如何在JavaScript中安全比较高精度Unix时间戳
我需要编写一个getValuesGreaterThan(num, values)函数,输入是系统调用find . -maxdepth 1 -type f -printf '%C@\n'返回的字符串数组,格式类似["1643764147.7500000000", "1643764147.7600000000", ...],要求返回其中大于等于指定num的数值子集,且输出不受精度问题或强制转换异常影响。
我尝试用localeCompare实现了一个版本:
let getValuesGreaterThan = function (num, values) { return values.filter((v) => v.localeCompare(num) >= 0) } console.log(getValuesGreaterThan("1643764147.7600000000", ["1643764147.7500000000", "1643764147.7600000000", "1643764147.7700000000"]))
这段代码看似能正常运行,但我担心存在某些输入场景会导致它失效,想知道正确的处理方式。
可行解决方案
1. 直接字符串比较(最优方案,前提是格式统一)
你的localeCompare思路是可行的,但其实在输入格式严格统一的情况下(所有时间戳都是整数部分.10位小数的格式,和find的%C@输出一致),字符串的字典序完全等价于数值大小,直接用普通的字符串比较运算符(>=)更简洁高效:
let getValuesGreaterThan = function (num, values) { return values.filter(v => v >= num); }
这种方式不需要任何类型转换,完全避免精度丢失问题,只要输入格式不出现异常(比如整数部分位数变化、小数部分长度不一致),就不会出错。
2. 高精度数值转换(兼容格式变化)
如果担心输入格式可能出现变动(比如小数部分位数不足或多余),可以将时间戳拆分为秒和纳秒两部分,用BigInt处理:
// 把时间戳字符串转成纳秒级的BigInt function parseTimestamp(str) { const [secStr, nsecStr = ''] = str.split('.'); // 标准化纳秒部分为10位,不足补0,超出截断 const normalizedNsec = nsecStr.padEnd(10, '0').slice(0, 10); return BigInt(secStr) * 10000000000n + BigInt(normalizedNsec); } let getValuesGreaterThan = function (num, values) { const targetNum = parseTimestamp(num); return values.filter(v => parseTimestamp(v) >= targetNum); }
这种方式把时间戳转换为精确的整数BigInt,完全规避了浮点数的精度问题,即使输入格式有微小变化也能正确处理。
3. 绝对要避免的错误做法
不要直接将这类字符串转换为Number类型,因为JavaScript的Number是双精度浮点数,只能精确表示253以内的整数。而Unix时间戳的整数部分已经接近231,加上10位小数后,转成Number会丢失纳秒级的精度,导致比较结果错误。
例如,"1643764147.7500000001"转成Number后会被近似为1643764147.75,和"1643764147.7500000000"比较时会误判为相等。
内容的提问来源于stack exchange,提问作者Daniel Kaplan

