Skip to content

Commit c470bcb

Browse files
committed
feat: add [Double] and [Float] array serialization extensions
Add array extensions following the FixedWidthInteger pattern from Standards library: - [Double].swift - Deserialize byte collections to Double arrays - [Float].swift - Deserialize byte collections to Float arrays Features: - Convert flat byte collections to typed arrays - Support for both little-endian and big-endian - Validates byte count is multiple of element size - Preserves special values (infinity, NaN, signed zero) Tests: - 9 tests for [Double] covering single/multiple values, endianness, special values, large arrays - 10 tests for [Float] with same coverage plus mixed values Total: 200 tests passing (was 181)
1 parent 8cb265c commit c470bcb

File tree

4 files changed

+377
-0
lines changed

4 files changed

+377
-0
lines changed

Sources/IEEE_754/[Double].swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// [Double].swift
2+
// swift-ieee-754
3+
//
4+
// Array extensions for Swift standard library Double (IEEE 754 binary64)
5+
6+
import Standards
7+
8+
extension [Double] {
9+
/// Creates an array of Doubles from a flat byte collection
10+
///
11+
/// Converts a collection of bytes in IEEE 754 binary64 format to an array
12+
/// of Double values. Each Double requires exactly 8 bytes.
13+
///
14+
/// - Parameters:
15+
/// - bytes: Collection of bytes representing multiple Doubles
16+
/// - endianness: Byte order of the input bytes (defaults to little-endian)
17+
/// - Returns: Array of Doubles, or nil if byte count is not a multiple of 8
18+
///
19+
/// Example:
20+
/// ```swift
21+
/// let bytes: [UInt8] = [
22+
/// 0x6E, 0x86, 0x1B, 0xF0, 0xF9, 0x21, 0x09, 0x40, // 3.14159
23+
/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F // 1.0
24+
/// ]
25+
/// let doubles = [Double](bytes: bytes)
26+
/// // [3.14159, 1.0]
27+
/// ```
28+
///
29+
/// - Note: Uses ``Double/init(bytes:endianness:)`` under the hood
30+
public init?<C: Collection>(bytes: C, endianness: [UInt8].Endianness = .little)
31+
where C.Element == UInt8 {
32+
let elementSize = MemoryLayout<Element>.size
33+
guard bytes.count % elementSize == 0 else { return nil }
34+
35+
var result: [Element] = []
36+
result.reserveCapacity(bytes.count / elementSize)
37+
38+
let byteArray: [UInt8] = .init(bytes)
39+
for i in stride(from: 0, to: byteArray.count, by: elementSize) {
40+
let chunk: [UInt8] = .init(byteArray[i..<i + elementSize])
41+
guard let element = Element(bytes: chunk, endianness: endianness) else {
42+
return nil
43+
}
44+
result.append(element)
45+
}
46+
47+
self = result
48+
}
49+
}

