コンテンツにスキップ

プログラミング/浮動小数点数

出典: フリー教科書『ウィキブックス(Wikibooks)』

プログラミングにおいて、数値を扱うことは非常に重要です。しかし、数値の表現にはいくつかの種類があり、それぞれに特徴があります。本章では、その中でも特に重要な浮動小数点数について解説します。

導入

[編集]

浮動小数点数とは

[編集]

浮動小数点数は、コンピューターで実数を表現するための方法の一つです。 この表現方法では、数値を「符号」「仮数」と「指数」の三つの部分に分け、それぞれが固定のビット数で表されます。 浮動小数点数は、非整数の数値や非常に大きな数値、小さな数値を効率的に表現することができ、科学技術計算やグラフィックス、金融など幅広い分野で利用されています。

浮動小数点数を使う理由

[編集]

浮動小数点数を使用する理由には、いくつかの利点があります。

まず、浮動小数点数は多様な値を表現できるため、整数だけでなく小数や非常に大きな値や小さな値など、様々な値を正確に表現することができます。このため、現実世界の多くの問題に対してより正確な数値計算を行うことができます。

また、浮動小数点数を使用すると数値計算を効率的に実行することができます。これは、多くの場合、コンピューターのハードウェアが浮動小数点数の計算を最適化するように設計されているためです。

さらに、浮動小数点数はエラーを減少させることができます。たとえば、数値が非常に大きな値になると整数ではオーバーフローする可能性がありますが、浮動小数点数では正確に表現することができます。

浮動小数点数はIEEE 754規格に基づいて標準化されており、さまざまなプログラムやシステムで同じ方法で数値計算を実行することができます。

以上のように、浮動小数点数は正確な数値計算、効率的な数値計算、エラーの減少、標準化された数値表現など、多くの利点を持っています。これらの理由から、プログラミングにおいて浮動小数点数を使用することが一般的になっています。

IEEE 754
プログラミングにおいて、浮動小数点演算は非常に重要な機能です。しかし、様々な浮動小数点実装には信頼性と移植性に欠ける問題がありました。そこで1985年に電気電子学会(IEEE)によって策定された技術的標準「IEEE 754」が登場しました。この標準は、有限数、無限大、および特別な非数(NaN)の値で構成される二進数および10進数の浮動小数点データの算術フォーマットを定義し、浮動小数点データを交換するための効率的でコンパクトなエンコーディングを提供しています。また、算術フォーマット上での算術演算や変換中に数値を四捨五入する際に満たすべきプロパティである丸めルールなども定義されています。 現在、多くのハードウェア浮動小数点ユニットがIEEE 754標準を採用しています。2008年に発表されたIEEE 754-2008には、基数に依存しない浮動小数点演算のIEEE 854-1987規格が追加され、浮動小数点演算に関する標準が一層充実しました。そして、2019年7月に発表されたIEEE 754-2019は、前のバージョンの小規模な改訂版で、明確化、欠陥修正、および新しい推奨操作が含まれています。IEEE 754標準は、浮動小数点演算の信頼性と移植性を向上させ、プログラマにとって非常に役立つ規格となっています。

浮動小数点数の種類

[編集]

浮動小数点数には、一般的にIEEE 754規格による表現方法が用いられています。この規格では、浮動小数点数を表現するために以下の3つの要素を使用します。

  • 符号ビット: 数が正か負かを示します。
  • 指数部: 数値の位置を決定します。
  • 仮数部: 実際の数値を表現します。

代表的な浮動小数点数の種類は以下のとおりです。

  • binary16(半精度浮動小数点数):16ビットで表現され、符号1ビット、指数部5ビット、仮数部10ビットで構成されます。
  • binary32(単精度浮動小数点数):32ビットで表現され、符号1ビット、指数部8ビット、仮数部23ビットで構成されます。
  • binary64(倍精度浮動小数点数):64ビットで表現され、符号1ビット、指数部11ビット、仮数部52ビットで構成されます。
  • binary128(四倍精度浮動小数点数):128ビットで表現され、符号1ビット、指数部15ビット、仮数部112ビットで構成されます。
  • decimal32:32ビットで表現され、符号1ビット、仮数部7桁、指数部5ビットで構成され、10進数をサポートします。
  • decimal64:64ビットで表現され、符号1ビット、仮数部16桁、指数部7ビットで構成され、10進数をサポートします。
  • decimal128:128ビットで表現され、符号1ビット、仮数部34桁、指数部10ビットで構成され、10進数をサポートします。
2024年10月現在、一般的に使用されているのはbinary32およびbinary64であり、10進数浮動小数点数をサポートする言語処理系やハードウェアはまだ普及段階にあります。

IEEE 754規格では、浮動小数点数の正確性に制限があります。たとえば、32ビットでは有効桁数が約7〜9桁、64ビットでは約15〜17桁です。この制限は、非常に大きな値や非常に小さな値を扱う際に特に問題となります。

浮動小数点数を使用する際は、その制限を理解し、正確な計算結果を得るために適切な処理を行う必要があります。

