初心者向けの話。
この文書では、 C11 の規格として n1570 (Committee Draft) を参照する。
TL;DR
- C 言語において、
printf()
等の可変長引数関数の可変長引数としてfloat
を渡した場合、double
へと自動で変換される (→ default argument promotion)- つまり、
float
が渡されてもdouble
が渡されても、printf()
内部からはどちらもdouble
に見える
- つまり、
- 規格 (C11) においては、
%f
,%lf
ともにdouble
を表現するものとして規定されている
おまけ:
-
long long int
には%lld
を使うが、long double
には%Lf
を使うことになっている - std::printf, std::fprintf, std::sprintf, std::snprintf - cppreference.com に format specifier の一覧がある
詳細
printf family の書式指定子
n1570 §7.21.6.1, The fprintf
function にて、以下の記述がある。
The length modifiers and their meanings are:
(中略)
- `l` (ell)
- _(前略)_; or has no effect on following `a`, `A`, `e`, `E`, `f`, `F`, `g`, or `G` conversion specifier.
—— n1570 §7.21.6.1/7 抜粋
ここに %lf
における l
が何の効果も持たないということが示されている。
The conversion specifiers and their meanings are:
(中略)
- `f`, `F`
- A `double` argument representing a floating-point number is converted to decimal notation _(後略)_
—— n1570 §7.21.6.1/8 抜粋
%f
や %F
などが (float
でなく) double
引数を指定していると記されている。
default argument promotion
n1570 §6.5.2.2 Function calls にて、以下の記述がある。
If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type
float
are promoted todouble
. These are called the default argument promotions.—— n1570 §6.5.2.2/6 抜粋
訳すとこんな感じ:
呼ばれる関数を記述する式がプロトタイプが include されていない型を持っていれば、 integer promotion が各引数へ行われ、さらに
float
型を持つ引数はdouble
に格上げ (promote) される。これは default argument promotions と呼ばれる。
また、次段落:
If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, (中略).
The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.—— n1570 §6.5.2.2/7 抜粋
訳すとこんな感じ:
呼ばれる関数を記述する式がプロトタイプが include された型を持っていれば、引数は暗黙に変換される……(中略)。
関数プロトタイプ宣言子の中の ellipsis notation (訳註:...
のこと) は、変数の型変換を最後に宣言されたパラメータ以降中断させる。それ以降の引数については、 default argument promotions が行われる。
以上の記述から、以下のことがいえる。
- default argument promotion が行われるとき、
float
の引数はdouble
に変換される - プロトタイプが include されていない(つまり、利用する箇所の C コードからプロトタイプ宣言が見える場所にない)関数については、すべての引数に default argument promotions が行われる
- プロトタイプが include された関数については、
...
以降の可変長引数部として渡された引数について default argument promotions が行われる
以上より、 printf()
の可変長引数部として渡される float
の値は、 double
に変換されて printf
へ伝わるということがわかる。
まとめ
-
printf()
のような可変長引数関数について、可変長部分の引数には default argument promotion が行われる- これによって、
float
を渡すとdouble
に変換される -
double
はdouble
のままになる - ゆえに
printf
では、元float
だった値も最初からdouble
だった値も、どちらもdouble
として受け取ることになり、区別は付かない
- これによって、
- printf family の関数について、
%f
はdouble
の値に対応するものであると規定されている - printf family の関数について、
%lf
におけるl
は何の効果も持たないことが規定されている
つまり
-
float
の表示には%f
を、double
の表示には%lf
を使え、という教えはおそらく誤解に基くものであり、float
,double
いずれも%f
で正しく表示することができる
おまけ
何故このような誤解が発生するか
- [迷信] double の出力書式は "%lf" | 株式会社きじねこ
-
C++においても、double に対する "%lf" は、現在もなお「未定義の動作」を引き起こします。
- ……とあるが、この記述は古い。後続セクションを参照のこと。
-
- 関数printf()内の%lf | 配電盤
scanf()
では float
や double
の値を渡すものではなく float*
や double*
を渡すとになるが、この場合 scanf
側では区別しないと困る(ポインタの参照先に書き込むべきサイズが異なるから)ので、 float
と double
を別々の指定子で指定することになる(つまり %f
と %lf
を使い分ける)。
この使い分けは必須であるが、これをそのまま printf()
でも必須であると思い込んだ人々が、 double
の表示には %f
でなく %lf
を使うのが正しい、と勘違いしているのだろう。
古い C
Foreword/7 に以下の記述がある。
Major changes in the second edition included:
(中略)
—%lf
conversion specifier allowed inprintf
ここで "second edition" とは、 Foreword/6 からわかるように C99 の規格のことであり、 C11 の規格は "third edition" に相当する。
上の記述は、printf family における %lf
の使用は C89/90 においては許されていなかったが、 C99 以降で許されるようになったということである。
すなわち、古い C を使う場合には、 printf
で %lf
を使ってはいけない。
また、 C++11 からは、参照する C 規格が C99 に切り替わったため、 C++ の std::printf()
においても %lf
は使えるようになっている。
C++11 未満はもはや C++ じゃないので駄目。
The library described in Clause 7 of ISO/IEC 9899:1999 and Clause 7 of ISO/IEC 9899:1999/Cor.1:2001 and Clause 7 of ISO/IEC 9899:1999/Cor.2:2003 is hereinafter called the C standard library. 1
- With the qualifications noted in Clauses 18 through 30 and in C.3, the C standard library is a subset of the C ++ standard library.
—— n3337 (C++11) §1.2/2 および関連する注釈を抜粋