ブラりザず浮動小数点数



画像-www.freepik.com



数幎前、私は浮動小数点挔算に぀いお倚くのこずを考え、曞きたした。ずおも面癜かったし、研究の過皋でたくさんのこずを孊びたしたが、実際には長い間䜿わなかったこずがあり、これらのスキルはすべお倧倉な劎力を芁したした。そのため、さたざたな専門知識が必芁なバグに取り組むたびに、ずおも嬉しく思いたす。この蚘事では、Chromiumで孊んだフロヌティングポむントのバグに぀いお3぀のストヌリヌを玹介したす。



パヌト1非珟実的な期埅



このバグは「JSONが64ビットの敎数を正しく解析しない」ず呌ばれおいたした。最初はフロヌティングポむントやブラりザの問題のようには芋えたせんが、crbug.comに投皿されたので、確認するように䟝頌されたした。再䜜成する最も簡単な方法は、Chrome開発者ツヌルF12たたはCtrl + Shift + Iを開き、次のコヌドを開発者コン゜ヌルに貌り付けるこずです。



json = JSON.parse(‘{“x”: 2940078943461317278}’); alert(json[‘x’]);


䞍明なコヌドをコン゜ヌルりィンドりに挿入するこずはハッキングするのに最適な方法ですが、コヌドは非垞に単玔で、悪意のあるものではないず刀断できたした。バグレポヌトで、著者は圌の期埅ず実際の結果を芪切に瀺したした



期埅される動䜜は䜕ですか2940078943461317278の敎数倀が返されたす。

゚ラヌは䜕ですか代わりに、敎数2940078943461317000が返されたす。


「バグ」はLinuxで芋぀かり、Chrome for Windowsで䜜業しおいたすが、この動䜜はクロスプラットフォヌムであり、浮動小数点数に぀いおの知識があったので、調査したした。



JavaScriptには実際には敎数型がないため、この敎数の動䜜は朜圚的に浮動小数点のバグです。同じ理由で、これは実際にはバグではありたせん。



入力した数倀はかなり倧きく、2.9e18ずほが同じです。そしおそれが問題です。 JavaScriptには敎数型がないため、数倀にはIEEE-754浮動小数点倍粟床を䜿甚したす。このバむナリ浮動小数点圢匏には、笊号ビット、11ビットの指数、および53ビットのマンティッサがありたすはい、65ビットで、1ビットは魔法で隠されおいたす。このdouble型は敎数の栌玍に非垞に優れおいるため、倚くのJavaScriptプログラマヌは敎数型がないこずに気づきたせんでした。しかし、非垞に倚くの人がこの幻想を砎壊したす。



JavaScript番号は、2 ^ 53たでの任意の敎数倀を正確に栌玍できたす。その埌、2 ^ 54たでのすべおの偶数を栌玍できたす。そしお、2 ^ 55たでの4぀の数倀のすべおの倍数を栌玍できたす。



問題番号は、ベヌス2の指数衚蚘で衚されたす。これは玄1.275 * 2 ^ 61です。この間隔で衚珟できる敎数の数はごくわずかです。数倀間の距離は512です。察応する3぀の数倀は次のずおりです。



  • 2 940 078 943 461 317 278は、バグレポヌトの䜜成者が保持したかった番号です。
  • 2 940 078 943 461 317 120-この数倀に最も近い2倍それ未満
  • 2 940 078 943 461 317 632-次に近い数字のdoubleそれより倧きい


必芁な数は、これら2぀のdoubleの間の間隔にあり、JSONモゞュヌルたずえば、JavaScript自䜓たたはテキストをdoubleに倉換するためのその他の正しく実装された関数が最善を尜くし、最も近いdoubleを返したした。簡単に蚀うず、レポヌトの䜜成者が保存したい番号は、組み蟌みのJavaScript数倀タむプには保存できたせん。



これたでのずころ、すべおが明確です。蚀語の限界に達した堎合は、それがどのように機胜するかに぀いおもっず知る必芁がありたす。しかし、ただもう1぀の謎がありたす。バグレポヌトによるず、実際には次の番号が返されたす。



2 940 078 943 461 317 000


入力された数倀ではなく、最も近いdoubleでも、実際にはdoubleずしお衚珟できる数倀でもないため、状況は䞍思議です。



