こんにちは、NAKAといいます。C言語基礎を確認させてください。
pow()っていう関数を使うと思うのですが
例えば2^15って0x8000だと思うのですが、実際やってみると0x7FFFになってしまいます。
NAKAさん、こんにちは。NoMaYです。> > その先生がコードを書くのに使用されていたプログラミング言語は何ですか?> C言語的な感じですが指示書みたいなものたぶん、まさに昨日の、こぼればなし、のことなのでしょうね。(Rとかの可能性もありますけれども。)> こぼればなし> blog.media.teu.ac.jp/2021/08/post-89817a.html「さて、毎年数人の学生が「^」を冪乗演算子として使用してくる(そして間違える)のですが、私は「何故学生は『^』を冪乗演算子と認識しているのか」ということを不思議に思いました。」「候補は色々とあったものの、どうも納得できずにモヤモヤしているところに、「数学の先生達がプレーンテキスト内で冪乗に ^ を使ってて、それを見たのでは?」という意見がありました。おそらくこれが私の求めていた答えなのだろうと考えています。」十中八九、その北米の有名大学の先生は、C言語のpow()べき乗数学関数のことは頭に無くて、ごく単純に、自然数の自然数べき乗、として書いただけですよね、そして、それはC言語では面倒だけれども自作しないといけない関数、なのですよ。C言語に、自然数の自然数べき乗、というのを一発で即かつ意図通りに計算する演算子は無いのですよ。今回のその用途にpow()べき乗数学関数を使ってはいけないのです。なんというか、そういう オチ なのだと、そういう気がし始めたのですけれども、、、
NAKAです。
NAKA:浮動小数点自体が仮数部で表現できるのに限界が~
Qiita:0.1を浮動小数点数にすると0.1よりわずかに大きくなるのに、10回足すと1.0より小さいのはなぜか、の答えは、2つの浮動小数点数を足す際に、仮数に入りきらない数を丸めるから、でした。同じ事のような気が........
Qiita:0.1
0.1
1.0
いっそ今回のような場合は自分で累乗の関数を作ったほうが早かったですね!
NAKAさん、こんにちは。NoMaYです。> の答えは、 2つの浮動小数点数を足す際に、仮数に入りきらない数を丸めるから、でした。 同じ事のような気が........いやいや、今回は、以下なのですよ。32768は、数値自体は、内部表現的には、丸める必要など無い数値、なのですよ。> 以下のQiitaの投稿で知ったのですけれども、ウェブ上で浮動小数点数の内部表現を確認出来るサイトがありました。そのサイトで、32768(とか32767とか32769とか)の単精度浮動小数点数の内部表現を確認すると、ちゃんと数値全体が収まっていますよ。なので、これらの数値ぴったりの数値が単精度浮動小数点数の内部表現限界の外にあるわけではないのですよ。そして、丸める必要が無い、という点では、以下の数値もそうである、なのですよ。2、4、8、16、32、64、128、256、、、16384
NoMaYさん NAKAです。>今回は、以下なのですよ。32768は、数値自体は、内部表現的には、丸める必要など無い数値、なのですよ
⇒確かに!単純なNAKAは上記のような関数を作ったほうが早かったなあ!そもそも、依頼元の意図が汲み取れれば2^15を0x8000に変更すればよかっただけだしでも、自信が無いから、大本に出来るだけ忠実にしたほうが....と考えちゃう!(;_;)
でも、ちょっと盛り上がりましたね!(^_-)-☆
NoMaYさん、こんにちは。Sugachanceです。
解説ありがとうございます。(そのシミュレータはたまに利用させてもらってます!便利ですよね)32768そのものをfloatに入れる分に関してはおっしゃる通り問題ないと思うのですがCのPow()関数がこれまでに出てきたアルゴリズムで実装されているとして```pow関数の中身のアルゴリズムの時点で(^の表現を使ってしまいますが)pow(2,15) = e^(y*log(x)) = 32768とはちょっとずれた値になってしまっている。これをfloatに渡すと近い値の3.276798E+004doubleに渡すと近い値の3.276800000000004E+004```という形"ではない"という事ですかね?Windowsの電卓でたたいてみましたが(おそらくdecimal型?)ln(2) = 0.6931471805599453094172321214581815 * ln(2) = 10.397207708399179641258481821873e^(15 * ln(2) ) = 32768ときれいに計算できました。
一方で内部表現シミュレータに15*ln(2)の結果を入れると10.39720770839917964125848182187310.397207708399179641258481821どちらもdouble型では0x4024cb5ecf0a9651となりました。Windowsの電卓ではe^10.397207708399179641258481821 = 32,767.999999999999999999999971405となったので、コンパイラ依存なのか計算機依存なのかは分かりませんがこういう内部の差が出たのではないかと考えていたのですが…
>でも、ちょっと盛り上がりましたね!皆様の回答を含めて、大変勉強になります。興味深い話題提供、ありがとうございます!
トピックの本筋じゃないのですが、私は整数変数を宣言するのにintなんて怖くてそうそう使えないと思ってしまう人です。int8_t、int16_t、int32_t、int64_tとビットサイズを明示するようにしています。#include <stdint.h>これは必須だと思っています。ましてやdoubleなんて怖くてなかなか手が出ません。MビットとNビットの掛け算はM+Nビット必要な訳ですよね。Mビット同士の足し算ならM+1ビット。でもビット数を明示しないコードですと移植した先でトラブルの元なんです。こういう部分はFPGAの論理合成は必要ビット数の割り当てで文句を言ってくれるので不注意な私にはありがたいです。なお️計算はなるべく整数化しています。PID制御はdoubleやfloatは使いません。整数化した後にmath.hのsqrt()とかも処理能力や単精度浮動小数型の誤差が怖いから整数演算で処理する関数で解くようにしています。確か「ハッカーのたのしみ」って書籍に書いてあったニュートン法を使ったコードだったかな。計算機で計算を実装する機会があるならNumerical Recipes in Cとかの関連する章を参考にするのも良いかもと思います。
Sugachanceさん、こんにちは。NoMaYです。> これをfloatに渡すと近い値の3.276798E+004> doubleに渡すと近い値の3.276800000000004E+004これらの「渡す」という言い回しが私にはピンと来なかったのですけれども、私なら「計算する」という言い回しを使うのですけれども。
単精度計算コンパイルオプション版 pow(2.0, 15.0) を、そのコンパイラの単精度浮動小数点ライブラリで、計算したら戻り値は○○○↑ doubleが32bitで、floatも32bit、の意倍精度計算コンパイルオプション版 pow(2.0, 15.0) を、そのコンパイラの倍精度浮動小数点ライブラリで、計算したら戻り値は□□□↑ doubleが64bitで、floatは32bit、の意
他方で、単精度計算版と倍精度計算版で、powf()とpow()というように関数が異なって、かつ、floatが32bitで、doubleが64bit、という場合であれば、以下の言い回しも私は使いますけれども。
単精度計算版 powf(2.0, 15.0) の戻り値の単精度浮動小数点数を、倍精度浮動小数点変数に代入する/渡す倍精度計算版 pow(2.0, 15.0) の戻り値の倍精度浮動小数点数を、単精度浮動小数点変数に代入する/渡す
でも、そういう話をされているようには思えなくて、何と言うか、pow(2.0, 15.0) が単精度浮動小数点数でも倍精度浮動小数点数でもない「型」の戻り値をもっているような、そういうことをイメージされているのでしょうか?そして、> pow関数の中身のアルゴリズムの時点で(^の表現を使ってしまいますが)> pow(2,15) = e^(y*log(x)) = 32768とはちょっとずれた値> になってしまっている。数学的には、上の式の後半部分の途中までの「e^(y*log(x)) = 32768」は厳密に正しいです。他方で、 pow(2.0, 15.0) の浮動小数点ライブラリの実装上の計算結果の戻り値は、「pow(2.0, 15.0) = 32768とはちょっとずれた値」になってしまいます。また、「ちょっとずれた値」というのも、単精度計算版 powf(2.0, 15.0) と倍精度計算版 pow(2.0, 15.0) とでは、異なる「ちょっとずれた値」になっている、のです。あと、ググってみたのですけれども、Windowsの電卓アプリケーションは、「そこそこ多量」から「めちゃくちゃ多量」といった計算量の科学計算(それと3Dゲームでの計算なども)をするものではないから、1回の計算に0.01秒とか掛かっても構わないけど人間社会の常識に従った計算をして欲しい、という要請に対応する為に、もはや咄嗟にどういう内部表現になっているのか分からない、というようものになっているのかなぁ?という印象を持ちました(以下の記事の斜め読みで)。なので、IEEE単精度浮動小数点数やIEEE倍精度浮動小数点数の話に、それを持ち出して大丈夫かなぁ?と思ったりします。Windows10 の新しい電卓は計算結果が演算誤差により正しくない場合がある上に指数表示になって使い物にならないしもとに戻すこともできないqiita.com/Q11Q/items/a724e7f51b22beeecac6あと、Windowsの電卓アプリケーションでは、これに類することも関係するかも知れません。0.1は浮動小数点数で正確に表せないのに、printしたときに0.1と表示されるのはなぜかqiita.com/yucatio/items/c03def631bc5eaaa9887
NoMaYさん、こんばんは。Sugachanceです。
ご回答ありがとうございます。
>これらの「渡す」ご指摘どおり変な感じで考えておりました...イメージとしてはdouble pow (double x, double y);という関数に対してfloat ansf = (float)pow(2.0,15.0);のようにキャストして受け取るようなものを思い浮かべて書いていたのですが確かに変ですね。doubleのpow(2.0,15.0)をfloatにキャストしてもご指摘のあった通り32768になりますものね。CC-RLにおいては、コンパイルオプションのdouble型/long~のところの違いは```倍精度計算版 pow(2.0, 15.0) の戻り値の倍精度浮動小数点数を、単精度浮動小数点変数に代入する```から来ていたのかと思っていましたが、double型/long~をはいにした場合は単精度計算版 powf(2.0, 15.0) の戻り値の単精度浮動小数点数を単精度浮動小数点変数に代入しているようですね。>単精度計算版 powf(2.0, 15.0) と倍精度計算版 pow(2.0, 15.0) とでは、異なる「ちょっとずれた値」ここが本題の答えになるんですかね?→NAKAさんが最初に示された例では表記上はpow()だが、内部的にはpowf()が使用されていた。→小さめになるちょっとずれた値がpowf()から返されていた→32768を期待したが32767に丸められてしまったちょっと個人的整理-------(1) pow(x,y)は数学的なxのy乗の答えとは異なる→計算アルゴリズムが異なる(2) pow(2.0, 15.0)とpowf(2.0, 15.0)は計算アルゴリズム上の理論値とは異なる「32768からちょっとずれた値」が返る------(2)はdoubleのpowの場合double pow(double x, double y){ //この中の計算においてdoubleの表現の限界値が現れることがある // ln(x)をmathの関数から持ってきているのであればそれこそdouble型?? //その影響で理論値からちょっとずれた値になる場合がある return ans;}という感じでしょうかね?
Sugachanceさん、こんにちは。NoMaYです。頂いたリプライの他の部分は後で見ますけれども、以下の部分に関して補足します。> CC-RLにおいては、コンパイルオプションのdouble型/long~のところの違いは ``` 倍精度計算版 pow(2.0, 15.0) の戻り値の倍精度浮動小数点数を、単精度浮動小数点変数に代入する ``` から来ていたのかと思っていましたが、 double型/long~をはいにした場合は 単精度計算版 powf(2.0, 15.0) の戻り値の単精度浮動小数点数を単精度浮動小数点変数に代入しているようですね。CS+ V8.09のCC-RLのヘルプには以下の通りに書かれていて、倍精度浮動小数点変数型サイズと倍精度浮動小数点計算数学ライブラリの組み合わせは、自由に出来そうですけれども、ヘッダファイル math.hにカラクリが入っているのです。「7.2 ライブラリ・ファイル命名規則標準ライブラリを,アプリケーション内で使用するときは,関連するヘッダ・ファイルをインクルードして,ライブラリ関数を使用します。ランタイム・ライブラリ関数は,浮動小数点演算や整数演算を行うときに,CC-RLが自動的に呼び出すルーチンです。また,リンク・オプション(-library)で,これらのライブラリを参照するようにします。同一のプロジェクト内で使用するライブラリ・ファイルの種類は統一しなければなりません。ライブラリ・ファイルの命名規則は,次のようになっています。【V1.03以降】malloc系のライブラリ関数は通常用とセキュリティ機能用とで機能が異なるため,他の標準ライブラリとは別のライブラリとなります。malloc系ライブラリはRL78-S1,RL78-S2,RL78-S3共通です。rl78<muldiv><memory_model><float><standard/runtime><lang>.libmalloc_<secure>.lib (malloc系ライブラリ) 【V1.03以降】<muldiv>n :拡張命令なし,演算器なし(RL78-S1コア/RL78-S2コア用)c :乗除・積和演算器使用(RL78-S2コア用)e :乗除算拡張命令使用(RL78-S3コア用)<memory_model>m :スモール/ミディアム・モデル用<float>4 :単精度浮動小数点数8 :倍精度浮動小数点数(乗除算拡張命令ありデバイスのみ対応)<standard/runtime>s :標準ライブラリr :ランタイム・ライブラリ<secure>n :通常用s :セキュリティ機能用 【Professional版のみ】<lang>なし :C90用99 :C99用 【V1.07以降】」math.h
#if defined(__DBL4)#define acos(_x) acosf((_x))#define asin(_x) asinf((_x))#define atan(_x) atanf((_x))#define atan2(_y, _x) atan2f((_y), (_x))#define cos(_x) cosf((_x))#define sin(_x) sinf((_x))#define tan(_x) tanf((_x))#define cosh(_x) coshf((_x))#define sinh(_x) sinhf((_x))#define tanh(_x) tanhf((_x))#define exp(_x) expf((_x))#define frexp(_value, _exp) frexpf((_value), (_exp))#define ldexp(_x, _exp) ldexpf((_x), (_exp))#define log(_x) logf((_x))#define log10(_x) log10f((_x))#define modf(_value, _iptr) modff((_value), (_iptr))#define pow(_x, _y) powf((_x), (_y))#define sqrt(_x) sqrtf((_x))#define ceil(_x) ceilf((_x))#define fabs(_x) fabsf((_x))#define floor(_x) floorf((_x))#define fmod(_x, _y) fmodf((_x), (_y))#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */#define acosh(_x) acoshf((_x))#define asinh(_x) asinhf((_x))#define atanh(_x) atanhf((_x))#define log1p(_x) log1pf((_x))#define scalbn(_x, _n) scalbnf((_x), (_n))#define scalbln(_x, _n) scalblnf((_x), (_n))#define nearbyint(_x) nearbyintf((_x))#define rint(_x) rintf((_x))#define lrint(_x) lrintf((_x))#define llrint(_x) llrintf((_x))#define round(_x) roundf((_x))#define lround(_x) lroundf((_x))#define llround(_x) llroundf((_x))#define trunc(_x) truncf((_x))#define copysign(_x, _y) copysignf((_x), (_y))#define nan(_tagp) nanf((_tagp))#define fdim(_x, _y) fdimf((_x), (_y))#define fmax(_x, _y) fmaxf((_x), (_y))#define fmin(_x, _y) fminf((_x), (_y))#endif /* C99 */#endif /* __DBL4 */
[追記]なお、コンパイルオプションの説明にも以下の通りに書かれています。「-dbl_sizedouble型とlong double型の解釈を変更します。[指定形式]-dbl_size={4|8} - 省略時解釈double型,およびlong double型を共に単精度浮動小数点型として扱います(-dbl_size=4オプションの指定と同じです)。[詳細説明]- double型とlong double型の解釈を変更します。- パラメータに4を指定した場合,double型,およびlong double型を単精度浮動小数点型として扱います。- パラメータに8を指定した場合,double型,およびlong double型を倍精度浮動小数点型として扱います。- パラメータに4,8を指定しない場合は,エラーとなります。- -cpu=S1オプションと-dbl_size=8オプションを同時に指定した場合は,エラーとなります。- -cpu=S2オプションと-dbl_size=8オプションを同時に指定した場合は,エラーとなります。- -dbl_size=4オプション指定時にdouble型用の標準ライブラリ関数を呼んだ場合,float型用の標準ライブラリ関数に置き換えます。- 本オプションは定義済みマクロに影響を与えます。」