Skip to content

Commit b9808e1

Browse files
committed
Merge branch 'main-master' into maint/1.2.x
* main-master: BF: fix tests for binary128 / longdouble BF: work around very odd POWER7 failure BF: workaround numpy bug when testing roundtrip BF: disable float128 datadtype if no binary128 NF: function to say if we have IEEE binary128 BF: add check for powerpc platform and longdouble BF: catch case where longdouble is float64 BF: try other checks for badly-detected longdouble
2 parents e6eb6e1 + 342e8d5 commit b9808e1

File tree

6 files changed

+163
-17
lines changed

6 files changed

+163
-17
lines changed

nibabel/casting.py

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ class FloatingError(Exception):
168168
pass
169169

170170

171+
def on_powerpc():
172+
""" True if we are running on a Power PC platform
173+
174+
Has to deal with older Macs and IBM POWER7 series among others
175+
"""
176+
return processor() == 'powerpc' or machine().startswith('ppc')
177+
178+
171179
def type_info(np_type):
172180
""" Return dict with min, max, nexp, nmant, width for numpy type `np_type`
173181
@@ -244,13 +252,97 @@ def type_info(np_type):
244252
# at float64 precision. The finfo values give nexp == 15 (as for intel
245253
# 80) but in calculations nexp in fact appears to be 11 as for float64
246254
ret.update(dict(width=width))
247-
elif vals == (1, 1, 16) and processor() == 'powerpc': # broken PPC
248-
ret.update(dict(nmant=106, width=width))
249-
else: # don't recognize the type
255+
return ret
256+
# Oh dear, we don't recognize the type information. Try some known types
257+
# and then give up. At this stage we're expecting exotic longdouble or their
258+
# complex equivalent.
259+
if not np_type in (np.longdouble, np.longcomplex) or width not in (16, 32):
250260
raise FloatingError('We had not expected type %s' % np_type)
261+
if (vals == (1, 1, 16) and on_powerpc() and
262+
_check_maxexp(np.longdouble, 1024)):
263+
# double pair on PPC. The _check_nmant routine does not work for this
264+
# type, hence the powerpc platform check instead
265+
ret.update(dict(nmant = 106, width=width))
266+
elif (_check_nmant(np.longdouble, 52) and
267+
_check_maxexp(np.longdouble, 11)):
268+
# Got float64 despite everything
269+
pass
270+
elif (_check_nmant(np.longdouble, 112) and
271+
_check_maxexp(np.longdouble, 16384)):
272+
# binary 128, but with some busted type information. np.longcomplex
273+
# seems to break here too, so we need to use np.longdouble and
274+
# complexify
275+
two = np.longdouble(2)
276+
# See: http://matthew-brett.github.com/pydagogue/floating_point.html
277+
max_val = (two ** 113 - 1) / (two ** 112) * two ** 16383
278+
if np_type is np.longcomplex:
279+
max_val += 0j
280+
ret = dict(min = -max_val,
281+
max= max_val,
282+
nmant = 112,
283+
nexp = 15,
284+
minexp = -16382,
285+
maxexp = 16384,
286+
width = width)
287+
else: # don't recognize the type
288+
raise FloatingError('We had not expected long double type %s '
289+
'with info %s' % (np_type, info))
251290
return ret
252291

253292

293+
def _check_nmant(np_type, nmant):
294+
""" True if fp type `np_type` seems to have `nmant` significand digits
295+
296+
Note 'digits' does not include implicit digits. And in fact if there are no
297+
implicit digits, the `nmant` number is one less than the actual digits.
298+
Assumes base 2 representation.
299+
300+
Parameters
301+
----------
302+
np_type : numpy type specifier
303+
Any specifier for a numpy dtype
304+
nmant : int
305+
Number of digits to test against
306+
307+
Returns
308+
-------
309+
tf : bool
310+
True if `nmant` is the correct number of significand digits, false
311+
otherwise
312+
"""
313+
np_type = np.dtype(np_type).type
314+
max_contig = np_type(2 ** (nmant + 1)) # maximum of contiguous integers
315+
tests = max_contig + np.array([-2, -1, 0, 1, 2], dtype=np_type)
316+
return np.all(tests - max_contig == [-2, -1, 0, 0, 2])
317+
318+
319+
def _check_maxexp(np_type, maxexp):
320+
""" True if fp type `np_type` seems to have `maxexp` maximum exponent
321+
322+
We're testing "maxexp" as returned by numpy. This value is set to one
323+
greater than the maximum power of 2 that `np_type` can represent.
324+
325+
Assumes base 2 representation. Very crude check
326+
327+
Parameters
328+
----------
329+
np_type : numpy type specifier
330+
Any specifier for a numpy dtype
331+
maxexp : int
332+
Maximum exponent to test against
333+
334+
Returns
335+
-------
336+
tf : bool
337+
True if `maxexp` is the correct maximum exponent, False otherwise.
338+
"""
339+
dt = np.dtype(np_type)
340+
np_type = dt.type
341+
two = np_type(2).reshape((1,)) # to avoid upcasting
342+
return (np.isfinite(two ** (maxexp - 1)) and
343+
not np.isfinite(two ** maxexp))
344+
345+
254346
def as_int(x, check=True):
255347
""" Return python integer representation of number
256348
@@ -548,6 +640,13 @@ def best_float():
548640
return np.float64
549641

