NestLog法〜logの近似計算アルゴリズムを考え、勢いでChatGPTにGUI化までしてもらった話〜

はじめまして！今回が初投稿となります！

内容はほぼタイトル通りなのですが、logの近似アルゴリズムを考え、ChatGPTにGUI化までしてもらった記録です。ハイになってChatGPTと一緒にNestLog法という名前までつけてしまいました:sweat_smile:
（とても簡単なアルゴリズムなので、ほぼ間違いなく誰かが既に考えていると思うのですが）
正式名称あったら教えてください

### 📊 概要

このアルゴリズムは、底 > 真数の条件のもと、連分数の構造を用いて実際のlog値を近似的に求めます。

例:log_10(2)を近似しようとした場合、以下のようになります。
![47019B98-3ED9-41A8-A9DF-F121CB8D26AA_1_201_a.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4069674/2a562fde-8824-4b24-940c-98ccc373ae5e.jpeg)

割る回数が少ないので精度は微妙ですが、これを繰り返せば、より精度良く近似できるはずだと考えました。

log_a(b)の近似手順としては、
1. 底をbとして底の変換公式を用いる(分母はlog_b(a)の形になる)
2. 真数を底の値で割っていく(真数を小さくしながら、+1を括り出すことができる)
3. 真数＜底になったら、1. を行い、繰り返す

を行います。

### ⚖️ アルゴリズム手順
さて、ここからはChatGPTさんの出番です。概要で示したアルゴリズムを文章で伝えたところ、プログラムにしてくれました。
（ここまで便利だと嬉しさ反面、自分で考えたアルゴリズムすら書き下せないのかと、ある種劣等感も覚えますね）

- 条件
・入力は底a,真数b
・a=1またはa=0, aまたはbが負の場合は定義されないとして例外とする
・a>bの場合、そのままNestLog法
・a< b のとき、底の変換公式を用いて、c をa > cかつb > cを満たすように選び、対数を変換する

・底>1, 真数<1 の場合と底<1, 真数>1 の場合は、真数を1/bとして符号を最後に反転する

これでどの値を入れても動作するようにしました。

```python:アルゴリズム
# loga(b)の形で入力する
print("loga(b) の a と b を入力してください: ")
a = float(input("底 a: "))
b = float(input("真数 b: "))

# 元のa, bを保存
orig_a = a
orig_b = b

# 符号フラグ
sign = 1
# 底>1, 真数<1 の場合
# 底<1, 真数>1 の場合
if (a > 1 and b < 1) or (a < 1 and b > 1):
    sign = -1
    b = 1 / b

# ----ここから例外処理----
# 底が0の場合
# 底が1の場合
if a == 1 or a == 0:
    print("底が0または1の場合は定義されません。")
    exit(1)
# 真数が<0の場合, 底が<0の場合
if b < 0 or a < 0:
    print("実数空間で考えましょうよ。")
    exit(1)
# 真数が1の場合
if b == 1:
    print(print(f"log{a}({b}) ≒ 0"))
    exit(1)

# ----ここまで例外処理----

# 底 < 真数かつ符号が正のとき、底の変換公式を使い、底と真数を同じ新しい底に変換する
if a < b and sign == 1:
    def approx_log(x, base):
        n_list = []
        current_base = base
        current_value = x
        for _ in range(10):
            if current_value < current_base:
                current_base, current_value = current_value, current_base
                continue
            count = 0
            epsilon = 1e-10
            while current_value - current_base > epsilon:
                current_value /= current_base
                count += 1
            if count == 0:
                n_list.append(1)
                break
            n_list.append(count)
            current_base, current_value = current_value, current_base

        ans = 0
        for i in range(len(n_list)-1, -1, -1):
            if ans == 0:
                ans = 1 / n_list[i]
            else:
                ans = 1 / (n_list[i] + ans)
        return ans
    # 新しい底（bの平方根を使って new_base < a となるように）
    # 正しいロジック：new_base は b よりも大きく、a よりも小さいもの
    new_base = b + 1  # 例：b より大きくすれば必ず底 > 真数になる
    log_a = approx_log(b, new_base)  # ← aとbの位置が正しくなるように
    log_b = approx_log(a, new_base)
    ans = log_a / log_b
    print(f"log{orig_a}({orig_b}) ≒ {ans}（底を変換して {new_base} にして近似）")
    exit(0)

# 変数の初期化
n_list = []
current_base = a
current_value = b

# 割り算と底・真数の入れ替えを繰り返す
for _ in range(10):  # 繰り返し回数は適当に設定（必要に応じて調整可）
    if current_value < current_base:
        # 底と真数を入れ替える
        current_base, current_value = current_value, current_base
        continue

    count = 0
    epsilon = 1e-10
    while current_value - current_base > epsilon:
        current_value /= current_base
        count += 1

    if count == 0:
        n_list.append(1)
        break

    n_list.append(count)
    current_base, current_value = current_value, current_base

# 近似値の計算
# 例: 1/n1 + 1/(n2 + 3) のような形で近似
ans = 0
for i in range(len(n_list)-1, -1, -1):
    if ans == 0:
        ans = 1 / n_list[i]
    else:
        ans = 1 / (n_list[i] + ans)

# 符号を元に戻す
ans *= sign
# 結果を表示
print(f"log{orig_a}({orig_b}) ≒ {ans}")
exit(0)
```

