わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

マイナス0を作る,使う

いきなりですが問題です.

wikipedia:-0を見て,Cで次のように書いたのですが,どうもマイナス0になってくれていないのです.

/* minus0_test.c */
#include <stdio.h>

int main(void)
{
  double x = -0;
  printf("%g\n", x);
  return 0;
}

出力は「0」なのです.「-0」のところを,「(double)-0」にしても,「(double)(-0)」にしても,ダメでした.
Cでは,マイナス0を表現できないのでしょうか?

さっそくですが解答です.CygwinUbuntuGCCであれば,マイナス0にも対応しています.上記のコードを,1箇所だけ,変更します.

  double x = -0.0;

これで,出力が「-0」となりました.
理由はというと,Cのコードとして「-0」と書いたら,これは,int型の0に,マイナスの符号をつけてできる式となるからです.結局それは0となります.その次に,「x = -0」により,代入時型変換がなされまして,int型の0をdouble型にしたもの,そしてそれはマイナス0とは異なる値が,xに代入されたというわけです.(double)による型キャストも,その対象はint型の0なのでした.
「-0.0」とすれば,double型のゼロ(プラス0)の符号反転で,マイナス0が得られるというわけです.
なお,「-0.0」に替えて「-0.」や「-.0」でも,同じ結果となりました.


プラス0とマイナス0の違いが,どこで出てくるかというと,atan2関数です.視覚的な例はhttp://www.da-tools.com/AE/exp/jisen/atan.htmlをご覧ください.
Wikipediaの日本語版には,この関数がなく,英語版のWikipedia:en:atan2では,前置きの段落のあと,"For any real number (e.g., floating point) arguments x and y not both equal to zero, atan2(y, x) is the angle in radians between the positive x-axis of a plane and the point given by the coordinates (x, y) on it."と説明しています.ページの途中には,xやyが0のときも考慮した,場合分けの式があります.
日本語情報は,JM ProjectのMan page of ATAN2が詳しいです.返り値について,Wikipediaのものよりも,もっと細かく書かれています.

成功すると、これらの関数は y/x の逆正接の主値をラジアン単位で返す。 返り値は [-pi, pi] の範囲となる。
y が +0 (-0) で x が 0 未満の場合、+pi (-pi) が返される。
y が +0 (-0) で x が 0 より大きい場合、+0 (-0) が返される。
y が 0 未満で x が +0 か -0 の場合、-pi/2 が返される。
y が 0 より大きく x が +0 か -0 の場合、pi/2 が返される。
x か y のいずかが NaN の場合、NaN が返される。
y が +0 (-0) で x が -0 の場合、+pi (-pi) が返される。
y が +0 (-0) で x が +0 の場合、+0 (-0) が返される。
y が 0 より大きい (小さい) 有限値で x が負の無限大の場合、+pi (-pi) が返される。
y が 0 より大きい (小さい) 有限値で x が正の無限大の場合、+0 (-0) が返される。
y が正の無限大 (負の無限大) で x が有限値の場合、pi/2 (-pi/2) が返される。
y が正の無限大 (負の無限大) で x が負の無限大の場合、+3*pi/4 (-3*pi/4) が返される。
y が正の無限大 (負の無限大) で x が正の無限大の場合、+pi/4 (-pi/4) が返される。

動作確認のコードを,書きました.

/* atan2_test.c */
#include <stdio.h>
#include <math.h>

#define p(e) (printf(#e " = %g\n", (e)))

