こんにちは、NAKAといいます。C言語基礎を確認させてください。
pow()っていう関数を使うと思うのですが
例えば2^15って0x8000だと思うのですが、実際やってみると0x7FFFになってしまいます。
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で最適化指示なしでコンパイルし実行すると
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でデータ型を切り替えられるように工夫する溢れた時の処理は?・符号付きの変数「+と+の加算でー」、「+とーの減算でー」などオーバーフロー発生を検出した時その変数の極性の持つ最大値、最小値をセットしてやるなど、応用問題に合わせて工夫する。データ型の持つ最大、最小の範囲の半分を有効範囲としてやると演算ごと極性判定ではなく最大、最小の判定で値の範囲を正常な領域に抑え込めるここの諸先輩方なら他にこんなのもあるよってのが出てくると思いますので、私はここまでとします。
pow()の実装によってはpow(2,15)が正確に32768.0を返さないケースもあるかもしれません。
double pow(double x, double y) { return exp(y * log(x)); }
は数学的には正しいと思いますが、gcc環境で実行したところpow(2,15)が32768.0より僅かに小さい値を返しました。
#include <stdio.h> #include <math.h> #include <stdint.h> #include <inttypes.h> double pow(double x, double y) { return exp(y * log(x)); } int main(void) { double d = pow(2, 15); printf("d = %f\n", d); printf("d = %.12f\n", d); int32_t i32 = pow(2, 15); printf("i32 = %"PRId32"\n", i32); }
実行結果
d = 32768.000000 d = 32767.999999999985 i32 = 32767
https://godbolt.org/z/sMTe1sEde
このような現象を避けるには
辺りが現実的でないかと思います。