|
1 | 1 | """ Utilties for casting floats to integers
|
2 | 2 | """
|
3 | 3 |
|
| 4 | +from platform import processor |
| 5 | + |
4 | 6 | import numpy as np
|
5 | 7 |
|
6 | 8 |
|
@@ -152,56 +154,75 @@ def shared_range(flt_type, int_type):
|
152 | 154 | except AttributeError: # float16 not present in np < 1.6
|
153 | 155 | _float16 = None
|
154 | 156 |
|
155 |
| -# The number of significand digits in IEEE floating point formats, not including |
156 |
| -# the implicit leading 0. See http://en.wikipedia.org/wiki/IEEE_754-2008 |
157 |
| -_flt_nmant = { |
158 |
| - _float16: 10, |
159 |
| - np.float32: 23, |
160 |
| - np.float64: 52, |
161 |
| - } |
162 |
| - |
163 | 157 |
|
164 | 158 | class FloatingError(Exception):
|
165 | 159 | pass
|
166 | 160 |
|
167 | 161 |
|
168 |
| -def flt2nmant(flt_type): |
169 |
| - """ Number of significand bits in float type `flt_type` |
| 162 | +def type_info(np_type): |
| 163 | + """ Return dict with min, max, nexp, nmant, width for numpy type `np_type` |
| 164 | +
|
| 165 | + Type can be integer in which case nexp and nmant are None. |
170 | 166 |
|
171 | 167 | Parameters
|
172 | 168 | ----------
|
173 |
| - flt_type : object |
174 |
| - Numpy floating point type, such as np.float32 |
| 169 | + np_type : numpy type specifier |
| 170 | + Any specifier for a numpy dtype |
175 | 171 |
|
176 | 172 | Returns
|
177 | 173 | -------
|
178 |
| - nmant : int |
179 |
| - Number of digits in the signficand |
| 174 | + info : dict |
| 175 | + with fields ``min`` (minimum value), ``max`` (maximum value), ``nexp`` |
| 176 | + (exponent width), ``nmant`` (significand precision not including |
| 177 | + implicit first digit) ``width`` (width in bytes). ``nexp``, ``nmant`` |
| 178 | + are None for integer types. Both ``min`` and ``max`` are of type |
| 179 | + `np_type`. |
| 180 | +
|
| 181 | + Raises |
| 182 | + ------ |
| 183 | + FloatingError : for floating point types we don't recognize |
| 184 | +
|
| 185 | + Notes |
| 186 | + ----- |
| 187 | + You might be thinking that ``np.finfo`` does this job, and it does, except |
| 188 | + for PPC long doubles (http://projects.scipy.org/numpy/ticket/2077). This |
| 189 | + routine protects against errors in ``np.finfo`` by only accepting values |
| 190 | + that we know are likely to be correct. |
180 | 191 | """
|
181 |
| - try: |
182 |
| - return _flt_nmant[flt_type] |
183 |
| - except KeyError: |
| 192 | + dt = np.dtype(np_type) |
| 193 | + np_type = dt.type |
| 194 | + width = dt.itemsize |
| 195 | + try: # integer type |
| 196 | + info = np.iinfo(dt) |
| 197 | + except ValueError: |
184 | 198 | pass
|
185 |
| - fi = np.finfo(flt_type) |
186 |
| - nmant, nexp = fi.nmant, fi.nexp |
187 |
| - # Assuming the np.float type is always IEEE 64 bit |
188 |
| - if flt_type is np.float and (nmant, nexp) == (52, 11): |
189 |
| - return 52 |
190 |
| - # Now we should be testing long doubles |
191 |
| - assert flt_type is np.longdouble |
192 |
| - if (nmant, nexp) == (63, 15): # 80-bit intel type |
193 |
| - return 63 # Not including explicit first digit |
194 |
| - # We test the declared nmant by stepping up and down. These tests assume a |
195 |
| - # binary format |
196 |
| - i_end_contig = 2**(nmant+1) # int |
197 |
| - f_end_contig = flt_type(i_end_contig) |
198 |
| - # We need as_int here because long doubles do not necessarily convert |
199 |
| - # correctly to ints with int() - see |
200 |
| - # http://projects.scipy.org/numpy/ticket/1395 |
201 |
| - if as_int(f_end_contig-1) == (i_end_contig-1): # still representable |
202 |
| - if as_int(f_end_contig+1) == i_end_contig: # Rounding down |
203 |
| - return nmant |
204 |
| - raise FloatingError('Cannot be confident of nmant value for %s' % flt_type) |
| 199 | + else: |
| 200 | + return dict(min=np_type(info.min), max=np_type(info.max), |
| 201 | + nmant=None, nexp=None, width=width) |
| 202 | + info = np.finfo(dt) |
| 203 | + # Trust the standard IEEE types |
| 204 | + nmant, nexp = info.nmant, info.nexp |
| 205 | + ret = dict(min=np_type(info.min), max=np_type(info.max), nmant=nmant, |
| 206 | + nexp=nexp, width=width) |
| 207 | + if np_type in (_float16, np.float32, np.float64, |
| 208 | + np.complex64, np.complex128): |
| 209 | + return ret |
| 210 | + if dt.kind == 'c': |
| 211 | + assert np_type is np.longcomplex |
| 212 | + vals = (nmant, nexp, width / 2) |
| 213 | + else: |
| 214 | + assert np_type is np.longdouble |
| 215 | + vals = (nmant, nexp, width) |
| 216 | + if vals in ((112, 15, 16), # binary128 |
| 217 | + (63, 15, 12), (63, 15, 16)): # Intel extended 80 |
| 218 | + pass # these are OK |
| 219 | + elif vals == (1, 1, 16) and processor() == 'powerpc': # broken PPC |
| 220 | + dbl_info = np.finfo(np.float64) |
| 221 | + return dict(min=np_type(dbl_info.min), max=np_type(dbl_info.max), |
| 222 | + nmant=106, nexp=11, width=width) |
| 223 | + else: # don't recognize the type |
| 224 | + raise FloatingError('We had not expected this type') |
| 225 | + return ret |
205 | 226 |
|
206 | 227 |
|
207 | 228 | def as_int(x, check=True):
|
@@ -342,14 +363,14 @@ def floor_exact(val, flt_type):
|
342 | 363 | faval = int_to_float(aval, flt_type)
|
343 | 364 | except OverflowError:
|
344 | 365 | faval = np.inf
|
| 366 | + info = type_info(flt_type) |
345 | 367 | if faval == np.inf:
|
346 |
| - return sign * np.finfo(flt_type).max |
| 368 | + return sign * info['max'] |
347 | 369 | if as_int(faval) <= aval: # as_int deals with longdouble safely
|
348 | 370 | # Float casting has made the value go down or stay the same
|
349 | 371 | return sign * faval
|
350 | 372 | # Float casting made the value go up
|
351 |
| - nmant = flt2nmant(flt_type) |
352 |
| - biggest_gap = 2**(floor_log2(aval) - nmant) |
| 373 | + biggest_gap = 2**(floor_log2(aval) - info['nmant']) |
353 | 374 | assert biggest_gap > 1
|
354 | 375 | faval -= flt_type(biggest_gap)
|
355 | 376 | return sign * faval
|
|
0 commit comments