3회 실험 · 유리수의 구멍#

이 실험

짝 서술

3회 · 실수의 공리

실험 목록

실험 1 (1층) — 확대해도 채워지지 않는다 / 실험 2 (3층) — 유리수와 무리수를 구별한다

이번 회차의 대상은 공리이다. 공리는 그림으로 확인할 수 있는 것이 아니므로 실험은 두 개뿐이며, 둘 다 서술 쪽 정리 7가 말하는 내용을 다른 각도에서 비추는 데 쓴다.

Hide code cell source

# 공통 준비 — 색 규약과 한글 글꼴을 맞춘다
import numpy as np
import matplotlib.pyplot as plt
import sympy as sp
from fractions import Fraction

from calc_viz import 
from calc_style import 한글글꼴설정

글꼴 = 한글글꼴설정()   # 돌려주는 값은 실제로 선택된 글꼴 이름이다

실험 1 (1층) — 확대해도 채워지지 않는다#

유리수는 조밀하다. 어떤 두 실수 사이에도 유리수가 있고, 확대해도 사정은 같다.

그런데 조밀하다는 것과 빈틈이 없다는 것은 다른 말이다. 아래 세 그림은 같은 지점을 세 배율로 확대한 것이다. 배율을 올리면 분모를 키워 유리수를 더 촘촘히 찍는다. 빨간 세로선은 제곱해서 \(2\)가 되는 수가 있어야 할 위치이다.

# [3회] 유리수의 조밀성과 구멍
# 목적 : 유리수는 어디에나 있지만 제곱해서 2가 되는 유리수는 없다는 것을 확인한다
# 층  : 1층
# 주의 : 그림은 유한개의 유리수만 찍는다. 그림이 증명이 될 수는 없다

중심 = 2 ** 0.5

그림,  = plt.subplots(3, 1, figsize=(9, 6))
for i, 반경 in enumerate([0.5, 0.05, 0.005]):
    분모 = int(20 / 반경)
     = np.array([j / 분모
                  for j in range(int((중심 - 반경) * 분모), int((중심 + 반경) * 분모) + 1)])
    [i].scatter(, np.zeros_like(), s=10, color=["하합"], label="유리수")
    [i].axvline(중심, color=["차이"], label="제곱해서 2가 되는 수의 위치")
    [i].set_xlim(중심 - 반경, 중심 + 반경)
    [i].set_yticks([])
    [i].set_title(f"확대 배율 {0.5 / 반경:.0f}배 (분모 {분모}까지)")
[0].legend(loc="upper right")

plt.tight_layout()
plt.show()
../_images/ef6eb4d5d512bf0e77d56b7be69c1a218bd5609732541164f251bfeedc9cf4d6.png

세 그림 모두에서 빨간 선 양옆에 파란 점이 빽빽하다. 그러나 어느 그림에서도 파란 점이 빨간 선 위에 정확히 놓이지 않는다. 분모를 아무리 키워도 그렇다.

이것을 유리수로 직접 확인해 보자. 분모를 키워 가며 제곱이 \(2\)에 가장 가까운 분수를 찾는다.

# [3회] 제곱이 2 에 가장 가까운 분수
# 목적 : 분모를 키우면 제곱값이 2 에 가까워지지만 도달하지는 않음을 확인한다
# 층  : 1층 (Fraction 으로 정확히 계산한다. 부동소수를 쓰지 않는다)
# 주의 : 가까워지는 것과 도달하는 것은 다르다

from calc_viz import    # 한글이 섞인 표의 열을 맞춘다

머리 = ["분모", "가장 가까운 분수", "제곱 − 2", "제곱 = 2 인가"]
 = [8, 20, 28, 14]
print(" ".join((, w) for , w in zip(머리, )))

for 분모 in (10, 100, 1000, 10000):
    # 제곱이 2 를 넘지 않는 가장 큰 분자를 찾는다
    분자 = max(j for j in range(1, 2 * 분모) if Fraction(j, 분모) ** 2 <= 2)
    후보 = Fraction(분자, 분모)
    차이 = 후보 ** 2 - 2
     = [분모, 후보, 차이, 후보 ** 2 == 2]
    print(" ".join((항목, w) for 항목, w in zip(, )))
    분모     가장 가까운 분수                     제곱 − 2  제곱 = 2 인가
      10                  7/5                        -1/25          False
     100              141/100                   -119/10000          False
    1000              707/500                  -151/250000          False
   10000            7071/5000                -959/25000000          False

차이 열이 정확한 분수로 나온다는 점에 유의한다. 부동소수로 계산하면 반올림 때문에 0.0이 나올 수도 있지만, Fraction은 정확히 계산하므로 차이가 결코 \(0\)이 되지 않는다는 것이 보인다.

확인할 것

  1. 세 그림에서 파란 점의 개수는 거의 같다. 그런데 배율은 100배까지 올라간다. 이것이 무엇을 뜻하는지 생각해 보자.

  2. 두 번째 코드에서 분모를 키울 때 제곱 2가 어떻게 변하는지 확인하자. \(0\)이 되는 경우가 있는가.

  3. 위 두 실험을 아무리 반복해도 정리 7를 증명할 수 없는 이유를 말해 보자.

