NI製品ディスカッション

キャンセル
次の結果を表示 
次の代わりに検索 
もしかして: 

特定の値で失敗する'自作'ステップ値生成関数

初めて質問させていただきます。

 

始まり値(i)、終わり値(e)、間隔(s) を与えると、i から s ごとに増加し eを超えないところまでの値を持つ1次元配列(v[n])を返す関数を書こうとしています。

例えば、i = 1, e = 1.8, s = 0.1 なら v = [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8] を返すものです。ここで、i, e, s は浮動小数点数とします。

 

添付のvi で動作すると思ったのですが、

i = 1, e = 1.7, s = 0.1 などとした場合、期待した配列([1, ... , 1.6, 1.7]) が得られません。

 

除算や for ループに整数値を入れていないところに問題がありそうなのですが、どのように書くべきでしょうか?

 

0 件の賞賛
メッセージ1/11
3,473件の閲覧回数

コンピュータが小数を扱う際の罠ですね。

 

「0.1」は実は「0.100000000000000006」なので、丸め込みの際に1~1.7だと「-6.99999999999999911」となり切下げ整数で-6になってしまうのが原因ですね。

 

最も近い値に丸め込んでは如何でしょう。

メッセージ2/11
3,451件の閲覧回数

ありがとうございます。

切り下げていたところを最も近い値に丸め込むことで解決しました。

勉強になりました。

0 件の賞賛
メッセージ3/11
3,434件の閲覧回数

横槍で恐縮です。

 

TAKASU様のご教示の通りで基本的には解決していると思いますが、この件は、2進数のコンピュータで10進小数を扱う際の根源的問題ですので、以前、似たようなスレッドで回答したこともありますが、ご参考になさって下さい(そのスレッドの途中からコメントしています)。

数値制御器の不具合

 

10進数なら割り切れて見える実数も2進表現では割り切れないものが多かったり、格納できるビット数が有限なことから、時々、直観的でない挙動をします。

「微小なもの」が効く例だけでなく、微小で片づけられないもっと「普通な」場面でも現れてしまうことがあり、その最たる挙動の1つが以下の例です。

170608-sample1.png

信じられないかも知れませんが、0.6×0.6≠0.4×0.9なのです。

doubleでもsingleでもこうなります。ベタ打ちの0.36とイコールなのは0.6×0.6の方です。

これは、浮動小数点の演算結果を、イコールで判定してはいけない例です。

(微小な計算許容誤差εを設定して、差の絶対値がそのε以下、として記述する)

これらを、内部表現をもとにビット列で表すと以下のようになり、結果が異なるのが分かります。

(ビット数が有限なので打ち切られていますが、真の2進数は無限小数ですのでご留意を)

170608-sample2.png

 

同じ理由で、doubleの0.36と、singleの0.36は「=」につないでもイコールになりません(singleのほうがビットが足りないので)。

大きな値に、小さな値を加算し続ける場合も、桁落ちが累積して、意図しないことになります。大きな値どうしを引き算する場合も桁落ちして同様です。

 

なお、今回のようにdiscreteな扱いで良い場合は「丸める」のも手ですね。

ご存知かも知れませんが、「丸める(最近接)」は、端数が0.5のとき、上がるか下がるかが独特ですのでお気を付け下さい。四捨五入ではなく「最近接の偶数」へ丸められます。つまり、1.5は2になりますが、2.5も2になります。3.5は4になります。今回の場合は整数に極めて近いところにあると思うので問題にはならないと思いますが・・

メッセージ4/11
3,409件の閲覧回数

もう解決されたようなので蛇足ですが、Full DevelopmentSystemをお持ちなら、ランプパターンVIを使えば今回のような動作を簡単に実装できます。

0 件の賞賛
メッセージ5/11
3,391件の閲覧回数

すごくわかりやすい例示で説明してくださりありがとうございます。
リンク先も興味深く読ませていただきました。
"普通の"小数どうしの割り算や比較で直観的でない値が出るのは驚きでした。
「最近接に丸める」の独特な振舞いも教えていただいて初めて知りました。

今回の場合も
・微小値εを足して切り上げ整数化
・値の比較は差分の絶対値が計算許容誤差ε以下かで判定
することで、解決できそうです。

まだ混乱しているのでご返信に時間がかかってしまったのですが、
適当な範囲内でそれぞれのパラメータをしらみつぶしに変化させて、おそらくうまく動くものがかけたと思います。

0 件の賞賛
メッセージ6/11
3,380件の閲覧回数

情報ありがとうございます。残念ながらFull DevelopmentSystem は持っていないようなのですが、組み込みVIが使えるならそれを使うべきですね。

参考までにランプパターンVI でも、上でみられたようなことは注意する必要があるという認識でよろしいでしょうか。

0 件の賞賛
メッセージ7/11
3,376件の閲覧回数

ランプパターンVIはお使いにはなれないのですね。失礼しました。

 

ランプパターンVIでは今回のような問題は発生しません。

下記ヘルプページの下部にある、「ランプパターンの詳細」にパターン生成のアルゴリズムが書いてありますので、参考になさってください。そこに載っている式に従ってご自身で実装すれば、同様の動作を実現できます。

ただ、そちらに変えても処理速度はあまり変わらないでしょうし、今お使いのもので他に問題がないのであれば、お作りになったものをそのまま使っても大丈夫だと思います。

ランプパターン(VI) - LabVIEW 2011ヘルプ - National Instruments

0 件の賞賛
メッセージ8/11
3,371件の閲覧回数

ランプパターン関数でも、問題は発生しますよ。

ランプパターン関数は、start、endと、sample数を指定しますが、start以外は、end-startを(sample数-1)で割ったものの0倍・1倍・2倍・・・をstartに加算していますから、配列の最後の数はendになるはずのところ、厳密には「引数で指定したend」と一致はしないのです。

 

例として、start=0.01、end=0.99、として、適当なサンプル数のランプパターンを生成し、その最終要素(論理的には引数のendと等しいはず)が、本当に引数のendと等しいかを確かめると、サンプル数8までは一致しますが、サンプル数9で、不一致となります。

170610-sample1.png

 

また、この不一致具合はいつも同じではなく、例えばサンプル数99(要は0.01刻み)の時には上記の3倍くらいの開きとなり、

170610-sample2.png

 

サンプル数100だとまたちょっと違った差になり、大小も逆転します。

170610-sample3.png

 

ランプパターンは、引数のendと、返ってきた配列の最後の数は、一致比較できない(大きいかも知れないし、小さいかも知れない)、という意味では、使い方によっては問題が起きるかも知れない、ということです。

(今回の用途での「ランプパターン関数で問題が起きない」とは、指定したサンプル数ぶんだけ、指定したend近傍までの値でちゃんと配列を生成する、という意味合い)

メッセージ9/11
3,367件の閲覧回数

いろいろためしてみましたが、M. Shiraishi様のおっしゃるとおりですね。

あまりそれほど下の桁を意識することがなかったので、気づいていませんでした。

知識不足で適当なことを言って申し訳ありません。勉強します。

メッセージ10/11
3,359件の閲覧回数