はじめに
物理の勉強をしていたり、研究/業務で取り扱っていると自然と近似計算を使っておおよそのオーダー感や数値感を計算することが多くなっている。
しかし、なかには近似式で計算していいのか微妙な場面に出くわす。この時は素直にちゃんと計算したほうがいいのだが、それでもどのあたりまで近似式が成り立つのかを知っておいたほうがいいと考えた。今回はよく使う近似式をグラフで確認しながら、どこまで近似が成り立つのかを確認する。
近似式と近似が成り立つ範囲
今回扱う近似式は以下の通りとなっている。
今回はこちらを参考に近似式を選んだ。高校物理の部屋|近似式
\sin \theta \approx \tan \theta \approx \theta \quad (|\theta| \ll 1)
\]\[
\cos \theta \approx 1 \quad (|\theta| \ll 1)
\]\[
\sqrt{n + x} \approx n^{1/2} (1 + \frac{x}{2n}) \quad (|x| \ll 1)
\]\[
(1 + x)^n \approx 1 + nx \quad (|x| \ll 1)
\]
ここで近似前の関数と1割の乖離が見られたときをしきい値として、近似が成り立たないとしたとき、次の範囲で近似が成り立たなくなる。
| 近似式 | 成立条件 | 条件の目安(1割以上乖離するとき) |
|---|---|---|
| \(\tan x \approx x\) | \(|x| \ll 1\) | \(|x| < 0.54\) |
| \(\sin x \approx x\) | \(|x| \ll 1\) | \(|x| < 0.75\) |
| \(\cos x \approx 1\) | \(|x| \ll 1\) | \(|x| < 0.43\) |
| \((n + x)^{\frac{1}{2}} \approx n^{\frac{1}{2}} \left(1 + \frac{x}{2n} \right) \) | \(\frac{|x|}{n} \ll 1\) | \(\frac{|x|}{n} < 1.427\)@\(n=4\)のとき |
| \((1 + x)^n \approx 1+nx \) | \(|x| \ll 1\) | \(|x| < 0.17\)@\(n=4\)のとき |
グラフによる近似の妥当性チェック
右上のグラフは、元の関数(黒)と近似式(黄)を比較したものである。また、水色のグラフ(右上)が数値の絶対値の差で、緑色のグラフ(左下)が近似前の関数からの乖離の割合となる。小さな値の範囲では非常によく一致するが、値が大きくなるにつれて乖離が顕著になる。どのあたりから近似が破綻していくかを視覚的に確認するのに役立つ。
\(\tan x \approx x\)

小さい角度では一致度が高いが、1割の誤差を超えるのは\(x \approx 0.5\)のあたりである。
\(\sin x \approx x\)

\(\sin x\) は比較的緩やかな変化を持つため、広い範囲で近似が成り立つ。ただし、\(x \approx 0.75\) を超えると乖離が無視できなくなる。
\(\cos x \approx 1\)

\(\cos x\) は \(x = 0\) 周辺で1に近い値を取るが、意外と早く下降し始める。1割以上の誤差が発生するのは \(x \approx 0.17) のあたりである。
\(\sqrt{n + x} \approx n^{1/2} (1 + \frac{x}{2n})\)

平方根関数はなだらかな増加を示すが、線形の近似式との差は次第に広がっていく。
\((1 + x)^n \approx 1 + nx\)

べき乗が関わる場合、乖離はより急激に拡大するはずだが今回は緩やか。 \(n=4\) のとき、\(x \approx 1\) あたりから乖離している。
Pythonコード
また、$n$などの別の定数が含まれていたり、差分が0.1割までが成り立つ範囲を確認したいとかの要望があると思うので、
今回計算に使ったソースコードを提示しておきます。ソースコードの説明はこちらも参考にしてください
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd
#フォントサイズを20に
plt.rcParams["font.size"] = 16
#メモリ線を内側に
plt.rcParams['ytick.direction'] = 'in'
#メモリ線を内側に
plt.rcParams['xtick.direction'] = 'in'
#上部に目盛り線を描く
plt.rcParams["xtick.top"] = True
#右部に目盛り線を描く
plt.rcParams["ytick.right"] = True
color_list = ["black", "#E69F00", "#56B4E9", "#009E73", "#F0E442",
"#0072B2", "#D55E00", "#CC79A7", "#999999"]
def fill_nan_with_neighbors(arr):
"""
NaNを前後の値で補完する。
最初や最後がNaNの場合も直近の有効な値で埋める。
"""
arr = np.array(arr, dtype=np.float64) # 確実にfloat型(NaN扱える)にする
s = pd.Series(arr)
filled = s.fillna(method='ffill').fillna(method='bfill')
return filled.to_numpy()
def compare_function(x,y1,y2,y1_label,y2_label):
plt.figure(figsize = (18,12))
#1個めのplot
plt.subplot(2,2,1)
plt.plot(x, y1, label = y1_label, color=color_list[1])
plt.plot(x, y2, label = y2_label, color=color_list[0])
plt.ylabel("y")
plt.xlabel("x")
plt.legend()
plt.grid()
y_ma = np.max([np.max(y1), np.max(y2)])
plt.ylim(0,y_ma + y_ma * 0.1)
plt.xlim(np.min(x),np.max(x))
#2個めのplot
plt.subplot(2,2,2)
plt.plot(x,np.abs(y2-y1),color=color_list[2])
plt.ylabel("近似式と元関数の差分の絶対値")
plt.xlabel("x")
plt.grid()
plt.ylim(0)
plt.xlim(np.min(x),np.max(x))
#3個めのplot
plt.subplot(2,2,3)
y3 = np.abs(y2-y1)/np.abs(y1)
y3 = fill_nan_with_neighbors(y3)
plt.plot(x,y3*100,color=color_list[3])
plt.ylabel("近似式と元関数の変化の割合(%)")
plt.xlabel("x")
plt.grid()
plt.ylim(0)
plt.xlim(np.min(x),np.max(x))
plt.hlines(y=10,xmin=np.min(x),xmax=np.max(x),color="r",linestyles="--")
plt.show()
# calculate the difference array
difference_array = np.absolute(0.1-y3)
# find the index of minimum element from the array
index = difference_array.argmin()
print("どこまで近似(<1割未満)が成り立つか",x[index])
# tan(x) ~ x, |x|<<1
x = np.arange(0,0.6,0.01)
y1 = np.tan(x)
compare_function(x,y1,x,"y = tanΘ","y = x")
# sin(x) ~ x, |x|<<1
x = np.arange(0,0.9,0.01)
y1 = np.sin(x)
compare_function(x,y1,x,"y = sinΘ","y = x")
# cos(x) ~ 1, |x|<<1
x = np.arange(0,0.5,0.01)
y1 = np.cos(x)
y2 = np.array([1 for _ in range(len(x))])
compare_function(x,y1,y2,"y = cosΘ","y = x")
# (1+x)^n ~ 1 + nx, |x|<<1
# n = 4
x = np.arange(0,0.2,0.01)
n = 4
y1 = (1+x)**n
y2 = 1 + n*x
compare_function(x,y1,y2,"y = (1+x)$^n$","y = 1+nx")
# (n+x)^1/2 = n^1/2 (1 + x/n)^1/2
# ~ n^1/2 (1 + x / (2n) )
# |x| << n, → |x|/n << 1
# n = 4
x = np.arange(0,7,0.01)
n = 4
y1 = n**(1/2) * (1 + x/n) ** (1/2)
y2 = n**1/2 * (1 + x /(2*n))
compare_function(x,y1,y2,"y = (n+x)$^{1/2}$","y = n$^{1/2}$ (1 + x/(2n))")



コメント