bfloat16について
bfloat16は、16ビットの浮動小数点形式で、深層学習や人工知能分野、特にGoogleのTensor Processing Unit(TPU)で使用されています。bfloat16はbinary16と似た形式ですが、表現可能な範囲が広いため、より正確なモデルの学習が可能です。

以下にbfloat16のビットフィールドを説明します。 (S) (Exponent) (Mantissa)

  • S: 符号ビット。0は正、1は負の数を表します。
  • Exponent: 8ビットの指数部。バイアス127で、-126から127までの範囲を表現します。
  • Mantissa: 7ビットの仮数部。最上位ビットは暗黙的に1です。
bfloat16は、binary16よりも広い範囲を表現できるため、深層学習アルゴリズムでより正確な演算を実現します。特定のハードウェア(TPUなど)では、高速な演算も可能です。

基本概念

[編集]

浮動小数点数は、コンピュータにおいて実数を扱うための方式の一つです。実数は有限桁の数値あるいは有理数で表現されますが、有限の桁数では正確に表現できない数があります。そのため、浮動小数点数では有効桁数を限定し、近似的に実数を表現します。

浮動小数点数は、符号、仮数、指数の3つの要素から構成されます。符号は正または負を表し、仮数は小数部分を、指数は10のべき乗を表します。これらの要素を組み合わせて、実数を表現します。

浮動小数点数は、IEEE 754規格に基づいて標準化されています。この規格では、32ビットまたは64ビットのビット列を使って浮動小数点数を表現します。32ビットの場合、符号は1ビット、指数は8ビット、仮数は23ビットで表現されます。64ビットの場合は、符号は1ビット、指数は11ビット、仮数は52ビットで表現されます。

浮動小数点数は、実数を近似的に表現するため、演算誤差が生じる可能性があります。例えば、0.1という数値は10進数で正確に表現できますが、2進数で表現する場合には循環小数になります。そのため、浮動小数点数では0.1を完全に表現することができず、近似値で表現することになります。このような演算誤差を考慮しながら、プログラミングで浮動小数点数を扱うことが重要です。

10進数と2進数の変換

[編集]

浮動小数点数を理解するためには、10進数と2進数の変換を理解することが必要です。以下は、その基本概念について説明します。

10進数とは、0から9までの数字を使って数を表現する方法です。例えば、123や3.14のような数は、10進数で表現されます。

一方、2進数とは、0と1の2つの数字を使って数を表現する方法です。例えば、10進数の5は2進数で101と表現されます。

10進数から2進数への変換方法は、以下のようになります。

  1. 変換したい10進数を2で割り、商と余りを求める。
  2. 求めた余りを一番右の桁に書く。
  3. 商を2で割り、商と余りを求める。
  4. 求めた余りを次の桁に書く。

上記の手順を商が0になるまで繰り返す。

例えば、10進数の5を2進数に変換すると、以下のようになります。

5 ÷ 2 = 2 余り 1 2 ÷ 2 = 1 余り 0 1 ÷ 2 = 0 余り 1

よって、10進数の5は2進数で101と表現されます。

2進数から10進数への変換方法は、以下のようになります。

  1. 2進数の一番左の桁から始めて、右に向かって桁を見ていく。
  2. 各桁について、その桁の数値に2のべき乗をかけた値を計算する。一番左の桁は2^0、次の桁は2^1、その次の桁は2^2、というように、右に向かって2のべき乗の指数を1ずつ増やしていく。
  3. 各桁で計算した値を足し合わせる。

例えば、2進数の101を10進数に変換すると、以下のようになります。

1 × 2^2 + 0 × 2^1 + 1 × 2^0 = 4 + 0 + 1 = 5

よって、2進数の101は10進数の5に相当します。

浮動小数点数の表現方法

[編集]

浮動小数点数は、計算機科学における数値表現の一種であり、非整数の数値を表すために用いられます。浮動小数点数は、仮数部と指数部に分けられた、以下のような形式で表現されます。

(sign) (mantissa) x (base)^exponent
  • (sign) : 符号を表します。0または1で、0は正数、1は負数を表します。
  • (mantissa) : 仮数部を表します。小数点以下の数値を表すために使われ、一定の桁数で表現されます。
  • (base) : 基数を表します。通常、2または10が使われます。
  • (exponent) : 指数部を表します。浮動小数点数の値を表すために必要な桁数を表現します。

例えば、以下のような数値があった場合、浮動小数点数の形式で表現することができます。

0.00123

この数値を2進数で表現すると、

0.00000000000101101011110000010100011110101110000101001...

となります。この数値を浮動小数点数の形式で表現すると、

0 1011010111100000101000111101011100001010010100011111 2^-10

となります。この場合、符号が0であるため正数を表し、仮数部は1011010111100000101000111101011100001010010100011111、指数部は2の-10乗を表します。

Cなどでは、16進浮動小数点リテラル (Hexadecimal Floating-Point Literal)という浮動小数点数を16進数表記と指数部で表現する形式がサポートされています。16進浮動小数点リテラルで 0.00123 を表すと。

