Round, With Ties to Even
We are considering a MATLAB Enhancement Request to support options for round(x) when x is exactly halfway between two integers.
Contents
Classic round
The Classic MATLAB calculator was written in Fortran forty years ago, before IEEE 754 and before MathWorks. It had only 71 functions and it was not easy to add more. But one of those functions was ROUND. The help entry had only one sentence.
ROUND ROUND(X) rounds the elements of X to the nearest integers.
That doesn't say what happens when there is a tie. The code for ROUND was essentially this one line, which relied on FLOOR.
ROUND(X) = SIGN(X)*FLOOR(ABS(X) + 0.5)
In particular:
ROUND(0.5) = 1.0
ROUND(1.5) = 2.0.
When MathWorks began producing the modern versions of MATLAB in 1984, we retained this simple definition for round.
roundoff
The function round can be used to clean up after roundoff.
format long e H = hilb(5) X = inv(H) X = round(X) XH = X*H XH = round(XH)
H = Columns 1 through 3 1.000000000000000e+00 5.000000000000000e-01 3.333333333333333e-01 5.000000000000000e-01 3.333333333333333e-01 2.500000000000000e-01 3.333333333333333e-01 2.500000000000000e-01 2.000000000000000e-01 2.500000000000000e-01 2.000000000000000e-01 1.666666666666667e-01 2.000000000000000e-01 1.666666666666667e-01 1.428571428571428e-01 Columns 4 through 5 2.500000000000000e-01 2.000000000000000e-01 2.000000000000000e-01 1.666666666666667e-01 1.666666666666667e-01 1.428571428571428e-01 1.428571428571428e-01 1.250000000000000e-01 1.250000000000000e-01 1.111111111111111e-01 X = Columns 1 through 3 2.499999999998526e+01 -2.999999999997500e+02 1.049999999998995e+03 -2.999999999997500e+02 4.799999999995864e+03 -1.889999999998367e+04 1.049999999998995e+03 -1.889999999998367e+04 7.937999999993641e+04 -1.399999999998561e+03 2.687999999997693e+04 -1.175999999999111e+05 6.299999999993241e+02 -1.259999999998929e+04 5.669999999995910e+04 Columns 4 through 5 -1.399999999998561e+03 6.299999999993241e+02 2.687999999997693e+04 -1.259999999998929e+04 -1.175999999999111e+05 5.669999999995910e+04 1.791999999998769e+05 -8.819999999994377e+04 -8.819999999994377e+04 4.409999999997448e+04 X = 25 -300 1050 -1400 630 -300 4800 -18900 26880 -12600 1050 -18900 79380 -117600 56700 -1400 26880 -117600 179200 -88200 630 -12600 56700 -88200 44100 XH = Columns 1 through 3 1.000000000000000e+00 0 0 0 1.000000000000000e+00 0 0 0 1.000000000000000e+00 0 0 -3.637978807091713e-12 0 0 0 Columns 4 through 5 0 0 0 0 0 0 1.000000000000000e+00 0 0 1.000000000000000e+00 XH = 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1
flints
round works on an array element-wise, mapping all elements to flints, floating point numbers whose values are integers. All values less than one-half are mapped to zero. All values larger than flintmax are already flints, so they are mapped to themselves. And all values between one-half and flintmax, except those exactly halfway between two flints, are mapped to the nearest flints. Pretty much everybody agrees to all of that.
The only leeway, and the primary reason for the Enhancement Request and this blog post, is the behavior of round for values exactly halfway between two flints.
round
I suggest that we add a tie-breaker option to the built-in function round. If you don't use this ties option, round will continue to function as it always has. And if you never encounter a tie, all of these options give the same results.
The most important option is 'even'. There is a statistical argument to be made in its favor. If you never encounter a tie, the mean of expected changes made by rounding data is zero. With the 'even' option', that is still true even if you do encounter ties.
The 'up' option is the default and the traditional MATLAB behavior. The 'odd' and 'down' options together provide a mirror image of the 'even' and 'up' pair.
type Round
function r = round(x,ties) % r = round(x) rounds the elements of x to the nearest integers. % Elements halfway between integers are rounded away from zero. % % r = round(x,'even') rounds to integers, ties to even. % r = round(x,'odd') rounds to integers, ties to odd. % r = round(x,'down') rounds to integers, ties towards zero. % r = round(x,'up') rounds to integers, ties away from zero (default). % a = abs(x) + 0.5; r = floor(a); if nargin == 2 switch ties case 'even' m = (r == a) & (mod(r,2) == 1); case 'odd' m = (r == a) & (mod(r,2) == 0); case 'down' m = (r == a); case 'up' m = []; end r(m) = r(m) - 1; end r = sign(x).*r; end
test round
x = (0.5:1:4.5)'; xRound(x)
x round even down odd 0.500 1 0 0 1 1.500 2 2 1 1 2.500 3 2 2 3 3.500 4 4 3 3 4.500 5 4 4 5
cities
Download the spread sheet available at www.census.gov/cities and use the Import Wizard to select the last column. This gives us the population of 788 US cities in 2019.
load cities.mat cities census
Round the data to nearest 1,000.
rcensus = 1000*Round(census/1000);
Here are the five largest cities, their population, and that population rounded.
disp(cities(1:5)) disp([census(1:5) rcensus(1:5)])
"New York city, New York" "Los Angeles city, California" "Chicago city, Illinois" "Houston city, Texas" "Phoenix city, Arizona" 8336817 8337000 3979576 3980000 2693976 2694000 2320268 2320000 1680992 1681000
And here are the five smallest,
disp(cities(end-4:end)) disp([census(end-4:end) rcensus(end-4:end)])
"Lakewood city, Ohio" "Troy city, New York" "Saginaw city, Michigan" "Niagara Falls city, New York" "Charleston city, West Virginia" 49678 50000 49154 49000 48115 48000 47720 48000 46536 47000
Here is the histogram of the changes made by rounding. Despite its ragged appearance, this is uniform.
delta = census - rcensus; histogram(delta,40)
About half of the 788 rounded down and about half rounded up. Only one city reported a 2019 population that was already a multiple of 1,000.
disp([nnz(delta < 0), nnz(delta == 0), nnz(delta > 0)])
396 1 391
k = find(delta == 0) disp(cities(k)) disp([census(k) rcensus(k)])
k = 69 Anchorage municipality, Alaska 288000 288000
How many cities reported population ties, i.e. populations halfway between multiples of 1,000?
k = find(mod(census,1000)==500) disp(cities(k)) disp([census(k) rcensus(k)])
k = 131 189 757 "Tallahassee city, Florida" "Roseville city, California" "Burien city, Washington" 194500 195000 141500 142000 51500 52000
Only one city out of the 788 reported a tie and a population that rounded to an odd multiple of 1,000, there by triggering the 'even' option.
ecensus = 1000*Round(census/1000,'even');
k = find(ecensus ~= rcensus)
disp(cities(k))
disp([census(k) rcensus(k) ecensus(k)])
k = 131 Tallahassee city, Florida 194500 195000 194000
IEEE 754
The IEEE 754 rounding modes are easily confused with the round function. IEEE 754 is about hardware; round is software. Floating point arithmetic operations are taking place at an accuracy scale that is 16 orders of magnitude finer and at a rate that is many orders of magnitude faster. Some of the considerations are similar, but the setting and the details are very different.
Which?
Should MATLAB have something like round(x,'even')? It's really a matter of taste. I happen to like the way we've always done things. To others, the statistical properties of round-ties-to-even might be relevant.
But I think the most important argument in favor of the Enhancement Request is providing compatibility with other mathematical software that doesn't offer a choice.
- Category:
- History,
- Numerical Analysis,
- Precision,
- Programming
Comments
To leave a comment, please click here to sign in to your MathWorks Account or create a new one.