不動点、浮動点 — コンピュータで小数を扱う
前講「オペレーティングシステムの誕生」までで、CPUとRAMと入出力装置とディスクがバスでつながり、 その全体をオペレーティングシステムが取りまとめるところまで見届けた。コンピュータの骨格は、これで ひととおり揃っている。残っているのは、ずっと後回しにしてきた数の話である。 2.6や1/3のような「半端な数」は、0と1しか知らないコンピュータの中でどう保存されているのか。 整数の足し算と引き算は第7講で片付いたが、小数はまだ手つかずのままだった。この講でそれを片付けてから、 次はコンピュータに指図するための「言葉」へ進む。 全5セクション・演習18問。回答はブラウザに保存されるので、途中でやめても続きから再開できる。
進捗 0 / 18 問(正解 0 問)
1. 数は連続、コンピュータは離散
前講「オペレーティングシステムの誕生」までで、CPU・RAM・入出力装置・ディスクという部品が出揃い、それらを取りまとめるオペレーティングシステムまで見届けた。骨格はもう揃っている。残っているのは数そのものの話——それも、整数では収まらない「半端な数」の話である。私たちは日常、整数も分数も百分率も気楽に行き来しているが、メモリの中ではそうはいかない。すべてはビットで保存され、ビットで表しやすい数とそうでない数がある。このセクションではまず数の分類(有理数・無理数・超越数)を整理し、次に「連続」と「離散」という決定的な違いを押さえ、最後に10進の1桁をそのまま4ビットに割り当てるBCDという素朴で実用的な方法を見る。
話を始める前に、数そのものを少し整理しておく。2つの整数の比として表せる数を 有理数と呼ぶ。1/2のように割り切れて小数で終わるものも、1/3のように小数にすると 無限に続くものも、比として書ける限りすべて有理数である。これに対して、2の平方根のように整数の比では 表せない数を無理数と呼び、さらにそのうち整数係数の代数方程式の解にならないもの (円周率や自然対数の底が代表例)を超越数と呼ぶ。これらをまとめたものが実数であり、 負数の平方根である虚数、そして虚数と実数を組み合わせた複素数とは区別される。
私たちは数直線を思い浮かべながら、数を連続したものとして扱うことに慣れている。 どんなに近い2つの数を取っても、その間にはまた別の数がいくらでも見つかる——それが連続ということである。 ところがデジタルコンピュータには、この連続体をそのまま扱う手段がない。ビットは0か1のどちらかで、 その間には何もないからだ。コンピュータが保持できるのは、有限個のビットパターンに対応した 離散の値だけである。
そして、扱える離散値がいくつあるかは、使えるビット数に直接結びついている。たとえば8ビットで正の整数 (符号なし整数)を保存するなら、表せるのは0から255までの256通りに限られる。同じ8ビットでも、 第7講で見た2の補数を使って符号付きとして解釈すれば、範囲は-128から127へと動く—— ビットの並びそのものは同じでも、どう解釈するかを決めるのはプログラムの側である。 この「解釈の約束」という考え方は、この講の最後まで何度も顔を出す。
小数を2進で表す最も素朴な方法がBCD(2進化10進数)である。10進の1桁を4ビットに そのまま対応させるだけの単純な方式で、桁上がりに丸め誤差が入り込む余地がない。ドルとセント、 あるいは円単位の金額を1桁も狂わせたくない銀行や保険会社のプログラムでは、この桁ごとの正確さが そのまま価値になる。10進2桁を1バイトに詰め込む方式はパックBCDと呼ばれる。 なおBCDでは2の補数を使わないので、正負を表したいときは符号のためのビットを別に1つ用意することになる。
「有理数」の定義として正しいものはどれか
デジタルコンピュータが「連続」した実数をそのまま扱えず「離散」値しか扱えない根本的な理由はどれか
8ビットで正の整数を保存するとき表現できる範囲はどれか
BCD(2進化10進数)が銀行や保険会社のプログラムで好まれる理由として正しいものはどれか
このセクション: 0 / 4 問正解
2. 固定小数点表現とその限界
小数点以下の桁数をあらかじめ決め打ちしてしまえば、小数はそれほど難しくない——これが固定小数点形式の考え方である。ただしこの形式には見落としやすい前提がある。小数点が「どの桁とどの桁の間にあるか」という情報は、数値と一緒にはメモリに保存されない。それを覚えているのはプログラムの側であり、決め打ちが外れれば数の意味そのものが変わってしまう。扱う数の大きさがある範囲に収まっているうちは、この方式はきわめて素直に機能する。破綻するのは、極端に大きい数と極端に小さい数を同じ保存領域で両立させようとしたときである。ここで固定小数点の限界を見届けてから、次のセクションでその解決策へ進む。
固定小数点形式とは、小数点以下の桁数をあらかじめ固定してしまう表現である。 たとえば「小数点以下は常に2桁」と決めておけば、金額のような数はそのまま素直に保存できる。 ここで見落としやすいのが、保存されるのは数字の並びだけで、 小数点が「どの桁とどの桁の間にあるか」という情報は数値と一緒には保存されない という点である。それを覚えているのはプログラムの側であり、決め打ちの約束が食い違えば、 同じビットの並びがまったく違う値として読まれてしまう。
扱う数の大きさがある範囲に収まっているうちは、この方式はきわめてうまく働く。金額のように 「桁数はだいたい決まっていて、小数点以下は必ず2桁」という世界では、固定小数点は素直で速く、 しかも誤差が入らない。
破綻するのは、桁の隔たりが極端な数どうしを同じ保存領域で両立させようとしたときである。 天文単位で測るような途方もなく大きい距離と、ウイルスの大きさのような途方もなく小さい寸法を、 同じ桁数の枠に収めることを考えてみればよい。枠を大きい数に合わせれば、小さい数は小数点以下の 限られた桁からこぼれ落ちて0になってしまう。逆に小さい数に合わせて小数部を厚くすれば、 今度は大きい数の上位の桁が枠に入らない。どちらかを立てればどちらかが潰れる—— これが固定小数点の限界である。
必要なのは、桁の位置そのものを数ごとに変えられる仕組みである。次のセクションで見る 浮動小数点は、まさにそこを解決する。名前の「浮動」は、小数点が固定されずに 動くことを指している。
「固定小数点」形式で、小数点の位置についての説明として正しいものはどれか
固定小数点形式が「完全に破綻する」のはどのような場合か
パックBCD形式(1バイトに10進数字を2桁ずつ格納する方式)で、3バイトを使って表現できる10進の桁数はどれか
このセクション: 0 / 3 問正解
3. 浮動小数点の土台 — 科学的記数法と正規化
極端に大きい数と極端に小さい数を同じ枠で扱う方法は、実は理科の授業ですでに出会っている——科学的記数法である。数を「有効部」と「指数」に分けて書けば、ゼロを大量に書き並べずに済む。浮動小数点はこの記数法をコンピュータに持ち込んだものだが、使うのは10の累乗ではなく2の累乗であり、小数点の右側の桁は2の負数乗を表す。10進の正規形で有効部を1以上10未満に揃えるのと同じ約束を2進で行うと、有効部は1以上「2進数の10」(つまり10進数の2)未満に収まる。この帰結として、正規化された2進の数には必ず小数点の左にちょうど1個の1が現れる——次のセクションで効いてくる、地味だが重要な性質である。
大きすぎる数と小さすぎる数を扱う道具は、理科の授業ですでに配られている—— 科学的記数法である。ゼロを大量に書き並べる代わりに、数を10の累乗との掛け算の形に 書き直す。このとき掛け算の左側にくる部分を、この講では有効部と呼ぶ (仮数部と呼ばれることもあるが、以降は有効部で統一する)。右側の10の肩に乗る数が 指数である。
同じ値でも書き方は何通りもある。そこで「正規」形という約束が置かれる。10進の科学的記数法の正規形では、 有効部の値を1以上10未満に揃える。こうしておけば、ある数の書き方はただ1通りに定まる。
浮動小数点表記は、この科学的記数法をそのままコンピュータに持ち込んだものである。ただしコンピュータが 使うのは10の累乗ではなく2の累乗で、2進の小数点(binary point)の右側の桁は 2の負数乗——1/2、1/4、1/8……——を表す。たとえば2進数の110.101は、整数部が 4+2=6、小数部が0.5+0.125=0.625で、合わせて10進の6.625になる。
110.101(2進) = 6 + 0.625 = 6.625(10進)
2進の正規形でも約束は同じ発想である。有効部を「1以上10未満」に揃える——ただしここでの10は 2進数の10、つまり10進数の2のことなので、有効部は実質的に1以上2未満に収まる。110.101を正規化するには、 小数点の左に1がちょうど1個だけ残るまで小数点を左へ動かせばよい。2つぶん動かして1.10101、 動かした回数がそのまま指数になるので、1.10101×2²と書ける。
110.101 = 1.10101 × 2²
ここで、地味だが決定的な性質が出てくる。正規化された2進の数は、必ず小数点の左に ちょうど1個の1が立つ。0が立つことはない——立っていたら、まだ正規化が終わっていないからである。 つまりこの1は「必ずそこにある」と分かっている。分かりきっているものを、わざわざメモリに保存する 必要があるだろうか。次のセクションの規格は、この問いに「不要」と答える。
10進の科学的記数法の「正規」形において、有効部(仮数部)の値の範囲として正しいものはどれか
2進の科学的記数法における正規化された有効部の範囲として正しいものはどれか
2進数 110.101 を10進数に変換するといくつになるか
2進数 110.101 を正規化された2進科学的記数法で表すとどうなるか
このセクション: 0 / 4 問正解
4. IEEE754 — 符号・指数・有効部の内部表現
2進の科学的記数法を、実際のビットの並びに落とし込む約束事がIEEE754である(正式名称はANSI/IEEE Std 754-1985)。単精度は4バイト=32ビットを符号1ビット・指数8ビット・有効部小数23ビットに分け、倍精度は8バイト=64ビットを符号1ビット・指数11ビット・有効部小数52ビットに分ける。ここには2つの仕掛けがある。1つは、正規化された有効部の左端に必ず立つ1をあえて保存しないこと。もう1つは、指数をそのままではなくバイアスを足した状態で保存すること。そして符号の持ち方は、第7講で見た整数の2の補数とはまったく違う設計になっている——この違いを曖昧にしないことが、このセクションの一番の勘所である。
2進の科学的記数法を実際のビットの並びに落とし込む約束事が、IEEE754という規格である (正式名称はANSI/IEEE Std 754-1985。以降は定着した略称のIEEE754で呼ぶ)。用意されているのは 2つの形式で、単精度は4バイト=32ビットを符号1ビット・指数8ビット・有効部小数23ビットに、 倍精度は8バイト=64ビットを符号1ビット・指数11ビット・有効部小数52ビットに分ける。
前のセクションの最後で確認したとおり、正規化された有効部の左端には必ず1が立つ。IEEE754はこの1を 保存しない。分かりきっているものを省いて、その1ビットぶんを小数側の精度に回すという 割り切りである。単精度が「23ビットしか保存しないのに精度は24ビット」と言われるのはこのためで、 省かれた1は暗黙の1と呼ばれる。
もう1つの仕掛けが指数の持ち方である。指数は正にも負にもなるが、IEEE754はこれを2の補数では表さない。 代わりにバイアス付き——単精度なら127、倍精度なら1023を足した状態——で保存する。 したがって実際の指数を取り出したいときは、保存されている値からバイアスを引く。保存値が130なら、 130から127を引いて実際の指数は3である。
値 = (−1)s × 1.f × 2(e−127)
この式が使えるのは、指数部eが0でも255でもない正規化数の場合に限られる。両端の値は 特別な意味に予約されているからである。e=0かつf=0なら値は0(符号ビットが1なら負の0)。 e=0でfが0でないときは非正規化数で、暗黙の1が付かない (−1)s × 0.f × 2−126 として解釈される。e=255でf=0なら正または負の無限大、 e=255でfが0でなければNaN——数でないことを示す特別な符号になる。
ここで、第7講で学んだ整数の符号付き表現と並べて確かめておきたい。整数はビット列全体を2の補数で 符号化することで、正の数と負の数を同じ足し算の規則で扱えるようにしていた。IEEE754浮動小数点数の 設計はこれとは違う。符号は独立した1ビットが持ち、残り31ビット(指数部と fraction=有効部小数)が値の大きさを符号化する。符号と大きさを分けて持つ設計であり、 「どちらも符号ビットがあるから同じ仕組み」と考えると、指数のバイアスの意味を取り違えることになる。
なお、単精度で表せる正規化数は最小がおよそ1.175×10⁻³⁸、最大がおよそ3.403×10³⁸である。 非正規化数まで含めれば、正の値としてはさらに小さいおよそ1.401×10⁻⁴⁵まで届く。 そして浮動小数点の四則演算は、すべて整数の演算に分解できる——加算なら指数をそろえてから有効部を足し、 乗算なら有効部どうしを掛けて指数を足す。三角関数のような関数も、突き詰めれば四則演算の 組み合わせで計算されている。
この配置図を、実際の32ビットで確かめてみよう。下のラボはビットを1つタップするたびにその桁を反転させ、 10進の値をその場で計算し直す。符号のビットを反転すれば正負だけが入れ替わり、指数のビットを動かせば 値は2の累乗の単位で大きく飛び、有効部小数のビットは同じ2の累乗の枠の中を細かく刻む—— 同じ1ビットでも、どの区画にあるかで効き方がまったく違うことが目で確かめられる。
ビットをタップすると0と1が入れ替わり、下の10進の値がその場で変わります(上の数字はビット番号)。
- 符号 s
- 0 → 正の数
- 指数 e
- 保存値 127 − バイアス 127 = 実際の指数 0
- 有効部
- 1.0
- 32ビット全体
- 0x3F800000
正規化された形: 1.0 × 20
このシミュレータの本題である正規化数のパターンです。値は (−1)ˢ × 1.f × 2⁽ᵉ⁻¹²⁷⁾ で決まります。
符号のビットを反転すると、値の大きさはそのままで正負だけが入れ替わります。指数のビットを動かすと、 値は2の累乗の単位で大きく飛びます。有効部小数のビットは、暗黙の1のうしろに続く桁を足し引きして、 同じ2の累乗の枠の中を細かく刻みます——同じ1ビットでも、どの区画にあるかで効き方がまるで違います。 指数のビットをすべて0またはすべて1にすると、ゼロ・非正規化数・無限大・NaNという特別なパターンに入ります。 そのとき (−1)ˢ × 1.f × 2⁽ᵉ⁻¹²⁷⁾ の式は使えないので、どのパターンなのかを値のとなりに表示しています。
IEEE754単精度浮動小数点形式(32ビット)の内訳として正しいものはどれか
IEEE754単精度形式の「バイアス付き指数」について正しい説明はどれか
整数の符号付き表現(第7講で学んだ2の補数)と、IEEE754浮動小数点数の符号の表し方の違いについて正しい説明はどれか
IEEE754単精度形式で、指数部e=255・有効部小数f≠0のときに表される値はどれか
単精度浮動小数点形式でバイアス付き指数として保存されている8ビットの値が130(10進)のとき、実際に適用される指数(2の累乗の指数)はいくつか(バイアスは127)
このセクション: 0 / 5 問正解
5. 精度の限界 — なぜ浮動小数点だけでは足りないか
浮動小数点は広い範囲の数を扱えるが、無限の精度を手に入れたわけではない。単精度の有効部は保存されない先頭の1を含めて24ビットで、これはおよそ10進7桁分の区別に相当する。ここを超えた範囲では、隣り合う異なる数が同じビットパターンとして保存されてしまう——値がぼやけるのではなく、区別そのものが失われる。倍精度なら53ビット・およそ10進16桁まで粘れるが、限界がなくなるわけではない。だからこそ、1セント単位・1円単位の正確さを常に保証したい金額の計算では、S2で限界を見たはずの固定小数点やBCDが今も選ばれ続けている。適材適所という、この講の締めくくりである。
単精度の有効部は、保存されない暗黙の1を含めて24ビットぶんある。これは「およそ10進7桁の精度」と 言い換えられることが多い。ただしこの「精度」という言葉は、少し注意して受け取る必要がある。 意味しているのは区別できる限界であって、単に誤差の大きさのことではない。
具体的には、ある範囲を超えると、隣り合う異なる整数が まったく同じビットパターンとして保存されてしまう。値がわずかにずれるのではなく、 2つの数の区別そのものが失われる。桁が大きくなるほどこの間隔は広がっていくので、 「7桁までは区別できるが、その先は保証できない」という言い方になる。 「0.1を10回足しても正確に1.0にならない」といった丸め誤差も、浮動小数点につきものの現象として よく知られているが、精度の限界の本体は、この区別できなくなる方である。
倍精度なら有効部は52ビット(暗黙の1を含めて53ビット)で、およそ10進16桁まで粘れる。単精度より ずっと余裕はあるが、限界がなくなるわけではない——桁を伸ばせば、やはり同じ現象が待っている。
そしてこの性質は、金額を扱うプログラムにとって致命的になりうる。金額の桁数がある程度大きくなると、 1セント(1円)だけ違う2つの金額が同じ浮動小数点値として保存されてしまう危険がある。帳簿の上で 2つの金額が区別できないという事態は、銀行業務では起きてはならない。だから ドル・セントの計算では、今も固定小数点やBCDの方が好まれる。S2で「破綻する」と 書いた固定小数点が、ここでは正しい選択になる——扱う数の範囲が決まっている世界では、 桁を固定できることがそのまま強みになるからである。
最後に来歴を一言だけ。浮動小数点の計算を専用のハードウエアで行った最初の商用コンピュータは 1954年のIBM 704であり、デスクトップの世界へ浮動小数点ハードウエアが広まる入口になったのは インテル8087という数値演算コプロセッサだった。やがてこの機能はCPUそのものに統合されていく。 こうしてハードウエアは着実に前進したが、実はそれよりずっと大きな出来事が、1950年代前半には すでに始まっていた——人がコンピュータに指図するための言葉、すなわち コンピュータ言語の誕生である。次講はそこから始まる。
単精度浮動小数点数について「約24ビットまたは10進7桁の精度を持つ」と言われることの意味として正しいものはどれか
銀行のプログラムでドルとセントの金額を扱うとき、単精度浮動小数点よりも固定小数点(またはBCD)が好まれる理由はどれか
このセクション: 0 / 2 問正解