見出し画像

文系でも分かる!Pythonプログラミング - Pythonが無量大数を超える数を扱える理由

← preview

next →



今回から「小学生でも分かる!」を
「文系でも分かる!」に変更したいと思います。

>> multiple-precision integer〔MPI〕 = 多倍長整数

long long long long long long long long long...

>> muptiple = 多数、多重、複数
〔 multiple ( マルティプル) 〕

>> precision = 正確さ、精密さ
〔 precision ( プリシジョン ) 〕


例えば、

「1234567890123456789012345678901234567890」

このような大きな数を
print文でコンソール出力してみましょう。

( 結果は省略しますが、)
同じ数字が当然のように出力されます。

ではさらに、
めっちゃくちゃ大きい数 と めっちゃくちゃ大きい数 の
掛け算をさせてみましょう。

一瞬で答えが出た。

また当たり前のように答えが出てきましたが...

これ実は、「当たり前」ではないんですよね。


そもそもの話、
あんな極端な数を使った計算は
現実的ではありません。

科学的、天文学的な分野ですら
扱うかどうかという。

計算結果は数え間違えてなければ267桁。

「無量大数」をとうに超えて
「阿伽羅(あから)」という仏教数詞を使わないと
表せないような数になってしまいました。

( ※無量大数=10⁶⁸ / 阿伽羅=10²²⁶ )


C#言語などにおけるint型は、
あなたがお使いのPCのメモリ(8GBとか64GBとか)のうち
32bit ( 4byte )を占めるらしいです。

8GBメモリ → 4 / 8,589,934,592(byte)
64GBメモリ → 4 / 68,719,476,736(byte)

つまり「1」とか「123」とか打ち込んだら
メモリをそのくらい消費しますって事です。

じゃあ、あれだけ長い整数を打ち込んだら
結構メモリ消費しちゃうのかな?と思いますよね。


int型を指定した時に消費する32bit
表現できる整数の限界というのは

「 2進数の『1』が32個並んだ数まで 」です。

つまりこう。

「 11111111111111111111111111111111 

👆これを10進数にすると

「 4,294,967,295 ( 42億9496万7295 ) 」。

およそ42億9500万通りの
int型(整数)
を扱えるわけです。


あくまでおよそ42億9500万"通り"
だという事に気を付けてください。

int型
ではマイナスゼロも表現するので
使える整数の絶対値は半分になります。

つまり半分である

-2,147,483,648 ~ 2,147,483,647
( 21億4748万3648 )

この範囲のint型が使えるということです。

(プラスの方は0も含んでいるので
マイナスより1つ分少ない数になっています。)


え?少なくね?と思いませんか?

これじゃあ国家予算や
GDP(国民総生産)が計算できないし、
100億円レベルの興行収入も扱えないし、
世界人口も天文学も扱えません。

実際、Pythonじゃない言語では、
±21億4748万....を超えるint型を使おうとすると

「 特別な記述方法でないと扱うことができません 
という趣旨のエラーが出てしまうようです。


そんな時、C#言語では
「uint型」「long型」「ulong型」という
特殊な型を指定して、より多くのメモリを占領して
扱える整数の幅を拡張します。

例えば「long型」では、
占有するメモリを32bitから64bitに拡張します。

64bitで表現できる整数の上限は、

「 2進数の『1』が64個並んだ数まで 」なので

18,446,744,073,709,551,616 通り
(1844京 6744兆 737億 955万 1616)

までということになります。

つまり、

-9,223,372,036,854,775,808 〜
9,223,372,036,854,775,807 ( 922京 )

この範囲内の整数なら
使えるということになります。


こんな風に占領するメモリの領域を増やせば
使える整数も増えるわけですが、

「long型」でさえ19桁を超える整数を使うと、
エラーになってしまいます。

(「ulong型」の場合、
使えるのは1844京...の20桁だけど、
マイナスは使えません。)

まあ、±922京...も使えるなら
さっき例に挙げた計算たちは
一通りできますから良いと言えば良いですよね。

逆に数千万程度の整数しか使わないのに
「long型」を指定して
無闇にメモリを占領するのは勿体無いですね。
1つ1つのサイズ自体は微々たる差ではありますけれど。


>> Pythonが10⁶⁸(無量大数)を超える整数を扱える理由

limit breaking.

じゃあなぜPythonは
無量大数を遥かに超える整数(int型)を扱えたのか?

それは、打ち込まれた整数がとある数を超えた時に、
Pythonが自動的に使うメモリの領域を広げてくれるからです。


Pythonの場合、
int型のオブジェクトを生成した時に

デフォルトで 28byte のメモリを
占有するようになっているようです。

getsizeofメソッド

※ ( )内のオブジェクトのメモリ消費量を返してくれる。
「メソッド」については今は理解不要です。


「とある数を超えた時に、
Pythonが自動的に使うメモリの領域を広げてくれる
と書きました。

実際に拡張されるのかどうかを見てみましょう。

とある数、具体的には
「1,073,741,824 ( 2の30乗 ) 」以上の整数を使うと

自動的に占有するメモリサイズが
28byteから32byteに増えます。👇

確かに2の30乗から
32byteになっているようだ。

※デフォルトを「28bit」と書いているサイト記事を読みました。

しかし、Pythonドキュメントの「getsizeofメソッド」の項目には

