Skip to content

Commit 66bdf4d

Browse files
committed
Refactor: Extract Precise class to separate header
- Move Precise class and related types to npsr/precise.h - Add deduction guides for cleaner template instantiation - Improve modularity by separating precision control from common utilities
1 parent 2de0607 commit 66bdf4d

2 files changed

Lines changed: 191 additions & 171 deletions

File tree

npsr/common.h

Lines changed: 3 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,11 @@
11
#ifndef NUMPY_SIMD_ROUTINES_NPSR_COMMON_H_
22
#define NUMPY_SIMD_ROUTINES_NPSR_COMMON_H_
33

4-
#include "hwy/highway.h"
4+
#include <hwy/highway.h>
55

66
#include <cfenv>
77
#include <type_traits>
88

9-
namespace npsr {
9+
#include "precise.h"
1010

11-
struct _NoLargeArgument {};
12-
struct _NoSpecialCases {};
13-
struct _NoExceptions {};
14-
struct _LowAccuracy {};
15-
constexpr auto kNoLargeArgument = _NoLargeArgument{};
16-
constexpr auto kNoSpecialCases = _NoSpecialCases{};
17-
constexpr auto kNoExceptions = _NoExceptions{};
18-
constexpr auto kLowAccuracy = _LowAccuracy{};
19-
20-
struct Round {
21-
struct _Force {};
22-
struct _Nearest {};
23-
struct _Down {};
24-
struct _Up {};
25-
struct _Zero {};
26-
static constexpr auto kForce = _Force{};
27-
static constexpr auto kNearest = _Nearest{};
28-
#if 0 // not used yet
29-
static constexpr auto kDown = _Down{};
30-
static constexpr auto kUp = _Up{};
31-
static constexpr auto kZero = _Zero{};
32-
#endif
33-
};
34-
35-
struct Subnormal {
36-
struct _DAZ {};
37-
struct _FTZ {};
38-
struct _IEEE754 {};
39-
#if 0 // not used yet
40-
static constexpr auto kDAZ = _DAZ{};
41-
static constexpr auto kFTZ = _FTZ{};
42-
#endif
43-
static constexpr auto kIEEE754 = _IEEE754{};
44-
};
45-
46-
struct FPExceptions {
47-
static constexpr auto kNone = 0;
48-
static constexpr auto kInvalid = FE_INVALID;
49-
static constexpr auto kDivByZero = FE_DIVBYZERO;
50-
static constexpr auto kOverflow = FE_OVERFLOW;
51-
static constexpr auto kUnderflow = FE_UNDERFLOW;
52-
};
53-
54-
/**
55-
* @brief RAII floating-point precision control class
56-
*
57-
* The Precise class provides automatic management of floating-point environment
58-
* settings during its lifetime. It uses RAII principles to save the current
59-
* floating-point state on construction and restore it on destruction.
60-
*
61-
* The class is configured using variadic template arguments that specify
62-
* the desired floating-point behavior through tag types.
63-
*
64-
* **IMPORTANT PERFORMANCE NOTE**: Create the Precise object BEFORE loops,
65-
* not inside them. The constructor and destructor have overhead from saving
66-
* and restoring floating-point state, so it should be done once per
67-
* computational scope, not per iteration.
68-
*
69-
* @tparam Args Variadic template arguments for configuration flags
70-
*
71-
* @example
72-
* ```cpp
73-
* using namespace hwy::HWY_NAMESPACE;
74-
* using namespace npsr;
75-
* using namespace npsr::HWY_NAMESPACE;
76-
*
77-
* Precise precise = {kLowAccuracy, kNoSpecialCases, kNoLargeArgument};
78-
* const ScalableTag<float> d;
79-
* typename V = Vec<DFromV<SclableTag>>;
80-
* for (size_t i = 0; i < n; i += Lanes(d)) {
81-
* V input = LoadU(d, &input[i]);
82-
* V result = Sin(precise, input);
83-
* StoreU(result, d, &output[i]);
84-
* }
85-
* ```
86-
*/
87-
template <typename... Args> class Precise {
88-
public:
89-
Precise(Args...) {
90-
if constexpr (!kNoExceptions) {
91-
fegetexceptflag(&_exceptions, FE_ALL_EXCEPT);
92-
}
93-
if constexpr (kRoundForce) {
94-
_rounding_mode = fegetround();
95-
int new_mode = _NewRoundingMode();
96-
if (_rounding_mode != new_mode) {
97-
_retrieve_rounding_mode = true;
98-
fesetround(new_mode);
99-
}
100-
}
101-
}
102-
void FlushExceptions() { fesetexceptflag(&_exceptions, FE_ALL_EXCEPT); }
103-
104-
void Raise(int errors) {
105-
static_assert(!kNoExceptions,
106-
"Cannot raise exceptions in NoExceptions mode");
107-
_exceptions |= errors;
108-
}
109-
~Precise() {
110-
FlushExceptions();
111-
if constexpr (kRoundForce) {
112-
if (_retrieve_rounding_mode) {
113-
fesetround(_rounding_mode);
114-
}
115-
}
116-
}
117-
static constexpr bool kNoExceptions =
118-
(std::is_same_v<_NoExceptions, Args> || ...);
119-
static constexpr bool kNoLargeArgument =
120-
(std::is_same_v<_NoLargeArgument, Args> || ...);
121-
static constexpr bool kNoSpecialCases =
122-
(std::is_same_v<_NoSpecialCases, Args> || ...);
123-
static constexpr bool kLowAccuracy =
124-
(std::is_same_v<_LowAccuracy, Args> || ...);
125-
// defaults to high accuracy if no low accuracy flag is set
126-
static constexpr bool kHighAccuracy = !kLowAccuracy;
127-
// defaults to large argument support if no no large argument flag is set
128-
static constexpr bool kLargeArgument = !kNoLargeArgument;
129-
// defaults to special cases support if no no special cases flag is set
130-
static constexpr bool kSpecialCases = !kNoSpecialCases;
131-
// defaults to exception support if no no exception flag is set
132-
static constexpr bool kException = !kNoExceptions;
133-
134-
static constexpr bool kRoundForce =
135-
(std::is_same_v<Round::_Force, Args> || ...);
136-
static constexpr bool _kRoundNearest =
137-
(std::is_same_v<Round::_Nearest, Args> || ...);
138-
static constexpr bool kRoundZero =
139-
(std::is_same_v<Round::_Zero, Args> || ...);
140-
static constexpr bool kRoundDown =
141-
(std::is_same_v<Round::_Down, Args> || ...);
142-
static constexpr bool kRoundUp = (std::is_same_v<Round::_Up, Args> || ...);
143-
// only one rounding mode can be set
144-
static_assert((_kRoundNearest + kRoundDown + kRoundUp + kRoundZero) <= 1,
145-
"Only one rounding mode can be set at a time");
146-
// if no rounding mode is set, default to round nearest
147-
static constexpr bool kRoundNearest =
148-
_kRoundNearest || (!kRoundDown && !kRoundUp && !kRoundZero);
149-
150-
static constexpr bool kDAZ = (std::is_same_v<Subnormal::_DAZ, Args> || ...);
151-
static constexpr bool kFTZ = (std::is_same_v<Subnormal::_FTZ, Args> || ...);
152-
static constexpr bool _kIEEE754 =
153-
(std::is_same_v<Subnormal::_IEEE754, Args> || ...);
154-
static_assert(!_kIEEE754 || !(kDAZ || kFTZ),
155-
"IEEE754 mode cannot be used "
156-
"with Denormals Are Zero (DAZ) or Flush To Zero (FTZ) "
157-
"subnormal handling");
158-
static constexpr bool kIEEE754 = _kIEEE754 || !(kDAZ || kFTZ);
159-
160-
private:
161-
int _NewRoundingMode() const {
162-
if constexpr (kRoundDown) {
163-
return FE_DOWNWARD;
164-
} else if constexpr (kRoundUp) {
165-
return FE_UPWARD;
166-
} else if constexpr (kRoundZero) {
167-
return FE_TOWARDZERO;
168-
} else {
169-
return FE_TONEAREST;
170-
}
171-
}
172-
int _rounding_mode = 0;
173-
bool _retrieve_rounding_mode = false;
174-
fexcept_t _exceptions;
175-
};
176-
177-
} // namespace npsr
178-
179-
#endif // NUMPY_SIMD_ROUTINES_NPSR_COMMON_H_
11+
#endif // NUMPY_SIMD_ROUTINES_NPSR_COMMON_H_

