C言語で指数関数(exp)と対数関数(log, log10)を正しく使えるようになると、値の桁数やスケールの大きい問題を安定して扱えるようになります。
本記事では自然対数logと常用対数log10の違いを軸に、expの役割と使い分け、math.hの準備とコンパイル、そして初心者がつまずきやすいポイントまで丁寧に解説します。
exp, log, log10の基礎
自然対数logと常用対数log10の違い
C言語のlogは自然対数を表し、底がe(約2.718281828…)です。
対してlog10は常用対数で、底が10です。
最も基本的な性質は次の通りです。
- 自然対数:
log(e) = 1、log(1) = 0 - 常用対数:
log10(10) = 1、log10(1) = 0
表にまとめると違いがはっきりします。
| 関数 | 底 | 入力の条件 | 主な用途 |
|---|---|---|---|
| log(x) | e | x > 0 | 解析学、確率・統計、指数成長/減衰の解析 |
| log10(x) | 10 | x > 0 | 工学(デシベル)、桁数判定、10進スケールの把握 |
| exp(x) | e^x | 入力は任意の実数 | 対数の逆関数、指数成長/減衰のモデル化 |
覚えるべき最重要ポイントは「Cのlogは自然対数」という点です。
常用対数が必要なら必ずlog10を使います。
exp(指数関数)の役割
exp(x)はeのx乗を返し、log(自然対数)の逆関数です。
つまり、exp(log(x)) = x(x>0)、log(exp(y)) = yが成り立ちます。
指数関数は次のような場面で頻出します。
- 連続複利、放射性崩壊、熱や確率(正規分布)などの数式モデル
- 浮動小数点のスケール調整(オーバーフロー・アンダーフローの回避)
使い分けの目安
- 現象の理論式がeを用いる場合(指数分布、正規分布、微分方程式の解など)は
logとexpを使います。 - 10進の桁数やデシベルなど工学的指標には
log10が自然です。 - 常用対数の代わりにlogを使うのは誤りなので注意してください。どうしても変換する場合は
log10(x) = log(x) / log(10.0)が使えます。
使い方の準備 math.hとコンパイル
#include <math.h> を追加
指数・対数を使うには#include <math.h>が必須です。
関数プロトタイプやマクロが定義されます。
// math.hを使ってexp, log, log10を呼び出す最小例
#include <stdio.h>
#include <math.h>
int main(void) {
double x = 2.0;
printf("exp(%.1f) = %f\n", x, exp(x));
printf("log(%.1f) = %f\n", x, log(x));
printf("log10(%.1f) = %f\n", x, log10(x));
return 0;
}
gccの例 gcc main.c -lm
Unix系(GCC/Clang)では、数学ライブラリのリンクが別なので-lmが必要です。
- GCC/Clangの例:
gcc main.c -lm - 最適化例:
gcc -O2 main.c -lm
Windows(MSVC)は-lm不要
MSVC(Visual Studioのcl)では-lmは不要です。
例えば次のようにビルドできます。
cl /EHsc main.c
基本はdouble型で計算
標準のexp、log、log10はdoubleを入出力とします。
可変長引数であるprintfではfloatは自動でdoubleに昇格するため、%fで表示して問題ありません。
float向けexpf, logf, log10f
単精度floatを使う場合はexpf、logf、log10fが用意されています。
計算コストはやや低くなることがありますが、精度は落ちます。
型を混在させず、式全体を同じ精度で統一するのが安全です。
必要なら拡張精度のexpl、logl、log10l(long double版)もあります。
基本コード例で学ぶ
expの例 eのべきの計算
expはeのべき乗を返します。
正の指数で増え、負の指数で減少します。
#include <stdio.h>
#include <math.h>
int main(void) {
// e^x をいくつか計算
double e1 = exp(1.0); // e^1
double e2 = exp(2.0); // e^2
double em3 = exp(-3.0); // e^-3
// e自体はexp(1.0)で得られます (M_Eマクロは環境依存)
printf("e = exp(1.0) = %.10f\n", e1);
printf("exp(2.0) = %.10f\n", e2);
printf("exp(-3.0) = %.10f\n", em3);
// 性質の確認: exp(a)*exp(b) = exp(a+b)
double left = exp(1.2) * exp(0.8);
double right = exp(2.0);
printf("exp(1.2)*exp(0.8) = %.10f\n", left);
printf("exp(2.0) = %.10f\n", right);
return 0;
}
e = exp(1.0) = 2.7182818285
exp(2.0) = 7.3890560989
exp(-3.0) = 0.0497870684
exp(1.2)*exp(0.8) = 7.3890560989
exp(2.0) = 7.3890560989
logの例 自然対数の計算
logは自然対数です。
log(1.0)=0、log(e)=1を確認してみます。
#include <stdio.h>
#include <math.h>
int main(void) {
double one = 1.0;
double e = exp(1.0); // e ≈ 2.71828...
double half = 0.5;
printf("log(1.0) = %.10f\n", log(one));
printf("log(e) = %.10f\n", log(e));
printf("log(0.5) = %.10f\n", log(half)); // 負の値になります
// 逆関係: log(exp(x)) = x
double x = 2.5;
printf("log(exp(%.1f)) = %.10f\n", x, log(exp(x)));
return 0;
}
log(1.0) = 0.0000000000
log(e) = 1.0000000000
log(0.5) = -0.6931471806
log(exp(2.5)) = 2.5000000000
log10の例 常用対数の計算
log10は桁数やデシベルの計算でよく使います。
#include <stdio.h>
#include <math.h>
int main(void) {
double a = 1000.0;
double b = 0.001;
double c = 2.0;
printf("log10(1000) = %.10f\n", log10(a)); // 3
printf("log10(0.001) = %.10f\n", log10(b)); // -3
printf("log10(2) = %.10f\n", log10(c)); // 約0.3010
// 桁数の目安: 整数nの桁数 ≈ floor(log10(n)) + 1 (n>=1)
// 例: 12345 は 5桁
int n = 12345;
int digits = (int)floor(log10((double)n)) + 1;
printf("12345の桁数の目安 = %d\n", digits);
return 0;
}
log10(1000) = 3.0000000000
log10(0.001) = -3.0000000000
log10(2) = 0.3010299957
12345の桁数の目安 = 5
printfでの出力 %fと桁数
printfの%fは小数点以下6桁が既定です。
桁数は%.nfで制御できます。
指数表記が欲しい時は%eも便利です。
#include <stdio.h>
#include <math.h>
int main(void) {
double v = exp(1.0); // e
// 既定(6桁)
printf("%%f : %f\n", v);
// 桁数の制御(3桁, 10桁)
printf("%%.3f : %.3f\n", v);
printf("%%.10f : %.10f\n", v);
// 指数表記
printf("%%e : %e\n", v);
printf("%%.5e : %.5e\n", v);
return 0;
}
%f : 2.718282
%.3f : 2.718
%.10f : 2.7182818285
%e : 2.718282e+00
%.5e : 2.71828e+00
初心者向けの注意点
logとlog10はx>0のみ
対数関数は定義域がx>0です。
0や負の入力は数学的に定義されません。
実装ではlog(0.0)は-inf(マイナス無限大)、log(-1.0)はNaNになるのが一般的です。
#include <stdio.h>
#include <math.h>
int main(void) {
double x1 = 0.0;
double x2 = -1.0;
printf("log(0.0) = %f\n", log(x1)); // -inf になることがある
printf("log(-1.0) = %f\n", log(x2)); // nan になることがある
// 実務では事前チェックを推奨
double x = 10.0;
if (x > 0.0) {
printf("log(%.1f) = %f\n", x, log(x));
} else {
printf("xは正でなければなりません\n");
}
return 0;
}
出力例(実装依存):
log(0.0) = -inf
log(-1.0) = nan
log(10.0) = 2.302585
0や負の数は計算できない
0や負の入力でlogやlog10を呼び出さないように、必ず事前に入力域をチェックしてください。
エラー処理を明確にしておくと、後続の計算がNaNに汚染されるのを防げます。
大きな値でinfやNaNになることがある
exp(x)はxが大きいとオーバーフローしてinfになります。
doubleでは概ねx ≳ 709.78あたりで溢れます(閾値はlog(DBL_MAX))。
#include <stdio.h>
#include <math.h>
#include <float.h> // DBL_MAX
int main(void) {
double t1 = 700.0;
double t2 = 1000.0;
printf("閾値の目安 log(DBL_MAX) = %.2f\n", log(DBL_MAX));
double y1 = exp(t1);
double y2 = exp(t2); // ほぼ確実に +inf
printf("exp(700) = %e\n", y1);
printf("exp(1000) = %e\n", y2);
return 0;
}
閾値の目安 log(DBL_MAX) = 709.78
exp(700) = 1.014232e+304
exp(1000) = inf
自然対数と常用対数の変換 log10(x)=log(x)/log
常用対数は自然対数から次式で変換できます。
- log10(x) = log(x) / log(10.0)
- より一般に、log_b(x) = log(x) / log(b)(b>0, b≠1)
#include <stdio.h>
#include <math.h>
int main(void) {
double x = 12345.0;
double via_formula = log(x) / log(10.0); // 変換式
double direct = log10(x); // 直接計算
printf("log(x)/log(10) = %.12f\n", via_formula);
printf("log10(x) = %.12f\n", direct);
printf("差(絶対値) = %.12e\n", fabs(via_formula - direct));
return 0;
}
log(x)/log(10) = 4.0914910943
log10(x) = 4.0914910943
差(絶対値) = 0.000000000000e+00
多くの環境で差はほぼゼロですが、丸め位置の違いで極小の差が出る場合もあります。
再現性重視ならlog10を直接使う方が安心です。
丸め誤差と表示桁数に注意
浮動小数点は有限精度です。
表示桁数を増やせば「正確になる」わけではありません。
内部精度(doubleは約15〜16桁)を超えて表示しても末尾はノイズになります。
結果比較は許容誤差(イプシロン)を使うのが定石です。
#include <stdio.h>
#include <math.h>
int nearly_equal(double a, double b, double eps) {
return fabs(a - b) <= eps * fmax(1.0, fmax(fabs(a), fabs(b)));
}
int main(void) {
double a = log(exp(20.0)); // 理論上は 20
double b = 20.0;
double eps = 1e-12;
printf("a=%.17g, b=%.17g\n", a, b);
printf("nearly_equal(a,b) = %s\n", nearly_equal(a,b,eps) ? "true" : "false");
// 表示桁数の例
double v = log(2.0);
printf("log(2) 既定 : %f\n", v);
printf("log(2) 10桁 : %.10f\n", v);
printf("log(2) 17桁G : %.17g\n", v); // doubleの有効桁が見える
return 0;
}
a=20, b=20
nearly_equal(a,b) = true
log(2) 既定 : 0.693147
log(2) 10桁 : 0.6931471806
log(2) 17桁G : 0.6931471805599453
見た目の桁数と計算の正しさは別問題です。
用途に応じて適切な桁数で表示しましょう。
まとめ
本記事では、C言語のexp、log、log10の基本から、math.hの導入とコンパイル手順、基礎的なコード例、そして初心者がつまずきやすい定義域やオーバーフロー、丸め誤差までを解説しました。
特に「Cのlogは自然対数、常用対数はlog10」という点と、log10(x)=log(x)/log(10.0)の変換式は必ず覚えておきましょう。
実装では#include <math.h>と適切なリンク(Unix系は-lm、MSVCは不要)を忘れず、対数の入力は必ずx>0を保証することが大切です。
精度や表示桁も目的に応じて調整し、安定した数値処理の基礎を身につけてください。