실험 2 (3층) — 유리수와 무리수를 구별한다#

부동소수는 유리수와 무리수를 구별하지 못한다. 어떤 값이든 유한한 자릿수로 잘라 저장하므로, 저장된 값은 언제나 유리수이다.

기호 계산은 값을 자르지 않고 식의 형태로 들고 있으므로 구별이 가능하다.

# [3회] 유리수인지 아닌지를 sympy 로 판정한다
# 목적 : 부동소수로는 구별할 수 없는 것을 기호 계산으로 구별한다
# 층  : 3층
# 주의 : is_rational 은 판정 알고리즘의 결과이다. 증명이 아니다

후보 = {
    "sqrt(2)":         sp.sqrt(2),
    "sqrt(4)":         sp.sqrt(4),
    "22/7":            sp.Rational(22, 7),
    "sqrt(2)*sqrt(2)": sp.sqrt(2) * sp.sqrt(2),
    "sqrt(2)+sqrt(2)": sp.sqrt(2) + sp.sqrt(2),
}
for 이름,  in 후보.items():
    print(f"  {이름:18s} 정리하면 {str(sp.nsimplify()):12s} 유리수인가 : {.is_rational}")
  sqrt(2)            정리하면 sqrt(2)      유리수인가 : False
  sqrt(4)            정리하면 2            유리수인가 : True
  22/7               정리하면 22/7         유리수인가 : True
  sqrt(2)*sqrt(2)    정리하면 2            유리수인가 : True
  sqrt(2)+sqrt(2)    정리하면 2*sqrt(2)    유리수인가 : False

sqrt(2)*sqrt(2)가 유리수로 판정되는 것에 유의한다. 무리수끼리의 연산 결과가 언제나 무리수인 것은 아니다.

같은 값을 부동소수와 기호로 각각 다루면 결과가 갈린다.

# [3회] 부동소수와 기호 계산의 차이
# 목적 : 같은 등식이 한쪽에서는 거짓, 다른 쪽에서는 참으로 나오는 것을 확인한다
# 층  : 1층과 3층의 대조
# 주의 : 부동소수의 결과가 틀린 것이 아니라, 저장 방식이 다른 것이다

부동 = float(sp.sqrt(2))
print("부동소수로 계산하면")
print(f"  float(sqrt(2))**2 == 2 : {부동 ** 2 == 2}")
print(f"  차이                   : {부동 ** 2 - 2:.3e}")

print("\n기호로 계산하면")
print(f"  sp.sqrt(2)**2 == 2     : {sp.simplify(sp.sqrt(2) ** 2 - 2) == 0}")
부동소수로 계산하면
  float(sqrt(2))**2 == 2 : False
  차이                   : 4.441e-16

기호로 계산하면
  sp.sqrt(2)**2 == 2     : True

이제 방정식 \(x^2 = 2\)를 두 정의역에서 각각 풀어 본다.

# [3회] 정의역을 바꾸어 방정식을 푼다
# 목적 : 같은 방정식이 실수에서는 해를 갖고 유리수에서는 갖지 않음을 확인한다
# 층  : 3층
# 주의 : 이것이 3회 4절의 결론과 같은 내용이다

x = sp.Symbol("x", real=True)
방정식 = sp.Eq(x**2, 2)

print(f"  실수 범위의 해   : {sp.solveset(방정식, x, domain=sp.S.Reals)}")
print(f"  유리수 범위의 해 : {sp.solveset(방정식, x, domain=sp.S.Rationals)}")
  실수 범위의 해   : {-sqrt(2), sqrt(2)}
  유리수 범위의 해 : EmptySet

유리수 범위에서 해집합이 EmptySet이다. 이것이 서술 쪽 정리 7가 말하는 내용이며, 동시에 3회 4절이 지적한 \(\R\)\(\Q\)의 차이이다.

확인할 것

  1. float(sqrt(2))**2가 정확히 2인지 확인하자. 아니라면 차이는 얼마인가.

  2. 유리수 범위에서 해집합이 무엇인지 확인하자. 공집합인가.

  3. 실험 1의 그림과 실험 2의 마지막 결과 중 어느 쪽이 3회의 정리를 뒷받침하는가.

어느 것도 증명이 아니다

세 번째 확인 항목이 이 회차의 요점이다.

실험 1의 그림은 정황이다. 유한개의 점을 찍어 보았을 뿐이므로, 찍지 않은 어딘가에 해가 있을 가능성을 배제하지 못한다.

실험 2의 EmptySet알고리즘의 결과이다. sympy가 내부에서 어떤 판정 절차를 돌린 결과이며, 그 절차가 옳다는 것은 별도의 문제이다.

둘 다 3회 4절의 귀류법 증명을 대신하지 못한다. 증명이 하는 일은 “찾지 못했다”가 아니라 “있을 수 없다”를 보이는 것이며, 이것은 유한 번의 확인으로는 도달할 수 없다.

짝이 되는 서술

3회 · 실수의 공리