0x1.426fe718a86d7p-10
| |        |     | |
| |        |     | +- 指数部: 2 の -10 乗 (基数は 2)
| |        |     +- 仮数部と指数部の区切子: p
| +- 仮数部: 1.426fe718a86d7 (基数は 16)
+- リテラルの先頭: 16進数 (0x)

となります。

16進浮動小数点リテラルを使える主な言語:

C
C99以降
C++
C++17以降
Java
Java SE 5.0以降
Double.parseDouble() などでサポート
Python
Python 3.6以降(例: float型で対応)
Rust
対応(16進数浮動小数点リテラルを直接記述可能)
Go
Go 1.13以降
Julia
サポートあり
Swift
サポートあり
Kotlin
サポートあり(Double/Float型)
Perl
特定の実装(Perl 5.22+)で対応
注意
サポートはコンパイラやランタイム環境に依存する場合もあるため、公式ドキュメントの確認が必要です。

IEEE 754規格に基づく浮動小数点数の表現

[編集]

IEEE 754規格に基づく浮動小数点数の表現は、浮動小数点数をビットパターンで表現するための標準的な方法です。この規格は、ほとんどのコンピュータやプログラミング言語で採用されており、浮動小数点数の表現に関する一貫性を提供しています。

浮動小数点数は、符号ビット、指数部、仮数部で構成されます。符号ビットは、正の数の場合は0、負の数の場合は1になります。指数部は、浮動小数点数の指数を表現するために使用されます。仮数部は、浮動小数点数の仮数を表現するために使用されます。

浮動小数点数の表現には、正規化された表現と非正規化された表現があります。正規化された表現は、仮数の先頭ビットが1である場合に使用され、指数部の最大値と最小値を持つことができます。非正規化された表現は、仮数の先頭ビットが0である場合に使用され、指数部が最小値の場合にのみ表現できます。

仮数部の隠れビット

正規化された数において、最も重要な桁は常に非ゼロです。二進数で作業する際、この制約によってこの桁は常に1になります。したがって、それは明示的に格納されず、隠れビットと呼ばれます。

有効数字は、(二進数の)桁数で特徴付けられ、文脈によっては、隠れビットが幅に含まれる場合と含まれない場合があります。たとえば、同じIEEE 754倍精度形式は、隠れビットを含む53ビットの有効数字または隠れビットを除いた52ビットの有効数字として一般的に説明されます。IEEE 754では、精度pを有効数字内の桁数と定義し、暗黙の先頭ビットを含む有効数字の数(たとえば、倍精度形式の場合はp = 53)を示すための用語を定義しています。したがって、符号化方法から独立しており、符号化されたものを表現する用語(つまり、先頭ビットを含まない有効数字)は後続の有効数字フィールドと呼ばれます。

指数部の下駄履き

IEEE 754における指数部の「下駄履き (biasing)」とは、指数部の値を調整するための手法です。IEEE 754では、浮動小数点数の指数部が負の値を扱えるように、指数部に固定のオフセット(バイアス)を加えて表現します。これにより、指数部が負の値でも正の値と同様の表現が可能になります。

具体的には、指数部にバイアスを加えることで、指数部の実際の値と内部表現の値との間にオフセットが生じます。このバイアスの値は、指数部のビット数によって異なります。例えば、単精度浮動小数点数では8ビットの指数部を持ち、指数部の範囲が-127から+128までなので、バイアスは127となります。指数部が0のときにはバイアスを引くことで指数が-127になり、最小の指数を表現できます。

このバイアスを利用することで、指数部の符号を持つ部分がないため、指数部の大小比較が単純化され、浮動小数点数の演算がより効率的に行えるようになり、正規化された表現であれば整数として比較しても正しい結果が得られます。

IEEE 754以外の浮動小数点数表現
IEEE 754は、浮動小数点数の標準的な表現形式として広く使用されていますが、IEEE 754以外の浮動小数点数表現も存在します。ここでは、IBMやDECの例を紹介します。

IBMの浮動小数点数表現は、IBM System/360やその後継のメインフレームコンピュータで使用されています。IBMの浮動小数点数表現は、3つの部分で構成されています。最初の部分は符号ビットであり、1ビットで表されます。次の部分は指数フィールドであり、8ビットまたは11ビットで表されます。最後の部分は仮数フィールドであり、23ビットまたは52ビットで表されます。IBMの浮動小数点数表現は、IEEE 754とは異なり、指数フィールドにバイアスを加えるのではなく、真の指数を表します。

DECの浮動小数点数表現は、DEC PDP-11やVAXコンピュータで使用されています。DECの浮動小数点数表現は、3つの部分で構成されています。最初の部分は符号ビットであり、1ビットで表されます。次の部分は指数フィールドであり、8ビットまたは11ビットで表されます。最後の部分は仮数フィールドであり、23ビットまたは52ビットで表されます。DECの浮動小数点数表現は、IBMと同様に、真の指数を表します。

