ftz 0.0.1
Fast, reproducible floating-point arithmetic
Loading...
Searching...
No Matches
ftz32_ops.h
Go to the documentation of this file.
1#pragma once
2#include "ftz/config.h"
3#include "native/attributes.h"
4#include "ftz/math/approx.h"
5#include "ftz/math/float.h"
6#include "ftz/math/words.h"
7
8// One binary32 value contract, shared by scalar, SIMD repair lanes and HLSL.
9// The caller supplies normal, signed zero, infinity or any NaN operands.
10// NaN signs/payloads are outside the contract. Finite arithmetic needs RNE/FMA.
11// Compiler contraction of ordinary a*b+c is disabled by the build configuration.
12namespace ftz { namespace detail {
13 static const unsigned int ftz32_nan = 0x7fc00000u;
14 static const unsigned int ftz32_infinity = 0x7f800000u;
15 static const unsigned int ftz32_sign = 0x80000000u;
16
17 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_canonical(unsigned int bits) {
18 unsigned int magnitude = bits & 0x7fffffffu;
19 // Import normalization is mandatory even on DAZ hardware: integer loads,
20 // comparisons, stores and casts observe the original bits without arithmetic.
21 return magnitude < 0x00800000u ? bits & ftz32_sign : bits;
22 }
23 [[nodiscard]] native_constexpr native_inline native_const bool ftz32_isnan(unsigned int bits) {
24 return (bits & 0x7fffffffu) > ftz32_infinity;
25 }
26 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
27 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_finish(unsigned int bits) {
28 return ::ftz::detail::math::fp32_encode(::ftz::detail::math::fp32_ftz<Hardware>(::ftz::detail::math::fp32_decode(bits)));
29 }
30 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_neg(unsigned int a) {
31 return a ^ ftz32_sign;
32 }
33 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_abs(unsigned int a) { return a & 0x7fffffffu; }
34
35 template <bool HardwareFtz>
36 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_add_policy(unsigned int a, unsigned int b) {
37#ifdef __cplusplus
38 if consteval {
39 namespace fp = ::native::detail::constexpr_float;
40 return ftz32_canonical(fp::add_bits<fp::binary32>(a, b));
41 }
42#endif
43#ifdef __cplusplus
44 float value = ::ftz::detail::math::fp32_decode(a) + ::ftz::detail::math::fp32_decode(b);
45#else
46 precise float value = asfloat(a) + asfloat(b);
47#endif
48 unsigned int bits = ::ftz::detail::math::fp32_encode(value);
49 // Finite operands are integer multiples of 2^-149. A sum below minimum
50 // normal is exact, so signed hardware FTZ needs no rounding-boundary repair.
51 // No NaN classification: every NaN sign and payload is outside the contract.
52 if (HardwareFtz) return bits;
53 if ((bits & 0x7fffffffu) < 0x00800000u) {
54 unsigned int aa = a & 0x7fffffffu, bb = b & 0x7fffffffu;
55 return (aa > bb ? a : aa < bb ? b : a & b) & ftz32_sign;
56 }
57 return bits;
58 }
59 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
60 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_add(unsigned int a, unsigned int b) {
61 // Hardware admission includes signed flushing of tiny add/sub results.
62 return ftz32_add_policy<Hardware>(a, b);
63 }
64 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
65 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_sub(unsigned int a, unsigned int b) {
66#ifdef __cplusplus
67 if consteval {
68 namespace fp = ::native::detail::constexpr_float;
69 return ftz32_canonical(fp::add_bits<fp::binary32>(a, b ^ ftz32_sign));
70 }
71#endif
72 if (Hardware) {
73#ifdef __cplusplus
74 return ::ftz::detail::math::fp32_encode(::ftz::detail::math::fp32_decode(a) - ::ftz::detail::math::fp32_decode(b));
75#else
76 precise float value = asfloat(a) - asfloat(b);
77 return asuint(value);
78#endif
79 }
80 return ftz32_add<Hardware>(a, ftz32_neg(b));
81 }
82 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_mul(unsigned int a, unsigned int b) {
83#ifdef __cplusplus
84 if consteval {
85 namespace fp = ::native::detail::constexpr_float;
86 return ftz32_canonical(fp::mul_bits<fp::binary32>(a, b));
87 }
88#endif
89 float value = ::ftz::detail::math::fp32_mul<false>(::ftz::detail::math::fp32_decode(a), ::ftz::detail::math::fp32_decode(b));
90 unsigned int bits = ::ftz::detail::math::fp32_encode(value);
91 if ((bits & 0x7fffffffu) > 0x00800000u) return bits;
92 unsigned int sign = (a ^ b) & ftz32_sign;
93 unsigned int ea = (a >> 23) & 255u, eb = (b >> 23) & 255u;
94 if (ea == 0u || eb == 0u || ea + eb < 127u) return sign;
95 if (ea + eb > 127u) return sign | 0x00800000u;
96 // At exponent sum 127, test the exact minnormal RNE midpoint. The
97 // normalized fused residual is zero at the tie and otherwise at least
98 // 2^-46 in magnitude, so neither rounding nor FTZ can change its sign.
99 float ma = ::ftz::detail::math::fp32_decode((a & 0x007fffffu) | 0x3f800000u);
100 float mb = ::ftz::detail::math::fp32_decode((b & 0x007fffffu) | 0x3f800000u);
101 float residual = ::ftz::detail::math::fp32_fma<false>(ma, mb,
102 ::ftz::detail::math::fp32_decode(0xbfffffffu));
103 return sign | (residual >= 0.0f ? 0x00800000u : 0u);
104 }
105 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_fma(unsigned int a, unsigned int b, unsigned int c) {
106#ifdef __cplusplus
107 if consteval {
108 namespace fp = ::native::detail::constexpr_float;
109 return ftz32_canonical(fp::fma_bits<fp::binary32>(a, b, c));
110 }
111#endif
112 float value = ::ftz::detail::math::fp32_fma<false>(::ftz::detail::math::fp32_decode(a),
113 ::ftz::detail::math::fp32_decode(b), ::ftz::detail::math::fp32_decode(c));
114 unsigned int bits = ::ftz::detail::math::fp32_encode(value);
115 // Native FTZ profiles disagree at the minimum-normal rounding boundary.
116 // Reconstruct only this rare region; a noop flush cannot repair it.
117 if ((bits & 0x7fffffffu) <= 0x00800000u)
118 return ::ftz::detail::math::fp32_fma_words(a, b, c).bits;
119 return bits;
120 }
121 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
122 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_div(unsigned int a, unsigned int b) {
123 unsigned int aa = a & 0x7fffffffu, bb = b & 0x7fffffffu;
124 unsigned int sign = (a ^ b) & ftz32_sign;
125 if (aa > ftz32_infinity || bb > ftz32_infinity ||
126 (aa == 0u && bb == 0u) || (aa == ftz32_infinity && bb == ftz32_infinity))
127 return ftz32_nan;
128 if (aa == ftz32_infinity || bb == 0u) return sign | ftz32_infinity;
129 if (bb == ftz32_infinity || aa == 0u) return sign;
130 ::ftz::detail::math::approx_result r = ::ftz::detail::math::approx_div_prechecked_words(a, b);
131 return r.valid != 0u ? ftz32_finish<Hardware>(r.bits) : sign | ftz32_infinity;
132 }
133 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_sqrt(unsigned int a) {
134 unsigned int magnitude = a & 0x7fffffffu;
135 if (magnitude == 0u) return a;
136 if ((a & ftz32_sign) != 0u || magnitude > ftz32_infinity) return ftz32_nan;
137 if (magnitude == ftz32_infinity) return a;
138 return ::ftz::detail::math::approx_sqrt_prechecked_words(a).bits;
139 }
140 [[nodiscard]] native_constexpr native_inline native_const bool ftz32_equal(unsigned int a, unsigned int b) {
141 return !ftz32_isnan(a) && !ftz32_isnan(b) &&
142 (a == b || ((a | b) & 0x7fffffffu) == 0u);
143 }
144 [[nodiscard]] native_constexpr native_inline native_const bool ftz32_less(unsigned int a, unsigned int b) {
145 if (ftz32_isnan(a) || ftz32_isnan(b) || ftz32_equal(a, b)) return false;
146 if (((a ^ b) & ftz32_sign) != 0u) return (a & ftz32_sign) != 0u;
147 return (a & ftz32_sign) != 0u ? a > b : a < b;
148 }
149}}
150
151#include "ftz/math/atan2.h"
152#include "ftz/math/tanh.h"
153#include "ftz/math/log.h"
154#include "ftz/math.h"
155namespace ftz { namespace detail {
156 struct ftz32_trig_fraction { unsigned int words[9]; };
157 struct ftz32_trig_reduction { unsigned int residual, quadrant; };
158 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_trig_window(ftz32_trig_fraction p, unsigned int shift) {
159 unsigned int word=shift/32u, bit=shift%32u;
160 if(word>=9u)return 0u;
161 unsigned int result=p.words[word]>>bit;
162 if(bit!=0u && word+1u<9u)result|=p.words[word+1u]<<(32u-bit);
163 return result;
164 }
165 // Finite positive magnitude >=8192. A 256-bit fixed 2/pi and 24-bit input
166 // significand cover every binary32 exponent using uint32 limbs only.
167 // Truncation contributes less than 2^-128 turns at the largest finite input.
168 [[nodiscard]] native_constexpr native_inline native_const ftz32_trig_reduction ftz32_trig_reduce(unsigned int magnitude) {
169 const unsigned int two_over_pi[8]={0xdebbc561u,0xfe5163abu,0x3c439041u,0xdb629599u,0xf534ddc0u,0xfc2757d1u,0x4e441529u,0xa2f9836eu};
170 unsigned int mantissa=(magnitude&0x007fffffu)|0x00800000u;
171 ftz32_trig_fraction p;
172 unsigned int carry=0u;
173 for(unsigned int i=0u;i<8u;++i){
174 ::ftz::detail::math::unsigned_word_product product=::ftz::detail::math::unsigned_multiply_words(mantissa,two_over_pi[i]);
175 p.words[i]=product.low+carry;
176 carry=product.high+(p.words[i]<product.low?1u:0u);
177 }
178 p.words[8]=carry;
179 unsigned int shift=406u-(magnitude>>23);
180 unsigned int quadrant=ftz32_trig_window(p,shift)&3u;
181 bool negative=(ftz32_trig_window(p,shift-1u)&1u)!=0u;
182 if(negative)quadrant=(quadrant+1u)&3u;
183 unsigned int limb=shift/32u, bit=shift%32u;
184 for(unsigned int j=limb+1u;j<9u;++j)p.words[j]=0u;
185 p.words[limb]&=bit==0u?0u:(1u<<bit)-1u;
186 if(negative){
187 unsigned int add=1u;
188 for(unsigned int j=0u;j<=limb;++j){
189 unsigned int inverted=~p.words[j];
190 p.words[j]=inverted+add;add=(p.words[j]<inverted)?1u:0u;
191 }
192 p.words[limb]&=bit==0u?0u:(1u<<bit)-1u;
193 }
194 int top=-1;
195 for(int j=8;j>=0;--j)if(p.words[j]!=0u){top=j*32+31-(int)::ftz::detail::math::word_leading_zeros(p.words[j]);break;}
196 ftz32_trig_reduction result;result.quadrant=quadrant;result.residual=0u;
197 if(top<0)return result;
198 ::ftz::detail::math::unsigned_word_product packed;
199 if(top>61){
200 unsigned int discarded_bits=(unsigned int)top-61u;
201 packed=::ftz::detail::math::word_pair(ftz32_trig_window(p,discarded_bits),ftz32_trig_window(p,discarded_bits+32u));
202 bool sticky=false;
203 for(unsigned int j=0u;j<discarded_bits/32u;++j)sticky=sticky||p.words[j]!=0u;
204 if(discarded_bits%32u!=0u)sticky=sticky||(p.words[discarded_bits/32u]&((1u<<(discarded_bits%32u))-1u))!=0u;
205 packed.low|=sticky?1u:0u;
206 }else packed=::ftz::detail::math::word_pair_left(::ftz::detail::math::word_pair(p.words[0],p.words[1]),61u-(unsigned int)top);
207 unsigned int fraction=::ftz::detail::math::fp32_pack(packed,top-(int)shift,negative?0x80000000u:0u).bits;
208 result.residual=ftz32_mul(fraction,0x3fc90fdbu);
209 return result;
210 }
211}}
212
213namespace ftz { namespace detail {
214 struct ftz32_sincos_bits { unsigned int sine, cosine; };
215 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
216 [[nodiscard]] native_constexpr native_inline native_const ftz32_sincos_bits ftz32_sincos(unsigned int bits) {
217 ftz32_sincos_bits result;
218 unsigned int magnitude=bits&0x7fffffffu;
219 if(magnitude>=ftz32_infinity){result.sine=result.cosine=ftz32_nan;return result;}
220 bool large=magnitude>=0x46000000u;
221 ftz32_trig_reduction reduced;reduced.residual=bits;reduced.quadrant=0u;
222 if(large)reduced=ftz32_trig_reduce(magnitude);
223 float x=::ftz::detail::math::fp32_decode(reduced.residual);
224#ifdef __cplusplus
225 auto [sine_value, cosine_value]=large?ftz::detail::native::sincos_reduced_ftz<Hardware>(x):ftz::detail::native::sincos_ftz<Hardware>(x);
226 unsigned int sine=::ftz::detail::math::fp32_encode(sine_value),cosine=::ftz::detail::math::fp32_encode(cosine_value);
227#else
228 ::ftz::detail::math::sincos_result pair;
229 if(large)pair=::ftz::detail::math::sincos_reduced_ftz(x);else pair=::ftz::detail::math::sincos_ftz(x);
230 unsigned int sine=::ftz::detail::math::fp32_encode(pair.sine),cosine=::ftz::detail::math::fp32_encode(pair.cosine);
231#endif
232 if(large){
233 result.sine=(reduced.quadrant&1u)!=0u?cosine:sine;
234 result.cosine=(reduced.quadrant&1u)!=0u?sine:cosine;
235 result.sine^=((reduced.quadrant&2u)!=0u?ftz32_sign:0u)^(bits&ftz32_sign);
236 result.cosine^=(((reduced.quadrant+1u)&2u)!=0u?ftz32_sign:0u);
237 }else{result.sine=sine;result.cosine=cosine;}
238 return result;
239 }
240#ifdef __cplusplus
241 // A known scalar quadrant selects one reduced polynomial, then its sign.
242 template <bool Cosine, bool Hardware>
243 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_trig_single(unsigned int bits) {
244 unsigned int magnitude=bits&0x7fffffffu;
245 if(magnitude>=ftz32_infinity)return ftz32_nan;
246 if(magnitude<0x46000000u) {
247 float x=::ftz::detail::math::fp32_decode(bits);
248 if constexpr(Cosine) return ::ftz::detail::math::fp32_encode(native::cos_ftz<Hardware>(x));
249 else return ::ftz::detail::math::fp32_encode(native::sin_ftz<Hardware>(x));
250 }
251 auto reduced=ftz32_trig_reduce(magnitude);
252 float x=::ftz::detail::math::fp32_decode(reduced.residual);
253 bool odd=(reduced.quadrant&1u)!=0u;
254 float value;
255 if constexpr(Cosine) value=odd?native::sin_reduced_ftz<Hardware>(x):native::cos_reduced_ftz<Hardware>(x);
256 else value=odd?native::cos_reduced_ftz<Hardware>(x):native::sin_reduced_ftz<Hardware>(x);
257 unsigned int result=::ftz::detail::math::fp32_encode(value);
258 if constexpr(Cosine) return result^(((reduced.quadrant+1u)&2u)!=0u?ftz32_sign:0u);
259 else return result^(((reduced.quadrant&2u)!=0u?ftz32_sign:0u)^(bits&ftz32_sign));
260 }
261#endif
262 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
263 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_sin(unsigned int bits){
264#ifdef __cplusplus
265 return ftz32_trig_single<false,Hardware>(bits);
266#else
267 return ftz32_sincos<Hardware>(bits).sine;
268#endif
269 }
270 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
271 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_cos(unsigned int bits){
272#ifdef __cplusplus
273 return ftz32_trig_single<true,Hardware>(bits);
274#else
275 return ftz32_sincos<Hardware>(bits).cosine;
276#endif
277 }
278 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
279 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_tanh(unsigned int bits){return ::ftz::detail::math::tanh_words<Hardware>(bits);}
280 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
281 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_log(unsigned int bits){return ::ftz::detail::math::log_words<Hardware>(bits).bits;}
282 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
283 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_log1p(unsigned int bits){return ::ftz::detail::math::log1p_words<Hardware>(bits).bits;}
284 template <unsigned int Degree = 6>
285 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_exp(unsigned int bits){
286#ifdef __cplusplus
287 return ::ftz::detail::math::fp32_encode(ftz::detail::native::exp_ftz<Degree>(
288 ftz::detail::native::fp32x1(::ftz::detail::math::fp32_decode(bits))).value);
289#else
290 unsigned int magnitude=bits&0x7fffffffu;
291 if(magnitude>ftz32_infinity)return ftz32_nan;
292 if(magnitude==ftz32_infinity)return (bits&ftz32_sign)!=0u?0u:ftz32_infinity;
293 return ::ftz::detail::math::exp_value<Degree>(bits).x;
294#endif
295 }
296 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
297 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_expm1(unsigned int bits){
298 unsigned int magnitude=bits&0x7fffffffu;
299 if(magnitude>ftz32_infinity)return ftz32_nan;
300 if(magnitude==ftz32_infinity)return (bits&ftz32_sign)!=0u?0xbf800000u:ftz32_infinity;
301 if((bits&ftz32_sign)==0u && magnitude>0x3f800000u)return ftz32_sub<Hardware>(ftz32_exp(bits),0x3f800000u);
302#ifdef __cplusplus
303 return ::ftz::detail::math::fp32_encode(ftz::detail::native::expm1_checked<Hardware>(::ftz::detail::math::fp32_decode(bits)).value);
304#else
305 return ::ftz::detail::math::fp32_encode(::ftz::detail::math::expm1_checked(::ftz::detail::math::fp32_decode(bits)).value);
306#endif
307 }
308 template <bool Hardware = FTZ_FP32_HARDWARE_FTZ != 0>
309 [[nodiscard]] native_constexpr native_inline native_const unsigned int ftz32_atan2(unsigned int y,unsigned int x){
310 unsigned int ay=y&0x7fffffffu,ax=x&0x7fffffffu,sign=y&ftz32_sign;
311 if(ay>ftz32_infinity || ax>ftz32_infinity)return ftz32_nan;
312 if(ay==0u)return ((x&ftz32_sign)!=0u?0x40490fdbu:0u)|sign;
313 if(ay==ftz32_infinity){
314 if(ax==ftz32_infinity)return ((x&ftz32_sign)!=0u?0x4016cbe4u:0x3f490fdbu)|sign;
315 return 0x3fc90fdbu|sign;
316 }
317 if(ax==ftz32_infinity)return ((x&ftz32_sign)!=0u?0x40490fdbu:0u)|sign;
318 return ::ftz::detail::math::atan2_words<Hardware>(y,x).bits;
319 }
320}}
321
Reciprocal-refined division, square root and reciprocal square root.
Finite atan2 with normalized division and a fixed Horner polynomial.
FP32 bit casts, precise arithmetic and signed flush-to-zero normalization.
constexpr std::array< R, N > add(std::array< R, N > const &a, std::array< R, N > const &b) noexcept
Adds matching register arrays; a non-array operand is converted once and broadcast,...
Definition simd.h:1112
Deterministic polynomial log and log1p.
Host implementations of FTZ math for scalar and SIMD register packs.
Deterministic piecewise-polynomial tanh.
Native 64-bit exact binary32 packing, multiplication, and FMA repair.