npsr/precise.h

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#ifndef NUMPY_SIMD_ROUTINES_NPSR_PRECISE_H_
2+
#define NUMPY_SIMD_ROUTINES_NPSR_PRECISE_H_
3+
#include <cfenv>
4+
#include <type_traits>
5+
6+
#include "hwy/highway.h"
7+
8+
namespace npsr {
9+
10+
struct _NoLargeArgument {};
11+
struct _NoSpecialCases {};
12+
struct _NoExceptions {};
13+
struct _LowAccuracy {};
14+
constexpr auto kNoLargeArgument = _NoLargeArgument{};
15+
constexpr auto kNoSpecialCases = _NoSpecialCases{};
16+
constexpr auto kNoExceptions = _NoExceptions{};
17+
constexpr auto kLowAccuracy = _LowAccuracy{};
18+
19+
struct Round {
20+
struct _Force {};
21+
struct _Nearest {};
22+
struct _Down {};
23+
struct _Up {};
24+
struct _Zero {};
25+
static constexpr auto kForce = _Force{};
26+
static constexpr auto kNearest = _Nearest{};
27+
#if 0 // not used yet
28+
static constexpr auto kDown = _Down{};
29+
static constexpr auto kUp = _Up{};
30+
static constexpr auto kZero = _Zero{};
31+
#endif
32+
};
33+
34+
struct Subnormal {
35+
struct _DAZ {};
36+
struct _FTZ {};
37+
struct _IEEE754 {};
38+
#if 0 // not used yet
39+
static constexpr auto kDAZ = _DAZ{};
40+
static constexpr auto kFTZ = _FTZ{};
41+
#endif
42+
static constexpr auto kIEEE754 = _IEEE754{};
43+
};
44+
45+
struct FPExceptions {
46+
static constexpr auto kNone = 0;
47+
static constexpr auto kInvalid = FE_INVALID;
48+
static constexpr auto kDivByZero = FE_DIVBYZERO;
49+
static constexpr auto kOverflow = FE_OVERFLOW;
50+
static constexpr auto kUnderflow = FE_UNDERFLOW;
51+
};
52+
53+
/**
54+
* @brief RAII floating-point precision control class
55+
*
56+
* The Precise class provides automatic management of floating-point
57+
* environment settings during its lifetime. It uses RAII principles to save
58+
* the current floating-point state on construction and restore it on
59+
* destruction.
60+
*
61+
* The class is configured using variadic template arguments that specify
62+
* the desired floating-point behavior through tag types.
63+
*
64+
* **IMPORTANT PERFORMANCE NOTE**: Create the Precise object BEFORE loops,
65+
* not inside them. The constructor and destructor have overhead from saving
66+
* and restoring floating-point state, so it should be done once per
67+
* computational scope, not per iteration.
68+
*
69+
* @tparam Args Variadic template arguments for configuration flags
70+
*
71+
* @example
72+
* ```cpp
73+
* using namespace hwy::HWY_NAMESPACE;
74+
* using namespace npsr;
75+
* using namespace npsr::HWY_NAMESPACE;
76+
*
77+
* Precise precise = {kLowAccuracy, kNoSpecialCases, kNoLargeArgument};
78+
* const ScalableTag<float> d;
79+
* typename V = Vec<DFromV<SclableTag>>;
80+
* for (size_t i = 0; i < n; i += Lanes(d)) {
81+
* V input = LoadU(d, &input[i]);
82+
* V result = Sin(precise, input);
83+
* StoreU(result, d, &output[i]);
84+
* }
85+
* ```
86+
*/
87+
template <typename... Args>
88+
class Precise {
89+
public:
90+
Precise() {
91+
if constexpr (!kNoExceptions) {
92+
fegetexceptflag(&_exceptions, FE_ALL_EXCEPT);
93+
}
94+
if constexpr (kRoundForce) {
95+
_rounding_mode = fegetround();
96+
int new_mode = _NewRoundingMode();
97+
if (_rounding_mode != new_mode) {
98+
_retrieve_rounding_mode = true;
99+
fesetround(new_mode);
100+
}
101+
}
102+
}
103+
template <typename T1, typename... Rest>
104+
Precise(T1&& arg1, Rest&&... rest) {}
105+
106+
void FlushExceptions() { fesetexceptflag(&_exceptions, FE_ALL_EXCEPT); }
107+
108+
void Raise(int errors) {
109+
static_assert(!kNoExceptions,
110+
"Cannot raise exceptions in NoExceptions mode");
111+
_exceptions |= errors;
112+
}
113+
~Precise() {
114+
FlushExceptions();
115+
if constexpr (kRoundForce) {
116+
if (_retrieve_rounding_mode) {
117+
fesetround(_rounding_mode);
118+
}
119+
}
120+
}
121+
static constexpr bool kNoExceptions =
122+
(std::is_same_v<_NoExceptions, Args> || ...);
123+
static constexpr bool kNoLargeArgument =
124+
(std::is_same_v<_NoLargeArgument, Args> || ...);
125+
static constexpr bool kNoSpecialCases =
126+
(std::is_same_v<_NoSpecialCases, Args> || ...);
127+
static constexpr bool kLowAccuracy =
128+
(std::is_same_v<_LowAccuracy, Args> || ...);
129+
// defaults to high accuracy if no low accuracy flag is set
130+
static constexpr bool kHighAccuracy = !kLowAccuracy;
131+
// defaults to large argument support if no no large argument flag is set
132+
static constexpr bool kLargeArgument = !kNoLargeArgument;
133+
// defaults to special cases support if no no special cases flag is set
134+
static constexpr bool kSpecialCases = !kNoSpecialCases;
135+
// defaults to exception support if no no exception flag is set
136+
static constexpr bool kExceptions = !kNoExceptions;
137+
138+
static constexpr bool kRoundForce =
139+
(std::is_same_v<Round::_Force, Args> || ...);
140+
static constexpr bool _kRoundNearest =
141+
(std::is_same_v<Round::_Nearest, Args> || ...);
142+
static constexpr bool kRoundZero =
143+
(std::is_same_v<Round::_Zero, Args> || ...);
144+
static constexpr bool kRoundDown =
145+
(std::is_same_v<Round::_Down, Args> || ...);
146+
static constexpr bool kRoundUp = (std::is_same_v<Round::_Up, Args> || ...);
147+
// only one rounding mode can be set
148+
static_assert((_kRoundNearest + kRoundDown + kRoundUp + kRoundZero) <= 1,
149+
"Only one rounding mode can be set at a time");
150+
// if no rounding mode is set, default to round nearest
151+
static constexpr bool kRoundNearest =
152+
_kRoundNearest || (!kRoundDown && !kRoundUp && !kRoundZero);
153+
154+
static constexpr bool kDAZ = (std::is_same_v<Subnormal::_DAZ, Args> || ...);
155+
static constexpr bool kFTZ = (std::is_same_v<Subnormal::_FTZ, Args> || ...);
156+
static constexpr bool _kIEEE754 =
157+
(std::is_same_v<Subnormal::_IEEE754, Args> || ...);
158+
static_assert(!_kIEEE754 || !(kDAZ || kFTZ),
159+
"IEEE754 mode cannot be used "
160+
"with Denormals Are Zero (DAZ) or Flush To Zero (FTZ) "
161+
"subnormal handling");
162+
static constexpr bool kIEEE754 = _kIEEE754 || !(kDAZ || kFTZ);
163+
164+
private:
165+
int _NewRoundingMode() const {
166+
if constexpr (kRoundDown) {
167+
return FE_DOWNWARD;
168+
} else if constexpr (kRoundUp) {
169+
return FE_UPWARD;
170+
} else if constexpr (kRoundZero) {
171+
return FE_TOWARDZERO;
172+
} else {
173+
return FE_TONEAREST;
174+
}
175+
}
176+
int _rounding_mode = 0;
177+
bool _retrieve_rounding_mode = false;
178+
fexcept_t _exceptions;
179+
};
180+
181+
Precise() -> Precise<>;
182+
183+
// For Precise{args...} -> Precise<decltype(args)...>
184+
template <typename T1, typename... Rest>
185+
Precise(T1&&, Rest&&...) -> Precise<std::decay_t<T1>, std::decay_t<Rest>...>;
186+
187+
} // namespace npsr
188+
#endif // NUMPY_SIMD_ROUTINES_NPSR_PRECISE_H_

0 commit comments

Comments
 (0)