他にも、浮動小数点数表現としては、Bfloat16、Posit、Fixed-pointなどがあります。Bfloat16は、Googleが開発した浮動小数点数表現であり、16ビットの仮数フィールドを持ちます。Positは、John Gustafsonが提唱した浮動小数点数表現であり、高い精度を持つことができます。Fixed-pointは、整数型の表現形式を拡張して、小数点以下の数値を表現する方法です。

これらの浮動小数点数表現は、それぞれの特性を持ち、特定の用途に適した表現形式を選択することが重要です。

精度と誤差

[編集]

浮動小数点数は、計算機科学における数値表現の一種で、実数を近似的に表現するために使用されます。

浮動小数点数は、整数部と小数部を持ち、基本的には次の形式で表されます。

ここで、 は仮数部、 は基数、 は指数です。

浮動小数点数の精度について、浮動小数点数は有限桁数で表現されるため、実数を完全に表現することはできません。浮動小数点数は、有限桁数で近似的に表現されるため、数値表現の精度が限定されます。つまり、小数点以下の桁数が多くなるほど、浮動小数点数の精度は低下します。

丸め誤差と桁落ち誤差は、浮動小数点数表現の誤差の主な原因です。丸め誤差は、浮動小数点数の表現において、有限桁数で表現されるため、正確な値と近似値との差によって生じます。桁落ち誤差は、近い数値同士の引き算で生じます。この場合、仮数部の上位の多くが0となり、正規化されると劇的に精度が低下します。

浮動小数点数の誤差を最小化する方法には、いくつかの方法があります。例えば、計算を実行する前に、データ型と変数の選択に十分注意を払い、必要な精度を確保することが重要です。また、丸め誤差を最小限に抑えるためには、数値表現の桁数を増やすことができます。最後に、桁落ち誤差を最小限に抑えるためには、代わりに加算を行うことができます。具体的には、代数的変形を使用して、値を表現するために使用される式を変更することができます。

演算

[編集]

浮動小数点演算の基本概念

[編集]

浮動小数点数は、計算機上で数値を表現するための方法の1つであり、実数を近似して表現します。

浮動小数点演算には、以下の基本概念があります。

加算

[編集]

浮動小数点数の加算は、以下の手順になります。

  1. 指数部の揃え
    加算する2つの浮動小数点数の指数部を合わせる必要があります。これは、同じ位の桁同士を加算できるようにするためです。
    指数部の小さい方の数を仮数部をビットシフトして調整します。
  2. 仮数部の加算
    指数部を揃えた後、仮数部の加算を行います。
    仮数部はビット列なので、ビット単位での加算となります。
  3. 結果の正規化
    加算結果を正規化します。つまり、仮数部の上位ビットが1になるようビットシフトし、指数部を調整します。
    この時、前処理での指数部の調整と相殺されます。
  4. 例外処理
    加算結果が指数部や仮数部の範囲を超える場合は、オーバーフロー、アンダーフローなどの例外処理が行われます。
  5. 丸め処理
    正規化後の仮数部が有効桁数を超えている場合は、丸め処理が行われます。これには複数の丸め方式があります。

加算は、指数部の揃え、仮数部の加算、正規化、例外処理、丸めの一連の処理が必要になるため、他の演算より複雑になります。 また、キャリーの発生や桁落ちなどの影響で、誤差が生じやすくなります。

減算

[編集]

浮動小数点数の減算も基本的には加算と同じ流れになります。

減算の手順は以下の通りです。

  1. 指数部の揃え
    加算と同様に、2つのオペランドの指数部を合わせる必要があります。指数の小さい方の数を仮数部でビットシフトして調整します。
  2. 仮数部の減算
    指数部を揃えた後、仮数部同士の減算を行います。これもビット単位での減算計算になります。
  3. 結果の正規化
    減算結果を正規化します。仮数部の上位ビットが1になるようビットシフトし、指数部を調整します。
  4. 例外処理
    減算結果がオーバーフロー、アンダーフロー、無限大などの例外の範囲に入る場合は、適切な例外処理を行います。
  5. 丸め処理
    正規化後の仮数部が有効桁数を超えている場合は、丸め処理を行います。

減算では、符号が異なる場合に、実質的な加算が行われる点が加算との違いですが、基本的な流れは共通しています。

減算時もキャリーや桁落ちの影響で誤差が生じる可能性があり、注意が必要です。 また、オペランドの値によっては、加算時に起こりえない例外(例えばアンダーフロー)が発生する可能性もあります。

乗算

[編集]

浮動小数点数の乗算は、以下の手順になります。

  1. 仮数部の乗算
    2つの浮動小数点数の仮数部(有効数字部分)同士を整数の掛け算と同じように掛け算します。
  2. 指数部の加算
    2つの指数部を加算します。これにより、掛け算の結果の指数部が求められます。
  3. 結果の正規化
    仮数部の掛け算結果を正規化します。つまり、仮数部の先頭が1になるようにビットシフトし、その分指数部の値を調整します。
  4. 例外処理
    指数部や仮数部がそれぞれの表現範囲を超える場合は、オーバーフロー、アンダーフロー、無限大の例外処理を行います。
  5. 丸め処理
    正規化後の仮数部が、有効桁数を超えている場合は丸め処理を行います。
    積は、乗数(および被乗数)の倍の桁数になるので、多くの桁が失われます。

