C言語でべき乗表現

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

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

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

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

Parents
  • C言語では浮動小数点数を整数オブジェクトに代入する場合、小点以下の数字は捨てられ整数部の値が代入先の整数オブジェクトで表現できる場合はそのまま代入され、表現できない場合はC言語の仕様として未定義動作となります。

    pow(2,15) は恐らく32768.0を正確に返します。d_BEKIの型が16bitの符号付き整数だった場合、32768という値は16bit符号付き整数では表現することができないためそれを代入しようとすると未定義動作となってしまいます。

    C言語では未定義動作は予測不可能な結果となってしまう可能性があります。

    例として

    
    #include <stdio.h>
    #include <math.h>
    #include <stdint.h>
    #include <inttypes.h>
    
    int main(void)
    {
        int32_t i32 = pow(2, 15);         // 32768で初期化される
        printf("i32 = %"PRId32"\n", i32); // 32768が出力される
        int16_t i16 = pow(2, 15);         // 32768で初期化されない(未定義動作)
        printf("i16 = %"PRId16"\n", i16); // 何が出力されるか
    }
    

    上記のコードをx86-64版gcc 13.1で最適化指示なしでコンパイルし実行すると

    i32 = 32768
    i16 = 32767
    という結果となりました。i16の値が32767というのは未定義動作によるものであり保証されたものではありません。
    clang 16.0.0で最適化指示なしコンパイル、実行すると
    i32 = 32768
    i16 = -32768
    という結果となりました。i16の値が-32768というのも未定義動作によるものであり保証されたものではありません。
    clang 16.0.0で最適化指示-O2を指定してコンパイル、実行すると
    i32 = 32768
    i16 =  (実行の度に変わる)
    という結果となりました。https://godbolt.org/z/TeM3vxK4a
    i16の値が実行の度に変化するよう見えるのも未定義動作によるものであり保証されたものではありません。
    C言語で結果を期待するプログラムを作成するのであれば未定義動作は避ける必要があります。
  • fujita nozomu先生おはようございます。 NAKAです。
    d_BEKIはl最初ong型で定義してたのですが、short でも、unsigned longでも32767になっちゃいます。
    やっぱり、べき乗関数の複雑な処理過程で仮数のまとめや誤差でこうなっちゃうのかな?って感じです。


    最適化はしてません。

  • 本来のトピックから逸脱して書いてる自覚はありますが、ちょっと書かせてください。

    そもそも扱える数値の性質、範囲に合わせて変数の型や計算方法を選択することに尽きるかなと思う次第であります。冪乗の低、冪指数が共に整数なら整数でやる方が良いですし、冪指数が小数でも固定ならニュートン法などの近似値計算で独自に関数を作成されるのが良いと思います。powが必要なのはその先なんじゃなかろうか?それこそ2の冪乗で冪指数が整数ならシフト演算や32ビット範囲までなら32要素のテーブルにその答えを入れてROMにテーブルを用意するだけです。ツールがどう扱うか?を追い込んでもそれは最後の最後なんじゃないかなと思っております。他人のコードの場合、それが何故そうやってるの?って意図が分からないよってことになるのですけどね。

    どこまで誤差を許容できるのか?
    ・小数の場合でダイナミックに桁が変動しない時、所謂固定小数点(C言語にそんなデータ型はないので自分で管理です)にすると良いと思います。小数はそのまま整数変数を使えば2進数である点には注意です。0.5や0.75はスッキリですが0.1はスッキリしません。つまり本来の数値から変数に値がセットされるときの誤差は許容する必要があります。
    ・ダイナミックに桁が変動するなら浮動小数点型ですが、各演算で加減の場合は二項(加減算は二項演算ですね)のうち小さい方は場合によっては消えてなくなります。もし計算が複数回もしくは複数要素の場合は計算順序などに数値の大きさを考慮することで消える数値を減らすことができます(絶対値が小さい順で計算していくことになります)。
    ・場合によっては分数表現に置き換える
    ・計算の誤差分を別変数に保存しておき、それを考慮して最終的な演算結果を得る、割り算で余りが生じる時に有効です

    扱う数値の範囲は?
    ・最大、最小が既知の場合はその最大、最小の数値に必要なビット数に合わせてマクロやtypedefでデータ型を切り替えられるように工夫する

    溢れた時の処理は?
    ・符号付きの変数「+と+の加算でー」、「+とーの減算でー」などオーバーフロー発生を検出した時その変数の極性の持つ最大値、最小値をセットしてやるなど、応用問題に合わせて工夫する。データ型の持つ最大、最小の範囲の半分を有効範囲としてやると演算ごと極性判定ではなく最大、最小の判定で値の範囲を正常な領域に抑え込める

    ここの諸先輩方なら他にこんなのもあるよってのが出てくると思いますので、私はここまでとします。

Reply
  • 本来のトピックから逸脱して書いてる自覚はありますが、ちょっと書かせてください。

    そもそも扱える数値の性質、範囲に合わせて変数の型や計算方法を選択することに尽きるかなと思う次第であります。冪乗の低、冪指数が共に整数なら整数でやる方が良いですし、冪指数が小数でも固定ならニュートン法などの近似値計算で独自に関数を作成されるのが良いと思います。powが必要なのはその先なんじゃなかろうか?それこそ2の冪乗で冪指数が整数ならシフト演算や32ビット範囲までなら32要素のテーブルにその答えを入れてROMにテーブルを用意するだけです。ツールがどう扱うか?を追い込んでもそれは最後の最後なんじゃないかなと思っております。他人のコードの場合、それが何故そうやってるの?って意図が分からないよってことになるのですけどね。

    どこまで誤差を許容できるのか?
    ・小数の場合でダイナミックに桁が変動しない時、所謂固定小数点(C言語にそんなデータ型はないので自分で管理です)にすると良いと思います。小数はそのまま整数変数を使えば2進数である点には注意です。0.5や0.75はスッキリですが0.1はスッキリしません。つまり本来の数値から変数に値がセットされるときの誤差は許容する必要があります。
    ・ダイナミックに桁が変動するなら浮動小数点型ですが、各演算で加減の場合は二項(加減算は二項演算ですね)のうち小さい方は場合によっては消えてなくなります。もし計算が複数回もしくは複数要素の場合は計算順序などに数値の大きさを考慮することで消える数値を減らすことができます(絶対値が小さい順で計算していくことになります)。
    ・場合によっては分数表現に置き換える
    ・計算の誤差分を別変数に保存しておき、それを考慮して最終的な演算結果を得る、割り算で余りが生じる時に有効です

    扱う数値の範囲は?
    ・最大、最小が既知の場合はその最大、最小の数値に必要なビット数に合わせてマクロやtypedefでデータ型を切り替えられるように工夫する

    溢れた時の処理は?
    ・符号付きの変数「+と+の加算でー」、「+とーの減算でー」などオーバーフロー発生を検出した時その変数の極性の持つ最大値、最小値をセットしてやるなど、応用問題に合わせて工夫する。データ型の持つ最大、最小の範囲の半分を有効範囲としてやると演算ごと極性判定ではなく最大、最小の判定で値の範囲を正常な領域に抑え込める

    ここの諸先輩方なら他にこんなのもあるよってのが出てくると思いますので、私はここまでとします。

Children
No Data