Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
7
Help us understand the problem. What is going on with this article?

More than 3 years have passed since last update.

@lo48576

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 を表現するものとして規定されている

おまけ:

詳細

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 to double. 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 に変換される
    • doubledouble のままになる
    • ゆえに printf では、元 float だった値も最初から double だった値も、どちらも double として受け取ることになり、区別は付かない
  • printf family の関数について、 %fdouble の値に対応するものであると規定されている
  • printf family の関数について、 %lf における l は何の効果も持たないことが規定されている

つまり

  • float の表示には %f を、 double の表示には %lf を使え、という教えはおそらく誤解に基くものであり、 float, double いずれも %f で正しく表示することができる

おまけ

何故このような誤解が発生するか

scanf() では floatdouble の値を渡すものではなく float*double* を渡すとになるが、この場合 scanf 側では区別しないと困る(ポインタの参照先に書き込むべきサイズが異なるから)ので、 floatdouble を別々の指定子で指定することになる(つまり %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

ここで "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

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 および関連する注釈を抜粋

7
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
7
Help us understand the problem. What is going on with this article?