Fixed-Point Arithmetic (Q64.96) TODO
Concept
Fixed-point representation encodes a real number as a single integer with an implicit binary point position; the Qm.n notation means m bits for the integer part and n bits for the fractional part, and the actual value is the stored integer divided by 2 to the power n. Q64.96 puts 96 bits in the fractional part and fits within 160 bits — best known as the format Uniswap v3 uses to store the square root of price. Addition and subtraction work as plain integer operations, but multiplication produces a result with 2n fractional bits that must be scaled back down by dividing by 2^n, and division must multiply first before dividing — which makes intermediate overflow the single biggest risk. That's why implementations need mulDiv-style logic that carries a 512-bit intermediate result, and since division inevitably truncates low-order bits, standard practice is to consistently round in the direction that doesn't favor the user over the protocol.
The EVM has no floating point, so every price, interest, and fee calculation runs on fixed-point arithmetic, and getting the order of a single multiply-then-divide wrong can turn into an overflow or a rounding vulnerability that favors the attacker.
Code & Formula
# Day 32 — 고정소수점 산술 (Q64.96)
# 정수 하나에 소수부 96비트를 암묵적으로 두는 Q64.96 형식을 흉내내어 곱셈/나눗셈 스케일링을 보여준다.
Q96 = 96
SCALE = 1 << Q96 # 2^96
def to_fixed(x: float) -> int:
return int(round(x * SCALE))
def from_fixed(x_fixed: int) -> float:
return x_fixed / SCALE
def fixed_mul(a_fixed: int, b_fixed: int) -> int:
# 곱셈 결과의 소수부는 2*96비트가 되므로 다시 SCALE로 나눠 되돌린다.
return (a_fixed * b_fixed) // SCALE
def fixed_div(a_fixed: int, b_fixed: int) -> int:
# 나눗셈은 먼저 SCALE을 곱해 정밀도를 보존한 뒤 나눈다.
return (a_fixed * SCALE) // b_fixed
price_a = to_fixed(1.0001) # Uniswap v3 스타일 sqrtPrice 유사값
price_b = to_fixed(2.5)
product_fixed = fixed_mul(price_a, price_b)
quotient_fixed = fixed_div(price_a, price_b)
print(f"a = {from_fixed(price_a)}, b = {from_fixed(price_b)}")
print(f"a * b (fixed) = {from_fixed(product_fixed):.10f} (참값 {1.0001 * 2.5})")
print(f"a / b (fixed) = {from_fixed(quotient_fixed):.10f} (참값 {1.0001 / 2.5})")
print(f"\n원시 정수 a_fixed 자릿수: {len(str(price_a))} (Q64.96은 160비트 안에 들어감)")
Exercise
Implement Q64.96 multiplication and division in Solidity, deliberately find inputs that trigger intermediate overflow, then fix it with a mulDiv-style approach and verify it produces the correct value on the same inputs.
Practical Connection
LMSR price calculation and settlement amounts both need fixed-point approximations of exponentials and logarithms, so the choice of scale and rounding direction directly determines whether the protocol's balance can leak.
If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/math-50-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.