乗算は加減算と比べて、仮数部と指数部の計算が分離されているので、比較的単純な処理になります。ただし、仮数部の掛け算が長い桁数になるため、演算が重くなる可能性があります。

また、乗算では指数部の加算により、指数がオーバーフローやアンダーフローの範囲に入りやすくなるという性質があります。そのため、例外処理への対応が重要になってきます。

以上が浮動小数点数の乗算の基本的な流れになります。適切な正規化、例外処理、丸め処理を行うことで、誤差を最小限に抑えた結果を得ることができます。

除算

[編集]

浮動小数点数の除算は、以下の手順になります。

  1. 仮数部の除算
    被除数の仮数部を除数の仮数部で割ります。これは整数の除算と同様のビット演算になります。
  2. 指数部の減算
    被除数の指数部から除数の指数部を引きます。これにより、商の指数部が求められます。
  3. 結果の正規化
    除算結果の仮数部を正規化します。つまり、仮数部の先頭が1になるようビットシフトし、その分指数部を調整します。
  4. 例外処理
    指数部や仮数部が表現範囲を超える場合は、オーバーフロー、アンダーフロー、ゼロ除算の例外処理を行います。
  5. 丸め処理
    正規化後の仮数部が有効桁数を超えている場合は、丸め処理を行います。

除算は乗算とは逆の操作になりますが、基本的な流れは似ています。仮数部の除算、指数部の減算、正規化、例外処理、丸め処理の一連の処理を行います。

除算では、特に除数が0の場合の例外処理が重要になります。さらに、除算は概して誤差が生じやすい演算なので、丸め処理の影響にも注意が必要です。

また、ハードウェアレベルでの除算は遅い演算なので、ソフトウェア的な工夫が求められる場合があります。

以上が浮動小数点数の除算の基本的な処理の流れとなります。適切な例外処理と丸め処理を行うことで、可能な限り誤差の少ない商を求めることができます。

融合積和演算

[編集]

融合積和演算(Fused Multiply-Add)とは、(A × B) + Cの計算を1ステップで行う演算のことです。通常の浮動小数点演算では、D = A × BとC + Dを別々に計算し、最後に加算しますが、融合積和演算ではこの2ステップを1ステップにまとめて行います。

融合積和演算の具体的な手順は以下の通りです。

  1. 仮数部の積算
    AとBの仮数部を掛け算します。
  2. 指数部の加算
    AとBの指数部を加算し、積の指数部を求めます。
  3. 中間結果の加算
    ステップ1で得た積の仮数部に、Cの仮数部を加算します。この時、指数部を合わせる必要があります。
  4. 結果の正規化
    加算結果を正規化し、最終的な融合積和の値を求めます。
  5. 例外処理と丸め
    必要に応じて例外処理(オーバーフロー等)と丸め処理を行います。
    丸め処理はこの1回しか行われません。

乗算と加算を2回に分けて実行すると丸め処理も2回行われます。これに対し融合積和演算では乗算後の丸め処理は行わず加算後に1度だけ丸め処理を行うことで、高速かつ精度の高い計算が可能です。

融合積和演算は、一部のプロセッサーアーキテクチャーでハードウェアレベルでサポートされており、一部のプログラミング言語(例:C、C++)では、FMA機能を利用するための特別な関数が提供されています。

積和演算は、線形代数、行列計算、幾何計算などで多用されており、計算精度と演算速度の両立を図る上で重要な演算と言えます。

一方で、中間結果の丸めが1度しか発生しない精度が高く、素朴に乗算と加算の組み合わせた場合と結果が違う場合があることに注意が必要です(融合積和演算のほうが相対的に高精度)。

数値の比較

[編集]

IEEE 754規格の浮動小数点数であれば、NaNやInfの例外処理を行った後は、単純に整数の比較演算を適用することで、正しく値の大小関係を判断できます。

つまり、以下の手順になります。

  1. NaNやInfなどの例外値をチェックし、適切に処理する
  2. 例外値ではない場合、ビット表現をそのまま符号付き整数として比較する
    これは
    1. 符号を比較する。異なれば符号が立っている(負の)数が小さい
    2. 符号が一致なら、指数を比較し、異なれば指数を絶対値として比較結果を返す
    3. 指数も一致なら、仮数を絶対値として比較結果を返す
    と等価な結果をもたらします

このように簡潔で、より本質的な比較方法を用いることができます。

指数部や仮数部を区別して個別に比較する必要はなく、IEEE 754で規定されたビット表現そのものを整数比較すれば、正しい大小関係が得られるということです。

浮動小数点数は、数値の表現方法によって誤差が生じるため、等しいかどうかを判定する場合は、ある程度の誤差を許容する必要があります。

浮動小数点数の演算と正規化

[編集]

浮動小数点数の加減算などの演算を行う際、結果として得られる値は必ずしも正規化された形式にはなっていません。つまり、仮数部の最上位ビットが1にならない場合があります。 例えば、単精度浮動小数点数で1.0を2で割ると、結果は0.5になりますが、これは

