こんにちは、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つあります。
- 固定小数点数
- 浮動小数点数
それぞれ見ていきたいと思います。
固定小数点数
小数点をビット列のどの位置に置くかを最初から決めておく方法です。
例えば、先頭6bitを整数として扱うとすると次のようになります。
000000.00
黄色のマーカを引いたところが整数部、ピンクのマーカを引いたところが小数部となります。
この表し方のメリットとしては
- 分かりやすい
- 処理が高速
という点があります。
一方、デメリットとしては
- 表現できる数が少ない
- 精度が低い
ことがあげられます。
デメリットの例として上の000000.00を見ます。
この数値が表すことができる数は0~63.75の範囲で0.25精度です。
範囲も非常に狭く、0.25単位ということで精度も荒いことが分かります。
では、固定小数点数はどんな時に使われているかというと、整数を表す時に使われています。
小数点の位置を最下位bitの右側としています。
00000000.としているわけです。
浮動小数点数
指数表現を使った数値の表現方法です。
コンピューターは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進数で表そう!」でした。
最後まで、ご覧いただきありがとうございます。