C言語でべき乗表現

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

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

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

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

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

    NAKAさんの場合、RL78なのかRXなのかRH850なのかはたまた他のやつなのか…
    分かりませんが、手元のRL78の環境の場合仰るように0x7FFFになりました(intに渡した時)。

    pow()関数自体、引数も戻値もdouble型ですが、
    RL78の環境だとdoubleはfloatと同じ4バイト(だった気がするので)
    そのあたりの精度・切り捨てとキャストの兼ね合いな気がします。

    double ans;
    int ansi;
    ans = pow(2,15);
    ansi= pow(2,15);

    の結果をウォッチで見てみたら
    ans 3.276798E+004 float(4)
    ansi 32767 (0x7fff) int(2)

    ってなってました。

    先日、Cに関して素人レベルだと怒られてしまったので
    プロレベルの詳しい方の解説が欲しいところです!

  • doubleのオプションを付けたら
    ans 3.276800000000004E+004 double(8)
    ansi -32768 (0x8000) int(2)
    になりました!

  • Sugachanceさん お久しぶりです。
    ナカです。いつもありがとうございます。
    僕も、基礎がないのですぐつまずいちゃいます。

    じつは先日までpow関数も知らずに A=2^15;とか書いちゃって
    「おかしいなあ計算結果が全然ちがう!」とかやってました(*_*)
    ビルドでエラーが出ないと正しいと思いこんじゃいますね。

  • Sugachanceさん NAKAです。


    >doubleのオプションを付けたら
    ⇒ってどこのオプションでしたっけ?
    プロパティのコンパイラ・オプションくらいかなと早合点してました。

    やっぱり、なんか変です。

    pow(2,2)=0x3になっちゃうし

    mathfをインクルードたら更におかしなことになりました。

    RL78でもRH850でも同じ感じでした。


    0.5乗は上手くいく!

    やっぱり、変

Reply
  • Sugachanceさん NAKAです。


    >doubleのオプションを付けたら
    ⇒ってどこのオプションでしたっけ?
    プロパティのコンパイラ・オプションくらいかなと早合点してました。

    やっぱり、なんか変です。

    pow(2,2)=0x3になっちゃうし

    mathfをインクルードたら更におかしなことになりました。

    RL78でもRH850でも同じ感じでした。


    0.5乗は上手くいく!

    やっぱり、変

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

    > 数学的本質みたいなのは理解できてませんが、

    この文面を見た時、これは分かっていないサインかな、と感じたのですけれども、そういうことだったな、と思いました。数値計算の分野では、浮動小数点数での数値計算ライブラリの計算結果が、机上の計算結果とは一致しないことがある、というのは常識といってもよいようなことなのです。

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

    (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さん こんにちは。Sugachanceです。

    私がいじったのはコンパイルオプションのdouble型/long~のところです。
    正しくはつけたらというか、いいえにしたらですが…

  • NoMaYさん いつもありがとうございます。 NAKAです。


    数値計算関数は浮動小数点で作られているので、浮動小数点自体が仮数部で表現できるのに限界があるため、誤差が発生し、必ずしも机上計算どうりにはいかないという理解でしょうか?

    元々、北米の有名大学の先生(ソフトの専門ではない)が書いたコードで先頭のbitの符号を確認するのに2^15(0x8000)をandしているような書き方だったで、論理演算のためFloatで誤差を少くしても意味ないし、int型をビットシフトしたり、色々悩ましいです。(;_;)

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

    > 北米の有名大学の先生(ソフトの専門ではない)が書いたコード

    ちなみに、その先生がコードを書くのに使用されていたプログラミング言語は何ですか?

    また、可能であれば、原著へのURLもあれば、と思います。

  • NoMaYさん NAKAです。
    すみません!NDA締結した共同研究先なのであまり情報は出せません。C言語的な感じですが指示書みたいなものです。

    Sugachanceさん

    ⇒ありがとうございました。♡ RH850のプロパティ見てました(*_*)

  • ちょっと気になるのですが、ビルド時に警告出ていないでしょうか?

    C言語の場合、宣言されてない関数を使うと、引数/戻り値 共に intと見なされます。

    関係なければ、良いですが、、。

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

    > > その先生がコードを書くのに使用されていたプログラミング言語は何ですか?

    > C言語的な感じですが指示書みたいなもの

    たぶん、まさに昨日の、こぼればなし、のことなのでしょうね。(Rとかの可能性もありますけれども。)

    > こぼればなし
    blog.media.teu.ac.jp/2021/08/post-89817a.html

    さて、毎年数人の学生が「^」を冪乗演算子として使用してくる(そして間違える)のですが、私は「何故学生は『^』を冪乗演算子と認識しているのか」ということを不思議に思いました。


    候補は色々とあったものの、どうも納得できずにモヤモヤしているところに、「数学の先生達がプレーンテキスト内で冪乗に ^ を使ってて、それを見たのでは?」という意見がありました。おそらくこれが私の求めていた答えなのだろうと考えています。


    十中八九、その北米の有名大学の先生は、C言語のpow()べき乗数学関数のことは頭に無くて、ごく単純に、自然数の自然数べき乗、として書いただけですよね、そして、それはC言語では面倒だけれども自作しないといけない関数、なのですよ。C言語に、自然数の自然数べき乗、というのを一発で即かつ意図通りに計算する演算子は無いのですよ。今回のその用途にpow()べき乗数学関数を使ってはいけないのです。


    なんというか、そういう オチ なのだと、そういう気がし始めたのですけれども、、、

  • 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で表示できちゃいますから。精度が違うのに表示できるのは、上記の文法があるためです。

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

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

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