0.1000...0 × 20

と表現されます。 このように、演算結果は一時的に非正規化数になる可能性があります。しかし、最終的に浮動小数点数として表現する際は、この非正規化数を正規化する必要があります。 正規化とは、以下の2ステップで行われます。

  1. 仮数部をビットシフトして、最上位ビットを1にする
  2. 指数部の値を、ステップ1のシフト量分調整する

つまり、上の0.5の例では

1.000...0 × 2-1

と正規化表現に直されます。 このように、浮動小数点数の演算ではまず計算が行われ、最後に正規化されて標準的な表現形式になります。この正規化は、例外的な0や無限大の場合を除いて、必ず行われます。 演算時に一時的に非正規化数になるため、演算の過程で情報が失われる可能性がありますが、最終的には正規化によりできる限り精度を保持することができます。

丸め誤差

[編集]

浮動小数点数の四則演算には、丸め誤差と呼ばれる現象があります。丸め誤差は、浮動小数点数を使用する計算で、正確な結果が得られないことを指します。これは、浮動小数点数が有限の精度で表現されるためです。 たとえば、0.1を10回足し合わせると、期待される結果は1.0ですが、実際には1.0000000000000002となる場合があります。これは、0.1が浮動小数点数として正確に表現できないためです。このような丸め誤差は、浮動小数点数を使用する計算で常に発生する可能性があります。

桁落ち

[編集]

桁落ちとは、絶対値のほぼ等しい二つの数値で減算を行ったときに、有効桁数が減少することにより発生する誤差のことです。この誤差は、小数点以下の桁数が多いほど顕著になります。 たとえば、以下のような例が考えられます。数値 x = 0.123456789 と y = 0.123456788 を引いた場合、正確な答えは 0.000000001 です。しかし、この計算を行う際には、x と y の差が非常に小さく、有効桁数が減少するため、誤差が生じる可能性があります。実際に、多くのプログラム言語では、この計算を行うと、結果は 0.000000000 になることがあります。 このような桁落ちの問題を回避するには、数値の桁数を減らす前に、桁数の多い数値から桁数の少ない数値を引いて、結果の精度を維持することが重要です。また、計算による誤差を最小限に抑えるために、浮動小数点数を使用する場合は、可能な限り倍精度の数値を使用することを推奨します。

桁溢れ

[編集]

浮動小数点演算における桁溢れ(オーバーフロー)は、計算結果が浮動小数点数の表現範囲を超える場合に発生します。これは、演算結果がコンピュータが表現できる最大の数値を超えた場合に発生します。具体的には、演算結果が指数部や仮数部の有効桁数を超え、有効数字が失われることで起こります。 桁溢れは、計算結果が不正確になるだけでなく、プログラムの安定性や信頼性にも影響を与える可能性があります。特に、数値計算のアルゴリズムやシミュレーションにおいて、桁溢れが起きると予期せぬ結果が生じる可能性があります。 桁溢れを回避するためには、次のような方法があります:

  1. 入力値の範囲を制限する:入力値の範囲を適切に制限することで、計算結果が浮動小数点数の表現範囲を超える可能性を減らすことができます。この方法は、問題の性質に応じて適切な入力値の範囲を定めることで、桁溢れを回避するのに役立ちます。
  2. 演算の順序を工夫する:計算の順序を適切に工夫することで、桁溢れを回避することができます。例えば、加算や乗算の順序を変えることで、計算結果が表現範囲を超える可能性を減らすことができます。
  3. オーバーフローを検出する:計算結果がオーバーフローする可能性がある場合、事前にオーバーフローを検出し、適切な処理を行うことで桁溢れを回避することができます。例えば、条件分岐や例外処理を使用して、オーバーフローを検出し、適切な対処を行うことができます。

これらの方法を組み合わせることで、浮動小数点演算における桁溢れを効果的に回避することができます。

NaNと無限大

[編集]

浮動小数点数の四則演算では、NaNと無限大の扱いに注意する必要があります。NaNは、計算結果が定義できない場合に発生する特殊な値です。NaNと他の値を演算すると、結果はNaNになります。無限大は、浮動小数点数の最大値を超えた場合に発生します。無限大と他の値を演算すると、結果は無限大になります。無限大同士で演算すると結果はNaNになります。

以上が、浮動小数点数の四則演算の基本原則です。浮動小数点数を扱う際には、これらの原則に注意して、正確な計算を行うようにしましょう。

丸めモードとその影響

[編集]

浮動小数点数の演算結果は、丸めモードによって異なる可能性があります。丸めモードには、次の4つのモードがあります。

  1. round to nearest(最近接丸め)
  2. round towards zero(ゼロに向かって丸め)
  3. round towards positive infinity(正の無限大に向かって丸め)
  4. round towards negative infinity(負の無限大に向かって丸め)

最近接丸めは、演算結果を最も近い整数に丸めます。このモードが最も一般的に使用されます。ゼロに向かって丸める場合、結果は演算によって得られる最大の整数または最小の整数になります。正の無限大または負の無限大に向かって丸める場合、結果は正の無限大または負の無限大になります。

