2024年10月4日金曜日

自家製 copysign() の顛末

 過日、Biomorph for fx-CG50 の記事で、自家製 cmath module を開陳致しました所、K 様から、コメントを頂戴致しました。

K 様の方法で cmath.sqrt() を書くと、計算がスムースに行く上、境界部分での符号の扱いも良い具合になるのですが、残念な事に、fx-CG50 には math.copysign() がなく、適用な手段で代替関数を用意する必要があると、K 様も申されておりました。

そこで、copysign() 風の関数を自作すれば、よかんべ、と調べ始めたのですが、copysign() では、符号チュエックのため、0.0 と -0.0 という値に対応するという話でありますネ。ナニソレ ?

浮動小数点数も符号が用意されているのは解っていたつもりですが、折角だから、0.0 にも符号を付けて、0.0 と -0.0 の2つを用意しよう、という話らしい。
処理系 (コンパイラ) が、ゼロ符号に対応した場合に、python もこれに対応できるらしく、最近のモダンな処理系では、-0.0 という浮動小数点数に対応されている様です。

そんな具合で、我らが fx-CG50 の upython も、-0.0 という数値自体は扱える様に出来ております。
シェルを呼び出して、-0.0 と入力すると、そのもの -0.0 が答えとして返ってきますネ。
しかし、数値としては 0.0 と -0.0 、同じ「ゼロ」でありますから、== で比較すると、True が返ります。何なの、ソレ !?

では、アトムの比較を行う、is で比較をしたら区別が出来るんじゃないか、と思ったのですが、fx-CG50 の場合、これはアカンのですネ。

>>> -0.0 is 0.0 
False
>>> -0.0 is -0.0 
False
>>> 0.0 is 0.0 
False


なんと、0.0 という同じ値を比較しても、False となりますネ。「どうなっとんジャイ、ワレ !」

この謎を解く鍵は、アトムのid を取得する id() でした。

>>> id(-0.0)
2351257904
>>> id(-0.0)
2351258160 
これは一例ですが、同じ操作をしているのに、毎回、id が変化します。

idという値、どうもアトムを保持するheap空間のテーブルインデクス、有り体に言えばポインタの様な値らしい。
これが毎度違う値になるというのは、毎度、浮動小数点数値アトムが振り出されてheapに作られる、という具合の様ですネ。
PCのpython では、0.0 は一意に決まった数値アトムという扱いなので、何度操作しても変化する事はなさそうですが、電卓の方は、そこまで手が回っていないのかも知れません。
( 但し、当方の fx-CG50 upython は、チョット古めです )

こうなると、0.0 と -0.0 の区別は難しいなァ、と思っていたのですが、(数値アトムを)文字列に変換する関数 str() がありました。これを使うと、0.0 , -0.0 、両方とも表示通りに文字列になるのです。これで、0.0 と -0.0 の区別が出来ますネ、ヨカッタ、ヨカッタ。

そんな事が解ったので、自家製の copysign() として、チョット苦し紛れなコードを開陳した次第です。「ご査収ください」

#  copysign() like something ...
def  csign(x, y) :
  if y == 0.0 :
    if str(y) == '-0.0' :
      return -abs(x)
    else :
      return abs(x)
  else :
    if y < 0 :
      return -abs(x)
    else :
      return abs(x)

本来なら math.copysign() と、math module にあるものではありますが、後から追加するわけにも行かないので、自家製cmath module の中に、邪魔にならないよう csign() と名前を変えて導入致しました。

これを使ってcmath.sqrt() もK 様提案の方法にしたのですが、cmat.csign() が足を引っ張っているので、速度は余り期待できません、ハイ。

さて、こうした具合で動いている upython ではありますから、以前に、電卓喫茶様が「fx-CG50 upython で 色指定をタプルで書くと、速度が激落ちする」という報告をされておりました。これは、毎度、数値を含むタプルを生成して、heapに置いているから、時間が掛かっているのではないかという推測をしたのですが、少しは判断材料になったのかも、と思う所です。色指定のタプルを予め作っておき、適当な変数名にbindして、変数を呼び出す様にする事で、オーバーヘッドが大分少なくなる模様です。


2 件のコメント:

K さんのコメント...

-0.0の扱い、やっかいですよね。copysignについてはいっそのこと、
if str(y)[0] == "-"
として、なんでもかんでも文字列化した時の先頭の文字だけを見て判別してしまうという潔い(?)手もいいかもしれません。

ただ、ネタ元のcopysignはそもそも符号ビットが立っているかどうかだけを見ていたり(nanの符号ビットによって結果が変わる)、-0.0の扱いが複素数になると本家CPythonでも雑だったりするようなので、-0.0はあまり気にしない方がいいのかもしれません^^;

akatuki さんのコメント...

K 様、コメント多謝であります。

いや、どうも pythonもかじり始めた程度なので、色々と不勉強な所があり。
御教示戴き、勉強になります。

> copysignについてはいっそのこと、
> if str(y)[0] == "-"
> として、なんでもかんでも文字列化した時の先頭の文字だけを見て判別してしまうという潔い(?)手もいいかもしれません。

言われてみれば、そうでありますネ。
その方が明解であります、ハイ。

> ただ、ネタ元のcopysignはそもそも符号ビットが立っているかどうかだけを見ていたり(nanの符号ビットによって結果が変わる)、-0.0の扱いが複素数になると本家CPythonでも雑だったりするようなので、-0.0はあまり気にしない方がいいのかもしれません^^;

あれって、NaNの符号のためだったのですね。
cmath のドキュメントをみたら、cmath.sqrt() で x.imag が 0.0 と -0.0 で値がちがーう、みてぇな事が出ておったんで、「何、-0.0 って、オイシイの ?」とびっくりしたんですヨ。
そんで、こうした混迷に突入してしまったという、お恥ずかしい次第。

でも、お陰様で久しぶりにオツムの勉強になりました。有難うございます。