このパズルは、JavaScript仕様でも説明されおいたす。仕様では、数倀を印刷する堎合、実装はそれを䞀意に識別するのに十分な桁数を出力する必芁があり、それ以䞊は出力しないず芏定されおいたす。これは、0.1のように正確にdoubleずしお衚すこずができない数倀を印刷する堎合に圹立ちたす。たずえば、JavaScriptで保存された倀ずしお0.1を出力する必芁がある堎合、次のように出力されたす。



0.1000000000000000055511151231257827021181583404541015625


正確な結果 になりたすが、有甚なものを远加しないこずで人々を混乱させるだけです。特定のルヌルはここにありたす「番号タむプに適甚されるToString」の行を探しおください。仕様に末尟のれロが必芁だずは思いたせんが、確かに必芁です。



したがっお、プログラムを実行するず、JavaScriptは2,940,078,943,461,317,000を出力したす。理由は次のずおりです。



  • JavaScript番号ずしお保存するず、元の番号の倀が倱われたした
  • 衚瀺される数倀は、保存されおいる倀に十分に近いため、䞀意に識別できたす
  • 衚瀺される番号は、保存されおいる倀を䞀意に識別する最も単玔な番号です。


すべおが正垞に機胜したす。これはバグではなく、問題はWontFix「回埩䞍胜」ずしおクロヌズされたす。元のバグはここにありたす。



パヌト2悪いむプシロン



今回は、将来の䞖代の開発者の混乱を避けるために、最初にChromiumで、次にgoogletestでバグを実際に修正したした。





このバグは、突然発生し始めた非決定論的なテストの倱敗でした。これらのあいたいなテストの倱敗は嫌いです。䜕幎も倉わっおいないテストで発生し始めるず、特に混乱したす。数週間埌、私は調査のために連れおこられたした。゚ラヌメッセヌゞ行の長さを少し倉曎は次のように始たりたした。



expected_microsecondsずconverted_microsecondsの差は512で、1.0を超えおいたす[expected_microsecondsずconverted_microsecondsの差は512で、1.0を超えおいたす]


はい、それは悪いですね。これは、1.0以䞋の間隔である必芁がある2぀の浮動小数点倀が実際には512離れおいるこずを瀺すgoogletest゚ラヌメッセヌゞです。



最初の蚌拠は、浮動小数点数の違いでした。2぀の数字が正確に2 ^ 9で区切られおいるこずは非垞に疑わしいようでした。䞀臎そうは思いたせん。比范されおいる2぀の倀を瀺した投皿の残りの郚分は、私にさらに倚くの理由を確信させたした



expected_microsecondsは4.2934311416234112e + 18ず

評䟡され、converted_microsecondsは4.2934311416234107e +18ず評䟡されたす。


IEEE 754ず 十分長い間戊っおきた堎合は、䜕が起こっおいるのかすぐに理解できたす。



あなたは最初の郚分を読んだので、同じ数のためにdéjàvuを感じるこずができたす。しかし、これはたったくの偶然です。私は出䌚った数字を䜿甚しおいたす。今回は指数圢匏で衚瀺されおいたため、蚘事が少し倚様化しおいたす。


䞻な問題は、最初の郚分からの問題のバリ゚ヌションです。コンピュヌタヌの浮動小数点数は、数孊者が䜿甚する実際の数ずは異なりたす。増加するに぀れお粟床が䜎䞋し、すべおのdoubleは、倱敗する数倀の範囲で必然的に512の倍数になりたす。Doubleの粟床は53ビットであり、これらの数倀は2 ^ 53よりはるかに倧きいため、粟床の倧幅な䜎䞋は避けられたせんでした。そしお今、私たちは問題を理解するこずができたす。



テストでは、2぀の異なる方法で同じ倀を蚈算したした。次に、結果が近いかどうかを確認したした。「近い」ずは、1.0以内の違いを意味したす。蚈算方法は非垞によく䌌た答えを出したので、ほずんどの堎合、結果は2倍の粟床で同じ倀に䞞められたした。しかし、時々、正解は屈曲の隣にあり、1぀の蚈算は䞀方向に䞞められ、他の蚈算は別の方向に䞞められたす。



より具䜓的には、結果ずしお、以䞋の数倀が比范されたした。



  • 4293431141623410688
  • 4293431141623411200


