web-dev-qa-db-ja.com

浮動小数点数の同等性を比較すると、たとえ私の場合に丸めエラーが発生しなくても、後輩の開発者を誤解させますか?

たとえば、0.5ごとにジャンプする0、0.5、... 5のボタンのリストを表示したいとします。そのためにforループを使用し、ボタンSTANDARD_LINEで異なる色を使用しています。

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0;i<=MAX;i=i+DIFF){
    button.text=i+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

この場合、各値はIEEE 754で正確であるため、丸めエラーは発生しないはずですが、浮動小数点の等価比較を回避するために値を変更する必要があるかどうか、私は苦労しています。

var MAX=10;
var STANDARD_LINE=3;

for(var i=0;i<=MAX;i++){
    button.text=i/2.0+'';
    if(i==STANDARD_LINE/2.0){
      button.color='red';
    }
}

一方では、元のコードはより単純であり、私に転送されます。しかし、私が検討していることが1つあります。i== STANDARD_LINEがジュニアチームメイトを誤解させることはありますか?浮動小数点数に丸め誤差がある可能性があるという事実を隠していますか?この投稿からのコメントを読んだ後:

https://stackoverflow.com/questions/33646148/is-hardcode-float-precise-if-it-can-be-represented-by-binary-format-in-ieee-754

浮動小数点数が正確であることを知らない開発者が多いようです。私の場合それが有効であっても、浮動小数点数の等しい比較を避けるべきですか?それとも私はこれについて考えすぎていますか?

31
ocomfd

計算しているモデルで必要な場合を除いて、連続する浮動小数点演算は常に避けます。浮動小数点演算は、ほとんどの主要なエラーの原因に直感的ではありません。そして、それがエラーを引き起こす場合とそうでない場合を区別することは、さらに微妙な違いです!

したがって、ループカウンターとして浮動小数点数を使用することは、発生するのを待つ欠陥であり、少なくともここで0.5を使用してもよい理由を説明する太い背景のコメントが必要であり、これは特定の数値に依存します。その時点で、浮動小数点カウンターを回避するようにコードを書き直す方が、おそらくより読みやすいオプションになります。そして、読みやすさは、専門家の要件の階層における正確さの次です。

116
Kilian Foth

一般的なルールとして、ループは、何かn回実行することを考えるような方法で記述する必要があります。浮動小数点インデックスを使用している場合、何かn回を実行する問題ではなく、条件が満たされるまで実行されます。この状態が、多くのプログラマが期待するi<nに非常に似ている場合、コードが実際に別のことをしているときに、あるコードを実行しているように見えます。

それはやや主観的ですが、私の考えでは、整数のインデックスを使用して一定回数ループするようにループを書き直すことができる場合は、そうする必要があります。したがって、次の代替案を検討してください。

var DIFF=0.5;                           // pixel increment
var MAX=Math.floor(5.0/DIFF);           // 5.0 is max pixel width
var STANDARD_LINE=Math.floor(1.5/DIFF); // 1.5 is pixel width

for(var i=0;i<=MAX;i++){
    button.text=(i*DIFF)+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

ループは整数で機能します。この場合、iは整数であり、STANDARD_LINEも整数に強制変換されます。もちろん、丸めが発生した場合は標準線の位置が変更され、同様にMAXも同様になります。そのため、正確なレンダリングのために丸めを防止するように努力する必要があります。ただし、浮動小数点数の比較を気にすることなく、整数ではなくピクセル単位でパラメーターを変更できるという利点があります。

40
Neil

このように正しく動作する場合でも、整数以外のループ変数を使用することは一般的に悪いスタイルであるという他のすべての回答に同意します。しかし、スタイルが悪いのには別の理由があるように思えます。

コードは、使用可能な線幅が正確に0から5.0までの0.5の倍数であることを「認識」しています。それでいいの?これは、簡単に変更できるユーザーインターフェイスの決定のようです(たとえば、幅が大きくなるにつれて、使用可能な幅の間のギャップを大きくしたい場合があります。0.25、0.5、0.75、1.0、1.5、2.0、2.5、3.0、4.0、 5.0か何か)。

コードは、使用可能な線幅がすべて浮動小数点数と10進数の両方で「適切な」表現であることを「認識」しています。それも変わるかもしれない何かのようです。 (ある時点で0.1、0.2、0.3などが必要になる場合があります。)

コードは、ボタンに配置するテキストが、Javascriptが浮動小数点値を変換するものであることを「認識」しています。それも変わるかもしれない何かのようです。 (たとえば、いつか1/3のような幅が必要になるかもしれませんが、0.33333333333333などと表示したくないでしょう。あるいは、「1.5」との一貫性のために「1」ではなく「1.0」を表示したいかもしれません。 。)

これらはすべて、一種の層の混合である単一の弱さの現れのように私に感じます。これらの浮動小数点数は、ソフトウェアの内部ロジックの一部です。ボタンに表示されるテキストは、ユーザーインターフェイスの一部です。これらは、ここのコードにあるものよりも分離されている必要があります。 「強調表示する必要があるデフォルトはどれですか?」のような概念。ユーザーインターフェイスの問題であり、おそらくこれらの浮動小数点値に関連付けるべきではありません。そして、ここでのループはボタンではなくボタンを介したループです(または少なくともそうである必要があります)。そのように書かれると、整数以外の値を取るループ変数を使用するという誘惑がなくなります。連続する整数またはfor ... in/for ... ofループを使用しているだけです。

私の感想はmost整数以外の数値をループするように誘惑される可能性があるケースは次のようなものです:数値の問題とはまったく関係のない他の理由があり、なぜコードを異なるように編成する必要があるのですか? (allの場合ではありません。一部の数学アルゴリズムは、整数以外の値に対するループという点で最も適切に表現されると想像できます。)

20

1つのコードのにおいは、そのようなループでフロートを使用しています。

ループはさまざまな方法で行うことができますが、99.9%の場合、1の増分に固執する必要があります。そうしないと、ジュニア開発者だけでなく、混乱が生じます。

8
Pieter B

はい、これは避けたいものです。

浮動小数点数は、疑いを持たないプログラマにとっての最大の罠の1つです(つまり、私の経験では、ほぼ全員が)。浮動小数点の等価テストに依存することから、浮動小数点としてお金を表すことまで、それはすべて大きな泥沼です。 1つのフロートを他のフロートに加算することは、最大の違反者の1つです。このようなことについては、大量の科学文献があります。

必要に応じて実際の数学計算(三角法、関数グラフのプロットなど)を行う場合など、適切な場所で浮動小数点数を正確に使用し、シリアル演算を行うときは細心の注意を払ってください。平等は正解です。 IEEE規格によってどの特定の数値セットが正確であるかについての知識は非常に難解であり、私はそれに依存することはありません。

あなたの場合、マーフィスの法則により、意志があり、経営陣が0.0、0.5、1.0 ...ではなく0.0、 0.4、0.8 ...または何でも;あなたはすぐに失敗し、後輩プログラマー(またはあなた自身)は、問題が見つかるまで、長くて難しいデバッグを行います。

あなたの特定のコードでは、実際に整数ループ変数があります。通し番号ではなく、ithボタンを表します。

そして、私はおそらく、より明確にするために、i/2 だが i*0.5何が起こっているのかを明確に示します。

var BUTTONS=11;
var STANDARD_LINE=3;

for(var i=0; i<BUTTONS; i++) {
    button.text = (i*0.5)+'';
    if (i==STANDARD_LINE) {
      button.color='red';
    }
}

注:コメントで指摘されているように、JavaScriptには実際には整数用の別個の型はありません。ただし、15桁までの整数は正確/安全であることが保証されています( https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer を参照)。このような引数(「整数または非整数を処理することは、混乱を招く/エラーが発生しやすくなります」)これは、別のタイプの「精神」を持っていることとほぼ同じです。日常的に使用する場合(ループ、画面座​​標、配列インデックスなど)、JavaScriptとしてNumberとして表される整数を使用しても驚くことはありません。

3
AnoE

あなたの提案のどちらも良いとは思いません。代わりに、最大値と間隔に基づいてボタンの数の変数を導入します。次に、ボタン自体のインデックスをループするだけの簡単な方法です。

function precisionRound(number, precision) {
  let factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

var maxButtonValue = 5.0;
var buttonSpacing = 0.5;

let countEstimate = precisionRound(maxButtonValue / buttonSpacing, 5);
var buttonCount = Math.floor(countEstimate) + 1;

var highlightPosition = 3;
var highlightColor = 'red';

for (let i=0; i < buttonCount; i++) {
    let buttonValue = i / buttonSpacing;
    button.text = buttonValue.toString();
    if (i == highlightPosition) {
        button.color = highlightColor;
    }
}

それはより多くのコードかもしれませんが、より読みやすく、より堅牢です。

1
Jared Goguen

ループカウンターを値として使用するのではなく、表示している値を計算することで全体を回避できます。

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0; (i*DIFF) < MAX ; i=i+1){
    var val = i * DIFF

    button.text=val+'';

    if(val==STANDARD_LINE){
      button.color='red';
    }
}
0
Arnab Datta