@@ -25,7 +25,8 @@ def __init__(self, array, out_dtype=None)
2525
2626import numpy as np
2727
28- from .casting import int_to_float , as_int , int_abs , type_info
28+ from .casting import (int_to_float , as_int , int_abs , type_info , floor_exact ,
29+ best_float )
2930from .volumeutils import finite_range , array_to_file
3031
3132
@@ -303,7 +304,11 @@ def _do_scaling(self):
303304 # (u)int to (u)int
304305 info = np .iinfo (out_dtype )
305306 out_max , out_min = info .max , info .min
306- if mx <= out_max and mn >= out_min : # already in range
307+ # If left as int64, uint64, comparisons will default to floats, and
308+ # these are inexact for > 2**53 - so convert to int
309+ if (as_int (mx ) <= as_int (out_max ) and
310+ as_int (mn ) >= as_int (out_min )):
311+ # already in range
307312 return
308313 # (u)int to (u)int scaling
309314 self ._iu2iu ()
@@ -331,12 +336,13 @@ def _range_scale(self):
331336 out_dtype = self ._out_dtype
332337 info = type_info (out_dtype )
333338 t_mn_mx = info ['min' ], info ['max' ]
339+ big_float = best_float ()
334340 if out_dtype .kind == 'f' :
335341 # But we want maximum precision for the calculations. Casting will
336342 # not lose precision because min/max are of fp type.
337- t_min , t_max = np .array (t_mn_mx , dtype = np . longdouble )
343+ t_min , t_max = np .array (t_mn_mx , dtype = big_float )
338344 else : # (u)int
339- t_min , t_max = [int_to_float (v , np . longdouble ) for v in t_mn_mx ]
345+ t_min , t_max = [int_to_float (v , big_float ) for v in t_mn_mx ]
340346 if self ._out_dtype .kind == 'u' :
341347 if mn < 0 and mx > 0 :
342348 raise WriterError ('Cannot scale negative and positive '
@@ -458,9 +464,25 @@ def _iu2iu(self):
458464 t_min , t_max = np .iinfo (out_dtype ).min , np .iinfo (out_dtype ).max
459465 type_range = as_int (t_max ) - as_int (t_min )
460466 mn2mx = mx - mn
461- if mn2mx <= type_range : # offset enough?
462- self .inter = mn - t_min
463- return
467+ if mn2mx <= type_range : # might offset be enough?
468+ if t_min == 0 : # uint output - take min to 0
469+ # decrease offset with floor_exact, meaning mn >= t_min after
470+ # subtraction. But we may have pushed the data over t_max,
471+ # which we check below
472+ inter = floor_exact (mn - t_min , self .scaler_dtype )
473+ else : # int output - take midpoint to 0
474+ # ceil below increases inter, pushing scale up to 0.5 towards
475+ # -inf, because ints have abs min == abs max + 1
476+ midpoint = mn + as_int (np .ceil (mn2mx / 2.0 ))
477+ # Floor exact decreases inter, so pulling scaled values more
478+ # positive. This may make mx - inter > t_max
479+ inter = floor_exact (midpoint , self .scaler_dtype )
480+ # Need to check still in range after floor_exact-ing
481+ int_inter = as_int (inter )
482+ assert mn - int_inter >= t_min
483+ if mx - int_inter <= t_max :
484+ self .inter = inter
485+ return
464486 # Try slope options (sign flip) and then range scaling
465487 super (SlopeInterArrayWriter , self )._iu2iu ()
466488
@@ -472,27 +494,28 @@ def _range_scale(self):
472494 self .inter = mn
473495 return
474496 # Straight mx-mn can overflow.
497+ big_float = best_float () # usually longdouble except in win 32
475498 if mn .dtype .kind == 'f' : # Already floats
476499 # float64 and below cast correctly to longdouble. Longdouble needs
477500 # no casting
478- mn2mx = np .diff (np .array ([mn , mx ], dtype = np . longdouble ))
501+ mn2mx = np .diff (np .array ([mn , mx ], dtype = big_float ))
479502 else : # max possible (u)int range is 2**64-1 (int64, uint64)
480503 # int_to_float covers this range. On windows longdouble is the same
481504 # as double so mn2mx will be 2**64 - thus overestimating slope
482505 # slightly. Casting to int needed to allow mx-mn to be larger than
483506 # the largest (u)int value
484- mn2mx = int_to_float (as_int (mx ) - as_int (mn ), np . longdouble )
507+ mn2mx = int_to_float (as_int (mx ) - as_int (mn ), big_float )
485508 if out_dtype .kind == 'f' :
486509 # Type range, these are also floats
487510 info = type_info (out_dtype )
488511 t_mn_mx = info ['min' ], info ['max' ]
489512 else :
490513 t_mn_mx = np .iinfo (out_dtype ).min , np .iinfo (out_dtype ).max
491- t_mn_mx = [int_to_float (v , np . longdouble ) for v in t_mn_mx ]
514+ t_mn_mx = [int_to_float (v , big_float ) for v in t_mn_mx ]
492515 # We want maximum precision for the calculations. Casting will
493516 # not lose precision because min/max are of fp type.
494517 assert [v .dtype .kind for v in t_mn_mx ] == ['f' , 'f' ]
495- scaled_mn2mx = np .diff (np .array (t_mn_mx , dtype = np . longdouble ))
518+ scaled_mn2mx = np .diff (np .array (t_mn_mx , dtype = big_float ))
496519 slope = mn2mx / scaled_mn2mx
497520 self .inter = mn - t_mn_mx [0 ] * slope
498521 self .slope = slope
0 commit comments