技術情報

小数点を含む数値を16進数で表そう!

こんにちは、MSKです。
組み込みエンジニアをやっていると、よく数値を16進数で考えます。
今回は小数点を含む数値を16進数表示する方法について解説します。

16進数ってそもそも何?

各桁が16になると桁上がりするような数値の表現方法です。
0から列挙すると
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, 1C, 1D, 1E, 1F, 20, ・・・
となります。
9の後にA,B,C,D,E,Fを使うことで1つの桁で16個の数値を扱うことを可能にしています。
※16進数を表す際には0xを先頭につけるのが一般的です。
 0x1A,0x1Bなど。

16進数で表現された数を10進数へ変換するには、各桁の数値に16の(桁目-1)乗をかけ、その総和をとります。
例として、0xABCを考えます。
A=10、 B=11、C=12なので、10 \times 16^2 + 11 \times 16^1 + 12 \times 16^0 = 2748 になります。

コンピューターでよく使われるのは2進数と16進数です。
2進数についてはまた別の機会に書こうと思いますが、各桁が2になると桁上がりするような数値の表現方法です。
例として、3=11、4=100、5=101となります。
2進数を表すときには先頭に0bとつけるのが一般的です。

余談ですが、16進数の表現と2進数の表現は相性が良いです。
ある数値を2進数で表現した場合に前半4itと後半4bitに分けてそれぞれを16進数にするとその数値の16進数表現になります。
例 : 0b11001010を考えます。
  前半4bitは1100、後半4bitは1010です。
  それぞれを16進数で表すと前半4bitはC、後半4bitはAです。
  もともとの数値の11001010=0xCAとなります。

小数点を含む数値の表現方法

コンピューターが小数点を含む数値を表す方法は2つあります。

  • 固定小数点数
  • 浮動小数点数

それぞれ見ていきたいと思います。

固定小数点数

小数点をビット列のどの位置に置くかを最初から決めておく方法です。
例えば、先頭6bitを整数として扱うとすると次のようになります。
  000000.00
黄色のマーカを引いたところが整数部、ピンクのマーカを引いたところが小数部となります。

この表し方のメリットとしては

  • 分かりやすい
  • 処理が高速

という点があります。
一方、デメリットとしては

  • 表現できる数が少ない
  • 精度が低い

ことがあげられます。

デメリットの例として上の000000.00を見ます。
この数値が表すことができる数は0~63.75の範囲で0.25精度です。
範囲も非常に狭く、0.25単位ということで精度も荒いことが分かります。

では、固定小数点数はどんな時に使われているかというと、整数を表す時に使われています。
小数点の位置を最下位bitの右側としています。
00000000.としているわけです。

参考として、上のように小数点を最下位bitの右側に置いた8bitの整数の範囲を紹介します。
・符号なしの場合
 00000000.
 最小 : 00000000. = 0
    00000001. = 1
    ・・・
    11111110. = 254
 最大 : 11111111. = 255

・符号ありの場合
 ±0000000. (2進数の負の数の表し方は別のブログを出そうと思います。)
 最小 : 10000000. = -128
    10000001. = -127
    ・・・
    01111110. = 126
 最大 : 01111111. = 127

浮動小数点数

指数表現を使った数値の表現方法です。
コンピューターは2進数で数値を表しますので、次の表記を使います。
\pm x \times 2^y
\pm符号部、xを仮数部、2を基数、yを指数部と言います。(今は基数が2の場合のみを考えています。)
この例として、0.101 \times 2^{-3}のような表記をします。

この仮数と指数の組み合わせは色々な組み合わせがありますが、有効な桁数が多く取れるように指数で桁を調整します。この操作を正規化と呼びます。
例えば、0.001 \times 2^{-1}0.1 \times 2^{-3}とすることです。

符号部、仮数部、指数部をビット列に割り当てることで数値を実現します

32bit形式で、浮動小数点数の例をあげます。
syyyyyyyxxxxxxxxxxxxxxxx
符号部(ビット列としては赤色の部分)は1bit、0の時に正の数、1の時負の数を表すとします。
指数部(ビット列としては青色の部分)は7bit、2進数で表し負の数の場合2の補数で表記します。
仮数部(ビット列としては緑色の部分)は24bit、0.~と正規化した場合の~を2進数の絶対値で表記します。

0.6875をこの表現で表してみます。
0.1875 = 0.125+0.0625 = 2^{-3}+2^{-4}なので、2進数でこの数値を表すと0.0011です。
0.~の形にするので、0.11 \times 2^{-2}となります。
よって、符号部は0、仮数部は11、指数部は-2なので2の補数だと1111110となります。
上の形式に当てはめると
01111110110000000000000000000000
となります。
これを16進数で表すと0x7EC00000となります。

この割り当て方はいろいろ考えることができますが、IEEE754が一般的に使われています。
IEEE754には128bit形式や64bit形式もありますが、今回は32bit形式を考えます。
この形式は次のようになっています。
syyyyyyyyxxxxxxxxxxxxxxxxxxxxxxx