特殊な値の取り扱い方法

[編集]

浮動小数点数の特殊な値には以下のようなものがあります。

NaN(非数)

[編集]

NaN (Not a Number)は、数値として解釈できない値を表すために使用されます。 0.0/0.0 や Inf/Inf などの不定形や未定義の数値演算の結果、または実数で表現できない数値、例えば √-1 などが含まれます。 NaN は、数値に対する演算の結果としても得られることがあります。 NaN は、どのような演算をしても NaN を返します。 NaN は、浮動小数点数の比較演算においては常に false として扱われます。

quiet NaN と signaling NaN
[編集]

NaNには、"quiet NaN"と "signaling NaN"の2種類があります。

"quiet NaN"は、算術演算に使用してシグナルが発生しないNaN値です。通常、初期化されていない値や未定義の値を表す場合や、不確定な計算結果を表す場合に使用されます。

一方、"signaling NaN" は、算術演算に使用されたときにシグナルを発生するNaN値です。このシグナルは、不正な演算や境界外メモリアクセスなどの問題をプログラムに警告するために使用することができる。

一般に、"quiet NaN" は "signaling NaN" よりも使用頻度が高く、プログラムにおいて予期せぬ動作を引き起こす可能性が低いからです。しかし、どちらのNaNも浮動小数点演算のエラーを検出し処理するために重要です。

NaNのペイロード
[編集]

NaNのペイロードとは、NaNを含むデータの表現方法のことです。IEEE 754規格に従った浮動小数点数では、NaNを表すために特別なビットパターンが定義されています。これは、指数部が全て1であり、仮数部が0でない任意のビットパターンであるというものです。このビットパターンは、NaNを表すために使用されます。

例えば、64ビットの浮動小数点数のNaNのペイロードは、次のように表されます。

符号ビット: 1ビット
指数部: 11ビットすべてが1
仮数部: 53ビットのうち任意のビットパターン

このように、NaNは特殊な値であり、そのペイロードも特殊なビットパターンで表されます。

プログラミングごとのサポートの違い
[編集]

プログラミング言語は、NaNをどのように扱うかについて異なる方法を取ることがあります。以下は、一部のプログラミング言語のNaNの扱いに関する概要です。

  1. C/C++: IEEE 754の規格に従い、NaNは「quiet NaN」と「signaling NaN」に分類されます。C/C++では、NaNを比較しても常にfalseになります。
  2. Java: IEEE 754の規格に従い、NaNは他のどの値とも異なるため、NaN == NaNの比較はfalseになります。Javaでは、Double.NaNとFloat.NaNを使用して、それぞれdoubleおよびfloatのNaN値を表現します。
  3. Python: IEEE 754の規格に従い、NaNは他のどの値とも異なるため、NaN == NaNの比較はfalseになります。Pythonでは、math.nanを使用して、floatのNaN値を表現します。
  4. JavaScript: IEEE 754の規格に従い、NaNは他のどの値とも異なるため、NaN == NaNの比較はfalseになります。JavaScriptでは、NaNを直接使用して、数値型のNaN値を表現します。
  5. Ruby: IEEE 754の規格に従い、NaNは他のどの値とも異なるため、NaN == NaNの比較はfalseになります。Rubyでは、Float::NANという定数を使用して、floatのNaN値を表現します。
  6. MATLAB: IEEE 754の規格に従い、NaNは他のどの値とも異なるため、NaN == NaNの比較はfalseになります。MATLABでは、NaNを直接使用して、数値型のNaN値を表現します。

これらのプログラミング言語は、NaNを扱うための関数や演算子を提供していることがあります。例えば、Pythonではmath.isnan(x)関数を使用して、xがNaNかどうかをチェックできます。また、C/C++ではisnan(x)マクロを使用して、xがNaNかどうかをチェックできます。

Inf(正の無限大)

[編集]

Inf (Infinity)は、正の無限大を表すために使用されます。例えば、1.0/0.0 のような演算の結果が Inf になります。Inf は、どのような数値とも比較演算ができますが、Inf と NaN を比較することはできません。

-Inf(負の無限大)

[編集]

-Inf (Negative Infinity)は、負の無限大を表すために使用されます。例えば、-1.0/0.0 のような演算の結果が -Inf になります。-Inf も Inf と同様に扱われます。

-0(負のゼロ)

[編集]

-0.0 (Negative zero)は、負の値をゼロに丸めた場合に得られます。 -0.0 は、0.0 と比較演算をすると等しくなりますが、一方で負の数として扱われます。 たとえば、1.0/-0.0は-Inf(負の無限大)です。

0(正のゼロ)

[編集]

0.0 (Positive zero)は、正の値をゼロに丸めた場合に得られ、正の数として扱われます。 たとえば、1.0/0.0はInf(正の無限大)です。

