To store floating-point numbers in binary files or binary data buffers, they are encoded in the manner defined by IEEE 754; in Swift, the BinaryFloatingPoint
protocol defines methods, etc., to support IEEE 754. Float, Double, and Float80 implements the BinaryFloatingPoint
protocol.
IEEE 754 has been adopted by many modern systems, processors, and programming languages. However, there are differences between the definition in the standard and the actual implementation. In particular, the handling of long double
in the C language deviates from the standard depending on the combination of OS, compiler, and processor.
This article explains how to encode floating-point numbers and write them to a binary file in Swift according to IEEE 754, and how to read the binaryized floating-point numbers and treat them 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 also compatible.
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 width | Information to be stored |
---|---|---|
0 | 22 | Mantissa |
23 | 8 | Index. Original value plus 127 . |
31 | 1 | Sign. Stand bit for negative values. |
Binaryizing Float in Swift
The BinaryFloatingPoint
protocol has a property to retrieve each piece of information, and you can create a 32-bit
bit pattern from the retrieved value, but 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 following code writes two Float
to the desktop in a file called FloatValues
.
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 following code is C code that 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 desktop FloatValues
file 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 position | Bit width | Information 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 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