<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Float16</title><link>https://blog.ast.moe/tags/float16/</link><description>Recent posts from 五里霧中</description><generator>Hugo</generator><item><title>x86_64とARM64でFloat16の変換結果が違った</title><link>https://blog.ast.moe/blog/2026-09-10/</link><pubDate>Thu, 10 Sep 2026 19:11:38 +0900</pubDate><guid>https://blog.ast.moe/blog/2026-09-10/</guid><description>&lt;p&gt;&lt;a href="https://github.com/ieee0824/omoikane/issues/655"&gt;OmoikaneのBoa移行前検証&lt;/a&gt;で、同じBoaとTest262をLinuxのx86_64とARM64で動かしたところ、結果が10ケースだけ違いました。&lt;/p&gt;
&lt;p&gt;実行したのは50,595ケースです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;環境&lt;/th&gt;
&lt;th style="text-align: right"&gt;Passed&lt;/th&gt;
&lt;th style="text-align: right"&gt;Failed&lt;/th&gt;
&lt;th style="text-align: right"&gt;Ignored&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux x86_64&lt;/td&gt;
&lt;td style="text-align: right"&gt;47,606&lt;/td&gt;
&lt;td style="text-align: right"&gt;933&lt;/td&gt;
&lt;td style="text-align: right"&gt;2,056&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux ARM64&lt;/td&gt;
&lt;td style="text-align: right"&gt;47,616&lt;/td&gt;
&lt;td style="text-align: right"&gt;923&lt;/td&gt;
&lt;td style="text-align: right"&gt;2,056&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;差が出た10ケースは、すべて&lt;code&gt;Float16&lt;/code&gt;への変換に関係するテストでした。&lt;/p&gt;
&lt;p&gt;同じソースと入力なのにCPUが変わるとJavaScriptの結果も変わるのは困ります。何が起きているのか調べました。&lt;/p&gt;
&lt;h2 id="原因はfloat16-015のソフトウェア変換"&gt;原因はfloat16 0.1.5のソフトウェア変換&lt;/h2&gt;
&lt;p&gt;先に結論を書くと、Rust本体やCPUの浮動小数点仕様の問題ではありませんでした。&lt;/p&gt;
&lt;p&gt;Boaが依存していた&lt;a href="https://crates.io/crates/float16"&gt;&lt;code&gt;float16&lt;/code&gt; crate&lt;/a&gt;のバージョン&lt;code&gt;0.1.5&lt;/code&gt;にある、&lt;code&gt;f64&lt;/code&gt;から&lt;code&gt;f16&lt;/code&gt;へのソフトウェア変換が原因です。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Alexhuszagh/float16/blob/2401a40bbdeac256070324ec954bf268bc8e7be7/src/binary16/arch.rs#L626"&gt;問題のあった0.1.5の実装&lt;/a&gt;は、&lt;code&gt;f64&lt;/code&gt;の64ビット表現を取得した直後に32ビット右へシフトしていました。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; val: &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;unsafe&lt;/span&gt; { mem::transmute::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(value) };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; x &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (val &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;u32&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;f16&lt;/code&gt;の仮数部は10ビットしかないので、&lt;code&gt;f64&lt;/code&gt;の下位ビットは最終結果にはそのまま残りません。&lt;/p&gt;
&lt;p&gt;ただし、丸める前に捨ててよいわけではありません。丸め境界より上なのか、ちょうど中間なのかを判定するために、捨てる部分にも非ゼロのビットがあるかという情報が必要です。&lt;/p&gt;
&lt;p&gt;0.1.5ではその情報まで先に消していたため、本当は中間値を少し超えている値を、ちょうど中間値だと判定する場合がありました。&lt;/p&gt;
&lt;h2 id="一番小さい値で見る"&gt;一番小さい値で見る&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;f16&lt;/code&gt;で表現できる最小の正の非正規化数は&lt;code&gt;2^-24&lt;/code&gt;です。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2^-24 = 5.960464477539063e-8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;との中間は&lt;code&gt;2^-25&lt;/code&gt;になります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2^-25 = 2.98023223876953125e-8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;今回の再現値は、その中間よりほんの少しだけ大きい値です。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;入力: 2.980232238769532e-8
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;f64 bits: 0x3e60000000000001
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;末尾の&lt;code&gt;1&lt;/code&gt;があるため、正しく丸めると&lt;code&gt;0&lt;/code&gt;ではなく、最小の正の&lt;code&gt;f16&lt;/code&gt;である&lt;code&gt;0x0001&lt;/code&gt;になります。&lt;/p&gt;
&lt;p&gt;ところが下位32ビットを先に捨てると末尾の&lt;code&gt;1&lt;/code&gt;が消えます。すると入力がちょうど中間に見えてしまい、最近接偶数丸めによって偶数側の&lt;code&gt;0x0000&lt;/code&gt;が選ばれます。&lt;/p&gt;
&lt;p&gt;結果に残らないビットでも、丸めの方向を決めるためには必要だったということです。&lt;/p&gt;
&lt;h2 id="最小の検証コードを書いた"&gt;最小の検証コードを書いた&lt;/h2&gt;
&lt;p&gt;0.1.5と修正後の0.1.7を同じプログラムから呼び、3個の境界値を比較する&lt;a href="https://github.com/ieee0824/blog-base/tree/master/examples/float16-rounding-repro"&gt;検証コード&lt;/a&gt;を作りました。&lt;/p&gt;
&lt;p&gt;0.1.5はIssueで確認したGit revisionを指定し、0.1.7はcrates.ioのリリースを固定しています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;float16_015&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;package&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;float16&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;git&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;https://github.com/Alexhuszagh/float16.git&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;rev&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;2401a40bbdeac256070324ec954bf268bc8e7be7&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;float16_017&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;package&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;float16&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;=0.1.7&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;確認している部分は次のようになっています。&lt;/p&gt;</description></item></channel></rss>