指数がない堎合、それらが正確に512で区切られおいるこずがより顕著になりたす。テスト関数によっお生成された2぀の無限に正確な結果は、垞に1.0未満しか異なりたせんでした。぀たり、429 ... 10653.5や429 ... 10654.3のような倀の堎合、䞡方ずも429 ... 10688に䞞められたした。この問題は、無限に正確な結果が4293431141623410944のような倀に近い堎合に発生したした。この倀は、2぀のdoubleのちょうど䞭間にありたす。䞀方の関数が429 ... 10943.9を生成し、もう䞀方の関数が429 ... 10944.1を生成する堎合、これらの結果をわずか0.2の倀で割るず、さたざたな方向に䞞められ、512の距離になりたす。



これは、屈折たたはステップ関数の性質です。互いに任意に近いが、屈曲の反察偎にある2぀の結果2぀の間のちょうど䞭間点を取埗できるため、異なる方向に䞞められたす。䞞めモヌドを倉曎するこずをお勧めしたすが、これは圹に立ちたせん。぀たり、屈曲点を移動するだけです。



それは真倜䞭頃に赀ちゃんを産むようなものです-わずかなずれがむベントの日付たたはおそらく1幎、䞖玀、たたは千幎玀を氞久に倉える可胜性がありたす。



おそらく私のコミットノヌトは過床に劇的でしたが、間違いはありたせんでした。私はこの状況に察凊できるナニヌクなスペシャリストのように感じたした。



commit 6c2427457b0c5ebaefa5c1a6003117ca8126e7bc

䜜成者Bruce Dawson

日付Fri Dec 08 2158502017ラヌゞ



ダブル比范のむプシロン蚈算を修正する



私の人生はこのバグ修正に至っおいたす。[私の人生はこのバグを修正するように私を導きたした。]


実際、私の投皿のうち2぀2に合理的にリンクしおいるコミットノヌトを䜿甚しお、Chromiumに倉曎を加えるこずはめったにありたせん。



この堎合の修正は、蚈算された倀の倧きさで2぀の隣接するdouble間の差を蚈算するこずでした。これは、めったに䜿甚されないnextafter関数を䜿甚しお行われたした。倚かれ少なかれこのように



epsilon = nextafter(expected, INFINITY)  –  expected;
if (epsilon < 1.0)
      epsilon = 1.0;


nextafter 関数は、次のdoubleこの堎合は無限倧方向を怜出し、枛算正確に実行され、これは非垞に䟿利ですは、その倀でdouble間の差を怜出したす。テストされたアルゎリズムは1.0の゚ラヌを瀺したので、むプシロンはこの倀を超えおはなりたせん。このむプシロンの蚈算により、倀の間隔が1.0未満であるか、隣接するdoubleであるかを非垞に簡単に確認できたす。



テストが突然倱敗し始めた理由は調査しおいたせんが、タむマヌの頻床たたはタむマヌの開始点の倉曎が原因で数倀が倧きくなったのではないかず思いたす。



. QueryPerformanceCounter (QPC), <int64>::max(), 2^63-1. , . , , QPC 2 148 . , QPC, , , , , 3 . QPC 2^63-1 , .



, , QueryPerformanceCounter.


googletest





問題を理解するには浮動小数点の詳现に関する難解な知識が必芁であるこずに腹を立おたので、googletestを修正したいず思いたした。私の最初の詊みはひどく終わった。



私はもずもず、わずかに小さいむプシロンを送信するずきにEXPECT_NEARを倱敗させるこずで、googletestを修正しようずしたしたが、Google内の倚くのテスト、そしおおそらくGoogle倖の倚くのテストが、double倀に察しお誀っおEXPECT_NEARを䜿甚しおいるようです。小さすぎお圹に立たないむプシロン倀を枡したすが、比范する数倀は同じであるため、テストは成功したす。問題の解決に近づくこずなくEXPECT_NEARを䜿甚する12のポむントを修正したので、あきらめたした。



私がこの投皿を曞いおいる間バグが珟れおからほが3幎埌、googletestを修正するこずがいかに安党で簡単であるかを理解したした。コヌドがむプシロンが少なすぎるEXPECT_NEARを䜿甚し、テストが成功した堎合぀たり、倀が実際に等しい堎合、これは問題ではありたせん。これはテストが倱敗した堎合にのみ問題になるため、倱敗した堎合にのみ小さすぎるむプシロン倀を怜玢し、同時に有益なメッセヌゞを衚瀺するだけで十分でした。



私はこの倉曎を行ったし、今、このように、この2017幎のクラッシュルックスのための゚ラヌメッセヌゞ



expected_microseconds converted_microseconds 512,

expected_microseconds 4.2934311416234112e+18,

converted_microseconds evaluates to 4.2934311416234107e+18.

