私の場合、丸めエラーが発生していなくても、浮動小数点数が等しいかどうかを比較すると、ジュニア開発者を誤解させますか?


31

たとえば、0、0.5、... 5のボタンのリストを表示し、0.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

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


23
これら2つのコードリストの動作は同等ではありません。3 / 2.0は1.5ですがi、2番目のリストでは整数になります。2番目を削除してみてください/2.0
candied_orange

27
2つのFPの等価性を絶対に比較する必要がある場合(整数でループカウンター比較を使用するだけでよいため、他の人が細かい答えで指摘しているように必要ではありません)、そうする場合は、コメントで十分です。個人的に私は長い間IEEE FPを使ってきましたが、たとえば、コメントなどを一切せずに直接SPFPの比較を見た場合、私はまだ混乱しています。それは非常に繊細なコードです-少なくとも私見のたびにコメントする価値があります。

14
どちらを選択するかにかかわらず、これは、方法と理由を説明するコメントが絶対に不可欠なケースの1つです。後の開発者は、注意を引くためにコメントなしで微妙な点さえ考慮しないかもしれません。また、buttonループのどこでも変わらないという事実に、私はかなり気を散らしています。ボタンのリストにはどのようにアクセスしますか?配列または他のメカニズムへのインデックス経由?配列へのインデックスアクセスによる場合、これは整数への切り替えを支持するもう1つの引数です。
jpmc26

9
そのコードを書きます。誰かが0.6がより良いステップサイズであると考え、その定数を単純に変更するまで。
トフロ

11
「...ジュニア開発者を誤解させるシニア開発者も誤解させるでしょう。あなたがこれに費やした多くの考えにもかかわらず、彼らはあなたが何をしていたのか知​​らないと仮定し、とにかくそれを整数バージョンに変更するでしょう。
グランドオープナー

回答:


116

私が計算しているモデルがそれらを必要としない限り、私は常に連続した浮動小数点演算を避けます。浮動小数点演算は、ほとんどの主要なエラーの原因には直観的ではありません。そして、それがそうでないものからエラーを引き起こすケースを伝えることは、さらに微妙な違いです!

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


48
私は「起こるのを待っている欠陥」が好きです。確かに、はうまくいくかもしれませんが、近くを歩いている人からの微風がそれを壊します。
AakashM

10
たとえば、要件が変更され、4番目のボタンに「標準ライン」を持つ0〜5の11個の等間隔ボタンではなく、6番目に「標準ライン」を持つ0〜5の16個の等間隔ボタンがあると仮定します。ボタン。したがって、このコードを継承したユーザーは、0.5を1.0 / 3.0に変更し、1.5を5.0 / 3.0に変更します。それではどうなりますか?
デビッドK

8
ええ、私は、任意の数字のように見えるもの(数字として「通常」)を別の任意の数字(同様に「通常」のよう見える)に変更すると実際に欠陥が生じるという考えに不満です。
アレクサンダー-モニカの復活

7
@Alexander:そうです、あなたは言ったコメントが必要でしょうDIFF must be an exactly-representable double that evenly divides STANDARD_LINE。そのコメントを書きたくない場合(そして将来のすべての開発者がIEEE754 binary64浮動小数点について十分に理解してそれを理解するのを頼りにしている場合)、このようにコードを書かないでください。つまり、このようにコードを書かないでください。特に、おそらくそれ以上に効率的ではないためです。FPの追加は、整数の追加よりも遅延が長く、ループに依存する依存関係です。また、コンパイラー(JITコンパイラーであっても)はおそらく、整数カウンターでループを作成するのに優れています。
ピーター

39

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

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

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、正確なレンダリングのために丸めを防止するよう努力する必要があります。ただし、浮動小数点の比較を心配することなく、整数ではなくピクセル単位でパラメーターを変更できるという利点があります。


3
必要に応じて、割り当てのフローリングではなく丸めを検討することもできます。割り算が整数の結果を与えることになっている場合、割り算がわずかにずれている数字につまずくと、floorは驚きを与えるかもしれません。
-ilkkachu

1
@ilkkachuはい。私の考えでは、最大ピクセル量として5.0を設定している場合、丸めにより、わずかに多くするのではなく、その5.0の下側に配置することが望ましいと考えました。5.0は実質的に最大値になります。ただし、何をする必要があるかによっては丸めが望ましい場合があります。いずれにせよ、部門が整数を作成しても、ほとんど違いはありません。
ニール

4
私は強く反対します。ループを停止する最良の方法は、ビジネスロジックを最も自然に表現する条件によるものです。11個のボタンが必要なビジネスロジックの場合、ループは反復11で停止する必要があります。どちらかのメカニズムに向かって選択を進めることができる他の考慮事項がありますが、それらの考慮事項がなければ、ビジネス要件に最も近いメカニズムを選択してください。
モニカの

あなたの説明は、Java / C ++ / Ruby / Python /については完全に正しいでしょう...しかし、Javascriptには整数がないためiSTANDARD_LINE整数のように見えます。そこには強制は全くません、とDIFFMAXSTANDARD_LINEすべてがちょうどですNumberね。Number整数として使用されるsは以下2**53でも安全であるはずですが、それらはまだ浮動小数点数です。
エリックドゥミニル

@EricDuminilはい、しかしこれはその半分です。残りの半分は読みやすさです。最適化のためではなく、この方法で行う主な理由として言及しています。
ニール

