Casio Python - 10進数除算の出力と精度:高速素因数分解(7)
Casioグラフ関数電卓の Python を使ってみる
- 10進数除算の出力と精度:高速素因数分解(7)
追記:2020/10/22
修正:2023/10/09
▲ 前の記事 - 12. 要素数の大きいリスト | ▼ 次の記事 - 14. CGモデルとFXモデルのPythonモードの違い
13. 10進数除算の出力と精度:高速素因数分解(7) <fx-CG50 OS3.40以降>
前回は、高速素因数分解スクリプト FactorG5.py (ver 5.0) と FactorG6.py (Ver 6.0) を作りました。
FactorG6.py (Ver 6.0) はスタック不足のために実行てきないことが分かったので、FactorG5.py (Ver 5.0) が前回の成果です。
⇒ 高速素因数分解 Ver 5.0 - FactorG5.py のダウンロード [2023/10/09 リンク先が違っていたのを修正]
Ver 5.0 で色々な入力を試していると、仕様通りに動作しないケースが見つかりました。ここで仕様とは、Casio Baisc のプログラムとできるだけ同じ動作にすることです。
例えば、Casio Basic では、計算式を入力するとその計算結果が入力される仕様になっていますが、このようにならないケースが見つかりました。これは、Casio Pythonでの10進数計算の仕様が Casio Basic と異なっているのが原因だと分かりました。
Casio Python (Python) の仕様だから、このまま受け入れることは全く問題ないと思います。但し管理人自身の興味と趣味から、できるだけ Casio Basic でのプログラムと動作が互換になるように工夫して、FactorG7.py (Ver 7.0) を作ってゆきます。
13.1 Casio Python での除算結果のデータ型
<fx-9750GIII, fx-9860GIII, fx-CG50 OS3.40以降>
FactorG5.py (Ver 5.0) を起動して入力する時、以下のようなことがあります。
入力時に 120/2 とすれば 整数 60 が入力されるべきところ *must be integer とエラー表示されます。同様に 15000/10 とすると 整数 1500 が入力されるはずなのに 整数でないというエラーが表示されます。つまり現在のロジックでは、小数として認識されているということです。
そこで、シェル画面で 120/2 と 15000/10 を計算させてみると、以下のような浮動小数点表示になっています。
数値演算は内部的には2進数で実行されているので、2の乗数を2で除算する例として 256/2 を計算すると 128.0 となります。
従って、Casio Python では、除算の結果は内部2進数演算とは無関係に、必ず浮動小数点型 (float型) で出力されることが分かります。そこで、入力ルーチンを変更して、小数点以下が 0 の浮動小数点を整数に変換して、整数入力と認識できるようにしようと思います。
13.2 Casio Python での関数計算結果のデータ型 <fx-9750GIII, fx-9860GIII, fx-CG50 OS3.40以降>
Ver 5.0 の入力で、関数を使った計算結果も浮動小数点になるので、それにも対応してみます。
200*log10(10) の計算結果は整数 200 になって欲しいのですが、Ver 5.0 の入力ルーチンでは小数と判定されています。
そこで、シェル画面でこの計算を実行してみます。
すると、両方とも 200.0 と小数になっています。
Casio Python では、関数計算の結果は浮動小数点型 (float型) になっていることが分かります。
従って、Ver 5.0 の入力ルーチンを 計算結果の小数点以下が 0 の時は整数に変換し、整数入力と判定するように変更します。
13.3 Casio Python での除算計算の精度 <fx-CG50 OS3.40以降専用>
Ver 5.0 で素数 821 を入力すると、素因数として 821 1つが得られます。
では、821 が計算結果になる 1642/2 を式として入力すると、以下のように小数として認識されてしまいます。
そこで、シェル画面で 1642/2 を入力してみると、以下の計算結果となります。
Casio Python は、小数点以下13桁目が 1 になっており、誤差が抑制されていません。内部では2進数計算を行っているので、10進数計算には必ず誤差が伴います。多くの場合では誤差がうまく抑制されますが、誤差が現れたのが今回の事例です。
CPython ではこの誤差をうまく抑制しています。Windows 10 にインストールした Python 3.6.8 での計算結果は、下記のように誤差を抑制したうえで、浮動小数点型 (float型) で出力されていることが分かります。
Note:Windows 版 Python のインストール
さて、CPython では、PCの演算仕様によりますが、多くの場合は 53桁までの精度が確保されています。一方 Casio Python は15桁の精度しかありません。
Note:浮動小数点演算 - その問題と制限 (Python 公式サイト) 参照
1642/2 の計算で誤差が表面化するのは、Casio Python の限界として、受け入れるしかなさそうです。
13.4 0 および負の整数入力時の処理 <fx-9750GIII, fx-9860GIII, fx-CG50 OS3.40以降>
オリジナルの Casio Basic プログラムでは 0 以下の整数入力をチェックしてエラーメッセージを出力し、そこでプログラムを終了しています。一方、Casio Python に移植したスクリプトでは、0以下の整数入力のチェックを行っていませんでした。うっかりしていました(^_^;
そこで、今回は 0 と負の整数入力をチェックして、エラーメッセージを出力したうえで、入力ルーチンを繰り返すように修正しようと思います。
13.5 入力ルーチンの修正
以下の Ver 5.0 の入力ルーチンを修正してゆきます。
try:
inp = str(eval(input('Number:')))
except (SyntaxError, TypeError, NameError) as e:
print(e)
print('*must be number or\n expression')
continue
if '.' in inp:
print('*must be integer')
continue
elif inp.isdigit():
if len(inp) > digit:
print('*must be '+str(digit)+' digit\n or less')
continue
else:
f = int(inp)
break
else:
continue
13.5.1 入力値が小数点以下 0 の小数の場合に処理を追加
if 文で最初に行っているのが、文字列中にコンマ . が含まれているかの判定で、ここで入力値が小数かどうかを調べています。そこで、この判定の前に、入力値の小数点以下が0の時は、入力値を整数に変換し、それを文字列変数 inp に格納する処理、具体的には以下の2行を追加します。
if frac(eval(inp))==0:
inp = str(int(eval(inp)))
▶ この2行の説明
eval()関数に 文字列変数 inp を渡すと、inp が計算式ならば、eval() 暗数はその計算結果を文字列で返します。
return x - int(x)
入力値が、小数点以下 0 の小数の場合は、
frac(eval(inp))==0
これが成立するときに、小数表現の文字列 inp を整数表現に変換するため、以下のように記述します。
inp=str(int(eval(inp)))
結果出力の disp() 関数において inp 文字列変数の文字数から桁数を算出して表示しているので、ここで inp変数を更新しておく必要があります。
これまでに修正したスクリプトは以下になります。
try:
inp = str(eval(input('Number:')))
except (SyntaxError, TypeError, NameError) as e:
print(e)
print('*must be number or\n expression')
continue
if frac(eval(inp))==0: ◀ 追加
inp = str(int(eval(inp))) ◀ 追加
if '.' in inp:
print('*must be integer')
continue
elif inp.isdigit():
if len(inp) > digit:
print('*must be '+str(digit)+' digit\n or less')
continue
else:
f = int(inp)
break
else:
continue
13.5.2 入力値が負の数値の場合の処理
inp は入力を文字列にしたものです。
inp にコンマ . が含まれるかどうかで、inp が小数かどうかを判定しています。そこで、小数かどうかの判定の if 文に続き elif 文で inp にマイナス記号 - を含むかどうかで、入力が負の数値かどうかを判定します。
elif '-' in inp:
print('*must be positive\n integer')
continue
これを追加して、入力ルーチンは以下のようになります。
try:
inp = str(eval(input('Number:')))
except (SyntaxError, TypeError, NameError) as e:
print(e)
print('*must be number or\n expression')
continue
if frac(eval(inp))==0:
inp = str(int(eval(inp)))
if '.' in inp:
print('*must be integer')
continue
elif '-' in inp: ◀ 追加
print('*must be positive\n integer') ◀ 追加
continue ◀ 追加
elif inp.isdigit():
if len(inp) > digit:
print('*must be '+str(digit)+' digit\n or less')
continue
else:
f = int(inp)
break
else:
continue
13.5.3 入力が 0 の時の処理
Ver 5 の入力ルーチンを修正してこれまでに得られたスクリプトでは、入力が 0 以上の整数の場合の処理は、上で追加した elif 文の次に続く以下の部分です。入力の文字列 inp に小数点のコンマ . を含まず、負の記号 - を含まないとき、以下のように .isdigit() 関数を用いて数値であるかどうかを判定しています。
if len(inp)>digit:
print('*must be '+str(digit)+' digit\n or less')
continue
else:
f=int(inp)
break
inp.isdigit() が True (真) の時、さらに文字列の長さが digit (15桁) を超える時は、15桁以内で入力するようエラーメッセージを表示したのち、入力ルーチンを繰り返します。15桁以内の場合は、else 節において文字列 inp を 整数 f に変換してから break により入力ルーチンのループから脱出し、素因数分解ルーチンへ移ります。入力ルーチンの中の if 文による条件分岐で、この時のみ break でループから脱出し、それ以外は continue でループを継続するようにしています。
さて、この else 節で行う文字列 inp を整数 f に変換するところで、入力が 0 の時の処理を行います。
f は整数変数なので、f が 0 でない時 (f != 0) は 適切な整数入力値だと判定し break でループを脱出、そうでない時は continue でループを継続するようにします。
具体的には、else 節に追記 (赤文字) して以下のようにします。
else:
f=int(inp)
if f != 0:
break
else:
print('*must be positive\n integer')
continue
以上を反映した入力ルーチンを示します。
try:
inp = str(eval(input('Number:')))
except (SyntaxError, TypeError, NameError) as e:
print(e)
print('*must be number or\n expression')
continue
if frac(eval(inp))==0:
inp = str(int(eval(inp)))
if '.' in inp:
print('*must be integer')
continue
elif '-' in inp:
print('*must be positive\n integer')
continue
elif inp.isdigit():
if len(inp) > digit:
print('*must be '+str(digit)+' digit\n or less')
continue
else:
f = int(inp)
if f != 0: ◀ 追加
break
else: ◀ 追加
print('*must be positive\n integer') ◀ 追加
continue ◀ 追加
else:
continue
以上で、Casio Basic 版とほぼ同じ入力仕様に修正できたと思います。
- FactorG7.py のダウンロード
fx-CG50 Pythonモード:高速素因数分解 - FactorG7.py
"""Sample script
Exercise;
ported from Casio Basic
"Prime Factor 15 digits"
factorG.py
ver 7.0
by Krtyski/e-Gadget
"""
from u import *
digit = 15
search = 0
def disp():
clear_screen()
locate(16, 0, ': ' + str(len(inp)) + ' digits', 3, 'm', 0)
locate(16, 11, 'search:'+str(search), 2, 'm', 0)
line(0,15,383,15,1,0)
for i in range(1, 15):
if i <= e:
dx = int(i/12)*16
dy = int(i/12)*11
locate(0+dx, i-dy, z[i], 1, 'm', 0)
locate(10+dx, i-dy, '^(', 2, 'm', 0)
locate(12+dx, i-dy, z[i+21], 1, 'm', 0)
locate(15+dx, i-dy, ')', 2, 'm', 0)
if i==12:
line(190,15,190,191,1,0)
locate(0, 0, '', 0, 'm', 1)
try:
inp = str(eval(input('Number:')))
except (SyntaxError, TypeError, NameError) as e:
print(e)
print('*must be number or\n expression')
continue
if frac(eval(inp))==0:
inp = str(int(eval(inp)))
if '.' in inp:
print('*must be integer')
continue
elif '-' in inp:
print('*must be positive\n integer')
continue
elif inp.isdigit():
if len(inp) > digit:
print('*must be '+str(digit)+' digit\n or less')
continue
else:
f = int(inp)
if f != 0:
break
else:
print('*must be positive\n integer')
continue
else:
continue
z = list(range(23))
for e in range(1,23):
z[e] = 0
e = 0
c = int(sqrt(a))
for b in prime_list:
search+=1
d = a/b
if frac(d)==0:
e+=1
z[e] = b
while 1:
a = int(d)
z[e+21]+=1
d = a/b
if frac(d):
break
c = int(sqrt(a))
if b > c: break
increment = \
[4,2,4,6,2,6,4,2,
4,6,6,2,6,4,2,6,
4,6,8,4,2,4,2,4,
14,4,6,2,10,2,6,6,
4,2,4,6,2,10,2,4,
2,12,10,2,4,2,4,6,
2,6,4,6,6,6,2,6,
4,2,6,4,6,8,4,2,
4,6,8,6,10,2,4,6,
2,6,6,4,2,4,6,2,
6,4,2,6,10,2,10,2,
4,2,4,6,8,4,2,4,
12,2,6,4,2,6,4,6,
12,2,4,2,4,8,6,4,
6,2,4,6,2,6,10,2,
4,6,2,6,4,2,4,2,
10,2,10,2,4,6,6,2,
6,6,4,6,6,2,6,4,
2,6,4,6,8,4,2,6,
4,8,6,4,6,2,4,6,
8,6,4,2,10,2,6,4,
2,4,2,10,2,10,2,4,
2,4,8,6,4,2,4,6,
6,2,6,4,8,4,6,8,
4,2,4,2,4,8,6,4,
6,6,6,2,6,6,4,2,
4,6,2,6,4,2,4,2,
10,2,10,2,6,4,6,2,
6,4,2,4,6,6,8,4,
2,6,10,8,4,2,4,2,
4,8,10,6,2,4,8,6,
6,4,2,4,6,2,6,4,
6,2,10,2,10,2,4,2,
4,6,2,6,4,2,4,6,
6,2,6,6,6,4,6,8,
4,2,4,2,4,8,6,4,
8,4,6,2,6,6,4,2,
4,6,8,4,2,4,2,10,
2,10,2,4,2,4,6,2,
10,2,4,6,8,6,4,2,
6,4,6,8,4,6,2,4,
8,6,4,6,2,4,6,2,
6,6,4,6,6,2,6,6,
4,2,10,2,10,2,4,2,
4,6,2,6,4,2,10,6,
2,6,4,2,6,4,6,8,
4,2,4,2,12,6,4,6,
2,4,6,2,12,4,2,4,
8,6,4,2,4,2,10,2,
10,6,2,4,6,2,6,4,
2,4,6,6,2,6,4,2,
10,6,8,6,4,2,4,8,
6,4,6,2,4,6,2,6,
6,6,4,6,2,6,4,2,
4,2,10,12,2,4,2,10,
2,6,4,2,4,6,6,2,
10,2,6,4,14,4,2,4,
2,4,8,6,4,6,2,4,
6,2,6,6,4,2,4,6,
2,6,4,2,4,12,2,12]
while 1:
for i in increment:
search+=1
b+=i; d = a/b
if frac(d)==0:
e+=1
z[e] = b
while 1:
a = int(d)
z[e+21]+=1
d = a/b
if frac(d):
break
c = int(sqrt(a))
if b > c: break
if b > c: break
if a > 1:
e+=1
z[e] = a
a = 1
z[e+11] = 1
disp()
13.6 完成した FactorG7.py の動作確認
120/2 と入力すると、計算値 60 が入力され、正常に素因数分解されます。
15000/10 と入力すると、計算値 1500 が入力され、正常に素因数分解されます。
200*log10(10) や 200*cos(0) と入力すると、計算値 200 が入力され、正常に素因数分解されます。
1642/2 と入力すると、Casio Python の計算精度の制限により、計算結果が 821.0000000000001 となり、整数でないのでエラー表示の上入力ルーチンが継続されます。
この問題は Casio Basic では発生しません。Casio Python の内部2進数演算が15桁の精度では、誤差が抑制しきれていないという仕様が明らかになりました。
[2020/10/11 追記]
但し、1642/2 と入力する代わりに Python の演算子を使って 1642//2 と入力すれば、その計算結果は 821 となるので、問題は解消されます。
なお、1642/2 と入力する必然性はなく、821 と入力すれば問題無く素因数分解できるので、大きな問題ではないと考えます。今後は、スクリプトの目的に応じて10進数の除算の誤差について留意する必要があります。
keywords: fx-CG50、Python、fx-9750GIII、fx-9860GIII、プログラム関数電卓