Skip to content

Commit ea3a400

Browse files
committed
BF: try other checks for badly-detected longdouble
Numpy gives wrong values from the np.finfo routine for PPC, and I put a hack in to detect that. It looks like numpy also gives wrong values on Debian on the IBM POWER7, and that might have a real binary128 longdouble. Try to make the checks more flexible to recognize both cases.
1 parent a9f37e7 commit ea3a400

File tree

2 files changed

+102
-4
lines changed

2 files changed

+102
-4
lines changed

nibabel/casting.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,91 @@ def type_info(np_type):
244244
# at float64 precision. The finfo values give nexp == 15 (as for intel
245245
# 80) but in calculations nexp in fact appears to be 11 as for float64
246246
ret.update(dict(width=width))
247-
elif vals == (1, 1, 16) and processor() == 'powerpc': # broken PPC
248-
ret.update(dict(nmant=106, width=width))
247+
# Oh dear, we don't recognize the type information. Try some known types
248+
# and then give up. At this stage we're expecting exotic longdouble or their
249+
# complex equivalent.
250+
if not np_type in (np.longdouble, np.longcomplex) or width not in (16, 32):
251+
raise FloatingError('We had not expected type %s' % np_type)
252+
if (vals == (1, 1, 16) and processor() == 'powerpc' and
253+
_check_maxexp(np.longdouble, 1024)):
254+
# double pair on PPC. The _check_nmant routine does not work for this
255+
# type, hence the processor check
256+
ret.update(dict(nmant = 106, width=width))
257+
elif (_check_nmant(np.longdouble, 112) and
258+
_check_maxexp(np.longdouble, 16384)):
259+
# binary 128, but with some busted type information. np.longcomplex
260+
# seems to break here too, so we need to use np.longdouble and
261+
# complexify
262+
two = np.longdouble(2)
263+
# See: http://matthew-brett.github.com/pydagogue/floating_point.html
264+
max_val = (two ** 113 - 1) / (two ** 112) * two ** 16383
265+
if np_type is np.longcomplex:
266+
max_val += 0j
267+
ret = dict(min = -max_val,
268+
max= max_val,
269+
nmant = 112,
270+
nexp = 15,
271+
minexp = -16382,
272+
maxexp = 16384,
273+
width = width)
249274
else: # don't recognize the type
250275
raise FloatingError('We had not expected type %s' % np_type)
251276
return ret
252277

253278

279+
def _check_nmant(np_type, nmant):
280+
""" True if fp type `np_type` seems to have `nmant` significand digits
281+
282+
Note 'digits' does not include implicit digits. And in fact if there are no
283+
implicit digits, the `nmant` number is one less than the actual digits.
284+
Assumes base 2 representation.
285+
286+
Parameters
287+
----------
288+
np_type : numpy type specifier
289+
Any specifier for a numpy dtype
290+
nmant : int
291+
Number of digits to test against
292+
293+
Returns
294+
-------
295+
tf : bool
296+
True if `nmant` is the correct number of significand digits, false
297+
otherwise
298+
"""
299+
np_type = np.dtype(np_type).type
300+
max_contig = np_type(2 ** (nmant + 1)) # maximum of contiguous integers
301+
tests = max_contig + np.array([-2, -1, 0, 1, 2], dtype=np_type)
302+
return np.all(tests - max_contig == [-2, -1, 0, 0, 2])
303+
304+
305+
def _check_maxexp(np_type, maxexp):
306+
""" True if fp type `np_type` seems to have `maxexp` maximum exponent
307+
308+
We're testing "maxexp" as returned by numpy. This value is set to one
309+
greater than the maximum power of 2 that `np_type` can represent.
310+
311+
Assumes base 2 representation. Very crude check
312+
313+
Parameters
314+
----------
315+
np_type : numpy type specifier
316+
Any specifier for a numpy dtype
317+
maxexp : int
318+
Maximum exponent to test against
319+
320+
Returns
321+
-------
322+
tf : bool
323+
True if `maxexp` is the correct maximum exponent, False otherwise.
324+
"""
325+
dt = np.dtype(np_type)
326+
np_type = dt.type
327+
two = np_type(2).reshape((1,)) # to avoid upcasting
328+
return (np.isfinite(two ** (maxexp - 1)) and
329+
not np.isfinite(two ** maxexp))
330+
331+
254332
def as_int(x, check=True):
255333
""" Return python integer representation of number
256334

nibabel/tests/test_floating.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import numpy as np
66

77
from ..casting import (floor_exact, ceil_exact, as_int, FloatingError,
8-
int_to_float, floor_log2, type_info)
8+
int_to_float, floor_log2, type_info, _check_nmant,
9+
_check_maxexp, ok_floats)
910

1011
from nose import SkipTest
11-
from nose.tools import assert_equal, assert_raises
12+
from nose.tools import assert_equal, assert_raises, assert_true, assert_false
1213

1314
IEEE_floats = [np.float32, np.float64]
1415
try:
@@ -80,6 +81,25 @@ def test_nmant():
8081
assert_equal(type_info(np.longdouble)['nmant'], 63)
8182

8283

84+
def test_check_nmant_nexp():
85+
# Routine for checking number of sigificand digits and exponent
86+
for t in IEEE_floats:
87+
nmant = np.finfo(t).nmant
88+
maxexp = np.finfo(t).maxexp
89+
assert_true(_check_nmant(t, nmant))
90+
assert_false(_check_nmant(t, nmant - 1))
91+
assert_false(_check_nmant(t, nmant + 1))
92+
assert_true(_check_maxexp(t, maxexp))
93+
assert_false(_check_maxexp(t, maxexp - 1))
94+
assert_false(_check_maxexp(t, maxexp + 1))
95+
# Check against type_info
96+
for t in ok_floats():
97+
ti = type_info(t)
98+
if ti['nmant'] != 106: # This check does not work for PPC double pair
99+
assert_true(_check_nmant(t, ti['nmant']))
100+
assert_true(_check_maxexp(t, ti['maxexp']))
101+
102+
83103
def test_as_int():
84104
# Integer representation of number
85105
assert_equal(as_int(2.0), 2)

0 commit comments

Comments
 (0)