Skip to content

Commit b6ea8a6

Browse files
committed
RF: make floor_exact like np.floor, add ceil_exact
Floor exact previously floored towards 0. This was unlike np.floor, which finds the nearest integer <= val. Added ceil_exact function (nearest exact integer >= val). Infs from floor / ceil_exact return infs (previously pulled infs down to float max) Consider -+ infs in shared_range calculation (now infs not removed by floor / ceil_exact).
1 parent 1e65617 commit b6ea8a6

File tree

2 files changed

+106
-39
lines changed

2 files changed

+106
-39
lines changed

nibabel/casting.py

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,10 @@ def shared_range(flt_type, int_type):
129129
130130
Examples
131131
--------
132-
>>> shared_range(np.float32, np.int32)
133-
(-2147483648.0, 2147483520.0)
134-
>>> shared_range('f4', 'i4')
135-
(-2147483648.0, 2147483520.0)
132+
>>> shared_range(np.float32, np.int32) == (-2147483648.0, 2147483520.0)
133+
True
134+
>>> shared_range('f4', 'i4') == (-2147483648.0, 2147483520.0)
135+
True
136136
"""
137137
flt_type = np.dtype(flt_type).type
138138
int_type = np.dtype(int_type).type
@@ -143,9 +143,15 @@ def shared_range(flt_type, int_type):
143143
except KeyError:
144144
pass
145145
ii = np.iinfo(int_type)
146-
mn_mx = floor_exact(ii.min, flt_type), floor_exact(ii.max, flt_type)
147-
_SHARED_RANGES[key] = mn_mx
148-
return mn_mx
146+
fi = np.finfo(flt_type)
147+
mn = ceil_exact(ii.min, flt_type)
148+
if mn == -np.inf:
149+
mn = fi.min
150+
mx = floor_exact(ii.max, flt_type)
151+
if mx == np.inf:
152+
mx = fi.max
153+
_SHARED_RANGES[key] = (mn, mx)
154+
return mn, mx
149155

150156
# ----------------------------------------------------------------------------
151157
# Routines to work out the next lowest representable integer in floating point
@@ -335,7 +341,7 @@ def int_to_float(val, flt_type):
335341

336342

337343
def floor_exact(val, flt_type):
338-
""" Get nearest exact integer to `val`, towards 0, in float type `flt_type`
344+
""" Return nearest exact integer <= `val` in float type `flt_type`
339345
340346
Parameters
341347
----------
@@ -344,15 +350,14 @@ def floor_exact(val, flt_type):
344350
because large integers cast as floating point may be rounded by the
345351
casting process.
346352
flt_type : numpy type
347-
numpy float type. Only IEEE types supported (np.float16, np.float32,
348-
np.float64)
353+
numpy float type.
349354
350355
Returns
351356
-------
352357
floor_val : object
353-
value of same floating point type as `val`, that is the next excat
354-
integer in this type, towards zero, or == `val` if val is exactly
355-
representable.
358+
value of same floating point type as `val`, that is the nearest exact
359+
integer in this type such that `floor_val` <= `val`. Thus if `val` is
360+
exact in `flt_type`, `floor_val` == `val`.
356361
357362
Examples
358363
--------
@@ -370,26 +375,74 @@ def floor_exact(val, flt_type):
370375
371376
>>> floor_exact(2**24+1, np.float32) == 2**24
372377
True
378+
379+
As for the numpy floor function, negatives floor towards -inf
380+
381+
>>> floor_exact(-2**24-1, np.float32) == -2**24-2
382+
True
373383
"""
374384
val = int(val)
375385
flt_type = np.dtype(flt_type).type
376-
sign = val > 0 and 1 or -1
377-
aval = abs(val)
386+
sign = 1 if val > 0 else -1
378387
try: # int_to_float deals with longdouble safely
379-
faval = int_to_float(aval, flt_type)
388+
fval = int_to_float(val, flt_type)
380389
except OverflowError:
381-
faval = np.inf
390+
return sign * np.inf
391+
if not np.isfinite(fval):
392+
return fval
382393
info = type_info(flt_type)
383-
if faval == np.inf:
384-
return sign * info['max']
385-
if as_int(faval) <= aval: # as_int deals with longdouble safely
386-
# Float casting has made the value go down or stay the same
387-
return sign * faval
394+
diff = val - as_int(fval)
395+
if diff >= 0: # floating point value <= val
396+
return fval
388397
# Float casting made the value go up
389-
biggest_gap = 2**(floor_log2(aval) - info['nmant'])
398+
biggest_gap = 2**(floor_log2(val) - info['nmant'])
390399
assert biggest_gap > 1
391-
faval -= flt_type(biggest_gap)
392-
return sign * faval
400+
fval -= flt_type(biggest_gap)
401+
return fval
402+
403+
404+
def ceil_exact(val, flt_type):
405+
""" Return nearest exact integer >= `val` in float type `flt_type`
406+
407+
Parameters
408+
----------
409+
val : int
410+
We have to pass val as an int rather than the floating point type
411+
because large integers cast as floating point may be rounded by the
412+
casting process.
413+
flt_type : numpy type
414+
numpy float type.
415+
416+
Returns
417+
-------
418+
ceil_val : object
419+
value of same floating point type as `val`, that is the nearest exact
420+
integer in this type such that `floor_val` >= `val`. Thus if `val` is
421+
exact in `flt_type`, `ceil_val` == `val`.
422+
423+
Examples
424+
--------
425+
Obviously 2 is within the range of representable integers for float32
426+
427+
>>> ceil_exact(2, np.float32)
428+
2.0
429+
430+
As is 2**24-1 (the number of significand digits is 23 + 1 implicit)
431+
432+
>>> ceil_exact(2**24-1, np.float32) == 2**24-1
433+
True
434+
435+
But 2**24+1 gives a number that float32 can't represent exactly
436+
437+
>>> ceil_exact(2**24+1, np.float32) == 2**24+2
438+
True
439+
440+
As for the numpy ceil function, negatives ceil towards inf
441+
442+
>>> ceil_exact(-2**24-1, np.float32) == -2**24
443+
True
444+
"""
445+
return -floor_exact(-val, flt_type)
393446

