printf() に float/double を渡したときの挙動と %lf の意義

初心者向けの話。

この文書では、 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](http://en.cppreference.com/w/cpp/io/c/fprintf) に format specifier の一覧がある

## 詳細
### printf family の書式指定子

[n1570 §7.21.6.1, The `fprintf` function](https://port70.net/~nsz/c/c11/n1570.html#7.21.6.1) にて、以下の記述がある。

> The length modifiers and their meanings are:
> _(中略)_
> <dl><dt>`l` (ell)</dt><dd>
> _(前略)_; or has no effect on following `a`, `A`, `e`, `E`, `f`, `F`, `g`, or `G` conversion specifier.
> </dd></dl>
>
> —— [n1570 §7.21.6.1/7](https://port70.net/~nsz/c/c11/n1570.html#7.21.6.1p7) 抜粋

ここに `%lf` における `l` が何の効果も持たないということが示されている。

> The conversion specifiers and their meanings are:
> _(中略)_
> <dl><dt>`f`, `F`</dt><dd>
> A `double` argument representing a floating-point number is converted to decimal notation _(後略)_
> </dd></dl>
>
> —— [n1570 §7.21.6.1/8](https://port70.net/~nsz/c/c11/n1570.html#7.21.6.1p8) 抜粋

`%f` や `%F` などが (`float` でなく) `double` 引数を指定していると記されている。

### default argument promotion

[n1570 §6.5.2.2 Function calls](https://port70.net/~nsz/c/c11/n1570.html#6.5.2.2) にて、以下の記述がある。

> 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 to `double`. These are called the _default argument promotions_.
>
> —— [n1570 §6.5.2.2/6](https://port70.net/~nsz/c/c11/n1570.html#6.5.2.2p6) 抜粋

訳すとこんな感じ:

> 呼ばれる関数を記述する式がプロトタイプが 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](https://port70.net/~nsz/c/c11/n1570.html#6.5.2.2p7) 抜粋

訳すとこんな感じ:

> 呼ばれる関数を記述する式がプロトタイプが 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" | 株式会社きじねこ](http://www.kijineko.co.jp/tech/superstitions/printf-format-for-double.html)
      + > C++においても、double に対する "%lf" は、現在もなお「未定義の動作」を引き起こします。 
      + ……とあるが、この記述は古い。後続セクションを参照のこと。
  * [関数printf()内の%lf | 配電盤](http://blog.unfindable.net/archives/682)

`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 in `printf`
>
> —— [n1570 Foreword/7](https://port70.net/~nsz/c/c11/n1570.html#Forewordp7)

ここで "second edition" とは、 [Foreword/6](https://port70.net/~nsz/c/c11/n1570.html#Forewordp6) からわかるように 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. <sup>1</sup>
>
> 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 および関連する注釈を抜粋