@@ -177,9 +177,10 @@ def type_info(np_type):
177
177
info : dict
178
178
with fields ``min`` (minimum value), ``max`` (maximum value), ``nexp``
179
179
(exponent width), ``nmant`` (significand precision not including
180
- implicit first digit) ``width`` (width in bytes). ``nexp``, ``nmant``
181
- are None for integer types. Both ``min`` and ``max`` are of type
182
- `np_type`.
180
+ implicit first digit), ``minexp`` (minimum exponent), ``maxexp``
181
+ (maximum exponent), ``width`` (width in bytes). (``nexp``, ``nmant``,
182
+ ``minexp``, ``maxexp``) are None for integer types. Both ``min`` and
183
+ ``max`` are of type `np_type`.
183
184
184
185
Raises
185
186
------
@@ -188,9 +189,10 @@ def type_info(np_type):
188
189
Notes
189
190
-----
190
191
You might be thinking that ``np.finfo`` does this job, and it does, except
191
- for PPC long doubles (http://projects.scipy.org/numpy/ticket/2077). This
192
- routine protects against errors in ``np.finfo`` by only accepting values
193
- that we know are likely to be correct.
192
+ for PPC long doubles (http://projects.scipy.org/numpy/ticket/2077) and
193
+ float96 on Windows compiled with Mingw. This routine protects against such
194
+ errors in ``np.finfo`` by only accepting values that we know are likely to
195
+ be correct.
194
196
"""
195
197
dt = np .dtype (np_type )
196
198
np_type = dt .type
@@ -200,13 +202,18 @@ def type_info(np_type):
200
202
except ValueError :
201
203
pass
202
204
else :
203
- return dict (min = np_type (info .min ), max = np_type (info .max ),
204
- nmant = None , nexp = None , width = width )
205
+ return dict (min = np_type (info .min ), max = np_type (info .max ), minexp = None ,
206
+ maxexp = None , nmant = None , nexp = None , width = width )
205
207
info = np .finfo (dt )
206
208
# Trust the standard IEEE types
207
209
nmant , nexp = info .nmant , info .nexp
208
- ret = dict (min = np_type (info .min ), max = np_type (info .max ), nmant = nmant ,
209
- nexp = nexp , width = width )
210
+ ret = dict (min = np_type (info .min ),
211
+ max = np_type (info .max ),
212
+ nmant = nmant ,
213
+ nexp = nexp ,
214
+ minexp = info .minexp ,
215
+ maxexp = info .maxexp ,
216
+ width = width )
210
217
if np_type in (_float16 , np .float32 , np .float64 ,
211
218
np .complex64 , np .complex128 ):
212
219
return ret
@@ -223,14 +230,12 @@ def type_info(np_type):
223
230
pass # these are OK
224
231
elif vals in ((52 , 15 , 12 ), # windows float96
225
232
(52 , 15 , 16 )): # windows float128?
226
- # On windows 32 bit at least, float96 appears to be a float64 padded to
227
- # 96 bits. The nexp == 15 is the same as for intel 80 but nexp in fact
228
- # appears to be 11 as for float64
229
- return dict (min = np_type (info_64 .min ), max = np_type (info_64 .max ),
230
- nmant = info_64 .nmant , nexp = info_64 .nexp , width = width )
233
+ # On windows 32 bit at least, float96 is Intel 80 storage but operating
234
+ # at float64 precision. The finfo values give nexp == 15 (as for intel
235
+ # 80) but in calculations nexp in fact appears to be 11 as for float64
236
+ return type_info (np .float64 ).update (dict (width = width ))
231
237
elif vals == (1 , 1 , 16 ) and processor () == 'powerpc' : # broken PPC
232
- return dict (min = np_type (info_64 .min ), max = np_type (info_64 .max ),
233
- nmant = 106 , nexp = 11 , width = width )
238
+ ret = type_info (np .float64 ).update (dict (nmant = 106 , width = width ))
234
239
else : # don't recognize the type
235
240
raise FloatingError ('We had not expected type %s' % np_type )
236
241
return ret
@@ -438,21 +443,32 @@ def floor_log2(x):
438
443
439
444
Returns
440
445
-------
441
- L : int
442
- floor of base 2 log of `x`
446
+ L : None or int
447
+ floor of base 2 log of `x`. None if `x` == 0.
443
448
444
449
Examples
445
450
--------
446
451
>>> floor_log2(2**9+1)
447
452
9
448
453
>>> floor_log2(-2**9+1)
449
454
8
455
+ >>> floor_log2(0.5)
456
+ -1
457
+ >>> floor_log2(0) is None
458
+ True
450
459
"""
451
460
ip = 0
452
461
rem = abs (x )
453
- while rem >= 2 :
454
- ip += 1
455
- rem //= 2
462
+ if rem > 1 :
463
+ while rem >= 2 :
464
+ ip += 1
465
+ rem //= 2
466
+ return ip
467
+ elif rem == 0 :
468
+ return None
469
+ while rem < 1 :
470
+ ip -= 1
471
+ rem *= 2
456
472
return ip
457
473
458
474
@@ -523,3 +539,41 @@ def able_int_type(values):
523
539
if mn >= info .min and mx <= info .max :
524
540
return ityp
525
541
return None
542
+
543
+
544
+ def ulp (val = np .float64 (1.0 )):
545
+ """ Return gap between `val` and nearest representable number of same type
546
+
547
+ This is the value of a unit in the last place (ULP), and is similar in
548
+ meaning to the MATLAB eps function.
549
+
550
+ Parameters
551
+ ----------
552
+ val : scalar, optional
553
+ scalar value of any numpy type. Default is 1.0 (float64)
554
+
555
+ Returns
556
+ -------
557
+ ulp_val : scalar
558
+ gap between `val` and nearest representable number of same type
559
+
560
+ Notes
561
+ -----
562
+ The wikipedia article on machine epsilon points out that the term *epsilon*
563
+ can be used in the sense of a unit in the last place (ULP), or as the
564
+ maximum relative rounding error. The MATLAB ``eps`` function uses the ULP
565
+ meaning, but this function is ``ulp`` rather than ``eps`` to avoid confusion
566
+ between different meanings of *eps*.
567
+ """
568
+ val = np .array (val )
569
+ if not np .isfinite (val ):
570
+ return np .nan
571
+ if val .dtype .kind in 'iu' :
572
+ return 1
573
+ aval = np .abs (val )
574
+ info = type_info (val .dtype )
575
+ fl2 = floor_log2 (aval )
576
+ if fl2 is None or fl2 < info ['minexp' ]: # subnormal
577
+ fl2 = info ['minexp' ]
578
+ # 'nmant' value does not include implicit first bit
579
+ return 2 ** (fl2 - info ['nmant' ])
0 commit comments