バイナリファイルやバイナリデータバッファに浮動小数点数を格納するには、IEEE 754で定義されている方法で符号化します。Swiftでは、BinaryFloatingPoint
プロトコルでIEEE 754に対応したメソッドなどが定義され、Float
、Double
、Float80
がBinaryFloatingPoint
プロトコルを実装しています。
IEEE 754は現代のシステム、プロセッサー、プログラミング言語の多くに採用されています。但し、規格での定義と実際の実装では異なる部分があり、特に、C言語のlong double
の扱いはOS、コンパイラ、プロセッサーの組み合わせによって規格とは乖離しています。
この記事では、SwiftでIEEE 754に従って、浮動小数点数を符号化してバイナリファイルに書き込む方法、バイナリ化された浮動小数点数を読み込み、Float
やDouble
として扱う方法を解説します。また、Swiftで作成したバイナリファイルをC言語のプログラムで読み込み、C言語のプログラムで書き込んだバイナリファイルをSwiftで読み込み、他のプログラミング言語も互換性があることを確認します。
単精度浮動小数点数
単精度浮動小数点数は、32ビット幅で浮動小数点数を表現する形式です。SwiftではFloat
が実装しています。各ビットが表現する情報は次の表のようになっています。
開始ビット位置 | ビット幅 | 格納される情報 |
---|---|---|
0 | 22 | 仮数 |
23 | 8 |
指数。本来の値に127 を加算した値
|
31 | 1 | 符号。負の値のときはビットを立てる |
SwiftでFloatをバイナリ化する
BinaryFloatingPoint
のプロトコルに各情報を取得するプロパティがあり、取得した値から32bitのビットパターンを作成するという方法もあるのですが、もっと、手軽な方法があります。Float.bitPattern
プロパティを使用すると、符号化した状態の32bit値を取得できます。
var bitPattern: UInt32 { get }
例えば、次のコードは2つのFloat
をデスクトップにFloatValues
というファイルを作って書き込むというコードです。
import Foundation
// FloatをIEEE 754に符号化してUInt32に変換する
let packedValues: [UInt32] = [Float(123.456).bitPattern,
Float(-456.789).bitPattern]
// 符号化した値を入れたバイナリデータを作成する
let data = Data(bytes: packedValues, count: MemoryLayout<UInt32>.size * packedValues.count)
// ファイルに書き出す
do {
let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
.appendingPathComponent("FloatValues")
try data.write(to: url)
} catch let error {
print(error)
}
次のコードは、この書き込まれたファイルを読み込んで、読み込まれた値をコンソールに出力するC言語のコードです。IEEE 754に従って符号化されているので、C言語では読み込んだバイナリをそのままfloat
として扱えています。
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, const char * argv[]) {
struct passwd *pw = getpwuid(getuid());
char path[1024] = {};
strcpy(path, pw->pw_dir);
strcat(path, "/Desktop/FloatValues");
FILE *fp = fopen(path, "rb");
if (fp != NULL) {
float values[2] = {};
fread(values, sizeof(values), 1, fp);
fclose(fp);
printf("%f, %f\n", values[0], values[1]);
}
return 0;
}
コンソールには次のように出力されます。浮動小数点数なのでコードに書いたリテラル値とは完全一致とはならず、近似値となっていますが、これは本記事のバイナリ化とは無関係の事象です。リテラル値を代入しても同様になります。
123.456001, -456.789001
SwiftでバイナリからFloatを読み込む
バイナリからFloat
を作成するときには、次のイニシャライザを使用して、ビットパターンを指定します。
init(bitPattern: UInt32)
次のコードは、デスクトップのFloatValues
ファイルを読み込み、読み込んだ値をコンソールに出力します。
import Foundation
do {
// ファイルから読み込む
let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
.appendingPathComponent("FloatValues")
let data = try Data(contentsOf: url)
// 読み込んだデータにアクセスする
data.withUnsafeBytes() {
// 32bit整数の配列としてアクセスする
let packedValues = $0.bindMemory(to: UInt32.self)
// Floatに変換する
let floatValues = [Float(bitPattern: packedValues[0]),
Float(bitPattern: packedValues[1])]
// コンソールに出力する
print(floatValues[0])
print(floatValues[1])
}
} catch let error {
print(error)
}
コンソールには次のように出力されます。
123.456
-456.789
倍精度浮動小数点数
倍精度浮動小数点数は、64ビット幅で浮動小数点数を表現する形式です。SwiftではDouble
が実装しています。各ビットが表現する情報は次の表のようになっています。
開始ビット位置 | ビット幅 | 格納される情報 |
---|---|---|
0 | 52 | 仮数 |
52 | 11 |
指数。本来の値に1023 を加算した値
|
63 | 1 | 符号。負の値のときはビット立てる |
SwiftでDoubleをバイナリ化する
Double
もビットパターンを取得するプロパティがあるので、それを使います。
var bitPattern: UInt64 { get }
倍精度なので64ビット整数が取得できるようになっています。次のようなコードで確かめてみましょう。
import Foundation
// DoubleをIEEE 754に符号化してUInt64に変換する
let packedValues: [UInt64] = [Double(123.456).bitPattern,
Double(-456.789).bitPattern]
// 符号化した値を入れたバイナリデータを作成する
let data = Data(bytes: packedValues, count: MemoryLayout<UInt64>.size * packedValues.count)
// ファイルに書き出す
do {
let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
.appendingPathComponent("DoubleValues")
try data.write(to: url)
} catch let error {
print(error)
}
次のコードは、DoubleValues
ファイルを読み込んで、値をコンソールに出力するC言語のコードです。double
が読み込まれることを前提としたコードになっています。
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, const char * argv[]) {
struct passwd *pw = getpwuid(getuid());
char path[1024] = {};
strcpy(path, pw->pw_dir);
strcat(path, "/Desktop/DoubleValues");
FILE *fp = fopen(path, "rb");
if (fp != NULL) {
double values[2] = {};
fread(values, sizeof(values), 1, fp);
fclose(fp);
printf("%f, %f\n", values[0], values[1]);
}
return 0;
}
コンソールには次のように出力されます。
123.456000, -456.789000
SwiftでバイナリからDoubleを読み込む
バイナリからDouble
を作成するときには、ビットパターンを指定するイニシャライザを使用します。64ビット整数を指定できるようになっています。
init(bitPattern: UInt64)
次のコードは、デスクトップのDoubleValues
ファイルを読み込み、読み込んだ値をコンソールに出力します。
import Foundation
do {
// ファイルから読み込む
let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
.appendingPathComponent("DoubleValues")
let data = try Data(contentsOf: url)
// 読み込んだデータにアクセスする
data.withUnsafeBytes() {
// 64bit整数の配列としてアクセスする
let packedValues = $0.bindMemory(to: UInt64.self)
// Doubleに変換する
let doubleValues = [Double(bitPattern: packedValues[0]),
Double(bitPattern: packedValues[1])]
// コンソールに出力する
print(doubleValues[0])
print(doubleValues[1])
}
} catch let error {
print(error)
}
コンソールには次のように出力されます。
123.456
-456.789