0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Project Euler 026を解いてみる。「逆数の循環節 その1」

Last updated at Posted at 2019-09-24

Project Euler 026

026

単位分数とは分子が1の分数である. 分母が2から10の単位分数を10進数で表記すると次のようになる.
1/2 = 0.5
1/3 = 0.(3)
1/4 = 0.25
1/5 = 0.2
1/6 = 0.1(6)
1/7 = 0.(142857)
1/8 = 0.125
1/9 = 0.(1)
1/10 = 0.1
0.1(6)は 0.166666... という数字であり, 1桁の循環節を持つ. 1/7 の循環節は6桁ある.
d < 1000 なる 1/d の中で小数部の循環節が最も長くなるような d を求めよ.

->次の問題

考え方

とりあえず、2・5以外の素数が分母の素因数に含まれると循環小数になるようです。

コード書いてから調べてみたところによると、フェルマーの小定理により、
2・5以外の素数pについて、1/pの循環小数はp-1桁で循環するとのこと。
ただ、これは循環節がp-1というわけではなく、p-1桁のところでは少なくとも循環するってことのようです。
1/3 = 0.33 33 33 33 33
1/7 = 0.142857 142857 142857
1/11 = 0.0909090909 0909090909 0909090909
つまり循環節はp-1の約数のどれかとなります。

上記を調べる前にコードを書いたので(T_T)、全ての数について循環節の数を調べていました。
まず、循環節の数を返す関数を定義しました。
手順としては、分母dについて
①1/dの余りを出す
②余りを[余りのリスト]に追加
③(余り*10)/dの余りを出す
④余りのリストに同じ余りがあるかチェック
⑤なければリストに追加し、③に戻る
⑥同じ余りがあればそこから循環するので循環節の長さを返す
これをそのままコードにしています。

euler026.py
def digits_recurring_cycle(denominator: int) -> int:
    """
    1/d(denominator)の循環小数の循環節の数を返す関数
    1/7 = 0.142857142857...循環節は6
    割り切れる場合は0を返す
    :param denominator: 分母 int
    :return: 循環節の数 int
    """
    remainder = 1
    remainders = []  # 各計算の余りを格納する
    while True:
        remainder = remainder % denominator  # 余りを求める
        if remainder == 0:  # 割り切れれば
            return 0
        # 出た余りが以前に出ていれば、そこから繰り返し(循環)に入るということ
        # 以前に同じ余りが出たところ(remainders.index(numerator))から
        # 今回出たところの直前(配列の終わり)までの長さが循環節の長さになる
        if remainder in remainders:  # 今回出た余りと同じ余りが以前出ていれば
            return len(remainders[remainders.index(remainder):])  # 循環節の長さを返す
        remainders.append(remainder)  # なければ余りのリストに追加して次のループへ
        remainder *= 10  # 余りを10倍して、次のループでもう一度denominatorで割る

コード

euler026.py
def main():
    n = 1000
    max_digits = 0
    answer = 0
    # 結果を見るに、探索範囲は素数だけでいいような気がする
    for i in range(3, n + 1, 2):
        digits = digits_recurring_cycle(i)
        if digits > max_digits:
            max_digits = digits
            answer = i
    print(f'{n}以下で1/dの循環節が最大となるのはd={answer}のとき、循環節は{max_digits}である')


def digits_recurring_cycle(denominator: int) -> int:
    """
    1/d(denominator)の循環小数の循環節の数を返す関数
    1/7 = 0.142857142857...循環節は6
    割り切れる場合は0を返す
    :param denominator: 分母 int
    :return: 循環節の数 int
    """
    remainder = 1
    remainders = []  # 各計算の余りを格納する
    while True:
        remainder = remainder % denominator  # 余りを求める
        if remainder == 0:  # 割り切れれば
            return 0
        # 出た余りが以前に出ていれば、そこから繰り返し(循環)に入るということ
        # 以前に同じ余りが出たところ(remainders.index(numerator))から
        # 今回出たところの直前(配列の終わり)までの長さが循環節の長さになる
        if remainder in remainders:  # 今回出た余りと同じ余りが以前出ていれば
            return len(remainders[remainders.index(remainder):])  # 循環節の長さを返す
        remainders.append(remainder)  # なければ余りのリストに追加して次のループへ
        remainder *= 10  # 余りを10倍して、次のループでもう一度denominatorで割る


if __name__ == '__main__':
    from time import time as t
    st = t()
    main()
    print(t() - st, 'sec')

paizaにて実行
結果 1000以下で1/dの循環節が最大となるのはd=983のとき、循環節は982である 0.15410661697387695 sec
結果を見るとなんとなく探索範囲は素数だけで良い気がしますが、
1/21の循環節が6だったりするので最も長い循環節になるのかなと思ったら1/49の循環節が42だったりするので分母が非素数だった場合の循環節のルールがよくわかりません…

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?