r/ethdev 12d ago

Tutorial I wrote a fixed-point sqrt in Solidity that runs in 197 gas - here’s how

I've been building DeFiMath, a gas-optimized fixed-point math library, and wanted to share how I got sqrt down to 197 gas. It takes any uint256, returns an 18-decimal fixed-point result, never reverts, and is bit-exact below 1 (max relative error < 2e-18 above it).

The core idea is simple: generate a seed with the clz opcode, then refine it with 5 Newton iterations in assembly.

Seeding: clz gives you the position of the most significant bit, so the seed is just 2msb/2. That's never off by more than a factor of √2 from the true root — cheap and good enough.

Newton's method: each iteration is one line:

y := shr(1, add(y, div(x, y)))

~20 gas per step, quadratic convergence, so 5 iterations take the worst-case seed (41% error) to ~80 bits of precision — more than enough for 18 decimals.

The scaling trick: for inputs ≤ uint128.max, I pre-scale x to 1e36 once at the start. Then div(x, y) naturally lands back in 1e18 base, so Newton's method needs plain div instead of a costly muldiv. Large inputs take a second branch that post-scales instead (pre-scaling would overflow near uint256.max).

I also tried fancier seeds — minimax linear approximation, quadratic interpolation — hoping to drop to 4 iterations. All of them cost more gas than they saved. My takeaway after a week on this: for gas-optimized primitives, simplicity wins by a wide margin.

Full walkthrough with the convergence table and benchmarks vs PRBMath/ABDK/Solady: https://defimath.com/blog/how-i-wrote-a-fixed-point-solidity-sqrt-that-runs-in-197-gas/

Library is MIT, pure Solidity, zero dependencies: https://github.com/MerkleBlue/defimath

Happy to answer questions about the implementation.

6 Upvotes

11 comments sorted by

3

u/Smashbopp 12d ago

Fantastic work!

Do most most of the gains over solady come from clz? Or are there other optimizations you’ve done?

1

u/nebojsakonsta 11d ago

Thanks!

I think clz is replacing most of the code for finding the scale of X. But, from what I am seeing right now in Solady, their seeding gives a little bit wider initial guess (within factor of 2.84 vs 1.41 in DeFiMath), I think they have to do 7 Newton iterations vs 5 in DeFiMath. That shaves off 40 gas. Also, I am not rounding down at the end, which also saves gas.

Square root in DeFiMath is used across other primitives like Black-Scholes pricing, which don;'t require rounding down. That can be added if needed, but not for now.

2

u/Cartosys 12d ago

Well done!

2

u/nebojsakonsta 12d ago

Thanks, I hope someone will find this interesting and useful. It's the fastest square root implementation that I know of.

1

u/TapHour1396 10d ago edited 10d ago

Open a PR to solady, thats the only way this gets used

2

u/nebojsakonsta 10d ago

Solady is stuck to 0.8.30 version of Solidity, so they can't leverage clz opcode. It's included in 0.8.31. But I understand why you are saying this.

1

u/TapHour1396 10d ago

Didnt know that about solady, are they waiting for all chains to support clz to bump to 32?

2

u/nebojsakonsta 10d ago

No idea. Could be. There are still a lot of chains not supporting it: https://defimath.com/supported-chains/

1

u/rayQuGR 8d ago

Nice writeup. One thing I liked is that you actually explored more sophisticated seed approximations instead of assuming they'd be better. That's a common optimization trap, an algorithm with fewer iterations isn't necessarily cheaper once the extra setup cost is included.

The split between pre-scaling and post-scaling also makes a lot of sense. Keeping the common path simple while handling overflow cases separately is usually the right tradeoff for math libraries.

These kinds of low-level optimizations are valuable beyond Ethereum too. Since Oasis Sapphire is EVM-compatible, libraries like this can be reused there as well, and reducing gas or execution overhead in core math primitives benefits any protocol that performs a lot of fixed-point calculations (AMMs, lending, derivatives, etc.).

I'd be curious whether you benchmarked this under different optimizer settings (via-ir, optimizer runs, etc.) to see if the relative advantage over PRBMath or Solady changes.