🐣

Pythonライブラリなしでlog計算してみた。math.log()関数を自作してみた。

2021/03/18に公開6

最初に

数学の勉強でlogの計算について、四則演算で理解したいと思ったので、Pythonで math.log() を自作してみたいと思った。自分は文系で高校の頃、数学が全然出来なかった。最近になって読解力が上がったのか数学が昔より出来るようになり、楽しくなってきたので中学・高校数学から勉強してる。それでも現役の高校生には余裕で負けるだろう。

対数関数(logのこと)

指数関数 y = 2^xという形になっており、xに値を入れる事でyが求まる式になっている。これを逆関数(yがわかってxを求めるなら)にすると x=\log2yとなる。yが真数と呼ばれるものでここに8を入れると底と呼ばれる2を何乗すると8になるかという式になる。xは3という事がわかる。

これくらいなら8を2で割り続けて、何回割る事が出来るかで暗算出来るが真数が4096とか膨大になると大変になる。

math.log(a, b)

ライブラリ使用する場合

import math

print(math.log(25, 5))

# 実行結果
2.0

ライブラリ使用しない場合

def myLog2(x, b):
    if x < b:
        return 0  
    return 1 + myLog2(x/b, b)

print(myLog2(25, 5))

# 実行結果
2

真数を底で割って、何回割れるかを数えている。

これがシンプルで一番分かりやすい。

別のやり方

ほんとはpythonに組み込まれた math.log() のソースコードを見れば良いのだが、そのコードの中で別の関数が使用されていたり読むの大変そうだったので諦めてしまった。

公式のドキュメントには log(x)/log(base) で求めていると書かれている。

※このbaseが何を指しているか分からない。(to base e)と書かれているが、ネイピア数の事を指しているのか?

ご存知の方はコメント欄で教えて下さい、お願いします。

With one argument, return the natural logarithm of x (to base e).
With two arguments, return the logarithm of x to the given base, calculated as log(x)/log(base).
Changed in version 2.3: base argument added.

底の変換公式に似ているから勝手にそれだと思った。

a, b, cが正の整数で、a \neq 1, c \neq 1のとき\\ \log_a{b} = \frac{\log_c{b}}{\log_c{a}}

log(x)を求める数式

\ln(x) = \lim_{n \to \infty} n(x^{\frac{1}{n}} -1)

Pythonコードにすると

極限なのでnの値を大きくすればするほど、より正確な値になると思う。

def ln(x):
    n = 1000.0
    return n * ((x ** (1/n)) - 1)

底の変換公式をPythonコードにすると

def ln(x):
    n = 1000.0
    return n * ((x ** (1/n)) - 1)

def mylog(b, a):
    x = ln(b)/ln(a)
    return x

print(round(mylog(4096, 2)))

#実行結果
12

この際のcがどれに当たるか分からないが、これでa, bが整数の場合はlogの値を計算出来るようになった。

最後に

少し最後は雑になってしまったが、もし数学が得意な方でこの記事を読んで下さっていたら base が何なのかと c がどこに行ってしまったか教えてくださると嬉しいです。

記事を読んで、数学得意な方広めて下さると幸いです。

大したコードではないですが、Githubにもコードを置いておきます。

記事に関するコメント等は

🕊:Twitter
📺:Youtube
📸:Instagram
👨🏻‍💻:Github
😥:Stackoverflow

でも受け付けています。

参照

Get logarithm without math log python

Calculate logarithm in python

対数 log の公式と計算

Pythonで指数関数・対数関数を計算(exp, log, log10, log2) | note.nkmk.me

Discussion

YutaUraYutaUra

結論

  • cは任意の数なので一般的には自然対数の底とします。
  • log(x)を求める数式で出てくる式は自然対数において成り立つ話
  • 任意の底にしたい場合は自然対数の底変換することで、log(x)を求める数式を利用できるようにしている
  • baseは対数の底のこと。指定しない場合は自然対数の底になる。

解説

公式ドキュメント math.log(x[, base])にはこのように書いてあります。

With one argument, return the natural logarithm of x (to base e).
With two arguments, return the logarithm of x to the given base, calculated as log(x)/log(base).
Changed in version 2.3: base argument added.

つまり、math.logは引数を1つか2つとることができ、

  • 引数が1つの場合: math.log(x) -> \log_{ e } x
  • 引数が2つの場合: math.log(x, base) -> \log_{ base } x

という挙動となります。また、値域に関して厳密にならなければ
\log_{ base } x = \log_{ c } x / \log_{ c } base (cは任意の数で、基本的には自然対数の底とすることが多い)
であり、
\log_{ e } e = 1
であるから、

# 自然対数の底
e = 2.71828

def ln(x):
    n = 1000.0
    return n * ((x ** (1/n)) - 1)

def mylog(a, b = e):
    x = ln(a) / ln(b) # 注:分母分子が逆でした!
    return x

print(round(mylog(4096, 2))) # 真数が4096で、底が2ですね

こんな感じの解説になるのですがなんとなくわかっていただけたでしょうか。

大学生だった.大学生だった.

とても詳しくありがとうございます。 def ln(x)c = e みたいな感じで c の値を使って計算出来るように出来たらより底の変換公式に近づけると思ったのですが、思いつかなかったです。 \log_e{e} = 1 ここだけなぜ提示されたのか分かりませんでした。
すいません。

あとコメント頂いといて申し訳ないのですが、コメントでの注意書きでなく、コード本体を修正して頂けると読む際の負担が減るのでとても助かります。

Pythonドキュメントの math.log()の引数とbase についての説明とても助かりました。
ありがとうございます。

YutaUraYutaUra

\log_e{e} = 1ここだけなぜ提示されたのか分かりませんでした。

全然当たり前の話になっちゃんですけど、以下のことを明らかにいうためです!

def mylog(a, b = e):
    """
    b = eのとき、ln(b) = 1となるので、x = ln(a)となる
    """
    x = ln(a) / ln(b)
    return x

コメントでの注意書きでなく、コード本体を修正して頂ける

僕の返信で書いたコードは@unemployed さんのコードを修正したものです。@unemployed さんのコードと違う部分をコメントにて指摘させていただきました!

大学生だった.大学生だった.

分かりました。ありがとうございます。

僕の返信で書いたコードは@unemployed さんのコードを修正したものです。@unemployed さんのコードと違う部分をコメントにて指摘させていただきました!

a, b, cが正の整数で、a \neq 1, c \neq 1のとき\\ \log_a{b} = \frac{\log_c{b}}{\log_c{a}}

を元に書いてみたコードなんですが、逆でしょうか?実際に修正の通り x = ln(a) / ln(b) と直して実行したら 0 になってしまったので違う気がします。

YutaUraYutaUra

その数式はあってます!
ただ、pythonでの実装との解離があります。

@unemployed さんの実装

def mylog(a, b):
    """ log_{b} a のこと """
    x = ln(b)/ln(a) # ←なので逆です
    return x

mylog\log_{b}aのことなので、分母分子が逆になっております。

実行したら 0 になってしまったので違う気がします。

おそらくですが、

print(round(mylog(2, 4096)))

このまま実行したのではないでしょうか?

実際に実行すべきは

print(round(mylog(4096, 2)))

こちらになります!