Pythonで小数、分数、n乗根を計算する
decimalモジュールを使い小数を正しく計算する
Decimalクラスによる少数の定義
浮動小数点方式では、有効桁数は17桁程度であり、それ以降は表示されてもあまり意味のない数値であることがわかりました。実務的に問題になることは余り考えられませんが、整数論の計算など本当に正確に計算する必要があるときには、decimalモジュールのDecimalクラスを使う必要があります。Decimalクラスを使うと学校で習ったような正確な計算が可能になります。
Decimalクラスによる小数の定義と計算
Decimalクラスを使った小数の足し算
- from decimal import Decimal
- print(Decimal(0.1))
- print(Decimal('0.1'))
- print(type(Decimal('0.1')),type(Decimal(0.1)))
- print(Decimal(0.1+0.1+0.1))
- print(Decimal('0.3'))
- print(Decimal('0.1')+Decimal('0.1')+Decimal('0.1'))
- print(Decimal('0.1')+Decimal('0.1')+Decimal('0.1') == Decimal('0.3'))
0.1000000000000000055511151231257827021181583404541015625 0.1 <class 'decimal.Decimal'> <class 'decimal.Decimal'> 0.3000000000000000444089209850062616169452667236328125 0.3 0.3 True
1. Decimalクラスを使うためには、decimalモジュールからDecimalクラスをimportします。
2. Decimalクラスの引数で単純に小数を含む数値を渡すと、浮動小数点方式と同じ誤差を含んだ数値として認識されます。
3. Decimalクラスの引数の中でクォート('...')を付けて数値を指定すると、誤差のない小数として認識されます。
4. 2.3.のようにDecimalクラスで小数を指定すると、Decimal型として認識されます。
5. Decimalクラスの引数の中で計算した値についても、誤差を含んだ数値となります。
6. Decimalクラスで1つ1つ値を指定するとそれぞれの値が正しく認識されるので、正確な演算が可能になります。
7. Decimalクラスを使うと、演算結果の比較も正しく行うことができます。
8. 整数部分と小数部分がある数値についても、クォート('...')を付けて数値を指定すると、誤差のない小数として認識されます。
Decimalクラスを使った割り算
小数点以下の計算で一番問題になるのは、割り算で結果が割り切れないと場合です。そこで、割り算をするときのDecimalクラスの使い方を確認します。
割り切れない場合の計算
- from decimal import Decimal
- print(Decimal('10') / Decimal('81'))
- print(Decimal('100') / Decimal('81'))
- print(Decimal(10) / Decimal(81))
- print(type(10 / 81),type(Decimal(10) / Decimal(81)))
- print(Decimal('0.1') / Decimal('0.81'))
- print(Decimal(0.1) / Decimal(0.81))
0.1234567901234567901234567901 1.234567901234567901234567901 0.1234567901234567901234567901 <class 'float'> <class 'decimal.Decimal'> 0.1234567901234567901234567901 0.1234567901234567888543403925
2. Decimalクラスを使って整数同士の割り算をすると、小数点以下第28位まで正しく表示され、それ以降は切り捨てられます。
3. 割り算の計算結果に整数が1桁含まれる場合は、小数点以下第27位までの表示になります。このことからDecimalクラスの場合、特に指定しなければ有効桁数は28桁になることがわかります。
4. 整数同士であれば、クォート('...')無しでDecimalクラスを適用しても割り算の結果は正しく表示されます。
5. 上記のことは、単純に割り算をすると計算結果はfloat型になりその特質から誤差が生じるのに対し、Decimalクラスで指定した数値同士の割り算は計算結果がDecimal型になり、誤差が生じないためです。
6. 小数を含む数値がある割り算の場合、クォート('...')付きでDecimalクラスを適用することで正しく計算することができます。
7. Decimalクラスでクォート('...')無しで定義した小数を含む数値による割り算では、それぞれの小数が正しく認識されないので、計算結果にも誤差が生じてしまい、正しく計算されるのは17桁程度にとどまります。
このように、Decimalクラスで割り算をする数値が全て整数であれば、クォート('...')付にする必要はありません。そうでない場合はクォート('...')を付きで指定する方が無難です。
Decimalクラスの有効桁数を増やす
Decimal型の小数は有効桁数が28桁に設定されており、ほとんどの場合にはこの範囲で問題なく計算することができます。もしこれを超える桁数や精度を求める場合にはgetcontext()関数のprecを設定することで、有効桁数を増やすことができます。
Decimalクラスで有効桁数を増やす
- from decimal import Decimal,getcontext
- getcontext().prec = 50
- print(Decimal(10) / Decimal(81))
- print(Decimal('100') / Decimal('0.81'))
- print(f'{Decimal(10) / Decimal(81):.60f}')
0.12345679012345679012345679012345679012345679012346 123.45679012345679012345679012345679012345679012346 0.123456790123456790123456790123456790123456790123460000000000
2. decimalモジュールのgetcontext().precで桁数を指定すると、有効桁数を変更することができます。ここでは有効桁数を50桁としています。
3. Decimalクラス同士で割り算をすると計算結果はDecimal型となり、2.で設定した有効桁数が適用されます。ここでの結果は整数部分が0なので、小数点以下50桁まで正しく計算されます。ただし、最後の1桁で微妙な誤差が発生します。
4. 計算結果に整数部分があるときは、整数部分1桁と小数部分49桁を合計した50桁まで正しく計算されます。この場合も最後の1桁で微妙な誤差が発生します。
5. 計算結果にフォーマット済み文字列リテラルを使い表示桁数を増やしても、有効桁数である50桁を超える部分は0と表示されてしまいます。
変数にDecimal型のデータを代入する
変数には小数が代入されている場合、この変数に対してDecimalクラスとstr関数を適用すると正確な数値演算をすることができます。
Decimal型のデータを変数に代入する
- x = 0.1
- print(Decimal(x))
- print(Decimal(str(y)))
- y1 = 0.1
- y2 = 0.1
- y3 = 0.1
- print(Decimal(str(y1))+Decimal(str(y2))+Decimal(str(y3)))
0.1000000000000000055511151231257827021181583404541015625 0.1 0.3
2. 1.で浮動小数点型の小数を代入した変数に対し、Decimalクラスを適用することができます。この場合、浮動小数点型にともなう誤差を含んだ値になります。
3. Decimalクラスにクォート('...')付きで適用するのと同様に、変数にstr関数を適用することにより、正確に小数を含む数値を定義することができます。
7. 浮動小数点型で定義したそれぞれの変数に対し、str関数とDecimalクラスを組み合わせて適用することで、正確な演算をすることが可能になります。
エクセルとの比較
前に1/81の計算をしましたが、さらにすすんで1/9801の計算をすると、
decimalモジュールを使った割り算(1/9801有効桁数393桁)
- from decimal import Decimal,getcontext
- getcontext().prec = 393
- print(Decimal(1) / Decimal(9801))
0.000102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969799000102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969799
このあたりエクセルだと有効桁数が17桁程度なので歯が立ちません。実務でどこまで使うかは分かりませんが、いろいろ調べてみると興味は尽きません。
もっとすごいのは1/113です。これも最大の112桁”8849557522123893805309734513274336283185840707964601769911504424778761061946902654867256637168141592920353982300”で循環します。こうなるとgetcontext().precを300位に設定しないと分かりません。ちなみに355/113は円周率πにとても近い値になります。
#5 113を分母にした割り算
- from decimal import Decimal,getcontext
- print(Decimal('1') / Decimal('113'))
- getcontext().prec = 10
- print(Decimal('355') / Decimal('113'))
0.008849557522123893805309734513 3.141592920
分数の計算
/演算子を使って分数を計算する
pythonでは、分数をprint関数で表示しても少数として計算されます。
分数の計算
- print(3 / 5)
- print(1 / 3)
- print(3 / 5 + 1 / 3)
0.6 0.3333333333333333 0.9333333333333333
通常は小数に変換して計算するので、分数同士の和を計算しても少数で表示されます。
Fraction関数を使った分数の計算
Fraction関数を使えるようにする
分数を少数に変換せず、分数のまま計算したいことがあります。このときにはfractionsモジュールのFraction関数を使います。fractionは分数を意味します。モジュール名はfractionsと小文字で始まり最後に”s”が付きます。いっぽう関数名のFractionは大文字で始まり最後に”s”が付きません。
fractionsモジュールのFractionクラスで分数の計算をする
- from fractions import Fraction
- print(Fraction(3,5))
- print(Fraction(1,3))
- print(Fraction(3,5)+Fraction(1,3))
3/5 1/3 14/15
2行目でFraction関数を使えるようにします。mathモジュールと同じように、import fractions~fractions.Fraction(××)という書き方もできますが、分数の計算にいちいちfractionsを付けるのは大変なので、上記のような書き方をします。Pythonでは関数名などの名前は索引を使って管理します。import fractionsとすると、Python本体とfractionsと2つの索引を使って管理することになります。このため、関数を使って計算するときにはfractionsを付ける必要があります。一方、今回にようにするとFraction関数はPython本体の索引で管理されることになるので、いちいちfractionsを付ける必要がなくなります
3行目、4行目のように、Fraction関数に分子と分母を指定することにより分数を定義することができます。
5行目のように複数の分数に対し、Fraction関数を使い和を計算すると、結果は通分されます。
fractionsモジュールのFraction関数で分数の計算をする
- x=Fraction(1,2)+Fraction(1,3)
- print(x)
- print(float(x))
5/6 0.8333333333333334
分数から分子、分母を取り出す
Fraction関数で3/5という分数を定義し、ここから分子と分母を取り出します。
分数の分子と分母を取り出す
- print(Fraction(3, 5).numerator)
- print(Fraction(3, 5).denominator)
3 5
2行目のようにFraction関数に”. numerator”をつなげることで分子を取り出すことができます。numeratorは英語で分子を示します。また、numeratorのようなものを属性(property)といいます。
- 3行目にように分母はdenominator属性度取り出すことができます。denominatorは分母を意味します。
Fraction関数は既約分数になる
Fraction関数で4/6のように分子と分母に共通の約数をもつような値を指定した場合、約分され既約分数として表示されます。
分数は既約分数に約分される
- from fractions import Fraction
- x=Fraction(4,6)
- print(x)
- print(x.numerator)
- print(x.denominator)
2/3 2 3
上記のように、分数表示もnumerator、denominatorとも約分されて表示されます。
既約分数にしたくない場合
Fraction関数に分子、分母を渡すときにそのままの状態で活かしたい場合は、,_normalize=Falseとすると、約分しない分数として認識されます。
分数を既約分数にしない
- y=Fraction(4,6,_normalize=False)
- print(y)
- print(y.numerator)
- print(y.denominator)
4/6 4 6
Pythonでn乗根を計算する
正の数値のn乗根を計算する
Pythonではべき乗の演算子を使い、平行根、立方根を計算することができます。
べき乗の演算子を使い平方根、立方根を計算する
- print(16**0.5)
- print(16**0.25)
- print(27**(1/3))
- print(2**0.5)
4.0 2.0 3.0 1.4142135623730951
平方根は正の数値の0.5乗と考えられます。そこで2行目のようにすると、平方根(ルート)を計算することができます。計算結果は4.0となり、きれいに割り切れても少数として認識されます。
3行目のように、0.25乗をすることにより4乗根を計算することができます。
4行目のように、3乗根は3分の1乗することで計算することができます。
5行目のように、2の平方根のように小数が続く場合は有効桁数17桁程度で表示されます。
mathモジュールの
べき乗の演算子を使えば平方根だけでなく、3乗根、4乗根も計算することができますが、平方根については一目見て何をやっているのかよくわからないという問題があります。そこで、Pythonにはsqrtという平方根を計算する関数が用意されています。
ところで、sqrt関数を使うためにはmathモジュールをあらかじめimportする必要があります。Pythonを起動すると、Pythonを使うことができます。これは、ハードディスクやフラッシュメモリといった補助記憶装置にあるPythonのプログラムがコンピュータの主記憶装置に呼び出されたことになります。イメージとしては、棚(補助記憶装置)に入っているPythonという道具を作業机(主記憶装置)に並べる感じです。このとき、Pythonの全ての機能を呼び出すようにしてしまうと、実際に作業ができるようになるまで時間がかかり、作業机(主記憶装置)もいっぱいになってしまうので作業の効率が良くありません。そこで、Pythonを起動するときに使える機能は最小限なものに絞り、そこそこよく使う機能は必要に応じて呼び出すようにしています。このような、そこそこよく使う機能を標準モジュールといわれており、mathモジュールはその1つです。mathモジュールには数学でよく使う関数などが多く含まれています。
平方根の計算の精度
- print(f'{2**0.5:.50f}')
- print('1.41421356237309504880168872420969807856967187537694')
1.41421356237309514547462185873882845044136047363281 1.41421356237309504880168872420969807856967187537694
##### Decimalクラスによる平方根の計算
- getcontext().prec = 50
- Decimal('2')**(Decimal('1')/Decimal('2'))
Decimal('1.4142135623730950488016887242096980785696718753769')
2行目のimport文により、mathモジュールを補助記憶装置から見つけ出し、使えるようにします。
sqrt関数を使い、2の平方根を計算します。計算結果は0.5乗して計算した時と同じように有効桁数17桁程度で表示します。sqrt関数を使うときにはmathモジュールであることを示すために”math.”を頭に付けます。sqrt とは平方根” square root”の略です。
さらに多くの桁数を表示したいときには、フォーマット済み文字列リテラルを使い、”.60f”のように桁数を指定します。ただしmathモジュールでは有効桁数53桁程度が上限となります。
3乗根などn乗根の計算
**演算子を使うと3乗根などn乗根を計算することができます。
3乗根の計算の精度
- print(f'{2**(1/3):.50f}')
- print('1.2599210498948731647672106072782283505702514647015')
1.25992104989487319066654436028329655528068542480469 1.2599210498948731647672106072782283505702514647015
1. 2の1/3乗を計算することで3乗根を計算することができます。
2. 有効桁数17桁程度となります。
Decimalクラスによる3乗根の計算
- getcontext().prec = 50
- Decimal('2')**(Decimal('1')/Decimal('3'))
Decimal('1.2599210498948731647672106072782283505702514647015')
Declmalクラスを使うと、50桁以上の精度で3乗根を計算することができます。