CMS 3D CMS Logo

approx_exp.h
Go to the documentation of this file.
1 #ifndef APPROX_EXP_H
2 #define APPROX_EXP_H
3 /* Quick and not that dirty vectorizable exp implementations
4  Author: Florent de Dinechin, Aric, ENS-Lyon
5  with advice from Vincenzo Innocente, CERN
6  All right reserved
7 
8 Warning + disclaimers:
9 
10 Feel free to distribute or insert in other programs etc, as long as this notice is attached.
11  Comments, requests etc: Florent.de.Dinechin@ens-lyon.fr
12 
13 Polynomials were obtained using Sollya scripts (in comments):
14 please also keep these comments attached to the code.
15 
16 If a derivative of this code ends up in the glibc I am too happy: the version with MANAGE_SUBNORMALS=1 and DEGREE=6 is faithful-accurate over the full 2^32 binary32 numbers and behaves well WRT exceptional cases. It is about 4 times faster than the stock expf on this PC, when compiled with gcc -O2.
17 
18 This code is FMA-safe (i.e. accelerated and more accurate with an FMA) as long as my parentheses are respected.
19 
20 A remaining TODO is to try and manage the over/underflow using only integer tests as per Astasiev et al, RNC conf.
21 Not sure it makes that much sense in the vector context.
22 
23 */
24 
25 // #define MANAGE_SUBNORMALS 1 // No measurable perf difference, so let's be clean.
26 // If set to zero we flush to zero the subnormal outputs, ie for x<-88 or so
27 
28 // DEGREE
29 // 6 is perfect.
30 // 5 provides max 2-ulp error,
31 // 4 loses 44 ulps (6 bits) for an acceleration of 10% WRT 6
32 // (I don't subtract the loop and call overhead, so it would be more for inlined code)
33 
34 // see the comments in the code for the accuracy you get from a given degree
35 
36 
37 #include <cstdint>
38 #include <cmath>
39 #include <limits>
40 #include <algorithm>
41 
42 #ifndef APPROX_MATH_N
43 #define APPROX_MATH_N
44 namespace approx_math {
45  union binary32 {
46  binary32() : ui32(0) {};
47  binary32(float ff) : f(ff) {};
48  binary32(int32_t ii) : i32(ii){}
49  binary32(uint32_t ui) : ui32(ui){}
50 
51  uint32_t ui32; /* unsigned int */
52  int32_t i32; /* Signed int */
53  float f;
54  };
55 
56 #ifdef __SSE4_1__
57  inline float fpfloor(float x) {
58  return std::floor(x);
59  }
60 #else
61  inline float fpfloor(float x) {
62  int32_t ret = x;
63  binary32 xx(x);
64  ret-=(xx.ui32>>31);
65  return ret;
66  }
67 #endif
68 
69 }
70 #endif
71 
72 
73 template<int DEGREE>
74 inline float approx_expf_P(float p);
75 
76 // degree = 2 => absolute accuracy is 8 bits
77 template<>
78 inline float approx_expf_P<2>(float y) {
79  return float(0x2.p0) + y * (float(0x2.07b99p0) + y * float(0x1.025b84p0)) ;
80 }
81 // degree = 3 => absolute accuracy is 12 bits
82 template<>
83 inline float approx_expf_P<3>(float y) {
84 #ifdef HORNER // HORNER
85  return float(0x2.p0) + y * (float(0x1.fff798p0) + y * (float(0x1.02249p0) + y * float(0x5.62042p-4))) ;
86 #else // ESTRIN
87  float p23 = (float(0x1.02249p0) + y * float(0x5.62042p-4)) ;
88  float p01 = float(0x2.p0) + y * float(0x1.fff798p0);
89  return p01 + y*y*p23;
90 #endif
91 }
92 // degree = 4 => absolute accuracy is 17 bits
93 template<>
94 inline float approx_expf_P<4>(float y) {
95  return float(0x2.p0) + y * (float(0x1.fffb1p0) + y * (float(0xf.ffe84p-4) + y * (float(0x5.5f9c1p-4) + y * float(0x1.57755p-4)))) ;
96 }
97 // degree = 5 => absolute accuracy is 22 bits
98 template<>
99 inline float approx_expf_P<5>(float y) {
100  return float(0x2.p0) + y * (float(0x2.p0) + y * (float(0xf.ffed8p-4) + y * (float(0x5.5551cp-4) + y * (float(0x1.5740d8p-4) + y * float(0x4.49368p-8))))) ;
101 }
102 // degree = 6 => absolute accuracy is 27 bits
103 template<>
104 inline float approx_expf_P<6>(float y) {
105 #ifdef HORNER // HORNER
106  float p = float(0x2.p0) + y * (float(0x2.p0) + y * (float(0x1.p0) + y * (float(0x5.55523p-4) + y * (float(0x1.5554dcp-4) + y * (float(0x4.48f41p-8) + y * float(0xb.6ad4p-12)))))) ;
107 #else // ESTRIN does seem to save a cycle or two
108  float p56 = float(0x4.48f41p-8) + y * float(0xb.6ad4p-12);
109  float p34 = float(0x5.55523p-4) + y * float(0x1.5554dcp-4);
110  float y2 = y*y;
111  float p12 = float(0x2.p0) + y; // By chance we save one operation here! Funny.
112  float p36 = p34 + y2*p56;
113  float p16 = p12 + y2*p36;
114  float p = float(0x2.p0) + y*p16;
115 #endif
116  return p;
117 }
118 
119 // degree = 7 => absolute accuracy is 31 bits
120 template<>
121 inline float approx_expf_P<7>(float y) {
122  return float(0x2.p0) + y * (float(0x2.p0) + y * (float(0x1.p0) + y * (float(0x5.55555p-4) + y * (float(0x1.5554e4p-4) + y * (float(0x4.444adp-8) + y * (float(0xb.6a8a6p-12) + y * float(0x1.9ec814p-12))))))) ;
123 }
124 
125 /* The Sollya script that computes the polynomials above
126 
127 
128 f= 2*exp(y);
129 I=[-log(2)/2;log(2)/2];
130 filename="/tmp/polynomials";
131 print("") > filename;
132 for deg from 2 to 8 do begin
133  p = fpminimax(f, deg,[|1,23...|],I, floating, absolute);
134  display=decimal;
135  acc=floor(-log2(sup(supnorm(p, f, I, absolute, 2^(-40)))));
136  print( " // degree = ", deg,
137  " => absolute accuracy is ", acc, "bits" ) >> filename;
138  print("#if ( DEGREE ==", deg, ")") >> filename;
139  display=hexadecimal;
140  print(" float p = ", horner(p) , ";") >> filename;
141  print("#endif") >> filename;
142 end;
143 
144 */
145 
146 
147 // valid for -87.3365 < x < 88.7228
148 template<int DEGREE>
149 inline float unsafe_expf_impl(float x) {
150  using namespace approx_math;
151  /* Sollya for the following constants:
152  display=hexadecimal;
153  1b23+1b22;
154  single(1/log(2));
155  log2H=round(log(2), 16, RN);
156  log2L = single(log(2)-log2H);
157  log2H; log2L;
158 
159  */
160  // constexpr float rnd_cst = float(0xc.p20);
161  constexpr float inv_log2f = float(0x1.715476p0);
162  constexpr float log2H = float(0xb.172p-4);
163  constexpr float log2L = float(0x1.7f7d1cp-20);
164 
165 
166  float y = x;
167  // This is doing round(x*inv_log2f) to the nearest integer
168  float z = fpfloor((x*inv_log2f) +0.5f);
169  // Cody-and-Waite accurate range reduction. FMA-safe.
170  y -= z*log2H;
171  y -= z*log2L;
172  // exponent
173  int32_t e = z;
174 
175 
176  // we want RN above because it centers the interval around zero
177  // but then we could have 2^e = below being infinity when it shouldn't
178  // (when e=128 but p<1)
179  // so we avoid this case by reducing e and evaluating a polynomial for 2*exp
180  e -=1;
181 
182  // NaN inputs will propagate to the output as expected
183 
184  float p = approx_expf_P<DEGREE>(y);
185 
186  // cout << "x=" << x << " e=" << e << " y=" << y << " p=" << p <<"\n";
187  binary32 ef;
188  uint32_t biased_exponent= e+127;
189  ef.ui32=(biased_exponent<<23);
190 
191  return p * ef.f;
192 }
193 
194 
195 #ifndef NO_APPROX_MATH
196 
197 template<int DEGREE>
198 inline float unsafe_expf(float x) {
199  return unsafe_expf_impl<DEGREE>(x);
200 }
201 
202 template<int DEGREE>
203 inline float approx_expf(float x) {
204 
205  constexpr float inf_threshold =float(0x5.8b90cp4);
206  // log of the smallest normal
207  constexpr float zero_threshold_ftz =-float(0x5.75628p4); // sollya: single(log(1b-126));
208  // flush to zero on the output
209  // manage infty output:
210  // faster than directly on output!
211  x = std::min(std::max(x,zero_threshold_ftz),inf_threshold);
212  float r = unsafe_expf<DEGREE>(x);
213 
214  return r;
215 }
216 
217 
218 #else
219 template<int DEGREE>
220 inline float unsafe_expf(float x) {
221  return std::exp(x);
222 }
223 template<int DEGREE>
224 inline float approx_expf(float x) {
225  return std::exp(x);
226 }
227 #endif // NO_APPROX_MATH
228 
229 
230 
231 #endif
float approx_expf_P< 2 >(float y)
Definition: approx_exp.h:78
float approx_expf_P(float p)
float approx_expf_P< 5 >(float y)
Definition: approx_exp.h:99
binary32(int32_t ii)
Definition: approx_exp.h:48
float approx_expf_P< 3 >(float y)
Definition: approx_exp.h:83
float approx_expf_P< 6 >(float y)
Definition: approx_exp.h:104
#define constexpr
T x() const
Cartesian x coordinate.
float unsafe_expf_impl(float x)
Definition: approx_exp.h:149
float unsafe_expf(float x)
Definition: approx_exp.h:198
T min(T a, T b)
Definition: MathUtil.h:58
binary32(uint32_t ui)
Definition: approx_exp.h:49
ii
Definition: cuy.py:588
float approx_expf_P< 4 >(float y)
Definition: approx_exp.h:94
float approx_expf_P< 7 >(float y)
Definition: approx_exp.h:121
float fpfloor(float x)
Definition: approx_exp.h:61
float approx_expf(float x)
Definition: approx_exp.h:203