How to use binary representation of floating point numbers in Swift

To store floating-point numbers in binary files or binary data buffers, they are encoded as defined by IEEE 754; in Swift, the BinaryFloatingPoint protocol defines methods, etc., to support IEEE 754. For example, float, Double, and Float80 implement the BinaryFloatingPoint protocol.

Many modern systems, processors, and programming languages have adopted IEEE 754. However, disparities exist between the standard’s definition and its implementation. In particular, handling long double in the C language deviates from the standard depending on the OS, compiler, and processor combination.

This article explains how to encode floating-point numbers and write them to a binary file in Swift, according to IEEE 754. It also elucidates how to interpret binarized floating-point numbers as Float or Double. We will also read binary files created in Swift with C programs and binary files written in C programs with Swift to ensure that other programming languages are compatible.

TOC

Single precision floating point number

Single-precision floating-point numbers are a 32-bit wide format for representing floating-point numbers, as implemented by Float in Swift. The information represented by each bit is shown in the table below.

Start bit position Bit widthInformation to be stored
0 22 Mantissa
23 8 Index. Original value plus 127.
31 1 Sign. The stand bit for negative values.

Binaryizing Float in Swift

The BinaryFloatingPoint protocol possesses a property to extract each piece of information. You can create a 32-bit bit pattern from the retrieved value. Still, there is a more convenient way: using the Float.bitPattern property, you can retrieve the encoded 32-bit value.

var bitPattern: UInt32 { get }

For example, the ensuing code inscribes two Float values to the FloatValues file on the desktop.

import Foundation

// Encode Float to IEEE 754 and convert to UInt32
let packedValues: [UInt32] = [Float(123.456).bitPattern,
                              Float(-456.789).bitPattern]

// Create binary data with encoded values.
let data = Data(bytes: packedValues, count: MemoryLayout<UInt32>.size * packedValues.count)

// Write to a file
do {
    let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("FloatValues")
    try data.write(to: url)
} catch let error {
    print(error)
}

The C code reads this written file and outputs the read value to the console; it is encoded according to IEEE 754, so the C language can treat the binary read as a float as is.

#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;
}

The console outputs the following. Since this is a floating-point number, it is not an exact match to the literal value written in the code but an approximation, but this is an event unrelated to the binaryization of this article. The same is true when a literal value is substituted.

123.456001, -456.789001

Read Float from Binary in Swift

When creating a Float from a binary, use the following initializer to specify the bit pattern.

init(bitPattern: UInt32)

The following code reads the FloatValues file in the desktop and outputs the read values to the console.

import Foundation

do {
    // Read from a file
    let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("FloatValues")
    
    let data = try Data(contentsOf: url)
    
    // Access read data
    data.withUnsafeBytes() {
        // Access as an array of 32-bit integers
        let packedValues = $0.bindMemory(to: UInt32.self)
        
        // Convert to Float
        let floatValues = [Float(bitPattern: packedValues[0]),
                           Float(bitPattern: packedValues[1])]
        
        // Output to console
        print(floatValues[0])
        print(floatValues[1])
    }
    
} catch let error {
    print(error)
}

The console outputs the following.

123.456
-456.789

Double precision floating point number

Double-precision floating-point numbers are a 64-bit wide representation of floating-point numbers, as implemented by Double in Swift. The information represented by each bit is shown in the table below.

Start bit positionBit widthInformation to be stored
0 52 Mantissa
52 11 Index. Original value plus 1023.
63 1 Sign. Stand bit for negative values..

Binaryizing Double in Swift

Double also has a property to get the bit pattern.

var bitPattern: UInt64 { get }

Since it is double precision, 64-bit integers can be obtained. Let’s check it out with the following code.

import Foundation

// Encode Double to IEEE 754 and convert to UInt64
let packedValues: [UInt64] = [Double(123.456).bitPattern,
                              Double(-456.789).bitPattern]

// Create binary data with encoded values.
let data = Data(bytes: packedValues, count: MemoryLayout<UInt64>.size * packedValues.count)

// Write to a file
do {
    let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("DoubleValues")
    try data.write(to: url)
} catch let error {
    print(error)
}

The following is C code that reads a DoubleValues file and outputs the values to the console. The code assumes that double is read.

#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;
}

The console outputs the following.

123.456000, -456.789000

Read Double from Binary in Swift

When creating a Double from a binary, use an initializer that specifies a bit pattern; 64-bit integers can be specified.

init(bitPattern: UInt64)

The following code reads the DoubleValues file on the desktop and outputs the read values to the console.

import Foundation

do {
    // ファイルから読み込む
    let url = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("DoubleValues")
    
    let data = try Data(contentsOf: url)
    
    // Access read data
    data.withUnsafeBytes() {
        // Access as an array of 64-bit integers
        let packedValues = $0.bindMemory(to: UInt64.self)
        
        // Convert to Double
        let doubleValues = [Double(bitPattern: packedValues[0]),
                            Double(bitPattern: packedValues[1])]
        
        // Output to console
        print(doubleValues[0])
        print(doubleValues[1])
    }
    
} catch let error {
    print(error)
}

The console outputs the following.

123.456
-456.789

Authored Books

Let's share this post !

Author of this article

Akira Hayashi (林 晃)のアバター Akira Hayashi (林 晃) Representative(代表), Software Engineer(ソフトウェアエンジニア)

アールケー開発代表。Appleプラットフォーム向けの開発を専門としているソフトウェアエンジニア。ソフトウェアの受託開発、技術書執筆、技術指導・セミナー講師。note, Medium, LinkedIn
-
Representative of RK Kaihatsu. Software Engineer Specializing in Development for the Apple Platform. Specializing in contract software development, technical writing, and serving as a tech workshop lecturer. note, Medium, LinkedIn

TOC