abs_error 1.0, double , 512; EXPECT_NEAR EXPECT_EQUAL. EXPECT_DOUBLE_EQ.


EXPECT_DOUBLE_EQは実際には等しいかどうかをチェックせず、doubleが最埌の桁の4ナニット最埌の堎所のナニット、ULPに等しいかどうかをチェックするこずに泚意しおください。この抂念の詳现に぀いおは、私の投皿「浮動小数点数の比范」を参照しおください。



ほずんどの゜フトりェア開発者がこの新しい゚ラヌメッセヌゞを芋お正しい道をたどるこずを願っおいたす。そしお、Googletestの修正は、Chromiumテストの修正よりも最終的に重芁であるず信じおいたす。



パヌト3x + y = xy= 0の堎合



これは、境界に近づくずきの粟床の問題の別のバリ゚ヌションです。おそらく、同じ浮動小数点のバグを䜕床も䜕床も芋぀けたすか



このパヌトでは、Chromium゜ヌスコヌドを調査したり、クラッシュの原因を調査したりする堎合に適甚できるデバッグ手法に぀いおも説明したす。





この問題に遭遇したずき、「chromeでのOOMOut of Memory゚ラヌによるクラッシュ//ズヌムむン時のトレヌス」ずいうタむトルのバグレポヌトを投皿したした。フロヌティングポむントのバグのようには聞こえたせん。



い぀ものように、私は自分で問題を探しおいたせんでしたが、クロムを勉匷しおいたした//トレヌスし、いく぀かのむベントを理解しようずしおいたす。悲しいタブが突然珟れたした-倱敗がありたした。



Chromeの最新のクラッシュはchromeで衚瀺およびダりンロヌドできたす。//クラッシュしたすが、クラッシュダンプをデバッガヌにロヌドしたかったので、ロヌカルに保存されおいる堎所を調べたした。



localappdata\ Google \ Chrome \ナヌザヌデヌタ\クラッシュパッド\レポヌト


最新のクラッシュダンプをwindbgにアップロヌドしVisual Studioも同様です、調査に進みたした。 ChromeずMicrosoftのシンボルサヌバヌを構成し、゜ヌスサヌバヌを有効にしたので、デバッガヌはPDBデバッグ情報ず必芁な゜ヌスファむルを自動的にダりンロヌドしたした。このスキヌムは誰でも利甚できるこずに泚意しおください。この魔法が機胜するために、Googleの埓業員やChromiumの開発者である必芁はありたせん。 Chrome / Chromiumのデバッグを蚭定する手順に぀いおは、こちらをご芧ください。゜ヌスコヌドの自動ダりンロヌドには、Pythonのむンストヌルが必芁です。



クラッシュ分析により、メモリ䞍足゚ラヌは、v8JavaScript゚ンゞン関数NewFixedDoubleArrayが原因であるこずが瀺されたした。75,209,227芁玠の配列を割り圓おようずしたす。このコンテキストで蚱可される最倧サむズは、67,108,86316進数で0x3FFFFFFです。



私が自分で匕き起こしたグリッチの良いずころは、より泚意深く監芖するこずでグリッチを再珟できるこずです。実隓によるず、ズヌムするず、クリティカルポむントに到達するたでメモリは安定したたたでした。その埌、メモリ䜿甚量が突然急増し、䜕もしなくおもタブがクラッシュしたした。



ここでの問題は、この倱敗のコヌルスタックを簡単に衚瀺できるこずでしたが、ChromeのコヌドのC ++郚分でしか衚瀺できたせんでした。ただし、明らかに、バグ自䜓はchromeに珟れたした// JavaScriptコヌドをトレヌスしたす。デバッガヌの䞋でChromeのカナリアビルド毎日でテストしようずするず、次の奇劙なメッセヌゞが衚瀺されたした。



==== JSスタックトレヌス=====================================


残念ながら、この興味深いラむンの背埌にはスタックトレヌスがありたせんでした。gitの荒野を少しさたよった埌、OOMを介しおJSコヌルスタックを出力する機胜が2015幎に远加され、2019幎12月に削陀されたこずがわかりたした。



私は2020幎1月の初めにこのバグを調査したしたすべおが無実で簡単だった叀き良き時代を芚えおいたすかそしおそれはスタックトレヌスコヌドOOMが毎日のビルドから削陀されたが、それでも安定したアセンブリのたたであるこずを意味したした...



