コンテンツにスキップ

数値の内部表現と演算誤差

指導者向けの公開補足

学習本文に加えて、指導の視点・背景・授業での確かめ方を示します。どなたでも読めます。

指導の焦点

小数計算の意外な結果を、コンピュータが適当に間違える現象にしない。有限の桁で表せる値が限られることと、丸めの影響を結びつける。

一歩踏み込んだ背景

JavaScriptのNumberでは多くの値をIEEE 754の倍精度形式で扱う。金額を整数単位で扱う方法も、すべての計算や範囲で万能ではない。

授業での確かめ方

掲載コードで小数の和と整数単位での計算を比較する。表示桁を丸めただけの場合と、計算方法そのものを変えた場合を区別させる。

制度・規格等は本文末尾の参考資料も確認してください。

JSFiddleでコードを試す

JSFiddleは、ブラウザーでHTML・CSS・JavaScriptを入力して実行できるサービスです。短い動作確認から始められます。

  1. 新しい編集画面を開きます。JavaScriptは通常のJavaScriptを選び、外部ライブラリは追加しません。
  2. HTML欄にはページの本文、CSS欄には見た目、JavaScript欄には処理を書きます。HTML欄に<html>や<body>は不要です。JavaScript欄に<script>は付けません。
  3. DOMを使う例では、JavaScriptの実行タイミングを「On Load」「On DOM Ready」または「No wrap - bottom of body」にします。
  4. 「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の両方を載せた実習例は、この表示用コードで囲まず、それぞれの欄へ分けて貼り付けます。保存・共有時は公開範囲を確認し、個人情報やパスワードを入力しないでください。コードは手元にも残しましょう。

NAS・GitHub・CodePenなど、作業環境の選び方

この単元でできるようになること

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

二の補数表現では、最上位の桁に負の重みを持たせて読む。四ビットの1101なら、-8+4+1で-3になる。正の3である0011の各ビットを反転して1100にし、1を加えることでも求められる。符号付きnビットの範囲は、負の二のn-1乗から、正の二のn-1乗より一つ小さい値までとなる。

同じビット列でも、符号なしと二の補数では意味が変わる。最上位を単に「負の印」として外し、残りを絶対値と読む方式とは異なる。足し算の仕組みを正負で共用しやすいことが、二の補数を使う理由の一つである。

固定小数点表現では、小数点の位置を決めて扱う。浮動小数点表現では、符号、仮数、指数を組み合わせ、広い大きさの範囲を表す。ただし、保持できる有効な桁数は有限である。十進数で有限小数でも、二進数では無限に続く場合がある。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になったのである。

丸め誤差は、有限の桁数に収めるため近い値へ置き換えることで生じる。桁落ちは、近い大きさの数を引いて先頭の桁が打ち消され、結果の有効な桁が少なくなる現象だ。小さな測定誤差が、差に対して大きな割合になることもある。情報落ちは、大きな値に非常に小さな値を加えると、小さな値の寄与が結果に残らなくなる現象である。

大きな値への小さな加算
const large = 1e16;
console.log(large + 1 === large);
console.log(Number.isSafeInteger(9007199254740991));
console.log(Number.isSafeInteger(9007199254740992));

出力は true、true、false。Numberで安全に連続した整数を扱える範囲にも限界がある。範囲外のすべての整数が保存不能なのではなく、隣り合う整数を常に区別できる保証がなくなる。

極端に大きな計算結果は無限大の値になることがある。極端に小さい値では、精度が下がる領域を経て零へ丸められる場合もある。小数を丸めたことすべてをアンダーフローと呼ぶのではない。

枚数や円単位など整数で表せる量は、整数単位で扱うと不要な小数計算を減らせる。ただし安全な整数の範囲は確認する。測定値の比較では、必要な精度に応じた許容差を定める。大きさが違えば適切な許容差も変わるので、固定した小さな値をどんな計算にも使えばよいわけではない。

注意: 誤差を消すために、途中結果を何度も表示用に丸めると、逆に誤差を増やすことがある。測定の不確かさ、数値表現の近似、モデルの前提の違いを混同せず、必要な精度と範囲を先に決めよう。

用語 意味
二の補数 最上位に負の重みを持たせる整数表現
浮動小数点数 仮数と指数などで数の大きさを表す方式
丸め誤差 保持できる桁に近似することで生じる誤差
桁落ち 近い数の減算で有効な桁が減ること
情報落ち 大小の差が大きい加算などで小さい寄与が失われること
  1. 五ビットの符号なし整数で表せる最大値はいくつか。組合せの数から説明しよう。
  2. 四ビットの二の補数として1110を読むといくつか。各桁の重みを使おう。
  3. 料金を整数単位で計算しても、どのような制約を確認する必要があるか。
問1の解答例を見る

31。五ビットは2の5乗=32通りを表し、符号なし整数では0から始まるので最大値は31となる。

問2の解答例を見る

−2。四ビットの二の補数の重みは左から−8・4・2・1なので、−8+4+2+0=−2となる。

問3の解答例を見る

合計や途中結果が扱える整数の範囲を超えないか、単位がそろっているか、割引や税の端数をどう丸めるかを確認する。整数化しても範囲や仕様の制約は残る。