Skip to main content

FullMath 快速参考指南

🎯 一句话总结​

FullMath 让你能安全地计算 (a × b) ÷ c,即使 a × b 会溢出 256 位。


📖 基本用法​

导入​

import {FullMath} from "./libraries/FullMath.sol";

API​

// 向下取整: floor((a * b) / denominator)
uint256 result = FullMath.mulDiv(a, b, denominator);

// 向上取整: ceil((a * b) / denominator)
uint256 result = FullMath.mulDivRoundingUp(a, b, denominator);

💡 何时使用​

✅ 应该使用的场景​

场景示例原因
🪙 价格计算amount * price / precision大数相乘易溢出
💧 流动性计算liquidity * sqrtPrice^2 / 2^192Uniswap V3 中的实际用法
📊 比例分配userStake * totalReward / totalStake需要精确分配
💰 费用计算value * feeBps / 10000避免精度损失
📈 复利计算principal * (1+rate)^n重复乘法容易溢出

❌ 不需要使用的场景​

  • 简单的加减法
  • 确定不会溢出的小数运算
  • 纯整数除法(无乘法)
  • Gas 极度敏感且精度要求不高的场景

📊 示例代码​

示例 1:代币价值计算​

function calculateValue(
uint256 tokenAmount,
uint256 pricePerToken,
uint256 decimals
) public pure returns (uint256) {
// tokenAmount = 1e30, pricePerToken = 1e20
// tokenAmount * pricePerToken 会溢出!
return FullMath.mulDiv(tokenAmount, pricePerToken, 10 ** decimals);
}

示例 2:比例分配奖励​

function calculateReward(
uint256 userStake,
uint256 totalRewards,
uint256 totalStake
) public pure returns (uint256) {
// reward = userStake * totalRewards / totalStake
return FullMath.mulDiv(userStake, totalRewards, totalStake);
}

示例 3:百分比计算​

function applyFee(
uint256 amount,
uint256 feeBasisPoints // 30 = 0.3%
) public pure returns (uint256) {
// fee = amount * feeBps / 10000
uint256 fee = FullMath.mulDiv(amount, feeBasisPoints, 10000);
return amount - fee;
}

示例 4:向上取整(用户支付)​

function calculateBorrowFee(
uint256 borrowAmount,
uint256 feeRate,
uint256 precision
) public pure returns (uint256) {
// 向上取整,确保协议不会少收费用
return FullMath.mulDivRoundingUp(borrowAmount, feeRate, precision);
}

⚡ Gas 成本​

操作Gas 消耗说明
简单除法 a / b~50基准
mulDiv(a, b, c) (简单)~200prod1 == 0 时
mulDiv(a, b, c) (完整)~600需要 512 位除法时

权衡:多花 10x gas,换取完美精度和溢出保护。


🔍 工作原理(简化版)​

步骤 1:512 位乘法​

a * b = [prod1 (高256位)] [prod0 (低256位)]

步骤 2:检查溢出​

if (prod1 >= denominator) revert;  // 结果会溢出

步骤 3:简单情况​

if (prod1 == 0) return prod0 / denominator;  // 直接除法

步骤 4:512 位除法​

1. 减去余数
2. 提取 2 的幂因子
3. 计算模逆元
4. 乘法替代除法

🎓 核心技术​

1. mulmod - EVM 的秘密武器​

// EVM 原生支持 512 位模运算!
uint256 result = mulmod(a, b, modulus);

这是整个算法的基础。

2. 模逆元​

// 除以 d 等价于乘以 d 的逆元
result = value / d;
result = value * inverse(d); // 更快!

3. Newton-Raphson 迭代​

// 每次迭代精度翻倍
inv *= 2 - denominator * inv; // 重复 6 次

⚠️ 注意事项​

会 Revert 的情况​

// 1. 除以零
FullMath.mulDiv(a, b, 0); // ❌ revert

// 2. 结果溢出 256 位
FullMath.mulDiv(MAX, MAX, 1); // ❌ revert

// 3. 向上取整溢出
FullMath.mulDivRoundingUp(MAX, MAX, MAX-1); // ❌ revert

精度注意​

// ✅ 好:先乘后除
result = FullMath.mulDiv(a, b, c);

// ❌ 差:先除后乘(损失精度)
result = (a / c) * b;

🧪 测试建议​

测试边界情况​

function testFullMath() public {
// 测试最大值
uint256 max = type(uint256).max;
FullMath.mulDiv(max, max, max); // 应该 = max

// 测试零
FullMath.mulDiv(0, max, 1); // 应该 = 0

// 测试溢出
vm.expectRevert();
FullMath.mulDiv(max, max, 1); // 应该 revert
}

测试实际场景​

function testRealisticScenario() public {
uint256 amount = 1_000_000 * 1e18;
uint256 price = 5000 * 1e18;
uint256 value = FullMath.mulDiv(amount, price, 1e18);

assertEq(value, 5_000_000_000 * 1e18);
}

📚 学习资源​

运行演示​

# 运行交互式示例
forge test --match-contract FullMathDemo -vvv

# 查看 gas 报告
forge test --match-contract FullMathDemo --gas-report

深入学习​


🎁 实用模板​

创建你自己的库​

library MyMath {
using FullMath for uint256;

uint256 constant PRECISION = 1e18;

function calculateWithFee(
uint256 amount,
uint256 feeRate // 以 PRECISION 为单位
) internal pure returns (uint256 amountAfterFee) {
uint256 fee = FullMath.mulDiv(amount, feeRate, PRECISION);
return amount - fee;
}

function proportionalSplit(
uint256 total,
uint256 ratio, // 0 到 PRECISION
uint256 precision
) internal pure returns (uint256 part1, uint256 part2) {
part1 = FullMath.mulDiv(total, ratio, precision);
part2 = total - part1;
}
}

✨ 关键要点​

记住这些​

  1. 用途:(a * b) / c 且 a * b 可能溢出
  2. 精度:完美精度,无舍入误差(除非要求向上取整)
  3. 成本:~10倍 gas,但在 DeFi 中值得
  4. 安全:会 revert 而不是给出错误结果

选择指南​

需要乘除法?
↓
中间值可能溢出?
↓ 是
需要完美精度?
↓ 是
→ 使用 FullMath ✅

否则:使用普通运算符

🚀 开始使用​

// 1. 导入
import {FullMath} from "./libraries/FullMath.sol";

// 2. 使用
function myFunction(uint256 a, uint256 b, uint256 c)
public
pure
returns (uint256)
{
return FullMath.mulDiv(a, b, c);
}

// 3. 完成!

祝你编写精确、安全的 DeFi 代码! 🎯

📢 Share this article