394447

395448
def int_abs(arr):

nibabel/tests/test_floating.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
import numpy as np
66

7-
from ..casting import (floor_exact, as_int, FloatingError, int_to_float,
8-
floor_log2, type_info)
7+
from ..casting import (floor_exact, ceil_exact, as_int, FloatingError,
8+
int_to_float, floor_log2, type_info)
99

1010
from nose import SkipTest
1111
from nose.tools import assert_equal, assert_raises
@@ -164,7 +164,8 @@ def test_floor_exact_16():
164164
# A normal integer can generate an inf in float16
165165
if not have_float16:
166166
raise SkipTest('No float16')
167-
assert_equal(floor_exact(2**31, np.float16), type_info(np.float16)['max'])
167+
assert_equal(floor_exact(2**31, np.float16), np.inf)
168+
assert_equal(floor_exact(-2**31, np.float16), -np.inf)
168169

169170

170171
def test_floor_exact_64():
@@ -192,37 +193,50 @@ def test_floor_exact():
192193
# When numbers go above int64 - I believe, numpy comparisons break down,
193194
# so we have to cast to int before comparison
194195
int_flex = lambda x, t : as_int(floor_exact(x, t))
196+
int_ceex = lambda x, t : as_int(ceil_exact(x, t))
195197
for t in to_test:
196198
# A number bigger than the range returns the max
197199
info = type_info(t)
198-
assert_equal(floor_exact(2**5000, t), info['max'])
200+
assert_equal(floor_exact(2**5000, t), np.inf)
201+
assert_equal(ceil_exact(2**5000, t), np.inf)
202+
# A number more negative returns -inf
203+
assert_equal(floor_exact(-2**5000, t), -np.inf)
204+
assert_equal(ceil_exact(-2**5000, t), -np.inf)
205+
# Check around end of integer precision
199206
nmant = info['nmant']
200207
for i in range(nmant+1):
201208
iv = 2**i
202209
# up to 2**nmant should be exactly representable
203-
assert_equal(int_flex(iv, t), iv)
204-
assert_equal(int_flex(-iv, t), -iv)
205-
assert_equal(int_flex(iv-1, t), iv-1)
206-
assert_equal(int_flex(-iv+1, t), -iv+1)
210+
for func in (int_flex, int_ceex):
211+
assert_equal(func(iv, t), iv)
212+
assert_equal(func(-iv, t), -iv)
213+
assert_equal(func(iv-1, t), iv-1)
214+
assert_equal(func(-iv+1, t), -iv+1)
207215
# The nmant value for longdouble on PPC appears to be conservative, so
208216
# that the tests for behavior above the nmant range fail
209217
if t is np.longdouble and processor() == 'powerpc':
210218
continue
211-
# 2**(nmant+1) can't be exactly represented
219+
# Confirm to ourselves that 2**(nmant+1) can't be exactly represented
212220
iv = 2**(nmant+1)
213221
assert_equal(int_flex(iv+1, t), iv)
222+
assert_equal(int_ceex(iv+1, t), iv+2)
214223
# negatives
215-
assert_equal(int_flex(-iv-1, t), -iv)
224+
assert_equal(int_flex(-iv-1, t), -iv-2)
225+
assert_equal(int_ceex(-iv-1, t), -iv)
216226
# The gap in representable numbers is 2 above 2**(nmant+1), 4 above
217227
# 2**(nmant+2), and so on.
218228
for i in range(5):
219229
iv = 2**(nmant+1+i)
220230
gap = 2**(i+1)
221231
assert_equal(as_int(t(iv) + t(gap)), iv+gap)
222-
for j in range(1,gap):
232+
for j in range(1, gap):
223233
assert_equal(int_flex(iv+j, t), iv)
224234
assert_equal(int_flex(iv+gap+j, t), iv+gap)
235+
assert_equal(int_ceex(iv+j, t), iv+gap)
236+
assert_equal(int_ceex(iv+gap+j, t), iv+2*gap)
225237
# negatives
226-
for j in range(1,gap):
227-
assert_equal(int_flex(-iv-j, t), -iv)
228-
assert_equal(int_flex(-iv-gap-j, t), -iv-gap)
238+
for j in range(1, gap):
239+
assert_equal(int_flex(-iv-j, t), -iv-gap)
240+
assert_equal(int_flex(-iv-gap-j, t), -iv-2*gap)
241+
assert_equal(int_ceex(-iv-j, t), -iv)
242+
assert_equal(int_ceex(-iv-gap-j, t), -iv-gap)

0 commit comments

Comments
 (0)