20

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

コードは、利用可能な線幅が正確に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または何か)。

あなたのコードは、利用可能な行幅がすべて浮動小数点数と小数の両方として「素敵な」表現を持っていることを「知っています」。それはまた変わるかもしれない何かのように思えます。(0.1、0.2、0.3などが必要になる場合があります。)

コードは、ボタンに配置するテキストが単にJavaScriptがそれらの浮動小数点値に変換するものであることを「知っています」。それはまた変わるかもしれない何かのように思えます。(たとえば、1日のように幅が1/3になり、0.33333333333333などとして表示したくない場合があります。または、 "1.5"との一貫性のために "1"ではなく "1.0"を表示する場合があります。 )

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

私の考えでは、整数以外の数値をループしようとするほとんどの場合は次のようになります。数値の問題とはまったく関係のない他の理由、コードの編成方法が異なる理由があります。(すべての場合ではありません。いくつかの数学アルゴリズムは、非整数値のループに関して最もきれいに表現されると想像できます。)


8

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

ループはさまざまな方法で実行できますが、99.9%のケースでは、1を増やす必要があります。そうしないと、ジュニア開発者だけでなく、間違いなく混乱が生じます。


私は同意しません、1の整数倍はforループで混乱しないと思います。私はコードが臭いだとは思わないでしょう。分数のみ。
CodeMonkey

3

はい、あなたはこれを避けたいです。

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

必要な場所で実際の数学的計算を行うとき(三角法、関数グラフのプロットなど)、浮動小数点数を適切な場所で正確に使用し、シリアル操作を行うときは十分に注意してください。平等はすぐに実現します。IEEE規格でどの特定の数値セットが正確であるかについての知識は非常に不可解であり、私はそれに頼ることはありません。

あなたのケースでは、そこになり、マーフィーズ法により、経営陣はあなたが0.0、0.5、1.0 ...しかし、0.0、0.4、0.8 ...または何を持っていたいポイントを来ります。あなたはすぐに退屈し、あなたのジュニアプログラマー(またはあなた自身)は、問題が見つかるまで長時間、ハードにデバッグします。

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

そして、私はおそらく、余分な明確化のために、書きません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を参照)。したがって、このような引数については( "is it整数または非整数で動作するより混乱しやすい/エラーが発生する可能性があります」)毎日の使用(ループ、画面座​​標、配列インデックスなど)ではNumber、JavaScriptとして表される整数値に驚くことはありません。


BUTTONSという名前を別の名前に変更します。10個ではなく、11個のボタンがあります。FIRST_BUTTON= 0、LAST_BUTTON = 10、STANDARD_LINE_BUTTON = 3の場合、それ以外はそうです。
gnasher729

@EricDuminil、それは本当です、そして私はこのことについて少し答えに加えました。ありがとうございました!
AnoE

1

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

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;
    }
}

コードは増えるかもしれませんが、読みやすく堅牢です。


0

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

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';
    }
}

-1

浮動小数点演算は遅く、整数演算は速いため、浮動小数点を使用する場合、整数を使用できる場所で不必要に使用することはありません。多少の誤差はありますが、浮動小数点数、定数であっても近似値であると常に考えると便利です。デバッグ中に、ネイティブの浮動小数点数を、各数値をポイントではなく範囲として扱うプラス/マイナスの浮動小数点オブジェクトに置き換えると非常に便利です。そうすれば、各算術演算の後に進行する不正確さを発見できます。したがって、「1.5」は「1.45から1.55の間の数字」と見なされ、「1.50」は「1.495から1.505の間の数字」と見なされます。


5
小さなマイクロプロセッサ用のCコードを記述する場合、整数と浮動小数点のパフォーマンスの違いは重要ですが、最新のx86派生CPUは浮動小数点を非常に高速に処理するため、動的言語を使用するオーバーヘッドによってペナルティが簡単に覆い隠されます。特に、JavaScriptは必要に応じてNaNペイロードを使用して、とにかく実際にはすべての数値を浮動小数点として表さないのですか?
左辺約

1
「浮動小数点演算は遅く、整数演算は速い」というのは歴史的な真実であり、福音が前進するにつれて保持すべきではありません。@leftaroundaboutが言ったことに加えて、ペナルティがほとんど無関係であるというだけでなく、コンパイラと命令セットを自動ベクトル化する魔法のおかげで、浮動小数点演算は同等の整数演算より高速であることがわかります1サイクルで大量のフロート。この質問には関係ありませんが、基本的な「整数は浮動小数点よりも速い」という仮定は、しばらくの間真実ではありませんでした。
Jeroen Mostert

1
@JeroenMostert SSE / AVXは、整数と浮動小数点数の両方の操作をベクトル化している、とあなたは(何のビットは、指数に浪費されていないため)ので、小さい整数を使用することができるかもしれ原則的には、コードの整数高度に最適化されたから、多くの場合、より一層のパフォーマンスを絞り出すかもしれない1フロートよりも。しかし、これもほとんどのアプリケーションには関係がなく、間違いなくこの質問には関係ありません。
左辺約

1
@leftaroundabout:もちろん。私のポイントは、特定の状況でどれが確実に速いかではなく、「FPが遅いことと整数が速いことを知っているので、可能な限り整数を使用する」ということは、あなたが取り組む前でも良い動機ではありませんあなたがやっていることが最適化を必要とするかどうかの質問。
Jeroen Mostert
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.