ゼロの偏角
複素数には偏角という概念がある。雑に言えば極座標表示したときの角度のこと。
0 の偏角はどうなるのか? 動径が 0 なんだから角度もヘチマもないが,極座標表示で考えると「角度は何だっていい」とも言える。
ふつう,0 の偏角は「不定」とされているようだ。
Wikipedia「複素数」でも「不定」と書かれている。
Ruby では
ただ,各種プログラミング言語で,複素数の 0 の偏角を求めると 0 だったりする1。
まあいんじゃね? 何でもいいんだから,代表値として 0 を返しても。
Ruby で確認してみよう。
Ruby では,Complex クラスだけでなく,実数系数値クラスである Integer, Float, Rational でも偏角を求めるメソッド arg が使える2。
こうなった:
# Integer
p 0.arg # => 0
# Float
p 0.0.arg # => 0
# Rational
p 0r.arg # => 0
# Complex
p 0i.arg # => 0.0
Complex だけ Float が返り,他は Integer が返るといった些細な違いはあるが,たしかにゼロの偏角はゼロになった3。
マニュアルでは
Ruby の日本語公式リファレンスマニュアルにはどう書かれているか。
Numeric#arg と Float#arg では
- 正なら 0
- 負なら π
という意味のことが書かれていて,0 のときどうなのかは言及が無い。
Float#arg の説明には偏角を返すとあり,サンプルコードでゼロの場合の結果が示されている。
英語のマニュアル(「rdoc」と呼ばれているようだ)ではどうなっているか。
Numeric#arg には
Returns zero if
selfis positive, Math::PI otherwise.
とある。「正のときゼロを,そうでないとき Math::PI を返す」と。
間違っている。
Float#arg もほぼ同じ
Returns 0 if
selfis positive, Math::PI otherwise.
となっていて間違い。
Complex#arg にはゼロの場合の言及が無く,サンプルコードも無い。
実装は
実装はどうなっているのだろう。実はこれが本記事のテーマである。
といっても実装は C で書かれていて,私は C が読めないのだが。
まず Numeric#arg だが,こうなっている:
static VALUE
numeric_arg(VALUE self)
{
if (f_positive_p(self))
return INT2FIX(0);
return DBL2NUM(M_PI);
}
どうも,self が正のとき 0 を返し,そうでないとき Math::PI を返すようだ。
f_positive_p てのは,よく分からないけど,_p は「〜かどうか」を調べる関数の命名によく使われるものだと思う4。なので,これは引数が正かどうかを調べる関数だと思う。
INT2FIX(0) は,C の int 型の 0 をマクロ INT2FIX で Ruby の整数にする,ということのようだ。
DBL2NUM(M_PI) も同様に,C の double 型の定数 M_PI を Ruby の Float にする,ということのようだ。
えっ? もしそういう解釈で合っているなら,rdoc の説明とは一致しているけど,「0 に対して 0 を返す」という実際の挙動とは食い違っているのだが? 激しく混乱@#&?+!
で,では Float#arg の定義を見てみよう……
static VALUE
float_arg(VALUE self)
{
if (isnan(RFLOAT_VALUE(self)))
return self;
if (f_tpositive_p(self))
return INT2FIX(0);
return rb_const_get(rb_mMath, id_PI);
}
Float なので NaN かどうかのチェックをかましているが,その点を除けばほぼ「正なら 0 を、そうでないなら Math::PI を返す」となっているようだ。
Complext#arg はどうか。実装はこう:
VALUE
rb_complex_arg(VALUE self)
{
get_dat1(self);
return rb_math_atan2(dat->imag, dat->real);
}
あー,実部と虚部から,Math.atan2(に相当する C の関数)を使って角度を出してるのね。知らんけど。
Math.atan(0, 0) は 0.0 を返すので,Complex に関しては納得だ。
謎の解明
Complex#arg はいいが,実数系数値クラスでゼロに対する arg がゼロを返すのは不可解だ。
f_positive_p で正かどうかを確認し,正のときだけ 0 を返すのではないのか?
いや,ひょっとして f_positive_p の理解が間違っているのか? うん,そうとしか考えられん。
f_positive_p の実装を見てみよう。
どうも ココ がそれのようだ5。
#define f_positive_p(x) (!f_negative_p(x))
えっ?!
いくら「C のコードは読めない」といっても,コレどう見ても f_negative_p の論理反転じゃね?
んじゃ,f_negative_p の定義は? math.c で定義されている コレ かしらん?
inline static VALUE
f_negative_p(VALUE x)
{
if (FIXNUM_P(x))
return RBOOL(FIX2LONG(x) < 0);
return rb_funcall(x, '<', 1, INT2FIX(0));
}
Fixnum かどうかで条件分岐しているものの,ともかく「0 より小さい」ことを確認しているように見える。
まあ f_negative_p て名前だしね。
これで謎が解けた。
f_positive_p は「正かどうか」ではなく「0 以上かどうか」だったんである。
だから,Numeric#arg や Float#arg は,「引数が正なら 0 を返す」ではなく「引数が 0 以上なら 0 を返す」だったのだ。
すべてが納得であーる。
はーい,めでたしめでたし。
……ってなるかいっ! 名前がまぎらわしいんじゃーっ!!!
なんで f_positive_p やねん! f_nonnegative_p やろが!
あっ,いや,つい興奮してしまって……。すみません,すみません。
rdoc の説明が間違っているのは,先に挙げた numeric_arg 関数につけられた 説明コメント をそのまま反映したもの。
実装箇所に間違った説明が付されているのだ。
なんで間違えたかというと,やはり f_positive_p という名前が災いしたのではないだろうか。
おわりに
「こーゆーこともあるんだね」という感想。
なお,日本語版リファレンスマニュアルの説明が不十分であることについては,現在絶賛編集中。というか,マニュアルを編集する過程で気付いたことが記事の発端であった。
rdoc や Ruby のほうは私は手出しできないので,どなたか……。
-
「各種」と書いたが,実は Ruby と Rust でしか確認していない。Ruby はバージョン 4.0.6 で,Rust は num-complex 0.4.0 で確認した。 ↩
-
実数系数値クラスでは,負数に対する
argは π を返す。 ↩ -
実は
-0.0.argは0ではなくMath::PIを返すのだが,本記事のテーマからやや逸れるので,ここでは追究しない。 ↩ -
predicate(述語)の p かな? 何十年も前に LISP をちょっとかじったとき,述語的関数の名前が
Pで終わってたような記憶が。 ↩ -
f_positive_pはてっきり関数だと思っていたが,この定義からして関数ではなさそう。マクロとかいうやつなのかな。よく分からん。 ↩