「objectのサイズをバイトで返す」と書かれていました。👇

bit」と「byte」を間違えないようにしたいですね。

1byte = 8bit

import sys

print(getsizeof(1<<32))

👆これを実行すると「32」とだけ出力されます。

32bit? 32byte?

「お使いのPCのメモリのうち32bitを占める」という話から

そのまま「32bitのことか〜」と思ってしまったのですが...

「 1<<32 」とは、

10進数の「1」を2進数にして、
そのビットを32個分
左にシフトして(ずらして)やる事
です。

⚠️10進数、あるいは2進数の「1」が
32個並んでいるという事ではありませんし、

10進数の「1」を
10進数のまま32個左にずらして
「1溝」にしたということでもありません。

1
👈あっち(左)に32個ずらす。
ズレた部分は「0」になる。
100000000000000000000000000000000

このようにビットを左シフトした結果
3 3 桁 2進数が出来上がります。↗️


考えてみてください。

32bitの限界

「 2進数の『1』が 32個 並んだ状態 

...だったはずですよねえ?

[ 32bitの限界 ]
11111111  11111111  11111111  11111111

[ 1<<32 ]
1  00000000  00000000  00000000  00000000

※8bitごとに区切っています。

32bitの限界を突破してしまってるじゃあないですか。

そもそも33桁の2進数
32bit」であるはずがありません。

完全におかしなことになってしまう。

とすればgetsizeofメソッドで出力されたのは
「28bit」「32bit」ではなく、

ドキュメントに記載があった通り
「 28byte 」「 32byte 」であると考えざるを得ません。

しかしながら、このままでは

「 28byte 」= 「 28×8bit = 224bit 」
「 32byte 」= 「 32×8bit = 256bit 

ということになりますよね?

「1」とか「5」とか「123」などの

int型オブジェクトを生成しただけで

普通224bitもメモリを消費するか???

さっき32bitポッキリとか言ってたじゃないか!!

ボッタクリだろこんなの!!!!
警察呼んだって払わねえぞ!!!!
コノヤロウ!!!( ? )

しかし、合ってるらしいんですよコレが。


占有するメモリが拡張されるタイミングは

2の30*n乗 ( 1<<30*n ) 
※nは自然数。
(2の30乗, 60乗, 90乗, 120乗, 150乗...ということ。)

本当にそうなってるかどうか調べてみてね。

実行結果をよく見ると、

28, 32, 36, 40, 44, 48, 52...(byte)
というように

4byte( 32bit )ずつ
占有メモリが増えている事が分かります。

4byte( 32bit ) あれば入力されたint型を
表現できるはずなので、

28 - 4 = 24 となって

int型オブジェクトを生成した時
謎の用途で 24byte メモリを占有している事が分かります。


24byteって何に使ってんのよ??

これに関しては考えてもしょうがないので
質問サイトでどういう事か尋ねてみました。

結果から言うと、
僕は今時点ではちゃんと理解するが
できそうもないので諦めました。

C言語を学んだらめっちゃ分かりやすく
解説できるかもしれません。

C言語の知識(ポインタ等)がある方はこちらをどうぞ。

メモリ上でint型のオブジェクトとは
どんなものかを定義する必要があって?

「参照カウントで8バイト、
TypeObjectへのポインタで8バイト、
可変部分のカウントで8バイトの24バイトっぽいです。 」?
「0 は可変部分が不要なのか、24バイトしか使わない模様。」?

...だそうです。

C言語の本を買う事にしました。


そういえば10進数の「0」消費する
メモリサイズを調べていませんでした。

0」の時は24byteになっている事を確認して
この話は一旦ここで切り上げたいと思います。

24byte

ちなみに僕の11インチ iPad Pro (第2世代)のPythonistaで調べると、
このような結果になりました。👇

2進数の「10000000000000.....」というのが
359億5777万9898桁を超えると
アプリがクラッシュする。

その時の使用メモリが4.79/6GB。


実際にその桁数の「10000000000000...」を
打ち込めるわけではありません。
打ち込んでいる間に別途でメモリを消費するからです。

だからこれを10進数にした時に
どんな数になるかは知る由もない。

というか知る必要がない。お疲れ様。


メモリの空き容量は常に変動しているわけですから、
あくまで参考値にしかならないのですが、

大事なのは、int型(整数)が制限なく
使えるようになった「当たり前」の裏にある
開発者や研究者たちの努力に気づくという事です。


まかり間違ってもデカすぎる数の
冪(べき)乗計算とかはさせないでね。
めっちゃ時間かかるし機械が可哀想だから。

10万桁の「111111... 」を
10進数に直すとこんな感じ。画面外にまだ続いている。

359億桁の「111111...」を10進数に直す事が
どれだけ無駄なのかがよく分かる。

まとめ

● Pythonは使えるint型(整数)が無制限
(※使っているメモリによって限界が変わる)

● int型オブジェクトを生成した時に消費するメモリサイズは
「0」→ 24byte
「1」〜「2³⁰ -1」→ 28byte
「2³⁰」〜「2⁶⁰ -1」→ 32byte

 2³⁰*ⁿ ( 1<<30*n ) ※nは自然数 」のタイミングで
占有メモリが 4byte( 32bit ) ずつ拡張される。

● 開発者たちは頑張っとる

● C言語などを学ぼう。


この記事が気に入ったらサポートをしてみませんか?