Skip to content

Commit 5524b09

Browse files
committed
Add scoped API for FPU rounding mode control
Introduces safer closure-based APIs for rounding mode and exception state management in CIEEE754, eliminating the need for manual defer blocks. New APIs: - withRoundingMode(_:_:) - Executes closure with specific rounding mode - withClearedExceptions(_:) - Executes closure with cleared exceptions - withRoundingModeAndClearedExceptions(_:_:) - Combines both Benefits: - Guarantees cleanup through structured programming - Prevents FPU state pollution between tests - More ergonomic than manual defer blocks - Automatically handles errors and early returns Implementation: - Created Sources/IEEE 754/CIEEE754+Extensions.swift - Internal visibility (C types aren't public) - Uses defer for guaranteed cleanup - Fully documented with usage examples Test Updates: - Refactored CIEEE754 Integration Tests to use scoped API - Cleaner test code without repetitive defer blocks - Better isolation guarantees All 707 tests passing.
1 parent e2d4ebd commit 5524b09

File tree

2 files changed

+157
-48
lines changed

2 files changed

+157
-48
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// CIEEE754+Extensions.swift
2+
// swift-ieee-754
3+
//
4+
// Swift convenience wrappers for CIEEE754 C APIs
5+
//
6+
// Provides scoped APIs that guarantee cleanup and prevent test pollution
7+
8+
#if canImport(CIEEE754)
9+
import CIEEE754
10+
11+
/// Scoped rounding mode execution
12+
///
13+
/// Executes a closure with a specific FPU rounding mode, then automatically
14+
/// restores the original mode - even if the closure throws.
15+
///
16+
/// ## Usage
17+
///
18+
/// ```swift
19+
/// let result = withRoundingMode(.towardZero) {
20+
/// return 10.0 / 3.0 // Rounded toward zero
21+
/// }
22+
/// // Original rounding mode automatically restored
23+
/// ```
24+
///
25+
/// ## Safety
26+
///
27+
/// This is the preferred way to change rounding modes in tests and application
28+
/// code. It guarantees the rounding mode is restored, preventing pollution of
29+
/// global FPU state.
30+
///
31+
/// - Parameters:
32+
/// - mode: The rounding mode to use during execution
33+
/// - body: The closure to execute with the specified rounding mode
34+
/// - Returns: The value returned by the closure
35+
/// - Throws: Rethrows any error thrown by the closure
36+
func withRoundingMode<T>(
37+
_ mode: IEEE754RoundingMode,
38+
_ body: () throws -> T
39+
) rethrows -> T {
40+
let originalMode = ieee754_get_rounding_mode()
41+
defer { ieee754_set_rounding_mode(originalMode) }
42+
43+
ieee754_set_rounding_mode(mode)
44+
return try body()
45+
}
46+
47+
/// Scoped exception state execution
48+
///
49+
/// Executes a closure with cleared exception state, then automatically
50+
/// restores the original exception flags - even if the closure throws.
51+
///
52+
/// ## Usage
53+
///
54+
/// ```swift
55+
/// withClearedExceptions {
56+
/// let result = 1.0 / 0.0 // Raises divByZero
57+
/// // Exception state is isolated to this scope
58+
/// }
59+
/// // Original exception state automatically restored
60+
/// ```
61+
///
62+
/// - Parameter body: The closure to execute with cleared exceptions
63+
/// - Returns: The value returned by the closure
64+
/// - Throws: Rethrows any error thrown by the closure
65+
func withClearedExceptions<T>(_ body: () throws -> T) rethrows -> T {
66+
let originalExceptions = ieee754_get_exceptions()
67+
defer {
68+
// Restore original exception state
69+
ieee754_clear_all_exceptions()
70+
if originalExceptions.invalid != 0 {
71+
ieee754_raise_exception(IEEE754_EXCEPTION_INVALID)
72+
}
73+
if originalExceptions.divByZero != 0 {
74+
ieee754_raise_exception(IEEE754_EXCEPTION_DIVBYZERO)
75+
}
76+
if originalExceptions.overflow != 0 {
77+
ieee754_raise_exception(IEEE754_EXCEPTION_OVERFLOW)
78+
}
79+
if originalExceptions.underflow != 0 {
80+
ieee754_raise_exception(IEEE754_EXCEPTION_UNDERFLOW)
81+
}
82+
if originalExceptions.inexact != 0 {
83+
ieee754_raise_exception(IEEE754_EXCEPTION_INEXACT)
84+
}
85+
}
86+
87+
ieee754_clear_all_exceptions()
88+
return try body()
89+
}
90+
91+
/// Scoped rounding mode and exception state execution
92+
///
93+
/// Executes a closure with a specific rounding mode and cleared exceptions,
94+
/// then automatically restores both - even if the closure throws.
95+
///
96+
/// ## Usage
97+
///
98+
/// ```swift
99+
/// let result = withRoundingModeAndClearedExceptions(.upward) {
100+
/// return 1.0 / 3.0
101+
/// }
102+
/// // Both rounding mode and exceptions automatically restored
103+
/// ```
104+
///
105+
/// - Parameters:
106+
/// - mode: The rounding mode to use during execution
107+
/// - body: The closure to execute
108+
/// - Returns: The value returned by the closure
109+
/// - Throws: Rethrows any error thrown by the closure
110+
func withRoundingModeAndClearedExceptions<T>(
111+
_ mode: IEEE754RoundingMode,
112+
_ body: () throws -> T
113+
) rethrows -> T {
114+
try withRoundingMode(mode) {
115+
try withClearedExceptions(body)
116+
}
117+
}
118+
119+
#endif

Tests/IEEE 754 Tests/CIEEE754 Integration Tests.swift

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,62 +20,54 @@ struct CIEEERoundingModeTests {
2020
}
2121

2222
@Test func setRoundingModeToNearest() {
23-
let originalMode = ieee754_get_rounding_mode()
24-
defer { ieee754_set_rounding_mode(originalMode) }
23+
withRoundingMode(IEEE754_ROUND_TONEAREST) {
24+
let result = ieee754_set_rounding_mode(IEEE754_ROUND_TONEAREST)
25+
#expect(result == 0, "Setting rounding mode should succeed")
2526

26-
let result = ieee754_set_rounding_mode(IEEE754_ROUND_TONEAREST)
27-
#expect(result == 0, "Setting rounding mode should succeed")
28-
29-
let mode = ieee754_get_rounding_mode()
30-
#expect(mode == IEEE754_ROUND_TONEAREST)
27+
let mode = ieee754_get_rounding_mode()
28+
#expect(mode == IEEE754_ROUND_TONEAREST)
29+
}
3130
}
3231

3332
@Test func setRoundingModeDownward() {
34-
let originalMode = ieee754_get_rounding_mode()
35-
defer { ieee754_set_rounding_mode(originalMode) }
36-
37-
let result = ieee754_set_rounding_mode(IEEE754_ROUND_DOWNWARD)
38-
#expect(result == 0)
33+
withRoundingMode(IEEE754_ROUND_DOWNWARD) {
34+
let result = ieee754_set_rounding_mode(IEEE754_ROUND_DOWNWARD)
35+
#expect(result == 0)
3936

40-
let mode = ieee754_get_rounding_mode()
41-
#expect(mode == IEEE754_ROUND_DOWNWARD)
37+
let mode = ieee754_get_rounding_mode()
38+
#expect(mode == IEEE754_ROUND_DOWNWARD)
39+
}
4240
}
4341

4442
@Test func setRoundingModeUpward() {
45-
let originalMode = ieee754_get_rounding_mode()
46-
defer { ieee754_set_rounding_mode(originalMode) }
47-
48-
let result = ieee754_set_rounding_mode(IEEE754_ROUND_UPWARD)
49-
#expect(result == 0)
43+
withRoundingMode(IEEE754_ROUND_UPWARD) {
44+
let result = ieee754_set_rounding_mode(IEEE754_ROUND_UPWARD)
45+
#expect(result == 0)
5046

51-
let mode = ieee754_get_rounding_mode()
52-
#expect(mode == IEEE754_ROUND_UPWARD)
47+
let mode = ieee754_get_rounding_mode()
48+
#expect(mode == IEEE754_ROUND_UPWARD)
49+
}
5350
}
5451

5552
@Test func setRoundingModeTowardZero() {
56-
let originalMode = ieee754_get_rounding_mode()
57-
defer { ieee754_set_rounding_mode(originalMode) }
53+
withRoundingMode(IEEE754_ROUND_TOWARDZERO) {
54+
let result = ieee754_set_rounding_mode(IEEE754_ROUND_TOWARDZERO)
55+
#expect(result == 0)
5856

59-
let result = ieee754_set_rounding_mode(IEEE754_ROUND_TOWARDZERO)
60-
#expect(result == 0)
61-
62-
let mode = ieee754_get_rounding_mode()
63-
#expect(mode == IEEE754_ROUND_TOWARDZERO)
57+
let mode = ieee754_get_rounding_mode()
58+
#expect(mode == IEEE754_ROUND_TOWARDZERO)
59+
}
6460
}
6561

6662
@Test func roundingModeAffectsOperations() {
67-
let originalMode = ieee754_get_rounding_mode()
68-
defer { ieee754_set_rounding_mode(originalMode) }
69-
70-
// Set to round toward zero
71-
ieee754_set_rounding_mode(IEEE754_ROUND_TOWARDZERO)
63+
// Test that rounding mode actually affects operations
64+
let result1 = withRoundingMode(IEEE754_ROUND_TOWARDZERO) {
65+
1.0 / 3.0 // Should round toward zero
66+
}
7267

73-
// 1.0 / 3.0 should round toward zero
74-
let result1 = 1.0 / 3.0
75-
76-
// Set to round upward
77-
ieee754_set_rounding_mode(IEEE754_ROUND_UPWARD)
78-
let result2 = 1.0 / 3.0
68+
let result2 = withRoundingMode(IEEE754_ROUND_UPWARD) {
69+
1.0 / 3.0 // Should round upward
70+
}
7971

8072
// Results should differ (though exact values depend on rounding)
8173
// At minimum, verify operations complete
@@ -353,17 +345,15 @@ struct CIEEESignalingCompareFloatTests {
353345
@Suite("CIEEE754 - Integration Scenarios")
354346
struct CIEEEIntegrationTests {
355347
@Test func roundingModeAndExceptions() {
356-
let originalMode = ieee754_get_rounding_mode()
357-
defer { ieee754_set_rounding_mode(originalMode) }
358-
359-
// Set rounding mode and perform operation
360-
ieee754_set_rounding_mode(IEEE754_ROUND_TOWARDZERO)
361-
ieee754_clear_all_exceptions()
348+
// Use the combined scoped API for both rounding mode and exceptions
349+
let result = withRoundingModeAndClearedExceptions(IEEE754_ROUND_TOWARDZERO) {
350+
let result = 10.0 / 3.0
362351

363-
let result = 10.0 / 3.0
352+
// Verify rounding mode is set within scope
353+
#expect(ieee754_get_rounding_mode() == IEEE754_ROUND_TOWARDZERO)
364354

365-
// Verify rounding mode is still set
366-
#expect(ieee754_get_rounding_mode() == IEEE754_ROUND_TOWARDZERO)
355+
return result
356+
}
367357

368358
// Verify result is reasonable
369359
#expect(result > 3.0 && result < 4.0)

0 commit comments

Comments
 (0)