スポンサーリンク
※サイト運営にサーバーは必須です※
~このサイトもエックスサーバーを使用しています~
目次
はじめに
float型が32ビットで表現されることは知っていました。
しかし、どうやって小数の数字を扱っているのか、あまりよくわかっていませんでした。
この記事では、float型について私がわかったことを整理します。
主な参考サイト
※間違いがあったら、コメント欄で指摘していただけると助かります。
32ビットの内訳
1ビット:符号
8ビット:指数部
23ビット:仮数部
符号(1ビット)
-、+かで0か1が割り振られる。
指数部(8ビット)
8ビットなので、byte型をイメージすると、0~255の256通りの表現を行うことができます。
sbyte型をイメージすると、2の(-128乗)~2の(127乗)まで表現できるような気がしますが、これは間違いです。
256通りの内、0と255の2通りに関しては以下の用途で予約されており、使えません。
「0」の場合:数字の0と非正規化数を表現するために使われます。
※非正規化数について詳しく知りたい場合は、以下のwikiのページで詳しく説明されています。
→非正規化数(wiki)
255の場合:無限大または NaNを表現するために使われます。
以上のことから指数の表現で使えるのは1~254の数字です。
そのため、指数部で表現できる範囲は2の(-126乗)~2の(127乗)となります。
※2の(x-127)乗という式で計算されます。xには1~254の数字が入ります。
これに対して、2の10乗(1024)で約10の3乗であることを考えると、
2の(127乗)≒10の(38.1乗)
となります。
以上のことから、float型の最大値が10の38乗のオーダーになることが納得できるでしょう。
仮数部(23ビット)
仮数部で表現するのは1以上2未満の数字です。
仮数部を理解する前に、どのように小数点のある数字を扱っているか説明します。
例えば、10進法で、0.00135という数字があった時に、
1.35×10^(-3)
と表現するでしょう。(このように表現することを正規化という)
これと同じことを2進数でも行われています。
2進数で例えば。0.0100101という数字があった時、内部では、
1.00101×2^(-2)
と表現されています。
この「1.00101」の部分が仮数部に相当し、2^(-2)が指数部に相当します。
このような説明をすると、仮数の部分に「100101…」(…は0が続く)が入っているかと思われるかもしれません。
しかし、この説明は不完全です。
2進数において、数字の大きさに合わせて小数点を動かすと、必ず、最初の一桁目(整数部)は1になります。
0になる心配がないのであれば、最初から表現しない方が得だろうということで表現されません。(精度という観点で1ビット分得をします)
※このような表現をケチ表現と言います
上の例では、「100101…」でなく仮数には「00101…」が入ります。
※例えば、仮数部が「000…」であれば、1になります。(1.000…=1)
一方「111…」であれば、仮数部で表現される数字は、2に限りなく近い数字になります。
(1.111…≒2)。
ケチ表現をすることで、有効数字を1ビット分得することを踏まえると、有効数字は2進数で24桁分あります。
これを10進法に直すと、
2の(24乗)≒10の(7.2乗)
となります。
つまり、有効数字は10進法に変換して7桁となります。
考察
以上のことを踏まえるとfloat型の最大値は、指数部で2の127乗になり、仮数部で「111…」となり、限りなく2に近いときです。
逆に、最小値は、これに-の符号がかかった時です。
では、0に近い最小の数字は何でしょうか?
指数部が2の-126乗になり、仮数部で「000…」となる場合ではありません。(つまり、1×2^(-126乗)ではないということです。)
指数部に0~255の内の0が入り、非正規化数を扱うときに最小の数字を取ります。
指数部が0の時、以下のように数字が扱われます。
(x.xxx…) ×2^(-127乗)
※(x.xxx…)には仮数部の数字が入ります。
今までの場合と大きく異なる点として、ケチ表現ができないことです。
つまり、float型で扱える指数の範囲を超えてしまったため、最初の1桁目に暗黙的に1と置くことができなくなります。
言い換えれば、小数点の位置を動かして、正規化することができなくなります。
この領域で扱える最大の数は、
「1.111…」×2^(-127乗)
です。
つまり、2^(-126乗)に近いが、2^(-126乗)と比べてわずかに小さな数となります。
一方で、最小の数は、
「0.000…1」×2^(-127乗)
です。
「0.000…1」は2^(-22乗)に相当するので、結論としては、2^(-149乗)となります。
有効数字という観点で、この領域を考えますと、2^(-127乗)のオーダーの領域から2^(-149乗)の領域に向かうにつれて、精度が悪くなることがわかります。
例えば、2^(-149乗)の手前の2^(-148乗)の領域では、以下の2つの数字しかありません。
「0.000…10」×2^(-127乗)と「0.000…11」×2^(-127乗)
つまり、1.0×2^(-148乗)と1.1×2^(-148乗)
※1.0 と1.1は2進数です。
この場合、有効数字は2進数で2桁しかありません。
10進法に変換した場合、有効数字が2桁より小さくなることがわかります。
ソースコードで確認
言語はC#でfloat型の最小値・最大値などを確認しています。
double型を使用して、上の考え方が妥当かどうか計算して確かめています。
※double型はfloat型の約2倍の精度があるとはいえ、同じような仕組みで成り立っています。そのため、float型の正しさをdouble型で確かめるという行為は、かなり怪しい部分を含んでいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace float01 { class Program { static void Main(string[] args) { Console.WriteLine("float型の最大値は"+float.MaxValue); Console.WriteLine("float型の最小値は" + float.MinValue); Console.WriteLine("float型のゼロに近い最小の数は" + float.Epsilon); //実際に計算をして確かめる //2の127乗 double pow_127 = 1; for (int j = 1; j <= 127; j++) { pow_127 *= 2; } Console.WriteLine("2の127乗は"+pow_127); //仮数部が「111…」の時 double near_2 = 1; double num = 1; for (int j = 1; j <= 23; j++) { num /= 2; near_2 += num; } Console.WriteLine("仮数部が1番2に近くなるのは"+ near_2); Console.WriteLine("float型の最大値を計算すると" + near_2* pow_127); //2の-149乗 double min = 1; for (int j = 1; j <= 149; j++) { min /= 2; } Console.WriteLine("float型のゼロに近い最小の数を計算すると" + min); } } } |
実行結果は、
float型の最大値は3.402823E+38
float型の最小値は-3.402823E+38
float型のゼロに近い最小の数は1.401298E-45
2の127乗は1.70141183460469E+38
仮数部が1番2に近くなるのは1.99999988079071
float型の最大値を計算すると3.40282346638529E+38
float型のゼロに近い最小の数を計算すると1.40129846432482E-45
関連記事
~プログラミングを勉強してみませんか?~
TechAcademy [テックアカデミー] は無料の体験講座が用意されているので、気軽に体験できます。
※私(サイト主)も無料体験講座を実際に受けてみました(→感想)