@@ -25,7 +25,8 @@ def __init__(self, array, out_dtype=None)
25
25
26
26
import numpy as np
27
27
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 )
29
30
from .volumeutils import finite_range , array_to_file
30
31
31
32
@@ -303,7 +304,11 @@ def _do_scaling(self):
303
304
# (u)int to (u)int
304
305
info = np .iinfo (out_dtype )
305
306
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
307
312
return
308
313
# (u)int to (u)int scaling
309
314
self ._iu2iu ()
@@ -331,12 +336,13 @@ def _range_scale(self):
331
336
out_dtype = self ._out_dtype
332
337
info = type_info (out_dtype )
333
338
t_mn_mx = info ['min' ], info ['max' ]
339
+ big_float = best_float ()
334
340
if out_dtype .kind == 'f' :
335
341
# But we want maximum precision for the calculations. Casting will
336
342
# 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 )
338
344
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 ]
340
346
if self ._out_dtype .kind == 'u' :
341
347
if mn < 0 and mx > 0 :
342
348
raise WriterError ('Cannot scale negative and positive '
@@ -458,9 +464,25 @@ def _iu2iu(self):
458
464
t_min , t_max = np .iinfo (out_dtype ).min , np .iinfo (out_dtype ).max
459
465
type_range = as_int (t_max ) - as_int (t_min )
460
466
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
464
486
# Try slope options (sign flip) and then range scaling
465
487
super (SlopeInterArrayWriter , self )._iu2iu ()
466
488
@@ -472,27 +494,28 @@ def _range_scale(self):
472
494
self .inter = mn
473
495
return
474
496
# Straight mx-mn can overflow.
497
+ big_float = best_float () # usually longdouble except in win 32
475
498
if mn .dtype .kind == 'f' : # Already floats
476
499
# float64 and below cast correctly to longdouble. Longdouble needs
477
500
# no casting
478
- mn2mx = np .diff (np .array ([mn , mx ], dtype = np . longdouble ))
501
+ mn2mx = np .diff (np .array ([mn , mx ], dtype = big_float ))
479
502
else : # max possible (u)int range is 2**64-1 (int64, uint64)
480
503
# int_to_float covers this range. On windows longdouble is the same
481
504
# as double so mn2mx will be 2**64 - thus overestimating slope
482
505
# slightly. Casting to int needed to allow mx-mn to be larger than
483
506
# 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 )
485
508
if out_dtype .kind == 'f' :
486
509
# Type range, these are also floats
487
510
info = type_info (out_dtype )
488
511
t_mn_mx = info ['min' ], info ['max' ]
489
512
else :
490
513
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 ]
492
515
# We want maximum precision for the calculations. Casting will
493
516
# not lose precision because min/max are of fp type.
494
517
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 ))
496
519
slope = mn2mx / scaled_mn2mx
497
520
self .inter = mn - t_mn_mx [0 ] * slope
498
521
self .slope = slope
0 commit comments