符号部(ビット列としては赤色の部分)は1bit、0の時に正の数、1の時負の数を表すとします。
指数部(ビット列としては青色の部分)は8bit、2進数で表し+127します(バイアス127)。
仮数部(ビット列としては緑色の部分)は23bit、1.~と正規化した場合の~を2進数の絶対値で表記します。

上の例と同じ0.6875をIEEE754で表してみます。
0.1875 = 0.125+0.0625 = 2^{-3}+2^{-4}なので、2進数でこの数値を表すと0.0011です。
1.~の形にするので、1.1 \times 2^{-3}となります。
よって、符号部は0、仮数部は1、指数部は-3ですが+127するので124になり2進数で表すと0b01111100となります。
上の形式に当てはめると
0011111001000000000000000000000
となります。
16進数で表すと0x3E400000となります。

変換ツールを作ってみる

簡単にJavaScriptを使ってIEEE754の32bit形式と小数の変換ツールを作ってみたいと思います。
まずは作ったツールです!

変換後 :


変換後 :

ソースコードを表示します。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Convert</title>
        <style>
            .from-float-to-hex {
                max-width: 800px;
                border: 3px solid #CCC;
                margin: 20px auto;
                padding: 20px;
            }

            .from-hex-to-float {
                max-width: 800px;
                border: 3px solid #CCC;
                margin: 20px auto;
                padding: 20px;
            }
        </style>
    </head>
    <body>
        <div class="from-float-to-hex">
            <label>数値を浮動小数点数への変換</label>
            <p>変換後 : <span id="changed-hex-value"></span></p>
            <label for="float-text">小数を入力</label>
            <input id="float-text" type="number" name="dec_value" >
            <button id="change_hex_btn">変換</button>
        </div>
        <hr>
        <div class="from-hex-to-float">
            <label>浮動小数点数から数値への変換</label>
            <p>変換後 : <span id="changed-float-value"></span></p>
            <label for="hex-text">浮動小数点数を16進数で入力(0xは含まない)</label>
            <input id="hex-text" type="text" name="hex_value" pattern="^[0-9A-Fa-f]+$">
            <button id="change_float_btn">変換</button>
        </div>

        <script>
            /*数値を浮動小数点数への変換*/
            document.getElementById('change_hex_btn').addEventListener('click' , function(){
                var str_value =  document.querySelector('#float-text').value;
                var str_result = "";
                var change_value = parseFloat(str_value);
                var buff = new ArrayBuffer(8);
                var data_view = new DataView(buff);
                data_view.setFloat32(1,change_value);
                str_result = "0x"+("00" + data_view.getUint8(1).toString(16)).slice(-2)+("00" + data_view.getUint8(2).toString(16)).slice(-2)+("00" + data_view.getUint8(3).toString(16)).slice(-2)+("00" + data_view.getUint8(4).toString(16)).slice(-2);
                document.getElementById('changed-hex-value').innerHTML = str_result;
            });
            /*浮動小数点数から数値への変換*/
            document.getElementById('change_float_btn').addEventListener('click' , function(){
                var str_value = document.querySelector('#hex-text').value;
                var str_result = "";
                if(str_value.length == 8)
                {
                    var buff = new ArrayBuffer(8);
                    var data_view = new DataView(buff);
                    var int_temp = parseInt(str_value,16);
                    data_view.setUint32(1,int_temp);
                    var result = data_view.getFloat32(1);
                    str_result = result.toString();
                }
                else
                {
                    str_result = "文字数が足りません";
                }
                document.getElementById('changed-float-value').innerHTML = str_result;
            });
            /*入力制限*/
            document.getElementById('hex-text').addEventListener('input' , function(event){
                var str_current_input_val = event.target.value;
                var regex = /^[0-9A-Fa-f]+$/;
                console.log(typeof(str_current_input_val));
                if(!regex.test(str_current_input_val))
                {
                    if(str_current_input_val.match(/[^0-9A-Fa-f]/g))
                    {
                        str_current_input_val=str_current_input_val.replace(/[^0-9A-Fa-f]/g,"");
                        if(str_current_input_val.length > 8)
                        {
                            str_current_input_val = str_current_input_val.slice(0,-1);
                        }
                    }
                    event.target.value=str_current_input_val;
                }
            });

        </script>
    </body>
</html>

DataViewを使って変換を行っています。(ちゃんとbit演算を使う方法もありますが・・・簡単のため)

まとめ

今回は小数を表す方法について解説しました。
普段はあまり意識しないことが多いかもしれませんが、知識としては知っておきたいですね。
特に組み込みエンジニアになりたい方は浮動小数点数の表し方や実現方法は知っておいて損はないと思います。
(以前開発で12bitで浮動小数点数を表すということもやりました・・・)

以上、「小数点を含む数値を16進数で表そう!」でした。
最後まで、ご覧いただきありがとうございます。

ABOUT ME
MSK
九州在住の組み込み系エンジニアです。 2児の父親でもあります。 数学やプログラミングが趣味です。 最近RustとReact、結び目理論と曲面結び目理論にはまっています。