数値の内部表現と演算誤差
指導者向けの公開補足
学習本文に加えて、指導の視点・背景・授業での確かめ方を示します。どなたでも読めます。
指導の焦点
小数計算の意外な結果を、コンピュータが適当に間違える現象にしない。有限の桁で表せる値が限られることと、丸めの影響を結びつける。
一歩踏み込んだ背景
JavaScriptのNumberでは多くの値をIEEE 754の倍精度形式で扱う。金額を整数単位で扱う方法も、すべての計算や範囲で万能ではない。
授業での確かめ方
掲載コードで小数の和と整数単位での計算を比較する。表示桁を丸めただけの場合と、計算方法そのものを変えた場合を区別させる。
制度・規格等は本文末尾の参考資料も確認してください。
JSFiddleでコードを試す
JSFiddleは、ブラウザーでHTML・CSS・JavaScriptを入力して実行できるサービスです。短い動作確認から始められます。
- 新しい編集画面を開きます。JavaScriptは通常のJavaScriptを選び、外部ライブラリは追加しません。
- HTML欄にはページの本文、CSS欄には見た目、JavaScript欄には処理を書きます。HTML欄に
<html>や<body>は不要です。JavaScript欄に<script>は付けません。 - DOMを使う例では、JavaScriptの実行タイミングを「On Load」「On DOM Ready」または「No wrap - bottom of body」にします。
- 「Run」を押してResult欄を確認します。書き換えたら再びRunを押します。
console.logの結果は通常、ページではなく開発者ツールのコンソールに出ます。このサイトの計算例をResult欄で読みたいときは、HTML欄に次を入れます。
<pre id="output"></pre>JavaScript欄には次を入れ、console.log(2 + 3);の行を、試したいコード一つに置き換えます。独立した例を複数まとめて貼らないでください。
{
const output = document.getElementById("output");
const console = {
log(...values) {
output.textContent += values.join(" ") + "\n";
}
};
console.log(2 + 3);
}Runを押すと「5」と表示されます。外側の波括弧の中だけで表示用のconsoleを用意しているため、ブラウザー本来のコンソールは変更しません。
HTMLとJavaScriptの両方を載せた実習例は、この表示用コードで囲まず、それぞれの欄へ分けて貼り付けます。保存・共有時は公開範囲を確認し、個人情報やパスワードを入力しないでください。コードは手元にも残しましょう。
この単元でできるようになること
Section titled “この単元でできるようになること”- 限られたビット数で表せる整数の範囲を求められる。
- 小数を近似して扱う理由と、代表的な誤差の原因を説明できる。
- 計算の目的に応じて、単位や比較方法を選べる。
桁の数が有限である
Section titled “桁の数が有限である”二進数は各桁の重みが右から1、2、4、8と二倍になる表現である。たとえば二進数1101は、8+4+1で十進数13になる。1ビットは二つの状態を区別するので、nビットには二のn乗通りの組合せがある。符号なし整数に全組合せを使えば、零から二のn乗より一つ小さい数までを表せる。
架空の四ビットの計数器なら範囲は0~15である。15の次を四ビットのまま数えると、必要な五桁目を保持できない。表現範囲を超えることをオーバーフローという。実際に折り返すか、エラーになるかなどは、言語や数値型、機器の仕様によって異なる。
| 四ビットの並び | 符号なしでの値 | 二の補数での値 |
|---|---|---|
| 0000 | 0 | 0 |
| 0111 | 7 | 7 |
| 1000 | 8 | -8 |
| 1111 | 15 | -1 |
負の数と二の補数
Section titled “負の数と二の補数”二の補数表現では、最上位の桁に負の重みを持たせて読む。四ビットの1101なら、-8+4+1で-3になる。正の3である0011の各ビットを反転して1100にし、1を加えることでも求められる。符号付きnビットの範囲は、負の二のn-1乗から、正の二のn-1乗より一つ小さい値までとなる。
同じビット列でも、符号なしと二の補数では意味が変わる。最上位を単に「負の印」として外し、残りを絶対値と読む方式とは異なる。足し算の仕組みを正負で共用しやすいことが、二の補数を使う理由の一つである。
小数も必ず正確とは限らない
Section titled “小数も必ず正確とは限らない”固定小数点表現では、小数点の位置を決めて扱う。浮動小数点表現では、符号、仮数、指数を組み合わせ、広い大きさの範囲を表す。ただし、保持できる有効な桁数は有限である。十進数で有限小数でも、二進数では無限に続く場合がある。0.1がその代表だ。
二進数の小数部の重みは、左から二分の一、四分の一、八分の一と半分ずつになる。十進数0.625は二分の一と八分の一の和なので、二進数では0.101と有限桁で表せる。すべての小数が不正確なのではなく、使う進数と保持できる桁の組合せが問題になる。
JavaScriptの通常のNumberは二進の浮動小数点数を使うため、0.1などを近くの表現可能な値に丸めて保存する。この近似は、表示の桁数を減らしたことだけが原因ではない。
const sum = 0.1 + 0.2;console.log(sum);console.log(sum === 0.3);const tenths = 1 + 2;console.log(tenths / 10);出力は 0.30000000000000004、false、0.3 である。最後も内部で十進数の0.3を厳密に保存できたという意味ではない。整数単位で加算してから変換した結果が、表示上0.3となるNumberになったのである。
誤差の原因を区別する
Section titled “誤差の原因を区別する”丸め誤差は、有限の桁数に収めるため近い値へ置き換えることで生じる。桁落ちは、近い大きさの数を引いて先頭の桁が打ち消され、結果の有効な桁が少なくなる現象だ。小さな測定誤差が、差に対して大きな割合になることもある。情報落ちは、大きな値に非常に小さな値を加えると、小さな値の寄与が結果に残らなくなる現象である。
const large = 1e16;console.log(large + 1 === large);console.log(Number.isSafeInteger(9007199254740991));console.log(Number.isSafeInteger(9007199254740992));出力は true、true、false。Numberで安全に連続した整数を扱える範囲にも限界がある。範囲外のすべての整数が保存不能なのではなく、隣り合う整数を常に区別できる保証がなくなる。
極端に大きな計算結果は無限大の値になることがある。極端に小さい値では、精度が下がる領域を経て零へ丸められる場合もある。小数を丸めたことすべてをアンダーフローと呼ぶのではない。
目的に合わせて対策する
Section titled “目的に合わせて対策する”枚数や円単位など整数で表せる量は、整数単位で扱うと不要な小数計算を減らせる。ただし安全な整数の範囲は確認する。測定値の比較では、必要な精度に応じた許容差を定める。大きさが違えば適切な許容差も変わるので、固定した小さな値をどんな計算にも使えばよいわけではない。
注意: 誤差を消すために、途中結果を何度も表示用に丸めると、逆に誤差を増やすことがある。測定の不確かさ、数値表現の近似、モデルの前提の違いを混同せず、必要な精度と範囲を先に決めよう。
| 用語 | 意味 |
|---|---|
| 二の補数 | 最上位に負の重みを持たせる整数表現 |
| 浮動小数点数 | 仮数と指数などで数の大きさを表す方式 |
| 丸め誤差 | 保持できる桁に近似することで生じる誤差 |
| 桁落ち | 近い数の減算で有効な桁が減ること |
| 情報落ち | 大小の差が大きい加算などで小さい寄与が失われること |
- 五ビットの符号なし整数で表せる最大値はいくつか。組合せの数から説明しよう。
- 四ビットの二の補数として1110を読むといくつか。各桁の重みを使おう。
- 料金を整数単位で計算しても、どのような制約を確認する必要があるか。
問1の解答例を見る
31。五ビットは2の5乗=32通りを表し、符号なし整数では0から始まるので最大値は31となる。
問2の解答例を見る
−2。四ビットの二の補数の重みは左から−8・4・2・1なので、−8+4+2+0=−2となる。
問3の解答例を見る
合計や途中結果が扱える整数の範囲を超えないか、単位がそろっているか、割引や税の端数をどう丸めるかを確認する。整数化しても範囲や仕様の制約は残る。