题目
下面的金额转换有什么问题?如果用户输入 "0.1" ETH,前端应该怎样安全地转换、比较和展示?
const amount = Number(input) * 10 ** 18;
考察目标
- 是否知道 JavaScript
number是浮点数且存在安全整数上限。 - 是否会让十进制输入以字符串进入
parseUnits,并在最小单位中使用bigint。 - 是否能区分计算值与展示值,避免错误舍入或硬编码 18 位 decimals。
考察安全整数、十进制输入与最小单位转换,以及 BigInt 的正确使用边界。
下面的金额转换有什么问题?如果用户输入 "0.1" ETH,前端应该怎样安全地转换、比较和展示?
const amount = Number(input) * 10 ** 18;
number 是浮点数且存在安全整数上限。parseUnits,并在最小单位中使用 bigint。number 无法精确表示所有小数,而且 10 ** 18 已超过 Number.MAX_SAFE_INTEGER,乘法结果不能保证是准确整数。用户输入应保持字符串,用代币实际的 decimals 交给 parseUnits 转成最小单位 bigint;余额、allowance 和交易金额都用 bigint 比较。展示时再用 formatUnits 转成十进制字符串,最后只在 UI 层做明确的截断或舍入。
JavaScript number 使用 IEEE 754 双精度浮点数。它能安全表示的最大整数是 2^53 - 1,而 1 ETH 的 wei 值是 10^18,已经远超这个范围。十进制小数也常常无法用二进制浮点数精确表示。
因此下面两种操作都有风险:
Number,可能在进入链上单位转换前就丢失精度。bigint 转成 number 做比较或加减,可能丢掉低位。推荐的数据流是:
parseUnits(input, decimals) 得到最小单位 bigint。bigint。formatUnits(value, decimals) 得到字符串。不能假设所有 ERC-20 都是 18 位小数。decimals 在 ERC-20 中是可选元数据,产品应从可信资产配置或合约读取并设置合理校验与回退;原生资产也应使用目标链定义的单位。
import { formatUnits, parseUnits } from "viem";
const decimals = 18;
const amount = parseUnits("0.1", decimals); // 100000000000000000n
if (amount > balance) {
throw new Error("余额不足");
}
const displayValue = formatUnits(amount, decimals); // "0.1"
如果产品只展示四位小数,应对 displayValue 做字符串或十进制定点处理,并明确采用截断还是四舍五入;不要先转回 Number 再修改业务金额。
Number(input) * 10 ** decimals 构造交易金额。bigint 转成 number。JSON.stringify 序列化 bigint,没有定义传输格式。0.1 + 0.2 的问题和 wei 超过安全整数是两类精度风险?bigint 通过接口或本地存储时需要注意什么?number 存在精度和安全整数问题。parseUnits 和 bigint。发现这道题有问题? 反馈此题