C言語でべき乗表現

こんにちは、NAKAといいます。

C言語基礎を確認させてください。

pow()っていう関数を使うと思うのですが

例えば2^15って0x8000だと思うのですが、実際やってみると0x7FFFになってしまいます。

  • NAKAさんのpow(144,0.5)などの計算結果がおかしくなるという話に関してです

    昔、powではないのですが、sqrtの計算で変な計算結果が出たので、何故おかしくなるか見てみた事があります。

    私が遭遇した問題では、

    #include "math.h"

    を入れておけば問題ない、という話であったかと?と思っています。(#include "mathf.h"ではダメ)

    CC-RXのヘルプより引用

    -----

    pow / powf / powl

    浮動小数点値のべき乗を計算します。

    [指定形式]

    #include <math.h>

    double pow(double x, double y);

    float powf(float x, float y);

    long double powl(long double x, long double y);

    -----

    pow(double型)の関数はmath.h内で定義

    -----

    powf
    浮動小数点値のべき乗を計算します。

    [指定形式]

    #include <mathf.h>

    float powf(float x, float y);

    -----

    mathf.h内で定義されているのは、powf (float型の関数)

    CS+(CC-RX)で試した結果を示します。

    ソースコードとしては

    -----

    volatile double a, b, c;

    a = 144.0;
    b = 0.5;

    c = pow(a, b); //期待値は12.0

    while(1);

    -----

    です。

    (1)math.hをインクルードした場合

    144.0 (0x4062 0000 0000 0000)がR2,R1に

    0.5(0x3fe0 0000 0000 0000)がR4,R3に格納されて、_pow(ライブラリ関数に渡される)

    計算結果がR2,R1に格納されて戻ってくる

    R2, R1(=0x4027 ffff ffff fffb)

    それを、[R0](変数cのアドレス)にコピーする

    丸め誤差はあるが、cの結果は12で妥当。

    (2)math.hをインクルードしない

    _powに引数を渡すところと、_powの戻り値

    R2=0x4027 ffff

    R1=0xffff ffffb

    は、(1)と同じ、但しその後のコードが異なる

    ITODなので、R1をdoubleに拡張してDR0(浮動小数点Reg)に代入

    DR0を変数cのアドレスにコピー

    DR0は-5(powの計算結果の内、R1側のみをdouble拡張した結果)

    変数cの実行結果は、-5

    (1)と(2)の違いは、math.hをインクルードしているか、していないかのみです。mathf.のみをインクルードした場合は(2)と同じ結果となります。

    math.hをインクルードしない場合、_pow(ライブラリ関数)への引数の受け渡しのところで、型が判っていないので、取り違えが起こっている様な感じかと思うのですが。

    (今回のNAKAさんの計算結果が変になるという話とリンクしているかは判りませんが、以前私が???となったのはmath.hのインクルードを忘れて、変な計算結果が出たという話です。)

  • プロトタイプ宣言のない関数を呼び出す場合(math.hをインクルードしない場合)、float型の引数はdouble型に変換してから格納すると言う文法がありますから、「型が判っていないので、取り違えが起こっている」という表現は正しくないです。むしろ文法通りです。

    結果、float型とdouble型の精度が異なる場合、プロトタイプ宣言しないと正しく動作しないのは当然ですよ!! なお、ここから先は小生の勝手な思い込みですが、この文法はprintfのためではないかと思っています。C89やC90までは、float型もdouble型も同じ変換指定%fで表示できちゃいますから。精度が違うのに表示できるのは、上記の文法があるためです。

  • おはようございます。NAKAです。

    皆さんの議論に中々ついていけてませんが、がんばります。
    今回、感じたのは2進数の15bit目を"1"にしたい場合(1000 0000 0000 0000)、
    僕は16進の0x8000くらいしか発想しませんが、
    世の中には2^15と発想する人もいるんだな?ということでしょうか?

  • NAKAさん、こんにちは。NoMaYです。

    その点に関しては、「数学の先生は(自然数の自然数)べき乗演算を ^ で書くことがしばしばあるけれども、プログラミング言語で ^ を(自然数の自然数)べき乗演算子として扱う言語はむしろ稀である」、あたりかな、と私は思いました。(そして、そういった先生やその著作からから学んだ学生さんなども ^ を(自然数の自然数)べき乗演算子としてプログラミングでつい使ってしまうことがある。)

    ただ、それ以前に、以下の点が大事な理解だと思うのですけれども。

    人間社会の常識では整数ぴったりになる浮動小数点演算式とか浮動小数点演算関数とかでも、プログラミング言語を問わず、浮動小数点演算が整数ぴったりになるとは限らない。人間社会の常識の整数値よりもちょっとだけ計算結果が大きかったりちょっとだけ小さかったりする。特に、ちょっとだけ小さかった計算結果を整数変数に代入すると、期待した整数値よりも 1 小さい値になってしまうので、1 という数値の違いが重大な問題を引き起こす場合、そのようなことをしてはならない。(むしろ、自分はちょっと不注意な性格かな、と感じている人なら、浮動小数点数を整数に代入しないように自主的に禁じてしまった方が良いかも知れない、かも。)

    > (例) 実際に計算していないので、たまたま、うまくいくこともあるかもしれません。

    > (1) 0.001を1000個足したからといって、1.000(それ以下の桁は省略) になるとは限らない
    > (2) √2 × √2 = 2 ではあるけども、SQRT(2.000) × SQRT(2.000) = 2.000(それ以下の桁は省略) になるとは限らない
    > (3) 2の2乗 = 4 ではあるけれども、POW(2.000, 2.000) = 4.000(それ以下の桁は省略) になるとは限らない

  • NAKAさん

    あくまで数学ベースは2^15です。逆に言えば8000hと考える方が稀だと思った方がいいです(ここに出入りする方々、コーディングを生業にしているは方々はその稀なのですが)。モーター制御のセミナーで他人のC言語コードを読んだことありますが、元々電機系の方のだったんでしょうね・・・数学書の数式を頑張ってコードに落としたような書き方でした。変数や関数も意味のわかる名前にしないし。rk_xxxみたいな関数名、資料にルンゲ=クッタ法って説明がないとわからないですよ。この場合はトピックとは関係なくコードの作法みたいな話ですが、往々にしてソフトウェア工学を履修してない方が必要にかられて書いたコードって話では通じることがあるかもしれません。

  • NAKAさん、こんにちは。NoMaYです。

    > 2進数の15bit目を"1"にしたい場合(1000 0000 0000 0000)

    あと、この点に関しては、以下の記述もそれなりに見掛けるように思います。([追記] ごめんなさい、右端が0bit目という表記ですね。)

    (1 << (15 - 1))    (1 << 15)

     

  • CC-RL(RL68)のマニュアルを見たところ、プロトタイプ宣言の無い浮動小数点の引数は、doubleになるようでした。(C99拡張) ただ、戻り値は宣言が無いので、intとみなされ、代入先が doubleなので、int →doubleの変換がされたと考えます。(その結果、意味不明な値)

    printf()の場合、プロトタイプ宣言で、可変長宣言がされているので、そのまま、(CC-RLでは)スタックに積み上げられ、最初のフォーマット指定(引数)に従って解釈され、型が一致しない場合、結果は不定(処理系依存で、うまくいく場合もある)   と思いましたが、確かに、float → double変換という仕様はありましたね。(昔から、、) ただ、整数の場合は別のよう。

    CC-RLだと、double=4byte指定という謎仕様もあるので、この場合、double=floatとなり、混在してても問題ないですね。

  • C言語のプロトタイプ宣言がない関数の呼び出しは「引数なし、戻り値int」として扱われるのでは?プロトタイプ宣言の引数、の型の違いについては精度が高い方へ自動的に型変換(確かプロモーションとか言ったかな?)が適用されるというルールとごちゃごちゃにしているように思われるのですが。多分、math.hはインクロードしなくてもビルトインなのでビルドは通り、想定通りに動くと思われます。GCCでは少なからずそうです。

    #include <stdio.h>

    int main(void) {
      printf("%f\n", sqrt(2.0));
      return 0;
    }

    これは警告が出ますが、GCCならビルドできます。

    でもこれはだめ。

    #include <stdio.h>

    // double my_sqrt(double); //コメント外すと動きますよ。

    int main(void) {
      printf("%f\n", my_sqrt(2.0));
      return 0;
    }

    #include <math.h>
    double my_sqrt(double x) {
      return sqrt(x);
    }

  • 最初の例は警告で、ダメな方はエラーではないでしょうか?
    ダメな方も、my_sqrt()関数を別ファイルをして、コンパイルするとビルドできると思います。
    (結果が正しいかは別)

  • エラーなのか警告なのかはCの規格バージョンによるところがあると思います。ちなみに私のところで確認した結果はGNU拡張のC17なのでプロトタイプ宣言なしは禁止となっています。コンパイラはgcc (Homebrew GCC 13.1.0) 13.1.0です。

    どちらにしてもプロトタイプ宣言の暗黙は引数なし、intを返すというのがC99(までの)仕様です。C11以降は細かいところまで見ていませんがルネサスのコンパイラはC99までしかサポートしてなかったのでここではあまり気にする必要もないですね。

    C99