int main(void)
{
  /* y が +0 (-0) で x が 0 未満の場合、+pi (-pi) が返される。*/
  p(atan2(0.0, -1.0));
  p(atan2(-0.0, -1.0));
  /* y が +0 (-0) で x が 0 より大きい場合、+0 (-0) が返される。 */
  p(atan2(0.0, 1.0));
  p(atan2(-0.0, 1.0));
  /* y が 0 未満で x が +0 か -0 の場合、-pi/2 が返される。 */
  p(atan2(-1.0, 0.0));
  p(atan2(-1.0, -0.0));
  /* y が 0 より大きく x が +0 か -0 の場合、pi/2 が返される。 */
  p(atan2(1.0, 0.0));
  p(atan2(1.0, -0.0));
  /* x か y のいずかが NaN の場合、NaN が返される。 */
  p(atan2(1.0, NAN));
  p(atan2(NAN, 1.0));
  p(atan2(NAN, NAN));
  /* y が +0 (-0) で x が -0 の場合、+pi (-pi) が返される。 */
  p(atan2(0.0, -0.0));
  p(atan2(-0.0, -0.0));
  /* y が +0 (-0) で x が +0 の場合、+0 (-0) が返される。 */
  p(atan2(0.0, 0.0));
  p(atan2(-0.0, 0.0));
  /* y が 0 より大きい (小さい) 有限値で x が負の無限大の場合、+pi (-pi) が返される。 */
  p(atan2(1.0, -INFINITY));
  p(atan2(-1.0, -INFINITY));
  /* y が 0 より大きい (小さい) 有限値で x が正の無限大の場合、+0 (-0) が返される。 */
  p(atan2(1.0, INFINITY));
  p(atan2(-1.0, INFINITY));
  /* y が正の無限大 (負の無限大) で x が有限値の場合、pi/2 (-pi/2) が返される。 */
  p(atan2(INFINITY, 1.0));
  p(atan2(INFINITY, 1.0));
  /* y が正の無限大 (負の無限大) で x が負の無限大の場合、+3*pi/4 (-3*pi/4) が返される。 */
  p(atan2(INFINITY, -INFINITY));
  /* y が正の無限大 (負の無限大) で x が正の無限大の場合、+pi/4 (-pi/4) が返される。 */
  p(atan2(INFINITY, INFINITY));

  return 0;
}

数学関数を使用していますので,コンパイルには-lmオプションが必要です.それとNANやINFINITYはC99で使用可能なマクロ(C言語関数辞典 - math.h)なので,-std=c99もつけておきましょう.コンパイルのコマンド,そして実行結果は以下の通りで,上記の返り値の説明と,一致しました.

$ cc -std=c99 atan2_test.c -o atan2_test -lm && ./atan2_test
atan2(0.0, -1.0) = 3.14159
atan2(-0.0, -1.0) = -3.14159
atan2(0.0, 1.0) = 0
atan2(-0.0, 1.0) = -0
atan2(-1.0, 0.0) = -1.5708
atan2(-1.0, -0.0) = -1.5708
atan2(1.0, 0.0) = 1.5708
atan2(1.0, -0.0) = 1.5708
atan2(1.0, NAN) = nan
atan2(NAN, 1.0) = nan
atan2(NAN, NAN) = nan
atan2(0.0, -0.0) = 3.14159
atan2(-0.0, -0.0) = -3.14159
atan2(0.0, 0.0) = 0
atan2(-0.0, 0.0) = -0
atan2(1.0, -INFINITY) = 3.14159
atan2(-1.0, -INFINITY) = -3.14159
atan2(1.0, INFINITY) = 0
atan2(-1.0, INFINITY) = -0
atan2(INFINITY, 1.0) = 1.5708
atan2(INFINITY, 1.0) = 1.5708
atan2(INFINITY, -INFINITY) = 2.35619
atan2(INFINITY, INFINITY) = 0.785398


本日はマイナス0から話を始めましたが,この記事を書くきっかけになったのは,また別の話です.具体的には比の値について寄せられたコメントへの回答 - 身勝手な主張の本文にある,「算数から離れるが、等しい比のこの定義が有用なのは、3:0のような比を考えることができる点にある」のところです.0で割るわけにいかないけれども,例えばa:b=c:d iff ∃k[(c,d)=k(a,b)]*1と定義すれば,3:0にも意味を与えることができるのだよと理解しました.
比は,2:3という,2つの数をコロンで挟んで書くのに対し,その比の値となる2/3や「3分の2」は,数量的には1つの数です.0を考慮しなければ,比と比の値を同一視できますが,考慮するなら,別途対応が必要となります.
同様の対応で,思い浮かんだのが,アークタンジェントの関数なのでした.atanは引数1つ,atan2は2つです.ポケコンのBASICのころにも,ATAN2を見た記憶があります.
比の値のブログ記事の本文は,ディフェンスする側の答弁としてはそんなもんかなと読んで思いましたが,そこに多くのコメントを書いている一人のコメンターさんについて,http://ameblo.jp/metameta7/entry-11160309392.html#cboxの1番目と似ているなという印象も持っています.別記事のコメントで,さくらんぼ計算が,大正期の師範学校のテキストに出ていたというのにも,興味があります.

*1:括弧はベクトルの成分表記と思われます.ベクトルなしで書くと,c=kaおよびd=kbです.それと,k≠0も必要です.k=0を許すと,3:0=0:0や0:3=0:0が成立してしまいます.