おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

Python初心者のお勉強ノート(2)数と演算

Pythonでは数にいくつかのタイプがありますが、ここではint型(整数型)とfloat型(浮動小数点型)を説明します。

int型とfloat型

コンピュータでは、数をメモリーに保存し、計算をするときはメモリーからCPUに移動して計算します。 詳しいことは長くなるので避けますが、整数はほぼそのままの形でメモリーに保存します。 それに対して小数部分のある数は二進数の浮動小数点という形式で保存します。

私たちが理科で使う浮動小数点は十進数のそれです。 例えば、1モルの中に含まれる分子の数であるアボガドロ数は、非常に大きな数で通常の数では桁が多すぎるので、浮動小数点表示にします。 それは、 6.02\times 10^{23}と表せます。 まず、整数部分が一桁の小数を書き、それに10の累乗をかけます。 この記法により、非常に大きな数や非常に小さい数(0に近い数)をコンパクトに表示できます。

コンピュータ内部では二進数を使っています。 次の対応表を見てください。 二進数では各桁が2になると左となりに繰り上がるので、各桁は0か1しかでてきません。

十進数 二進数
1 1
2 10
3 11
4 100
5 101
6 1110
7 1111
8 1000

浮動小数点では、2をベースにします。

1.01\times (10)^{101}

これはすべて二進数なので、累乗の底になっている「10」も二進数で、十進数の「2」であることに注意してください。 二進数の「1.01」は、十進数では

1.01=1+2^{-2}=1.25

になります。したがって、この数は十進数では

1.01\times (10)^{101}=1.25\times 2^5=1.25\times 32=40

です。 あるいは、二進数では2の累乗をかけると、その指数の分だけ小数点が右にずれますから

1.01\times (10)^{101}=101000=2^5+2^3=32+8=40

この方が計算は簡単かもしれません。

少しわき道にそれてしまいました。 コンピュータでは小数は浮動小数点表示だということに戻りましょう。 浮動小数点は、(1)符号(2)仮数部ーーこれは浮動小数点表示の前半部で小数点を含む数字のこと(3)指数部ーー後半の2の累乗、の3つに分かれます。 これをセットにしてメモリーに保存します。 この形式は複雑ですが、現代のCPUはその計算のための専用ユニット(専用のハードウェア)を持っており、高速に計算できます。

以上のような理由から、コンピュータ言語には2つのタイプint型(整数型)とfloat型(浮動小数点型)があることが多いです。 ただ、CPUとしては、intやfloatの大きさに制限があります。 例えば64ビットCPUでは通常intの大きさは64ビットまでです。 一方、Python言語では、これよりも大きな整数も使え、その計算を実行するときには専用のライブラリを使うなどして実行しています。 つまり、Pythonには整数の大きさに制限はありません。

Pythonにのfloat型はint型とは異なり、大きさの制限があります。 これは、IEEE 754倍精度浮動小数点数Pythonが採用しているので、その制限と同じです。 浮動小数点はどうしても誤差が発生するので、正確な計算が求められる場合には向いていません。

プログラム中にint型やfloat型を記述するとき、それらはリテラルと呼ばれます。

int型のリテラル

  • 十進数を表す場合は普通にそのまま書けばよい。例えば、123, -5, 0など。
  • 二進数で表すときは、先頭に0bまたは0Bをつける。例えば、0b101など。
  • 八進数で表すときは、先頭に0o(ゼロ、オー)または0O(ゼロ、大文字のオー)をつける。例えば0o77など。
  • 十六進数で表すときは、先頭に0xまたは0Xをつける。例えば0xabなど。
  • いずれの場合も、桁の多い数字を見やすくするためにアンダースコアを入れることができる。例えば、1_234_567は百二十三万四千五百六十七。プログラムが数を認識するときにアンダースコアは無視される。

float型のリテラル

  • 小数点を含む十進数で表す。これを固定小数点表示という。例えば1.23など。
  • 十進の浮動小数点で表現する。累乗の底はeまたはEを使う。例えば2.3e5(2.3\times 10^5)など。
  • 整数同様に数をみやすくするためのアンダースコアを使うことができる。

注意が必要なのは、2.0のようなリテラルです。 これは小数点があるので、float型で2を表していることを意味します。 int型の2のリテラルには小数点がありません。

ですから、プログラム中に「2」とあればint型の2、「2.0」とあればfloat型の2です。 同じ数字の2でもintとfloatでは振る舞いが違うことがあるので、注意が必要です。

演算

int型の演算

ほとんどのint型の演算(計算のことと思って構わない)ではint型が返されます。 「返される」というのは、その計算結果が得られるということです。 「返される」という言い方は関数やメソッドが「値を返す」ということから来ていますが、計算式も関数のように考えて「返す」ということがあります。 そして、その返された値は、print関数の引数などに使うことができます。 Pythonを実行して、プロントが出た状態で説明します。

>>> print(1+2)
3

割り算では、結果は常にfloat型になります。 割り切れるか割り切れないかは関係ありません。

>>> print(1/2)
0.5
>>> print(2/1)
2.0

2.0はfloat型です。 int型の2の場合は「.0」がつきません。

算術演算で四則計算ではないものに、整数の割り算の商と余りを返す演算子、累乗の演算子があります。

  • // 商を返す。例えば20//3は6を返す。
  • % 余りを返す。例えば20%3は2を返す。

正の数でこれを使うかぎりは混乱はありませんが、負の数を使うときは注意してください。 例えば

>>> (-20)//3
-7
>>> (-20)%3
1

のようになります。

