The original blog post is here.
In @cfelton 's implementation of fixbv
, there are some kinds of round modes implemented in _resize.py
, as in the source code:
# round :
ROUND_MODES = ( # towards :
'ceil', # +infinity: always round up
'fix', # 0 : always down
'floor', # -infinity: truncate, always round down
'nearest', # nearest : tie towards largest absolute value
'round', # nearest : ties to +infinity
'convergent', # nearest : tie to closest even (round_even)
'round_even', # nearest : tie to closest even (convergent)
)
The behavior of ceil
, fix
and floor
is quite clear in this case. Whatever fractional part is, it will be rounded to the integer towards +inf, 0, or -inf.
But it seems not quite clear for the last four modes.
OK, let’s see the source code in _resize.py
first:
def _round(val, fmt, round_mode):
"""Round the initial value if needed"""
# Scale the value to the integer range (the underlying representation)
assert is_round_mode(round_mode)
assert isinstance(fmt, tuple)
wl,iwl,fwl = fmt
_val = val
val = val * 2.0**fwl
#print(" [rsz][rnd]: %f %f, %s" % (val, _val, fmt))
if round_mode == 'ceil':
retval = math.ceil(val)
elif round_mode == 'fix':
if val > 0:
retval = math.floor(val)
else:
retval = math.ceil(val)
elif round_mode == 'floor':
retval = math.floor(val)
elif round_mode == 'nearest':
fval,ival = math.modf(val)
if fval == .5:
retval = int(val+1) if val > 0 else int(val-1)
else:
retval = round(val)
elif round_mode == 'round':
retval = round(val)
elif round_mode == 'round_even' or round_mode == 'convergent':
fval,ival = math.modf(val)
abs_ival = int(abs(ival))
sign = -1 if ival < 0 else 1
if (abs(fval) - 0.5) == 0.0:
if abs_ival%2 == 0:
retval = abs_ival * sign
else:
retval = (abs_ival + 1) * sign
else:
retval = round(val)
else:
raise TypeError("invalid round mode!" % self.round_mode)
return int(retval)
To read the last 4 kinds of resolution, it is necessary to know the behavior of Python’s built-in round
function.
Here we assume we do not provide ndigits
parameter to round
function.
Python’s document says that round
will round the numbers to the nearest integer. However, if the fractional part is 0.5, round
will round to the nearest even number. That is to say, round(2.5)
will be 2, but round(3.5)
will be 4.
So, I guess that the behavior of round modes round
, round_even
, and convergent
are the same.
Finally, nearest
will be the same as the above three round modes in negative values, but for positive values, it will be different. If the fractional part is 0.5, it will advance to the larger integer, otherwise round to the nearest.
To verify this, I wrote a small program for it. The rounding code is copied from corresponding function in _resize.py
.
import math
import csv
# round :
ROUND_MODES = ( # towards :
'ceil', # +infinity: always round up
'fix', # 0 : always down
'floor', # -infinity: truncate, always round down
'nearest', # nearest : tie towards largest absolute value
'round', # nearest : ties to +infinity
'convergent', # nearest : tie to closest even (round_even)
'round_even', # nearest : tie to closest even (convergent)
)
def _round(val, round_mode):
if round_mode == 'ceil':
retval = math.ceil(val)
elif round_mode == 'fix':
if val > 0:
retval = math.floor(val)
else:
retval = math.ceil(val)
elif round_mode == 'floor':
retval = math.floor(val)
elif round_mode == 'nearest':
fval,ival = math.modf(val)
if fval == .5:
retval = int(val+1) if val > 0 else int(val-1)
else:
retval = round(val)
elif round_mode == 'round':
retval = round(val)
elif round_mode == 'round_even' or round_mode == 'convergent':
fval,ival = math.modf(val)
abs_ival = int(abs(ival))
sign = -1 if ival < 0 else 1
if (abs(fval) - 0.5) == 0.0:
if abs_ival%2 == 0:
retval = abs_ival * sign
else:
retval = (abs_ival + 1) * sign
else:
retval = round(val)
else:
raise TypeError("invalid round mode!" % self.round_mode)
return int(retval)
values = [-3.5, -3.14, -3., -2.718, -2.5, -2., -1.618, -1.5, -1., -.618, -.5,
0., .5, .618, 1., 1.5, 1.618, 2., 2.5, 2.718, 3., 3.14, 3.5]
with open('round_test.csv', 'w', newline='') as csvfile:
wr = csv.writer(csvfile)
wr.writerow([''] + values)
for round_mode in ROUND_MODES:
rounded_values = [_round(val, round_mode) for val in values]
wr.writerow([round_mode] + rounded_values)
It tests different values for different round modes.
Here is the result:
EDIT: The code of “nearest” rounding did not handle the case of negative values, so the condition case should be:
if fval == .5 or fval == -.5:
And its behavior when the fractional part is 0.5 (either positive or negative) should be rounding away from 0.