したがっお、次のステップは、安定したバヌゞョンのChromeでバグを再珟するこずでした。これにより、次の結果が埗られたしたわかりやすくするために少し線集したした。



0ExitFrame [pc00007FFDCD887FBD]

1drawGrid_ [000016011D504859] [chrome//tracing/tracing.js〜4750]

2draw [000016011D504821] [chrome//tracing/tracing.js4750]




぀たり、OOMのクラッシュは、x_axis_track.htmlでChromiumコヌドルックアップペヌゞを䜿甚しお芋぀けたdrawGrid_が原因で発生したした。このファむルを少し調敎した埌、updateMajorMarkDataを呌び出すように絞り蟌みたした。この関数には、問題の原因であるmajorMarkWorldPositions_.push関数を呌び出すルヌプが含たれおいたす。



ここで蚀及する䟡倀があるのは、私はブラりザヌを開発しおいたすが、私は䟝然ずしお䞖界最悪のJavaScriptプログラマヌです。C ++システムプログラミングのスキルは、「フロント゚ンド」の魔法を私に䞎えたせん。このバグを理解するためにJavaScriptをハッキングするこずは、私にずっお非垞に苊痛なプロセスでした。


ルヌプここで衚瀺できたすは次のようになりたした。



for (let curX = firstMajorMark;
curX < viewRWorld;
         curX += majorMarkDistanceWorld) {
    this.majorMarkWorldPositions_.push(
        Math.floor(MAJOR_MARK_ROUNDING_FACTOR * curX) /
        MAJOR_MARK_ROUNDING_FACTOR);
}


ルヌプの前にデバッグ出力ステヌトメントを远加し、以䞋に瀺すデヌタを取埗したした。画像を拡倧するず、重芁であるがクラッシュを匕き起こすには䞍十分な数倀は、次のようになりたした。



firstMajorMark885.0999999642371

majorMarkDistanceWorld1e-13


次に、ズヌムむンしおクラッシュを匕き起こしたした。次のような数倀が衚瀺されたした。



firstMajorMark: 885.0999999642371

majorMarkDistanceWorld: 5e-14


885を5e-14で割るず1.8e16になり、倍粟床浮動小数点数の粟床は2 ^ 53、぀たり9.0e15になりたす。したがっお、majorMarkDistanceWorldグリッドポむント間の距離がfirstMajorMark最初のメゞャヌグリッドマヌクの䜍眮に比べお非垞に小さいため、ルヌプに远加しおも䜕も起こらない堎合にバグが発生したす。぀たり、小さい数倀を倧きい数倀に远加するず、小さい数倀が「小さすぎる」堎合、倧きい数倀暙準/最も近いモヌドぞの正垞な䞞めは同じ倀のたたになりたす。



このため、ルヌプは無期限に実行され、配列がそのサむズに制限されるたでプッシュコマンドが実行されたす。サむズ制限がない堎合、プッシュコマンドはマシン党䜓のメモリがなくなるたで実行され続けたす。やったヌ、問題は解決した



修正は非垞に簡単であるこずが刀明したした-できない堎合はグリッドラベルを衚瀺しないでください



if (firstMajorMark / majorMarkDistanceWorld > 1e15) return;




私が行った倉曎でよくあるこずですが、私のバグ修正は1行のコヌドず6行のコメントで構成されおいたした。50行のiambicpentameterコミットノヌト、衚蚘衚蚘、およびブログ投皿がなかったこずに驚いただけです。ちょっず埅っおください...



残念ながら、呌び出しスタックの曞き蟌みにメモリが必芁なため、OOMクラッシュではJavaScriptスタックフレヌムが衚瀺されたせん。これは、この段階では安党ではないこずを意味したす。OOMスタックフレヌムが完党に削陀された今日、このバグをどのように調査するかはよくわかりたせんが、方法は芋぀かるず確信しおいたす。



したがっお、非垞に倧きな数倀を䜿甚しようずしおいるJavaScript開発者、最倧の敎数倀を䜿甚しようずしおいるテストラむタヌ、たたは無制限のズヌムでUIを実装しおいる堎合は、浮動小数点挔算の境界に近づくず、それらの境界が壊れる可胜性があるこずを芚えおおくこずが重芁です。






広告



開発サヌバヌはVdsinaの叙事詩です。

Intelの非垞に高速なNVMeドラむブを䜿甚しおおり、ハヌドりェアを節玄するこずはありたせん。ブランド化された機噚ず垂堎で最も最新の゜リュヌションのみです。






All Articles