로그함수와 로그 스케일 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
"""로그함수와 로그 스케일 검증.
문서: 08.로그함수와 로그 스케일 (예시, 정의, 예제, 활용, 카드 C1~C3, 자주 하는 오해)
주장 1: y = c·x^k이면 로그-로그 좌표에서 기울기 k인 직선, y = a·b^x이면 반로그 좌표에서 기울기 log b인 직선이다.
주장 2: 두 측정점 (x1, y1), (x2, y2)의 로그-로그 기울기 (log y2 - log y1)/(log x2 - log x1)는 로그의 밑과 무관하다.
주장 3: 역순 입력의 삽입 정렬 비교 횟수는 정확히 n(n-1)/2이고, n = 1000 -> 8000의 로그-로그 기울기는 약 2.00이다.
병합 정렬의 비교 횟수는 기울기가 약 1.1로, 1과 2 사이다 (n lg n).
주장 4: 10 log10 2 ≈ 3.01 dB, 1/1000은 -30 dB.
주장 5: lg x는 x = 1, 2, 1024, 10^6, 10^9에서 0, 1, 10, 19.9, 29.9로 아주 느리게 자란다.
"""
import math
import random
def insertion_sort_comparisons(a):
a = list(a)
count = 0
for i in range(1, len(a)):
key, j = a[i], i - 1
while j >= 0:
count += 1
if a[j] > key:
a[j + 1] = a[j]
j -= 1
else:
break
a[j + 1] = key
assert a == sorted(a)
return count
def merge_sort_comparisons(a):
if len(a) <= 1:
return list(a), 0
mid = len(a) // 2
left, cl = merge_sort_comparisons(a[:mid])
right, cr = merge_sort_comparisons(a[mid:])
out, i, j, c = [], 0, 0, cl + cr
while i < len(left) and j < len(right):
c += 1
if left[i] <= right[j]:
out.append(left[i]); i += 1
else:
out.append(right[j]); j += 1
out += left[i:] + right[j:]
return out, c
def slope(p1, p2, base=10):
(x1, y1), (x2, y2) = p1, p2
L = lambda t: math.log(t, base)
return (L(y2) - L(y1)) / (L(x2) - L(x1))
def main():
for base in (2, math.e, 10):
assert math.isclose(slope((2, 5 * 2 ** 3), (7, 5 * 7 ** 3), base), 3)
ys = [3 * 2 ** x for x in range(6)]
diffs = [math.log(b, base) - math.log(a, base) for a, b in zip(ys, ys[1:])]
assert all(math.isclose(d, math.log(2, base)) for d in diffs)
print("[OK] 주장 1·2: 5x^3의 로그-로그 기울기 3, 3·2^x의 반로그 간격 log 2 — 밑 2, e, 10에서 같다")
counts = {n: insertion_sort_comparisons(range(n, 0, -1)) for n in (1000, 2000, 4000, 8000)}
assert all(c == n * (n - 1) // 2 for n, c in counts.items())
s_ins = slope((1000, counts[1000]), (8000, counts[8000]))
assert f"{s_ins:.2f}" == "2.00"
rng = random.Random(8)
mc = {n: merge_sort_comparisons([rng.random() for _ in range(n)])[1] for n in (1000, 2000, 4000, 8000)}
s_merge = slope((1000, mc[1000]), (8000, mc[8000]))
assert mc == {1000: 8690, 2000: 19412, 4000: 42761, 8000: 93643} and f"{s_merge:.2f}" == "1.14"
print(f"[OK] 예시: 삽입 정렬 비교 {counts[1000]:,} -> {counts[8000]:,}, 기울기 {s_ins:.3f} / 병합 정렬 {mc[1000]:,} -> {mc[8000]:,}, 기울기 {s_merge:.3f}")
assert math.isclose(slope((1000, 0.02), (8000, 1.28)), 2)
print("[OK] 카드 C1: (1000, 0.02 s), (8000, 1.28 s)의 기울기 2")
assert f"{10 * math.log10(2):.2f}" == "3.01" and 10 * math.log10(1 / 1000) == -30
print(f"[OK] 카드 C3: 2배 = {10*math.log10(2):.2f} dB, 1/1000 = -30 dB")
vals = [math.log2(x) for x in (1, 2, 1024, 1e6, 1e9)]
assert vals[:3] == [0, 1, 10] and f"{vals[3]:.1f}" == "19.9" and f"{vals[4]:.1f}" == "29.9"
print("[OK] lg: 1, 2, 1024, 10^6, 10^9 -> " + ", ".join(f"{v:.1f}" for v in vals))
# 오해: y = x^2은 로그-로그에서 직선(기울기 2)이지만 선형 관계가 아니다
assert math.isclose(slope((1, 1), (10, 100)), 2) and (2 * 10) ** 2 != 2 * 10 ** 2
print("[OK] 오해: y = x^2도 로그-로그 직선(기울기 2). 선형이면 기울기가 1이어야 한다")
print("ALL CHECKS PASSED")
if __name__ == "__main__":
main()