Workspace IndexMath › Day 32

Fixed-Point Arithmetic (Q64.96) TODO

Math · Day 32 / 52 · October — Calculus & Optimization (Day 27-34)

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.

Where it lands in Jayverse

Key expressions

Words and phrases from this page worth keeping, with the Korean meaning and the sentence they come from.

Expression뜻 · 쓰이는 자리
scale back down(원래 규모로) 다시 축소하다 · 곱셈 후 늘어난 소수부 비트를 되돌려 줄이는 과정. "must be scaled back down by dividing by 2^n"
the single biggest risk가장 큰 단일 위험 요소 · 중간 오버플로가 최대 위험임을 강조할 때. "makes intermediate overflow the single biggest risk"
carry (an intermediate result)(계산 중간값을) 담아 처리하다 · 512비트 중간 결과를 유지하는 mulDiv 로직. "carries a 512-bit intermediate result"
truncate(하위 비트를) 잘라내다, 버리다 · 나눗셈 과정에서 정밀도가 손실되는 이유. "division inevitably truncates low-order bits"
favor X over YY보다 X에 유리하게 처리하다 · 반올림 방향이 프로토콜보다 사용자에게 유리하지 않게 한다는 원칙. "doesn't favor the user over the protocol"
leak (balance)(자금·잔고가) 의도치 않게 새어나가다 · 반올림 실수가 프로토콜 잔고 유출로 이어지는 상황. "the protocol's balance can leak"
Q64.96정수부 64비트, 소수부 96비트의 고정소수점 표기(Qm.n notation) · Uniswap v3가 가격의 제곱근을 저장하는 포맷. "Q64.96 puts 96 bits in the fractional part"
Uniswap v3대표적인 탈중앙 거래소(DEX) 프로토콜 · Q64.96 포맷으로 가격의 제곱근을 저장하는 주체. "the format Uniswap v3 uses to store the square root"
LMSR로그 시장 스코어링 규칙(Logarithmic Market Scoring Rule) · 예측시장 가격 결정에 쓰이는 메커니즘, 고정소수점 근사가 필요한 지점. "LMSR price calculation and settlement amounts both need fixed-point approximations"
mulDiv곱한 뒤 나누되 512비트 중간값을 유지해 오버플로를 막는 함수 패턴(mulDiv) · 정밀도 손실과 오버플로를 동시에 방지하는 구현 방식. "implementations need mulDiv-style logic that carries a 512-bit intermediate result"

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/.


한국어

고정소수점 산술(Q64.96) TODO

Math · Day 32 / 52 · 10월 — 미적분·최적화 (Day 27–34)

개념

고정소수점은 정수 하나에 이진 소수점의 위치를 암묵적으로 정해 실수를 표현하는 방식으로, Qm.n 표기는 정수부 m비트와 소수부 n비트를 뜻하며 실제 값은 저장된 정수를 2의 n제곱으로 나눈 값이다. Q64.96은 소수부를 96비트로 두는 형식으로 160비트 안에 담기며, Uniswap v3가 가격의 제곱근을 이 형식으로 저장하는 것으로 잘 알려져 있다. 덧셈과 뺄셈은 정수 연산 그대로지만, 곱셈은 결과의 소수부가 2n비트가 되므로 다시 2의 n제곱으로 나눠 스케일을 되돌려야 하고 나눗셈은 반대로 먼저 곱해야 해서, 중간값 오버플로가 가장 큰 위험 요소가 된다. 그래서 512비트 중간 결과를 다루는 mulDiv 계열 구현이 필요하며, 나눗셈에서 하위 비트가 버려지는 절단 오차는 불가피하므로 반올림 방향을 프로토콜에 불리하지 않은 쪽으로 일관되게 정하는 것이 표준 관행이다.

EVM에는 부동소수점이 없어 모든 가격·이자·수수료 계산이 고정소수점으로 이루어지고, 곱셈과 나눗셈의 순서 하나가 오버플로나 사용자에게 유리한 반올림 취약점으로 이어진다.

코드 · 수식

# 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비트 안에 들어감)")

연습

Q64.96 곱셈과 나눗셈을 Solidity로 구현해 중간 오버플로가 발생하는 입력을 직접 찾아낸 뒤, mulDiv 방식으로 고쳐 같은 입력에서 정확한 값이 나오는지 검증하라.

실무 · Verex 연결

LMSR 가격 계산과 정산 금액 산출은 지수와 로그를 고정소수점으로 근사해야 하므로, 스케일 선택과 반올림 방향이 곧 프로토콜에서 잔액이 새는지 여부를 결정한다.

Jayverse에서의 위치

핵심 표현

이 페이지의 영어 본문에서 배울 만한 단어와 표현, 뜻과 나온 자리.

Expression뜻 · 쓰이는 자리
scale back down(원래 규모로) 다시 축소하다 · 곱셈 후 늘어난 소수부 비트를 되돌려 줄이는 과정. "must be scaled back down by dividing by 2^n"
the single biggest risk가장 큰 단일 위험 요소 · 중간 오버플로가 최대 위험임을 강조할 때. "makes intermediate overflow the single biggest risk"
carry (an intermediate result)(계산 중간값을) 담아 처리하다 · 512비트 중간 결과를 유지하는 mulDiv 로직. "carries a 512-bit intermediate result"
truncate(하위 비트를) 잘라내다, 버리다 · 나눗셈 과정에서 정밀도가 손실되는 이유. "division inevitably truncates low-order bits"
favor X over YY보다 X에 유리하게 처리하다 · 반올림 방향이 프로토콜보다 사용자에게 유리하지 않게 한다는 원칙. "doesn't favor the user over the protocol"
leak (balance)(자금·잔고가) 의도치 않게 새어나가다 · 반올림 실수가 프로토콜 잔고 유출로 이어지는 상황. "the protocol's balance can leak"
Q64.96정수부 64비트, 소수부 96비트의 고정소수점 표기(Qm.n notation) · Uniswap v3가 가격의 제곱근을 저장하는 포맷. "Q64.96 puts 96 bits in the fractional part"
Uniswap v3대표적인 탈중앙 거래소(DEX) 프로토콜 · Q64.96 포맷으로 가격의 제곱근을 저장하는 주체. "the format Uniswap v3 uses to store the square root"
LMSR로그 시장 스코어링 규칙(Logarithmic Market Scoring Rule) · 예측시장 가격 결정에 쓰이는 메커니즘, 고정소수점 근사가 필요한 지점. "LMSR price calculation and settlement amounts both need fixed-point approximations"
mulDiv곱한 뒤 나누되 512비트 중간값을 유지해 오버플로를 막는 함수 패턴(mulDiv) · 정밀도 손실과 오버플로를 동시에 방지하는 구현 방식. "implementations need mulDiv-style logic that carries a 512-bit intermediate result"

공부한 날 원본 커리큘럼(docs/knowledge/math-50-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 1035. 뉴턴법/고정점 반복(StableSwap 필수)1037. 테일러 급수(1차 근사) →