Swiftでの浮動小数点数のバイナリ表現の扱い方

バイナリファイルやバイナリデータバッファに浮動小数点数を格納するには、IEEE 754で定義されている方法で符号化します。Swiftでは、BinaryFloatingPointプロトコルでIEEE 754に対応したメソッドなどが定義され、FloatDoubleFloat80BinaryFloatingPointプロトコルを実装しています。

IEEE 754は現代のシステム、プロセッサー、プログラミング言語の多くに採用されています。但し、規格での定義と実際の実装では異なる部分があり、特に、C言語のlong doubleの扱いはOS、コンパイラ、プロセッサーの組み合わせによって規格とは乖離しています。

この記事では、SwiftでIEEE 754に従って、浮動小数点数を符号化してバイナリファイルに書き込む方法、バイナリ化された浮動小数点数を読み込み、FloatDoubleとして扱う方法を解説します。また、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

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

アプリ開発が好きなアプリ開発者。このブログは学習メモを記事にしたテックブログです。仕事ではアプリ開発をメインに、技術書の執筆やセミナーの講師などもしています。業務や著書のサイトはこちらです→ アールケー開発

目次