という感じになります。実行してみるといい感じに近似できました！

### 🔄 GUIで動くNestLog
さて、プログラムは完成したので、最後はGUI化しましょう！
GUI化にあたって、Tkinterを使用しました。
とは言っても、今回はChatGPTさんに頼っていたのでTkinter自体をこの機会に初めて知ったのですが、、、
新たな発見ということで、良しとしておきましょう。
Tkinterについては、こちらが参考になるかと思います。
[PythonのTkinterを使ってみる](https://qiita.com/nnahito/items/ad1428a30738b3d93762)

では、実際のコードは以下のようになります。

```python:GUI
import tkinter as tk

# ---- 近似 log 計算ロジック ----
def approx_log(x, base):
    n_list = []
    current_base = base
    current_value = x
    for _ in range(10):
        if current_value < current_base:
            current_base, current_value = current_value, current_base
            continue
        count = 0
        epsilon = 1e-10
        while current_value - current_base > epsilon:
            current_value /= current_base
            count += 1
        if count == 0:
            n_list.append(1)
            break
        n_list.append(count)
        current_base, current_value = current_value, current_base
    ans = 0
    for i in range(len(n_list)-1, -1, -1):
        ans = 1 / n_list[i] if ans == 0 else 1 / (n_list[i] + ans)
    return ans

# ---- GUIロジック ----
def calculate_log():
    try:
        a = float(entry_base.get())
        b = float(entry_value.get())
        orig_a, orig_b = a, b
        sign = 1
        if (a > 1 and b < 1) or (a < 1 and b > 1):
            sign = -1
            b = 1 / b
        if a <= 0 or a == 1 or b < 0:
            result_var.set("定義されていない入力です。")
            return
        if b == 1:
            result_var.set(f"log{a}({b}) ≒ 0")
            return
        if a < b and sign == 1:
            new_base = b + 1
            log_a = approx_log(b, new_base)
            log_b = approx_log(a, new_base)
            ans = log_a / log_b
        else:
            n_list = []
            current_base = a
            current_value = b
            for _ in range(10):
                if current_value < current_base:
                    current_base, current_value = current_value, current_base
                    continue
                count = 0
                epsilon = 1e-10
                while current_value - current_base > epsilon:
                    current_value /= current_base
                    count += 1
                if count == 0:
                    n_list.append(1)
                    break
                n_list.append(count)
                current_base, current_value = current_value, current_base
            ans = 0
            for i in range(len(n_list)-1, -1, -1):
                ans = 1 / n_list[i] if ans == 0 else 1 / (n_list[i] + ans)
        ans *= sign
        result_var.set(f"log{orig_a}({orig_b}) ≒ {ans}")
    except:
        result_var.set("数値を正しく入力してください。")

# ---- GUIの見た目部分 ----
root = tk.Tk()
root.title("log 近似計算機")

tk.Label(root, text="底 a:").grid(row=0, column=0)
entry_base = tk.Entry(root)
entry_base.grid(row=0, column=1)

tk.Label(root, text="真数 b:").grid(row=1, column=0)
entry_value = tk.Entry(root)
entry_value.grid(row=1, column=1)

tk.Button(root, text="計算", command=calculate_log).grid(row=2, column=0, columnspan=2)

result_var = tk.StringVar()
tk.Label(root, textvariable=result_var, font=("Arial", 14)).grid(row=3, column=0, columnspan=2)

root.mainloop()
```

動作させてみると、以下のようになりました！！
![nestlog.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4069674/ec803f3a-8e8a-47b0-b4a4-283c5729b8ee.gif)


ここまで簡単にGUIまでができると、手軽にアイデアを形にできそうですね！鍛錬します。

最後までご覧いただき、ありがとうございました🙇
今回の記事は、勢いのまま駆け抜けていったので、どこかミスや、もっと効率的な方法等ありましたら、教えてください！