これらの特殊な値は、プログラムの実行中に特別な意味を持ちます。NaN は、特別なエラー状態を示すために使用されることがあります。Inf と -Inf は、無限大を表すために使用され、計算結果が限りなく大きくなった場合に返されます。また、0.0 と -0.0 は、数値計算において特定の条件分岐に使われることがあります。

実践的な使用例

[編集]

浮動小数点数の応用例

[編集]

浮動小数点数は、様々な分野で広く使用されています。以下に、浮動小数点数が使用される主な分野とその応用例を紹介します。

  • グラフィックス処理: コンピューターゲームや映画などで使用される3Dグラフィックスの描画では、浮動小数点数が広く使用されています。浮動小数点数は、点や線、多角形などの座標を表現するのに最適な形式であり、高精度の計算が必要な場合にも適しています。
  • 物理シミュレーション: 物理現象のシミュレーションには、浮動小数点数が頻繁に使用されます。例えば、衝突検出や剛体力学のシミュレーションでは、オブジェクトの速度や位置を計算する必要があります。また、流体力学のシミュレーションにも浮動小数点数が使用されます。
  • 金融分析: 証券取引や金融分析においても、浮動小数点数は広く使用されています。株価や為替レートの変動を表すのに最適な形式であり、高精度の計算が必要な場合にも適しています。

代表的なプログラミング言語の浮動小数点数のサポート状況とその特徴

[編集]

浮動小数点数は、多くのプログラミング言語でサポートされています。

以下に、代表的なプログラミング言語の浮動小数点数のサポート状況とその特徴を紹介します。

  • C言語: C言語は、浮動小数点数をサポートしています。C言語の浮動小数点数は、IEEE 754規格に準拠しており、単精度浮動小数点数はfloat型、倍精度浮動小数点数はdouble型で表現されます。
  • Java: Javaは、浮動小数点数をサポートしています。Javaの浮動小数点数も、IEEE 754規格に準拠しており、単精度浮動小数点数はfloat型、倍精度浮動小数点数はdouble型で表現されます。
  • Python: Pythonは、浮動小数点数をサポートしています。Pythonの浮動小数点数は、C言語のdouble型と同等の倍精度浮動小数点数であり、IEEE 754規格に準拠しています。
  • JavaScript: JavaScriptは、整数をサポートせず浮動小数点数のみサポートしています。IEEE 754規格に準拠しており、Number型は倍精度浮動小数点数で表現されます。
  • Ruby: Rubyは、浮動小数点数をサポートしています。Rubyの浮動小数点数は、C言語のdouble型と同等の倍精度浮動小数点数であり、IEEE 754規格に準拠しています。Rubyでは、Floatクラスを使用して浮動小数点数を表現します。
  • Go: Goは、浮動小数点数をサポートしています。Goの浮動小数点数も、IEEE 754規格に準拠しており、単精度浮動小数点数はfloat32型、倍精度浮動小数点数はfloat64型で表現されます。
  • PHP: PHPは、浮動小数点数をサポートしています。PHPの浮動小数点数も、C言語のdouble型と同等の倍精度浮動小数点数であり、IEEE 754規格に準拠しています。PHPでは、floatまたはdouble型を使用して浮動小数点数を表現します。

浮動小数点数の注意点

[編集]

浮動小数点数を扱う上での問題点とその回避策

[編集]
  • 浮動小数点数の精度による誤差:浮動小数点数は実数を近似的に表現するため、計算のたびに誤差が蓄積されます。この問題を回避するためには、計算の途中結果をできるだけ小数点以下の桁数を減らして保存し、必要に応じて適切に丸めることが重要です。
  • 浮動小数点数の表現範囲による問題:浮動小数点数は、ある程度の範囲までしか表現できません。極端に大きな数や極端に小さな数を表現する場合には、誤差が大きくなることがあります。この問題を回避するためには、必要に応じて値のスケールを変更したり、整数の演算を行ったりするなどの対策をとる必要があります。

浮動小数点数と整数の比較の問題点とその解決策

[編集]

浮動小数点数と整数を比較する場合には、両者の表現方法の違いにより、思わぬ結果になることがあります。例えば、次のようなコードを考えてみましょう。

a = 0.1 + 0.1 + 0.1
b = 0.3

if a == b:
    print("a and b are equal")
else:
    print("a and b are not equal")

このコードを実行すると、予想通り、「a and b are equal」という結果が表示されるはずです。しかし、実際には、次のように「a and b are not equal」と表示されることがあります。この原因は、0.1や0.3といった数値が浮動小数点数として表現された際に、完全な値として表現できないため、計算結果に誤差が生じることにあります。

この問題を回避するためには、浮動小数点数の比較には誤差を許容した方法を使う必要があります。たとえば、次のように書き換えることができます。

import math

a = 0.1 + 0.1 + 0.1
b = 0.3

if math.isclose(a, b):
    print("a and b are equal")
else:
    print("a and b are not equal")

math.isclose()関数を使うことで、2つの数を精度の範囲で一致性を確認できます。

総括とまとめ

[編集]

浮動小数点数の重要性と今後の展望

[編集]

浮動小数点数の利用上の留意点

[編集]

まとめ

[編集]