550642

643+
def have_binary128():
644+
""" True if we have a binary128 IEEE longdouble
645+
"""
646+
ti = type_info(np.longdouble)
647+
return (ti['nmant'], ti['maxexp']) == (112, 16384)
648+
649+
551650
def ok_floats():
552651
""" Return floating point types sorted by precision
553652

nibabel/nifti1.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from .quaternions import fillpositive, quat2mat, mat2quat
2121
from . import analyze # module import
2222
from .spm99analyze import SpmAnalyzeHeader
23+
from .casting import have_binary128
2324

2425
# Needed for quaternion calculation
2526
FLOAT32_EPS_3 = -np.finfo(np.float32).eps * 3
@@ -76,13 +77,12 @@
7677
header_dtype = np.dtype(header_dtd)
7778

7879
# datatypes not in analyze format, with codes
79-
try:
80-
_float128t = np.float128
81-
except AttributeError:
80+
if have_binary128():
81+
# Only enable 128 bit floats if we really have IEEE binary 128 longdoubles
82+
_float128t = np.longdouble
83+
_complex256t = np.longcomplex
84+
else:
8285
_float128t = np.void
83-
try:
84-
_complex256t = np.complex256
85-
except AttributeError:
8686
_complex256t = np.void
8787

8888
_dtdefs = ( # code, label, dtype definition, niistring

nibabel/tests/test_arraywriters.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ def test_arraywriters():
8787
# Byteswapped is OK
8888
bs_arr = arr.byteswap().newbyteorder('S')
8989
bs_aw = klass(bs_arr)
90-
assert_array_equal(bs_arr, round_trip(bs_aw))
90+
# assert against original array because POWER7 was running into
91+
# trouble using the byteswapped array (bs_arr)
92+
assert_array_equal(arr, round_trip(bs_aw))
9193
bs_aw2 = klass(bs_arr, arr.dtype)
92-
assert_array_equal(bs_arr, round_trip(bs_aw2))
94+
assert_array_equal(arr, round_trip(bs_aw2))
9395
# 2D array
9496
arr2 = np.reshape(arr, (2, 5))
9597
a2w = klass(arr2)
@@ -508,6 +510,10 @@ def test_float_int_min_max():
508510
for in_dt in FLOAT_TYPES:
509511
finf = type_info(in_dt)
510512
arr = np.array([finf['min'], finf['max']], dtype=in_dt)
513+
# Bug in numpy 1.6.2 on PPC leading to infs - abort
514+
if not np.all(np.isfinite(arr)):
515+
print 'Hit PPC max -> inf bug; skip in_type %s' % in_dt
516+
continue
511517
for out_dt in IUINT_TYPES:
512518
try:
513519
aw = SlopeInterArrayWriter(arr, out_dt)

nibabel/tests/test_floating.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
""" Test floating point deconstructions and floor methods
22
"""
3-
from platform import processor
4-
53
import numpy as np
64

75
from ..casting import (floor_exact, ceil_exact, as_int, FloatingError,
8-
int_to_float, floor_log2, type_info)
6+
int_to_float, floor_log2, type_info, _check_nmant,
7+
_check_maxexp, ok_floats, on_powerpc, have_binary128)
98

109
from nose import SkipTest
11-
from nose.tools import assert_equal, assert_raises
10+
from nose.tools import assert_equal, assert_raises, assert_true, assert_false
1211

1312
IEEE_floats = [np.float32, np.float64]
1413
try:
@@ -80,6 +79,25 @@ def test_nmant():
8079
assert_equal(type_info(np.longdouble)['nmant'], 63)
8180

8281

82+
def test_check_nmant_nexp():
83+
# Routine for checking number of sigificand digits and exponent
84+
for t in IEEE_floats:
85+
nmant = np.finfo(t).nmant
86+
maxexp = np.finfo(t).maxexp
87+
assert_true(_check_nmant(t, nmant))
88+
assert_false(_check_nmant(t, nmant - 1))
89+
assert_false(_check_nmant(t, nmant + 1))
90+
assert_true(_check_maxexp(t, maxexp))
91+
assert_false(_check_maxexp(t, maxexp - 1))
92+
assert_false(_check_maxexp(t, maxexp + 1))
93+
# Check against type_info
94+
for t in ok_floats():
95+
ti = type_info(t)
96+
if ti['nmant'] != 106: # This check does not work for PPC double pair
97+
assert_true(_check_nmant(t, ti['nmant']))
98+
assert_true(_check_maxexp(t, ti['maxexp']))
99+
100+
83101
def test_as_int():
84102
# Integer representation of number
85103
assert_equal(as_int(2.0), 2)
@@ -214,7 +232,7 @@ def test_floor_exact():
214232
assert_equal(func(-iv+1, t), -iv+1)
215233
# The nmant value for longdouble on PPC appears to be conservative, so
216234
# that the tests for behavior above the nmant range fail
217-
if t is np.longdouble and processor() == 'powerpc':
235+
if t is np.longdouble and on_powerpc():
218236
continue
219237
# Confirm to ourselves that 2**(nmant+1) can't be exactly represented
220238
iv = 2**(nmant+1)
@@ -240,3 +258,13 @@ def test_floor_exact():
240258
assert_equal(int_flex(-iv-gap-j, t), -iv-2*gap)
241259
assert_equal(int_ceex(-iv-j, t), -iv)
242260
assert_equal(int_ceex(-iv-gap-j, t), -iv-gap)
261+
262+
263+
def test_usable_binary128():
264+
# Check for usable binary128
265+
yes = have_binary128()
266+
exp_test = np.longdouble(2) ** 16383
267+
assert_equal(yes,
268+
exp_test.dtype.itemsize == 16 and
269+
np.isfinite(exp_test) and
270+
_check_nmant(np.longdouble, 112))

nibabel/tests/test_nifti1.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import numpy as np
1616

17-
from ..casting import type_info
17+
from ..casting import type_info, have_binary128
1818
from ..tmpdirs import InTemporaryDirectory
1919
from ..spatialimages import HeaderDataError
2020
from ..affines import from_matvec
@@ -309,6 +309,14 @@ def test_binblock_is_file(self):
309309
hdr.write_to(str_io)
310310
assert_equal(str_io.getvalue(), hdr.binaryblock + ZEROB * 4)
311311

312+
def test_float128(self):
313+
hdr = self.header_class()
314+
if have_binary128():
315+
hdr.set_data_dtype(np.longdouble)
316+
assert_equal(hdr.get_data_dtype().type, np.longdouble)
317+
else:
318+
assert_raises(HeaderDataError, hdr.set_data_dtype, np.longdouble)
319+
312320

313321
class TestNifti1Image(tana.TestAnalyzeImage):
314322
# Run analyze-flavor spatialimage tests

nibabel/tests/test_scaling.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ def check_int_a2f(in_type, out_type):
224224
this_min, this_max = info['min'], info['max']
225225
if not in_type in np.sctypes['complex']:
226226
data = np.array([this_min, this_max], in_type)
227+
# Bug in numpy 1.6.2 on PPC leading to infs - abort
228+
if not np.all(np.isfinite(data)):
229+
if DEBUG:
230+
print 'Hit PPC max -> inf bug; skip in_type %s' % in_type
231+
return
227232
else: # Funny behavior with complex256
228233
data = np.zeros((2,), in_type)
229234
data[0] = this_min + 0j

0 commit comments

Comments
 (0)