[応用編]JavaScriptでバイナリデータを扱ってみる~IEEE-754とIEEE-11073の浮動小数点~(2/3)

[BRIGHT VIE Advent Calendar 2017 - Qiita](https://qiita.com/advent-calendar/2017/brightvie) の5日目です！

「JavaScriptでバイナリデータを扱ってみる」シリーズでお届けしておりますが、本日は応用編2回目！

最近では、「Web Bluetooth API」も普及しつつあり、
BLE経由でWebサイトとデバイス間でのデータのやり取りが可能となってきているため、
データを扱うときの参考になれば嬉しいです。

なお、ここまでの記事は下記をご参考下さい。

- [[基礎編]JavaScriptでバイナリデータを扱ってみる](https://qiita.com/megadreams14/items/dded3cf770010bb8ff08)
- [[応用編]JavaScriptでバイナリデータを扱ってみる~Bluetoothの温度データ形式を理解する~(1/3)](https://qiita.com/megadreams14/items/5e0faae03fdfbca2f7fa)]


# はじめに

昨日はBluetooth経由で温度データを受信する際の共通フォーマット及び処理フローを理解しました。
その中で、温度のデータフォーマットである浮動小数点数において
「IEEE-754で定義されているfloat」と「IEEE-11073で定義されているFLOAT」があることが分かり、
本日はその疑問を解消するとともに実際の生データを処理してみたいと思います。


# 浮動小数点数とは

浮動小数点とは、2進数(0と1)しか扱えないコンピュータの世界において、小数を表現するために定められた方式のことを指します。
小数点の位置を固定せずに表現することが出来るため、浮動小数点と言われており、非常に大きな数から小さな数まで表すことができます。

図に表すと下記のように、「仮数部」 ×　「基数部」 ^ 「指数部」の形で表現されます。

<img width="249" alt="仮数_基数_指数.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/a58abed7-a379-cb64-fe77-d2f329eb6861.png">

詳細は他の記事をご参考に

- 参考
   - [浮動小数点型の数値はメモリ上でどのように格納されているのか](https://qiita.com/nia_tn1012/items/340a1f0ad71bf6085f7f)
   - [ビットで表す数字の世界～浮動小数点編～](http://www.altima.jp/column/fpga_edison/bit_number_float.html)
   - [【5分で覚えるIT基礎の基礎】ゼロから学ぶ2進数　第4回 2進数で小数を表す方法](http://itpro.nikkeibp.co.jp/members/ITPro/ITBASIC/20020624/1/?rt=nocnt)


# IEEE-754 と IEEE-1107

## IEEE-754の浮動小数点数について

浮動小数点数の計算で最も広く採用されている標準規格です。
基本形式としては、基数が二進形式が3種類、十進形式が2種類の計5種類が指定されています。

| 形式名 | 一般名	| 基数 | 備考 |
|:-:|:-:|:-:|:-:|
| binary32   | 単精度 | 2 | 符号ビット: 1ビット、指数部: 8ビット、仮数部: 23ビット |
| binary64   | 倍精度 | 2 | 符号ビット: 1ビット、指数部: 11ビット、仮数部: 52 ビット |
| binary128  | 四倍精度 | 2 | 符号ビット: 1ビット、指数部: 15ビット、仮数部: 112 ビット |
| decimal64  | 十進倍精度 | 10 | - |
| decimal128 | 十進四倍精度 | 10 | - |
 ※ binary16の半精度 と decimal32の十進単精度は、交換形式であって基本形式ではないようです

十進倍精度や十進四倍精度などについては調べきれていませんが、
binary32（単精度浮動小数点数)の場合で例にすると下記のようになります。

<img width="1069" alt="[binary32] IEEE-754 単精度浮動小数点数.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/adf5f5cb-366a-cb67-23d2-81934e00a418.png">


- 参考
   - [754-2008 - IEEE Standard for Floating-Point Arithmetic](http://ieeexplore.ieee.org/document/4610935/)
   - [Online converter for IEEE 754 numbers with single precision](http://www.binaryconvert.com/result_float.html?decimal=045049048046049)

 
## IEEE-11073の浮動小数点について

IEEE-11073はパブリックドメインではないため、なかなか調べるのに苦戦しましたが
下記のやりとりから自分なりに解釈してみました。

- 参考
   - [How to convert IEEE-11073 16-bit SFLOAT to simple float in Java -
 stackoverflow](https://stackoverflow.com/questions/11564270/how-to-convert-ieee-11073-16-bit-sfloat-to-simple-float-in-java)
   - [Swift3 で CoreBluetooth 実装するときにあったら良さそうな Data Extension](http://tech.unifa-e.com/entry/2017/09/14/181638)
   - [IOS/IEEE 11073 個人健康データチュートリアル](http://www.presentica.com/ppt-presentation/iso-262252)


全ては網羅できませんでしたが、Bluethoothなどで温度をやり取りするためのフォーマットとしては、下記の2種類になるのかなと思っております。

## SFLOATの場合(IEEE-11073 16-bit SFLOAT)

- 16ビット
- 基数部が10
- 指数部が4ビット
- 仮数部が12ビット
- 仮数部、指数部ともに2の補数で表現

<img width="729" alt="SFLOATの場合(IEEE-11073 16-bit SFLOAT).png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/fa2f455e-d0e7-5b3d-a427-5e3503da7152.png">


## FLOATの場合(IEEE-11073 32-bit FLOAT)
- 32ビット
- 基数部が10
- 指数部が8ビット
- 仮数部が24ビット
- 仮数部、指数部ともに2の補数で表現

<img width="1072" alt="FLOATの場合(IEEE-11073 32-bit FLOAT).png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/a464cfa4-a928-90aa-6691-6444da8f2cc6.png">



## 結局違いって！？

BLEのFormat Typeに記載されている浮動小数点型を比較して
一番単純に違いを考えるならば基数部が2の場合がIEEE-754で定められた浮動小数点、
基数部が10の場合がIEEE-11073で定められた浮動小数点ということになるかと思います。

また、計算方法としても符号ビットを利用するのか、2の補数を利用するのかとかでしょうか？
（IEEE-754の「十進倍精度」などがこれに当てはまるのかどうかまでは分かっていないですが...詳しい人教えてください...><


# 計算方法

では実際のデータを扱いながら、それぞれの定義で処理がどのように変わるのかみてみましょう。
なお、IEEE-754の「binary32」や「binary64」については、DateViewクラスのgetFloat32やgetFloat64で扱い可能なので、ここでの詳細は割愛させていただきます。

## サンプルデータで検証

とりあえずサンプルデータを確認します。

| 型 | bit数 | メモリ上での表記(16進数表記) | 実数値|
|:--:|:--:|:--:|:--:|
| FLAOT(IEEE-11073 32-bit FLOAT) |  32bit | 0x69 0x01 0x00 0xFF | 36.1
| FLOAT(IEEE-11073 32-bit FLOAT) |  32bit | 0x1F 0x0E 0x00 0xFE | 36.15
| SFLOAT(IEEE-11073 16-bit SFLOAT) |  16bit |  0x72 0x00 |  114 |

上記の「メモリ上での表記」を与えた場合に、「実数値」が正しく計算されるようなロジックを組んでいきます。


## FLAOT(IEEE-11073 32-bit FLOAT)の計算方法

「36.1」を表す「0x69 0x01 0x00 0xFF」というバイナリデータを扱ってみます。

### 手順1. 2進数の表記を確認

<img width="1095" alt="手順1_16進数の値を2進数に変換しメモリ上での管理状態を可視化.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/f0d94998-4e24-cc85-7792-5cb559c2fac2.png">

なおJavaScriptで算出する場合、下記のようにすれば16進数を2進数で表現することが可能です。

```JavaScript
// 16進数[0x69 0x01 0x00 0xFF]を2進数に変換する
> (0x690100FF).toString(2);
1101001000000010000000011111111
```

### 手順2. バイトオーダーがリトルエンディアンの場合はメモリ上の状態を反転する

これはプロセッサによって変わる部分だとは思いますが、（IEEE-11073で定められているのかまでは分かっていないです...）
今回はリトルエンディアン方式のためメモリ上の並び順を反転します。

<img width="1096" alt="手順2_リトルエンディアン表記の場合、メモリの状態を反転する.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/2443e490-7692-58c1-f1bc-b0b7386d3e81.png">


### 手順3. 指数部を求める

今回は「32ビット」のFLOATであるため、「指数部が8ビット」「仮数部が24ビット」です。
そのため、上位8ビットが指数部となります。

<img width="1094" alt="サンプルデータ_FLAOT(IEEE-11073 32-bit FLOAT)の場合.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/cb1620dd-3303-e3cf-656e-afda8c6829b0.png">

上記を元に指数部の値を求めてみます。

<img width="1099" alt="手順3_指数部を求める.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/d29b140d-8bb6-4b91-466f-7360753c5a3f.png">

2の補数で表されているため、変換を行った結果、「指数部 = -1」ということがわかりました。

※ 10進数のマイナス値を2の補数に変換するときは、2進数に直す -> ビット反転する -> 1を加える という手順で行うため、2進数から10進数を求める場合はその逆の手順で求める。

- 参考:2の補数について
   - [2の補数についてわかりやすく](https://qiita.com/YamadaTakahito/items/79693ce08cad8baf130b)
   - [2の補数とは？ - Web便利ノート](https://note.cman.jp/convert/2hosu/)

### 手順4. 仮数部を求める

指数部を求めた時と同様ですが、32ビットFLOATの場合「指数部が8ビット」「仮数部が24ビット」なので、
下位24ビットが仮数部となります。

<img width="1093" alt="手順4. 仮数部を求める.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/b7a36a58-c3ae-c05d-db04-3069882f57db.png">


上記計算式より、仮数部は「361」ということがわかりました。

### 手順5. 数式に当てはめて実数を求める

上記の計算結果より

- 基数部: 10
- 指数部： -1
- 仮数部： 361

<img width="333" alt="361*10^-1.png" src="https://qiita-image-store.s3.amazonaws.com/0/17346/84d1d831-9df5-ed05-d17b-b59da889da39.png">


となり、「0x69 0x01 0x00 0xFF」というバイナリデータが「36.1」になることがわかりました。

なお、SFLOATの場合はビットの計算位置が違うのみで、
計算の流れはほぼおなじのため割愛します。


# JavaScriptで実装してみる

## IEEE-11073 32-bit FLOAT型

それでは、上記の処理を実装してみましょう。

```JavaScript
/**
 * IEEE-11073 32-bit FLOAT型をバイナリから取得し実数に変換する
 * 
 * @param dv:DataView   32ビットのバイナリデータ
 * @param littleEndian  バイトオーダーの扱い(デフォルトは、fakse = ビッグエンディアン方式：)
 */
function readFLOAT(dv, littleEndian = false) {

  // 1. バイトオーダーを指定して32ビットデータを取得
  var data = dv.getUint32(0, littleEndian);

  // 2. 仮数部を取得する(32ビットの場合は下位24ビットが仮数部)
  //    - 取得した32ビットデータに下位24ビットの全てのフラグを立てた値を掛け算することで取得
  var mantissa = (data & 0x00FFFFFF);

  // 仮数部が2の補数かどうかを判定する
　　　　// 下位24ビットのうちの上位1ビット目にフラグが立っていれば2の補数
  if ((mantissa & 0x00800000) > 0) {
    // 負の値の場合は、2の補数を用いて数値を求める
    mantissa = -1 * (~(mantissa - 0x01) & 0x00FFFFFF)
  }

  // 3. 指数部を取得する(32ビットの場合は上位8ビットが指数部)
  // 上位8ビットを取得するためには、32ビットの場合右へ24ビットシフトさせると取得可能
  // なお、JavaScriptの場合「>>」を利用すると、符号を維持する右シフトであるため、 
  // ビット演算を用いると2の補数も考慮して10進数に変換してくれる
  var exponential = data >> 24;

  // 「仮数部 × 基数部 ^ 指数部」の公式に当てはめて変換
  return mantissa * Math.pow(10, exponential);
}
```

正しい値が取得できるかチェック

```JavaScript
/**
 *  「0x69 0x01 0x00 0xFF」が「36.1」であることを確認する
 */
// 32ビットのバッファ領域を確保
var bufferA = new ArrayBuffer(4);
// 32ビット データを書き込み
var dvA = new DataView(bufferA);
// 「0x69 0x01 0x00 0xFF」を書き込み
dvA.setUint32(0, 0x690100FF);
var littleEndian = true;

var resultA = readFLOAT(dvA, littleEndian);
console.log(resultA);   // 36.1と表示される

/**
 *  「0x1F 0x0E 0x00 0xFE」が「36.15」であることを確認する
 */
var bufferB = new ArrayBuffer(4);
var dvB = new DataView(bufferB);
// 「0x1F 0x0E 0x00 0xFE」を書き込み
dvB.setUint32(0, 0x1F0E00FE);
var littleEndian = true;

var resultB = readFLOAT(dvB, littleEndian);
console.log(resultB);   // 36.15と表示される
```


## IEEE-11073 32-bit SFLOAT型

ロジックは、FLOAT型と同じですがビット数が違ったりするので念のため


```JavaScript
/**
 * IEEE-11073 16-bit SFLOAT型をバイナリから取得し実数に変換する
 * 
 * @param dv:DataView   16ビットのバイナリデータ
 * @param littleEndian  バイトオーダーの扱い(デフォルトは、fakse = ビッグエンディアン方式：)
 */
function readSFLOAT(dv, littleEndian = false) {
  // 1. バイトオーダーを指定して16ビットデータを取得
  var data = dv.getUint16(0, littleEndian);

  // 2. 仮数部を取得する(16ビットの場合は下位12ビットが仮数部)
  //    - 取得した16ビットデータに下位12ビットの全てのフラグを立てた値を掛け算することで取得
  var mantissa = (data & 0x0FFF);

  // 仮数部も2の補数で判定するため16ビットのうちの12ビット中の上位1桁がマイナスを表していないかを確認
  if ((mantissa & 0x0800) > 0) {
      // もしマイナス値の場合は、1を引いた後反転処理と12ビット分の論理和を取得する)
      mantissa = -1 * (~(mantissa - 0x01) & 0x0FFF)
  }

  // 3. 指数部を取得する(16ビットの場合は上位4ビットが指数部)
  // 上位4ビットを取得するためには16ビットの場合右へ12ビットシフトさせると取得可能
  var exponential = data >> 12;

  // 「仮数部 × 基数部 ^ 指数部」の公式に当てはめて変換
  return mantissa * Math.pow(10, exponential);
}
```


正しい値が取得できるかチェック

```JavaScript
/**
 *  「0x72 0x00」が「114」であることを確認する
 */
// 16ビットのバッファ領域を確保
var buffer = new ArrayBuffer(2);
// 16ビットのデータを書き込み
var dv = new DataView(buffer);

// 144である「0x72 0x00」を書き込み（なお符号なしで書き込む）
dv.setUint16(0, 0x7200);
var littleEndian = true;

var result = readSFLOAT(dv, littleEndian);
console.log(result);   // 114と表示される
```


とりあえず上記処理にてサンプルに記載されていた値を入れてみたところ正常な実数が返ってきたのであっているのかなぁと
（仮数部がマイナスのときの動きが若干怪しい気もしていますが...）

ひとまずこれでBLEから取得したデータを読み解くことはできそうなので安心

# まとめ

さて、JavaScriptでBLEで定義されている浮動小数点型のデータを扱う場合をまとめると、下記のようになるのかな...

|Format|short Name| Description | 計算方法 |
|:-:|:-:|:-:|:-:|
| 0x14 | float32 | IEEE-754 32-bit floating point | DataViewクラスのgetFloat32/setFloat32 |
| 0x15 | float64 | IEEE-754 64-bit floating point | DataViewクラスのgetFloat64/setFloat64 |
| 0x16 | SFLOAT  | IEEE-11073 16-bit SFLOAT | 上記計算方法 |
| 0x17 | FLOAT	 | IEEE-11073 32-bit FLOAT | 上記計算方法 |
[引用元][Format Types - Bluetooth](https://www.bluetooth.com/specifications/assigned-numbers/format-types)

(※ 間違っていたら指摘下さい...m(_ _)m)

組み込み系のエンジニアの方なら当たり前のことかもしれませんが、
Web系出身のエンジニアからするとあまり馴染みが無いので、とても新鮮でした。
（昔は資格を取得することを目的にIPAの「基本情報」や「応用情報」を勉強しておりましたが、やはり勉強しておいてよかったと今になって思います。）


さて、明日はいよいよ最終回。
実際にBluetoothから受信した生データを利用して、温度を解析してみたいと思います。


# 参考

- [How to convert IEEE-11073 16-bit SFLOAT to simple float in Java? - stack overflow](https://stackoverflow.com/questions/11564270/how-to-convert-ieee-11073-16-bit-sfloat-to-simple-float-in-java)
- [ IEEE 11073 32bit float format-  National Instruments](https://forums.ni.com/t5/LabVIEW/IEEE-11073-32bit-float-format/td-p/3283084)