Sources/IEEE_754/[Float].swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// [Float].swift
2+
// swift-ieee-754
3+
//
4+
// Array extensions for Swift standard library Float (IEEE 754 binary32)
5+
6+
import Standards
7+
8+
extension [Float] {
9+
/// Creates an array of Floats from a flat byte collection
10+
///
11+
/// Converts a collection of bytes in IEEE 754 binary32 format to an array
12+
/// of Float values. Each Float requires exactly 4 bytes.
13+
///
14+
/// - Parameters:
15+
/// - bytes: Collection of bytes representing multiple Floats
16+
/// - endianness: Byte order of the input bytes (defaults to little-endian)
17+
/// - Returns: Array of Floats, or nil if byte count is not a multiple of 4
18+
///
19+
/// Example:
20+
/// ```swift
21+
/// let bytes: [UInt8] = [
22+
/// 0xD0, 0x0F, 0x49, 0x40, // 3.14159
23+
/// 0x00, 0x00, 0x80, 0x3F // 1.0
24+
/// ]
25+
/// let floats = [Float](bytes: bytes)
26+
/// // [3.14159, 1.0]
27+
/// ```
28+
///
29+
/// - Note: Uses ``Float/init(bytes:endianness:)`` under the hood
30+
public init?<C: Collection>(bytes: C, endianness: [UInt8].Endianness = .little)
31+
where C.Element == UInt8 {
32+
let elementSize = MemoryLayout<Element>.size
33+
guard bytes.count % elementSize == 0 else { return nil }
34+
35+
var result: [Element] = []
36+
result.reserveCapacity(bytes.count / elementSize)
37+
38+
let byteArray: [UInt8] = .init(bytes)
39+
for i in stride(from: 0, to: byteArray.count, by: elementSize) {
40+
let chunk: [UInt8] = .init(byteArray[i..<i + elementSize])
41+
guard let element = Element(bytes: chunk, endianness: endianness) else {
42+
return nil
43+
}
44+
result.append(element)
45+
}
46+
47+
self = result
48+
}
49+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// [Double] Tests.swift
2+
// swift-ieee-754
3+
//
4+
// Tests for [Double] array extensions
5+
6+
import Testing
7+
@testable import IEEE_754
8+
9+
@Suite("Array<Double> - Deserialization")
10+
struct DoubleArrayTests {
11+
12+
@Test("Single Double from 8 bytes")
13+
func singleDouble() {
14+
let bytes: [UInt8] = [0x6E, 0x86, 0x1B, 0xF0, 0xF9, 0x21, 0x09, 0x40]
15+
let doubles = [Double](bytes: bytes)
16+
#expect(doubles != nil)
17+
#expect(doubles?.count == 1)
18+
#expect(doubles?[0] == 3.14159)
19+
}
20+
21+
@Test("Multiple Doubles from bytes")
22+
func multipleDoubles() {
23+
let bytes: [UInt8] = [
24+
0x6E, 0x86, 0x1B, 0xF0, 0xF9, 0x21, 0x09, 0x40, // 3.14159
25+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, // 1.0
26+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 // 2.0
27+
]
28+
let doubles = [Double](bytes: bytes)
29+
#expect(doubles != nil)
30+
#expect(doubles?.count == 3)
31+
#expect(doubles?[0] == 3.14159)
32+
#expect(doubles?[1] == 1.0)
33+
#expect(doubles?[2] == 2.0)
34+
}
35+
36+
@Test("Empty bytes returns empty array")
37+
func emptyBytes() {
38+
let bytes: [UInt8] = []
39+
let doubles = [Double](bytes: bytes)
40+
#expect(doubles != nil)
41+
#expect(doubles?.isEmpty == true)
42+
}
43+
44+
@Test("Invalid byte count returns nil")
45+
func invalidByteCount() {
46+
// 7 bytes - not a multiple of 8
47+
let bytes: [UInt8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
48+
let doubles = [Double](bytes: bytes)
49+
#expect(doubles == nil)
50+
}
51+
52+
@Test("Big-endian deserialization")
53+
func bigEndian() {
54+
let bytes: [UInt8] = [
55+
0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18, // 3.141592653589793 (big-endian)
56+
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 1.0 (big-endian)
57+
]
58+
let doubles = [Double](bytes: bytes, endianness: .big)
59+
#expect(doubles != nil)
60+
#expect(doubles?.count == 2)
61+
#expect(doubles?[0] == 3.141592653589793)
62+
#expect(doubles?[1] == 1.0)
63+
}
64+
65+
@Test("Special values - infinity and NaN")
66+
func specialValues() {
67+
let infBytes = Double.infinity.bytes()
68+
let negInfBytes = (-Double.infinity).bytes()
69+
let nanBytes = Double.nan.bytes()
70+
71+
let bytes = infBytes + negInfBytes + nanBytes
72+
let doubles = [Double](bytes: bytes)
73+
74+
#expect(doubles != nil)
75+
#expect(doubles?.count == 3)
76+
#expect(doubles?[0] == .infinity)
77+
#expect(doubles?[1] == -.infinity)
78+
#expect(doubles?[2].isNaN == true)
79+
}
80+
81+
@Test("Special values - signed zero")
82+
func signedZero() {
83+
let posZeroBytes = (0.0 as Double).bytes()
84+
let negZeroBytes = (-0.0 as Double).bytes()
85+
86+
let bytes = posZeroBytes + negZeroBytes
87+
let doubles = [Double](bytes: bytes)
88+
89+
#expect(doubles != nil)
90+
#expect(doubles?.count == 2)
91+
#expect(doubles?[0] == 0.0)
92+
#expect(doubles?[1] == -0.0)
93+
94+
// Verify sign bit is preserved
95+
#expect(doubles?[0].sign == .plus)
96+
#expect(doubles?[1].sign == .minus)
97+
}
98+
99+
@Test("Large array of Doubles")
100+
func largeArray() {
101+
let count = 1000
102+
let originalDoubles = (0..<count).map { Double($0) }
103+
104+
var allBytes: [UInt8] = []
105+
for double in originalDoubles {
106+
allBytes += double.bytes()
107+
}
108+
109+
let deserializedDoubles = [Double](bytes: allBytes)
110+
#expect(deserializedDoubles != nil)
111+
#expect(deserializedDoubles?.count == count)
112+
#expect(deserializedDoubles == originalDoubles)
113+
}
114+
115+
@Test("Round-trip through array serialization")
116+
func roundTrip() {
117+
let original: [Double] = [3.14159, 2.71828, 1.41421, 1.61803]
118+
119+
var bytes: [UInt8] = []
120+
for double in original {
121+
bytes += double.bytes()
122+
}
123+
124+
let roundtripped = [Double](bytes: bytes)
125+
#expect(roundtripped != nil)
126+
#expect(roundtripped == original)
127+
}
128+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// [Float] Tests.swift
2+
// swift-ieee-754
3+
//
4+
// Tests for [Float] array extensions
5+
6+
import Testing
7+
@testable import IEEE_754
8+
9+
@Suite("Array<Float> - Deserialization")
10+
struct FloatArrayTests {
11+
12+
@Test("Single Float from 4 bytes")
13+
func singleFloat() {
14+
let bytes: [UInt8] = [0xD0, 0x0F, 0x49, 0x40]
15+
let floats = [Float](bytes: bytes)
16+
#expect(floats != nil)
17+
#expect(floats?.count == 1)
18+
#expect(floats?[0] == 3.14159)
19+
}
20+
21+
@Test("Multiple Floats from bytes")
22+
func multipleFloats() {
23+
let bytes: [UInt8] = [
24+
0xD0, 0x0F, 0x49, 0x40, // 3.14159
25+
0x00, 0x00, 0x80, 0x3F, // 1.0
26+
0x00, 0x00, 0x00, 0x40 // 2.0
27+
]
28+
let floats = [Float](bytes: bytes)
29+
#expect(floats != nil)
30+
#expect(floats?.count == 3)
31+
#expect(floats?[0] == 3.14159)
32+
#expect(floats?[1] == 1.0)
33+
#expect(floats?[2] == 2.0)
34+
}
35+
36+
@Test("Empty bytes returns empty array")
37+
func emptyBytes() {
38+
let bytes: [UInt8] = []
39+
let floats = [Float](bytes: bytes)
40+
#expect(floats != nil)
41+
#expect(floats?.isEmpty == true)
42+
}
43+
44+
@Test("Invalid byte count returns nil")
45+
func invalidByteCount() {
46+
// 3 bytes - not a multiple of 4
47+
let bytes: [UInt8] = [0x01, 0x02, 0x03]
48+
let floats = [Float](bytes: bytes)
49+
#expect(floats == nil)
50+
}
51+
52+
@Test("Big-endian deserialization")
53+
func bigEndian() {
54+
let bytes: [UInt8] = [
55+
0x40, 0x49, 0x0F, 0xD0, // 3.14159 (big-endian)
56+
0x3F, 0x80, 0x00, 0x00 // 1.0 (big-endian)
57+
]
58+
let floats = [Float](bytes: bytes, endianness: .big)
59+
#expect(floats != nil)
60+
#expect(floats?.count == 2)
61+
#expect(floats?[0] == 3.14159)
62+
#expect(floats?[1] == 1.0)
63+
}
64+
65+
@Test("Special values - infinity and NaN")
66+
func specialValues() {
67+
let infBytes = Float.infinity.bytes()
68+
let negInfBytes = (-Float.infinity).bytes()
69+
let nanBytes = Float.nan.bytes()
70+
71+
let bytes = infBytes + negInfBytes + nanBytes
72+
let floats = [Float](bytes: bytes)
73+
74+
#expect(floats != nil)
75+
#expect(floats?.count == 3)
76+
#expect(floats?[0] == .infinity)
77+
#expect(floats?[1] == -.infinity)
78+
#expect(floats?[2].isNaN == true)
79+
}
80+
81+
@Test("Special values - signed zero")
82+
func signedZero() {
83+
let posZeroBytes = (0.0 as Float).bytes()
84+
let negZeroBytes = (-0.0 as Float).bytes()
85+
86+
let bytes = posZeroBytes + negZeroBytes
87+
let floats = [Float](bytes: bytes)
88+
89+
#expect(floats != nil)
90+
#expect(floats?.count == 2)
91+
#expect(floats?[0] == 0.0)
92+
#expect(floats?[1] == -0.0)
93+
94+
// Verify sign bit is preserved
95+
#expect(floats?[0].sign == .plus)
96+
#expect(floats?[1].sign == .minus)
97+
}
98+
99+
@Test("Large array of Floats")
100+
func largeArray() {
101+
let count = 1000
102+
let originalFloats = (0..<count).map { Float($0) }
103+
104+
var allBytes: [UInt8] = []
105+
for float in originalFloats {
106+
allBytes += float.bytes()
107+
}
108+
109+
let deserializedFloats = [Float](bytes: allBytes)
110+
#expect(deserializedFloats != nil)
111+
#expect(deserializedFloats?.count == count)
112+
#expect(deserializedFloats == originalFloats)
113+
}
114+
115+
@Test("Round-trip through array serialization")
116+
func roundTrip() {
117+
let original: [Float] = [3.14159, 2.71828, 1.41421, 1.61803]
118+
119+
var bytes: [UInt8] = []
120+
for float in original {
121+
bytes += float.bytes()
122+
}
123+
124+
let roundtripped = [Float](bytes: bytes)
125+
#expect(roundtripped != nil)
126+
#expect(roundtripped == original)
127+
}
128+
129+
@Test("Mixed positive and negative values")
130+
func mixedValues() {
131+
let original: [Float] = [-100.5, 0.0, 100.5, -0.0, Float.infinity, -Float.infinity]
132+
133+
var bytes: [UInt8] = []
134+
for float in original {
135+
bytes += float.bytes()
136+
}
137+
138+
let deserialized = [Float](bytes: bytes)
139+
#expect(deserialized != nil)
140+
#expect(deserialized?.count == 6)
141+
142+
if let deserialized = deserialized {
143+
#expect(deserialized[0] == -100.5)
144+
#expect(deserialized[1] == 0.0)
145+
#expect(deserialized[2] == 100.5)
146+
#expect(deserialized[3] == -0.0)
147+
#expect(deserialized[4] == .infinity)
148+
#expect(deserialized[5] == -.infinity)
149+
}
150+
}
151+
}

0 commit comments

Comments
 (0)