C の中核言語が与えてくれるのは + - * / と % だけで、それ以外はありません - 累乗演算子も、平方根も、丸めもありません。算術を超えるものはすべて標準の数学ライブラリにあり、<math.h> で宣言されています。
これらの関数はすべて double の精度で動きます。double の引数を取り、double を返します。まずこれを身につけてください。int を渡しても静かに動く(変換される)一方で、結果を int に代入すると小数部分が静かに捨てられるからです。
はじめの一歩と -lm フラグ
自分のマシンでは、これのコンパイルが失敗するかもしれません:
/usr/bin/ld: /tmp/ccXYZ.o: 関数 `main' 内:
program.c:(.text+0x1a): `sqrt' への未定義の参照です
collect2: error: ld returned 1 exit status
これは math.h でいちばんよくあるつまずきで、あなたのコードの誤りではありません。ヘッダは宣言を供給したのでコンパイラは満足しました。しかし実装は別のライブラリ(libm)にあり、Linux ではリンカが既定でそれを含めません。-lm を追加します:
gcc program.c -o program -lm
このフラグはソースファイルより後に来なければなりません - リンカは入力を順に処理するので、それを必要とするコードより前に並べたライブラリは何も解決しません。macOS と Windows の MinGW では数学のルーチンがすでに標準 C ライブラリの一部なので、-lm は不要です(macOS では付けても害はありません)。
累乗と根
負の数の sqrt(x) はクラッシュせず NaN を返します。引数が負になりうるなら守りを入れましょう。hypot は知っておく価値があります。sqrt(x*x + y*y) と同じ値を、x や y が非常に大きいときでもオーバーフローせずに計算するからです。
小さな整数の累乗なら、素朴な掛け算のほうが速く、しかも正確です - x * x は pow(x, 2.0) に勝ります。後者は対数を経由するので、9 を期待したところで 8.999999999999998 を返すことがあります。
丸め: floor、ceil、round、trunc
4 つの関数、負の数に対して 4 通りの異なる答え。この表こそ覚える価値のある部分です:
floor(x)-xを超えない最大の整数。常に負の無限大へ向かいます:floor(-2.3)は-3。ceil(x)-xを下回らない最小の整数。常に正の無限大へ向かいます:ceil(-2.3)は-2。round(x)- 最も近い整数で、ちょうど半分はゼロから遠いほうへ:round(2.5)は3、round(-2.5)は-3。trunc(x)- 小数部分を切り捨て、ゼロへ向かいます:trunc(-2.7)は-2。
4 つとも double を返します。整数が欲しいときに int へキャストするのはかまいませんが、(int)x 単体は切り捨てを行うことに注意してください - そのため (int)(x + 0.5) が古くからの「四捨五入」の定石ですが、負の数では間違います。round() を使いましょう。
fabs と fmod
fabs は浮動小数点の絶対値です。代わりに abs に手を伸ばすのは定番の間違いです。abs は int を取り、変換が静かに起こるからです:
% は double にはまったく使えません - 整数用の演算子であり、コンパイラが拒否します。fmod(a, b) がその浮動小数点版で、a の符号を保ちます。
fabs は 2 つの double を比較する正しい方法でもあります。浮動小数点における == は罠だからです:
0.1 と 0.2 は 2 進数で正確に表現できないので、その和は 0.3 からわずかにずれています。== ではなく、必ず許容誤差つきで比較しましょう。
三角関数
三角関数は度ではなくラジアンで動きます - もう 1 つの確実な誤答の源です。
一式はこうです: sin、cos、tan、その逆関数 asin、acos、atan、2 引数の atan2(y, x)、そして双曲線関数の一族 sinh、cosh、tanh。角度を求めるときは atan(y / x) より atan2(y, x) を優先しましょう - x == 0 を処理でき、象限も正しく扱います。
math.h はたいていの環境で M_PI を定義しますが、これは標準 C ではなく POSIX の拡張なので、-std=c17 のもとでは存在しないことがあります。上のように自分で定数を定義するほうが移植性があります。
対数と指数
数学から引き継がれた命名の罠に注意してください。log() は 自然対数(底 e)であって、底 10 ではありません。底 10 は log10() です。
log(0.0) は負の無限大を、log(-1.0) は NaN を返します - どちらもクラッシュしません。つまり不正な引数が静かに計算の中を旅していき、どこかで nan が表示されるまで気づけないということです。
INFINITY、NAN、そしてその判定
浮動小数点には数ではない値があり、math.h がそれらに名前を与えています:
最後の行が肝心です。if (x == NAN) と書くことはできません - それは常に偽です。isnan(x) を使いましょう。(x != x という等式が真になるのは NaN のときだけで、これは isnan がしばしばそう実装されている方法であり、それがないツールチェーンに出会ったときの手軽な代替にもなります。)
無限大はオーバーフロー(exp(1000.0))や、ゼロでない double をゼロで割ったときに生じます。後者は - 整数の 0 除算とは違って - 定義された動作でクラッシュしません。
float と long double の派生形
すべての関数に 3 つの形があります。double 版、f が付いた float 版、l が付いた long double 版です。
float sqrtf(float x);
double sqrt(double x);
long double sqrtl(long double x);
測定に基づく理由がない限り、素の double 版を使いましょう - double は C が既定で昇格させる型であり、float 版を混ぜると、たいていは節約分より変換のコストのほうが高くつきます。この接尾辞が効いてくる唯一の場面は、double をサポートしないハードウェアの上で float だけの計算が性能上の急所になっているときです。
実践的な例
いくつかを組み合わせてみましょう - 2 点間の距離と、2 次方程式の解です:
disc == 0.0 ではなく fabs(disc) < 1e-12 になっていることに注目してください - 先ほどと同じ許容誤差の規則を、測定値から計算された判別式がぴったりゼロに着地することはまずない場面に適用しています。
<math.h> はより大きな道具箱の中の 1 つのヘッダにすぎません。標準ライブラリのページが残りを地図にしており、乱数では、ここではなく <stdlib.h> に属する rand を扱います。
よくある質問
C言語で数学関数を使うにはどうしますか?
ファイルの先頭に #include <math.h> を追加して呼び出します: sqrt(16.0)、pow(2.0, 10.0)、fabs(-3.5)。これらは double を受け取り double を返します。Linux では数学ライブラリを -lm でリンクする必要もあります: gcc program.c -o program -lm。
コンパイル時に 'undefined reference to sqrt' が出るのはなぜですか?
ヘッダがコンパイラに宣言を与えた一方で、実装は Linux ではリンカが自動的には取り込まない別のライブラリにあるからです。コマンドの末尾に -lm を追加してください: gcc program.c -o program -lm。macOS や Windows の MinGW では数学のコードが標準 C ライブラリの一部なので、フラグは不要です。
C言語の abs と fabs の違いは何ですか?
<stdlib.h> の abs() は int を受け取り int を返します。<math.h> の fabs() は double を受け取り double を返します。abs(-3.7) と呼ぶと引数がまず int に変換されて 3 になり、小数部分が静かに捨てられます - 浮動小数点の値には fabs を使いましょう。
C言語で NaN を判定するにはどうしますか?
<math.h> の isnan(x) を使います。x == NAN では判定できません。NaN は自分自身も含めてあらゆるものと等しくないと比較されるからです - この性質そのものが代替の判定になります: x != x が真になるのは NaN のときだけです。無限大には isinf(x)、普通の数かどうかの確認には isfinite(x) を使います。