Skip to content

Commit d919a3d

Browse files
committed
NF - type_info function with tests
Function to get nmant etc from type. Works round PPC bug in finfo.
1 parent 4cbd963 commit d919a3d

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

nibabel/casting.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
""" Utilties for casting floats to integers
22
"""
33

4+
from platform import processor
5+
46
import numpy as np
57

68

@@ -165,6 +167,71 @@ class FloatingError(Exception):
165167
pass
166168

167169

170+
def type_info(np_type):
171+
""" Return dict with min, max, nexp, nmant, width for numpy type `np_type`
172+
173+
Type can be integer in which case nexp and nmant are None.
174+
175+
Parameters
176+
----------
177+
np_type : numpy type specifier
178+
Any specifier for a numpy dtype
179+
180+
Returns
181+
-------
182+
info : dict
183+
with fields ``min`` (minimum value), ``max`` (maximum value), ``nexp``
184+
(exponent width), ``nmant`` (significand precision not including
185+
implicit first digit) ``width`` (width in bytes). ``nexp``, ``nmant``
186+
are None for integer types.
187+
188+
Raises
189+
------
190+
FloatingError : for floating point types we don't recognize
191+
192+
Notes
193+
-----
194+
You might be thinking that ``np.finfo`` does this job, and it does, except
195+
for PPC long doubles (http://projects.scipy.org/numpy/ticket/2077). This
196+
routine protects against errors in ``np.finfo`` by only accepting values
197+
that we know are likely to be correct.
198+
"""
199+
dt = np.dtype(np_type)
200+
width = dt.itemsize
201+
try: # integer type
202+
info = np.iinfo(dt)
203+
except ValueError:
204+
pass
205+
else:
206+
return dict(min=info.min, max=info.max, nmant=None, nexp=None,
207+
width=width)
208+
info = np.finfo(dt)
209+
vals = info.nmant, info.nexp, width
210+
if vals == (10, 5, 2): # binary16
211+
assert dt.type is _float16
212+
elif vals == (23, 8, 4): # binary32
213+
assert dt.type is np.float32
214+
elif vals == (23, 8, 8): # binary32, complex
215+
assert dt.type is np.complex64
216+
elif vals == (52, 11, 8): # binary64
217+
assert dt.type in (np.float64, np.longdouble)
218+
elif vals == (52, 11, 16): # binary64, complex
219+
assert dt.type is np.complex128
220+
elif vals == (112, 15, 16): # binary128
221+
assert dt.type is np.longdouble
222+
elif vals in ((63, 15, 12), (63, 15, 16)): # Intel extended 80
223+
assert dt.type is np.longdouble
224+
elif vals == (1, 1, 16) and processor() == 'powerpc': # broken PPC
225+
assert dt.type is np.longdouble
226+
dbl_info = np.finfo(np.float64)
227+
return dict(min=dbl_info.min, max=dbl_info.max, nmant=106, nexp=11,
228+
width=width)
229+
else: # don't recognize the type
230+
raise FloatingError('We had not expected this type')
231+
return dict(min=info.min, max=info.max, nmant=info.nmant, nexp=info.nexp,
232+
width=width)
233+
234+
168235
def flt2nmant(flt_type):
169236
""" Number of significand bits in float type `flt_type`
170237

nibabel/tests/test_floating.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44

55
from ..casting import (floor_exact, flt2nmant, as_int, FloatingError,
6-
int_to_float, floor_log2)
6+
int_to_float, floor_log2, type_info)
77

88
from nose import SkipTest
99
from nose.tools import assert_equal, assert_raises
@@ -20,6 +20,42 @@
2020

2121
LD_INFO = np.finfo(np.longdouble)
2222

23+
24+
def test_type_info():
25+
# Test routine to get min, max, nmant, nexp
26+
for dtt in np.sctypes['int'] + np.sctypes['uint']:
27+
info = np.iinfo(dtt)
28+
infod = type_info(dtt)
29+
assert_equal(dict(min=info.min, max=info.max, nexp=None, nmant=None,
30+
width=np.dtype(dtt).itemsize), infod)
31+
for dtt in IEEE_floats + [np.complex64, np.complex64]:
32+
info = np.finfo(dtt)
33+
infod = type_info(dtt)
34+
assert_equal(dict(min=info.min, max=info.max,
35+
nexp=info.nexp, nmant=info.nmant,
36+
width=np.dtype(dtt).itemsize),
37+
infod)
38+
# What is longdouble?
39+
info = np.finfo(np.longdouble)
40+
dbl_info = np.finfo(np.float64)
41+
infod = type_info(np.longdouble)
42+
width = np.dtype(np.longdouble).itemsize
43+
vals = (info.nmant, info.nexp, width)
44+
# Information for PPC head / tail doubles from:
45+
# https://developer.apple.com/library/mac/#documentation/Darwin/Reference/Manpages/man3/float.3.html
46+
if vals in ((52, 11, 8), # longdouble is same as double
47+
(63, 15, 12), (63, 15, 16), # intel 80 bit
48+
(112, 15, 16), # real float128
49+
(106, 11, 16)): # PPC head, tail doubles, expected values
50+
assert_equal(dict(min=info.min, max=info.max,
51+
nexp=info.nexp, nmant=info.nmant, width=width),
52+
infod)
53+
elif vals == (1, 1, 16): # bust info for PPC head / tail longdoubles
54+
assert_equal(dict(min=dbl_info.min, max=dbl_info.max,
55+
nexp=11, nmant=106, width=16),
56+
infod)
57+
58+
2359
def test_flt2nmant():
2460
for t in IEEE_floats:
2561
assert_equal(flt2nmant(t), np.finfo(t).nmant)

0 commit comments

Comments
 (0)