Swift で 半精度浮動小数点数 (16 bit Float) を扱う

## 半精度浮動小数点数

Float の値を保存する時など、CGFloat だと現状では 64bit、Float でも 32bit です。 0.0〜1.0 とかのたいした精度を必要としない値を大量に保存したい場合などには、GPU などでは普通に使われている 16-bit 精度の浮動小数点を Swift で使いたいところです。しかし残念ながら、公式 API では `Float32` はあっても `Float16` は用意されていません。

![](https://upload.wikimedia.org/wikipedia/commons/2/21/IEEE_754r_Half_Floating_Point_Format.svg)

仕様を調べて、コツコツ実装すればいいかもしれませんが、正直やってられません。

ところが、ふとした事から、Accelerate フレームワークで 変換できる事に気がつきました。そこで、Swift で 独自 `Float16` を実装してみました。

```Float16.swift
import Foundation
import Accelerate

struct Float16: CustomStringConvertible {

	var rawValue: UInt16

	static func float_to_float16(value: Float) -> UInt16 {
		var input: [Float] = [value]
		var output: [UInt16] = [0]
		var sourceBuffer = vImage_Buffer(data: &input, height: 1, width: 1, rowBytes: MemoryLayout<Float>.size)
		var destinationBuffer = vImage_Buffer(data: &output, height: 1, width: 1, rowBytes: MemoryLayout<UInt16>.size)
		vImageConvert_PlanarFtoPlanar16F(&sourceBuffer, &destinationBuffer, 0)
		return output[0]
	}

	static func float16_to_float(value: UInt16) -> Float {
		var input: [UInt16] = [value]
		var output: [Float] = [0]
		var sourceBuffer = vImage_Buffer(data: &input, height: 1, width: 1, rowBytes: MemoryLayout<UInt16>.size)
		var destinationBuffer = vImage_Buffer(data: &output, height: 1, width: 1, rowBytes: MemoryLayout<Float>.size)
		vImageConvert_Planar16FtoPlanarF(&sourceBuffer, &destinationBuffer, 0)
		return output[0]
	}

	static func floats_to_float16s(values: [Float]) -> [UInt16] {
		var inputs = values
		var outputs = Array<UInt16>(repeating: 0, count: values.count)
		let width = vImagePixelCount(values.count)
		var sourceBuffer = vImage_Buffer(data: &inputs, height: 1, width: width, rowBytes: MemoryLayout<Float>.size * values.count)
		var destinationBuffer = vImage_Buffer(data: &outputs, height: 1, width: width, rowBytes: MemoryLayout<UInt16>.size * values.count)
		vImageConvert_PlanarFtoPlanar16F(&sourceBuffer, &destinationBuffer, 0)
		return outputs
	}

	static func float16s_to_floats(values: [UInt16]) -> [Float] {
		var inputs: [UInt16] = values
		var outputs: [Float] = Array<Float>(repeating: 0, count: values.count)
		let width = vImagePixelCount(values.count)
		var sourceBuffer = vImage_Buffer(data: &inputs, height: 1, width: width, rowBytes: MemoryLayout<UInt16>.size * values.count)
		var destinationBuffer = vImage_Buffer(data: &outputs, height: 1, width: width, rowBytes: MemoryLayout<Float>.size * values.count)
		vImageConvert_Planar16FtoPlanarF(&sourceBuffer, &destinationBuffer, 0)
		return outputs
	}

	init(_ value: Float) {
		self.rawValue = Float16.float_to_float16(value: value)
	}

	var floatValue: Float {
		return Float16.float16_to_float(value: self.rawValue)
	}

	var description: String {
		return self.floatValue.description
	}

	static func + (lhs: Float16, rhs: Float16) -> Float16 {
		return Float16(lhs.floatValue + rhs.floatValue)
	}

	static func - (lhs: Float16, rhs: Float16) -> Float16 {
		return Float16(lhs.floatValue - rhs.floatValue)
	}

	static func * (lhs: Float16, rhs: Float16) -> Float16 {
		return Float16(lhs.floatValue * rhs.floatValue)
	}

	static func / (lhs: Float16, rhs: Float16) -> Float16 {
		return Float16(lhs.floatValue / rhs.floatValue)
	}
}
```
<font color="silver">Jan 2017: 複数のまとめて変換するコードを追加しました。</font>

## 実行結果

実行結果は以下のとおりです。やはり精度に問題はありますが、分かって使うには使えそうです。

```.swift
let a = Float16(0.5) // 0.5
let b = Float16(1000.5) // 1000.5
let c = Float16(1.0 / 32768.0) // 3.05176e-05
let d = Float16(16000.0) // 16000.0
let e = Float16(17000.0) // 16992.0
let f = Float16(20.0 / 0.0) // inf
let g = Float16(0.3) // 0.300049
let h = Float16(Float.pi) // 3.14062
```

## Gist

ソースは [Gist](https://gist.github.com/codelynx/eeaeeda00828568aaf577c0341964c38) からも入手可能になっています。

## 最後に

`Float16` という名前は将来本家に `Float16` がやって来たときに（え！ないって？）衝突する可能性があるので、かっこ悪くても、`MyFloat16` とかするのがいいかもしれません。

冒頭で、大量に変換する必要がある場合と言いつつ、実は `vImageConvert_PlanarFtoPlanar16F()` や `vImageConvert_Planar16FtoPlanarF()` は一気にまとめて変換できたりします。なのに、`Float16` では一個一個しか変換しないのは、どうなの？と突っ込みが入りそうですが、そこはシレっと無視する事にします。

## 環境表示

```.log
Xcode Version 8.0 (8A218a)
Apple Swift version 3.0 (swiftlang-800.0.46.2 clang-800.0.38)
```