累乗はアスタリスク2個で表します。 この演算子は右結合(2つ累乗の演算子がある場合は右から計算する)なので注意が必要です。

>>> 2**3
8
>>> 2**2**3
256
>>> (2**2)**3
64
>>> 2**(2**3)
256

コンピュータ内部では数が二進数で扱われています。 さきほどfloat型についてこのことを書きましたが、int型でも同様です。 メモリは電気的に情報を保存していて、その最小単位である1ビットでは、1か0かの情報しか収められません。 数字をメモリーに保存するときは必要なビット数を確保して格納します。 ただし、効率を考えて、通常は64ビット単位で整数を格納しています。 Pythonではさらに大きな数もサポートしていますが、その場合はその数を分割して格納するなどが必要になります。 それはPythonの側で自動的に行うので、プログラマーはそのことを気にする必要はありません。

例えば、4は二進数で100、5は101となります。

それぞれのビットごとに、下記のビット演算が使えます。

  • ビット論理積 (&): 対応するビットが両方1のときだけ1になる。
  • ビット論理和 (|): 対応するビットがどちらか一方または両方が1のとき1になる。
  • ビット排他的論理和 (^): 対応するビットが異なる場合に1になる。
  • ビット否定 (~): 各ビットを反転させる。
  • 左シフト (<<): ビットパターンを左にシフトする。
  • 右シフト (>>): ビットパターンを右にシフトする。

これらは、非常に低レベル(ハードウェアに近いレベル)の計算で用いられることが多いです。 論理積論理和は、いくつかの情報をビット単位で管理し、それらをまとめて整数の中に埋め込むケースで使われます。 これは数というよりも情報の塊としてintが使えるということです。 アセンブラC言語では良く見かけますが、Pythonのようなより抽象化された言語ではあまり使わないかもしれません。

左シフトはその整数の2倍に相当し、高速に計算することができます。 右シフトは逆に半分にする計算です。 ですが、Pythonでその目的でシフトを使う必要はありません。 普通に2を掛けたり、2で割ったりすることで十分で、速度的にも問題はありません。

比較演算は、条件判断の場面で非常に良く使われます。

  • 等しい(==)
  • 等しくない(!=)
  • 小さい(<)
  • 大きい(>)
  • 以下(<=)
  • 以上(>=)

これらについては、後にif文などのところで詳しく説明します。

float型の演算

floatでは加減乗除ができます。 整数と異なり、あまりのある割り算はできません。

floatにはビット演算はありません。

floatは比較演算が可能です。 ==、!=、<、>、<=、>=の6つが使えます。 しかし、floatには誤差がつきもので、比較演算を使う場合は注意が必要ですし、できるだけ使わない方が良いです。

float型は多くの場合近似値になります。 しかも、十進数では割り切れても二進数では割り切れないことがあります。 例えば、0.1は1を10で割ったもので、十進数では割り切れますが、二進数では0.0001100110011...という無限小数になります。 floatには桁数の限界があるので、この数は近似値になります。 十進小数では有限桁で誤差なく表されるのに、二進数では無限小数なので、有限桁にするときに丸め(近似)が起こり、誤差が発生します。

このような誤差のため、コンピュータでは0.1+0.2が0.3とは異なる可能性があります。 それぞれを二進数で表してみましょう。

0.1 = 0.0001100110・・・
0.2 = 0.0011001100・・・
0.3 = 0.0100110011・・・

仮に、10桁までで、残りを切り捨てたと仮定しましょう。 すると、

0.1+0.2  = 0.0100110010

となり、0.3とわずかな差ができます。 実際のコンピュータではもっと長い桁数が確保できますが、それでも切り捨て、切り上げ、ゼロ捨イチ入(四捨五入の二進数版)のどれかが行われ、その先は誤差になります。 その誤差のため、正確な計算ができないのです。 実際、私がPythonを試したところ

>>> 0.1+0.2 == 0.3
False

という結果になりました。

このことに対し、プログラマーはどのように対処すべきでしょうか?

誤差があるため、floatで大小比較はすべきでない

例えば、米ドルを記述するとき、1ドル20セントは1.20と書きたくなりますが、これはfloatでは誤差がでます。 これを120セントと考え、整数のみを使うのが誤差を避ける正しい方法です。

また、ループの脱出の判断ではfloatに対して == を使うと無限ループに陥る可能性があるので、<を使うべきです。 この場合境界のイコールを正しく判断することはできませんが、少なくとも無限ループは回避することができます。

代入

代入演算は変数に値を代入するのに使います。 変数について詳しくは別の記事に書きますが、ここでは、a=10のような簡単な例だけを考えます。 これは変数aに整数の10を代入するということです。 今後aを参照すると、それは10を返すようになります。

>>> a = 10
>>> print(a)
10

なお、代入も「演算子」と呼ばれることがありますが、他と違い値を返しません。 値を返す「+」などでは、次の行にその値がいつも表示されていました。 一方、上の例を見ても分かるように、代入の後には何も出力されていません。 それは、代入が値を返さないということの証拠です。 ですので、代入を他の演算子と同列に扱うのは問題です。

代入と算術演算を組み合わせるとき、省略形を使うことができます。

>>> a=10
>>> a = a + 10
>>> print(a)
20
>>> a += 10
>>> print(a)
30

a=a+10はa+10をaに代入するという意味です。 はじめaは10ですから、a+10は20になります。その20をaに代入することになります。

a+=10はa=a+10の省略形で、+=を複合代入演算子といいます。 これにより、少ないタイプで同じことを記述することができます。

この他に-=、*=、/=、//=、%=、**=などの複合代入演算子を使えます。