# Round, With Tie Breakers, A Prototype

In February and March I published three blog posts about an enhancement request for MATLAB concerning tie breakers with the `round` function, including the ability to round ties to even. Round One, Round Two and Round Three. Since then, a group of us at MathWorks, organized by my colleague (and academic great-great-grand-descendant) Heiko Weichelt, have been considering the request. We had a virtual design review recently. This post describes a prototype incorporating the features that were discussed at that review.

### Contents

#### round

Traditionally, `help round` had only one line.

ROUND(X) rounds each element of X to the nearest integer.

A few years ago, two optional arguments were added.

ROUND(X, N), for positive integers N, rounds to N digits to the right of the decimal point. If N is zero, X is rounded to the nearest integer. If N is less than zero, X is rounded to the left of the decimal point. N must be a scalar integer.

ROUND(X, N, 'significant') rounds each element to its N most significant digits, counting from the most-significant or left side of the number. N must be a positive integer scalar.

#### floor and ceil

The functions `floor` and `ceil` have important roles in any discussion of rounding. Every real number, `x`, that is not an integer, lies between two integers,

floor(x) < x < ceil(x) = floor(x) + 1

If `x` is an integer, then

floor(x) = x = ceil(x)

The function `floor(x)` defines "rounding `x` towards minus infinity" and the function `ceil(x)` is "rounding `x` towards plus infinity".

The function `fix(x)`, which is also known as "integer part of `x`", is

fix(x) = floor(x) if x >= 0, = ceil(x) if x < 0.

The "fractional part of `x`" is

frac(x) = x - fix(x).

Two examples

x = 9.64 x = -9.64 floor(x) = 9 floor(x) = -10 ceil(x) = 10 ceil(x) = -9 fix(x) = 9 fix(x) = -9 frac(x) = 0.64 frac(x) = -0.64

#### Ties

The traditional code for `round(x)` is one line.

round(x) = sign(x).*floor(abs(x) + 0.5)

This implies that

round(x) = floor(x) if frac(x) < 0.5 = ceil(x) if frac(x) > 0.5

This unambiguously defines `round(x)` for almost all `x`. The only ambiguous situations are the *rounding ties*. They occur when the fractional part of `x` is exactly 1/2, or 0.5 in decimal. Ties are relatively rare. Traditionally, MATLAB has rounded ties away from zero.

When there is no scaling specified, the ties are the numbers exactly halfway between two consecutive integers,

halfs = -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, ...

The "round ties away from zero" rule produces

round(halfs) = -4, -3, -2, -1, 1, 2, 3, 4, ...

Recently, there has been some interest in alternatives to this behavior. One alternative is "round ties to nearest even". For the halfway numbers, this would be

halfs_even = -4, -2, -2, 0, 0, 2, 2, 4, ...

#### Nearest decimal

Here are four examples of rounding to the nearest decimal value.

format short format compact

- Express intercity mileage distance in whole numbers. The distance from my home office to MathWorks headquarters is 406.3 miles. What is this to the nearest mile?

miles = Round(406.3)

miles = 406

- The patient's body temperature is 98.64 degrees. Express this in nearest tenths of a degree.

temp = Round(98.64,1)

temp = 98.6000

- Express monetary values in hundredths. The computed interest is 13.8327 Euros. Express this in hundredths of a Euro.

cents = Round(13.8327,2)

cents = 13.8300

- The population of Tallahassee in 2019 was reported to be 194,500. What is this to the nearest one-thousand people?

pop = Round(194500,-3)

pop = 195000

Only one of these examples involves a tie. With a round-ties-to-even rule, the population of Tallahassee would be listed as 194,000 instead of 195,000.

#### IEEE 754

The `round` function is not the same as the round-to-even setting for IEEE 754 floating point arithmetic. The `round` function is done with software by MATLAB; its input is doubles (or singles) and the output is flints (floating point numbers whose values are integers.) The IEEE 754 setting is done in hardware, its input is extended precision or registers with guard bits and its output is doubles that are rarely flints.

#### Name-value pairs

Several reviewers preferred name-value pairs. Name-value pairs have been part of MATLAB syntax for a long time and a recent enhancement involves the use of an equals sign to specify them. For example

plot(x,y,'ko','markersize',10)

can now be written

plot(x,y,marker='o',color='black',markersize=10)

The left hand portion of a name-value pair is the `paramName` and the list of possible right hand portions is `paramValues`.

At first, I was not enthusiastic about using a name-value pair for tie-braking in `round`. But after working on this prototype and blog post, I am now in favor. The `paramName` could be

roundTies

There would be six possible `paramValues`.

awayFromZero towardZero toEven toOdd towardPlusInfinity towardMinusInfinity

For example

x = (0.1:0.2:0.9)' r_from = Round(x,roundTies='awayFromZero') r_even = Round(x,roundTies='toEven')

x = 0.1000 0.3000 0.5000 0.7000 0.9000 r_from = 0 0 1 1 1 r_even = 0 0 0 1 1

#### Not-a-Number

This occurred to me while writing this post. How about a seventh choice for handling ties?

```
r_nan = Round(x,roundTies='toNaN')
```

r_nan = 0 0 NaN 1 1

#### help

The next five sections are the code for a prototype. The file name is `Round.m` with a capital `R` so we can also access the `round` function from the current release. The code is available here: Round.m

code = split(string(fileread('Round.m')),'%_');

The help text is concise.

disp(code(1))

function x = Round(varargin) % Rounds toward nearest decimal or integer. % % round(x) Round each element of x to the nearest integer. % % round(x, n) Round to the n-th decimal place. % round(x, n, digits="significant") Round to n significant digits. % round(x, n, digits="decimals") Same as round(x,n). % % round(x,..., roundTies="awayFromZero") % round(x,..., roundTies="towardsZero") % round(x,..., roundTies="toOdd") % round(x,..., roundTies="toEven") % round(x,..., roundTies="towardsPlusInfinity") % round(x,..., roundTies="towardsMinusInfinity") % % Ties are rare. round(x,n) is a tie only when 10^n*x is within % roundoff error of a point halfway between two consecutive integers.

#### examples

The help text continues with a few examples.

disp(code(2))

% Examples % % x = 123456.789 % % round(x) 123457 % round(x,-3) 123000 % round(x,2) 123456.79 % % x = 1.125 % % round(x) 1.000 % round(x,1) 1.100 % round(x,2) 1.130 % round(x,2,roundTies="toEven") 1.120 % round(x,2,roundTies="toOdd") 1.130 % round(x,3) 1.125 % round(x,3,"significant") 1.130 % % x = 1.115 % xlo = 1115/1000 - eps/25 % xhi = 1115/1000 + 24*eps/25 % % round(x,2) 1.120 % round(xlo,2,roundTies="toOdd") 1.110 % round(xhi,2,roundTies="toEven") 1.120

#### main

Here is the body of `Round`. The numerically sensitive portions are the definition of `exact` and the choice of `<` or `<=` in the determination of `t`, the ties.

disp(code(3))

% main program [x,n,sig,tie] = parse(varargin{:}); x0 = x; s = sign(x); x = abs(x); if sig n = n - ceil(log10(x)); else n = n - zeros(size(x)); end x = scale(x,n); z = x; f = z - floor(z); m = (f < 0.5); x(m) = floor(z(m)); m = (f >= 0.5); x(m) = ceil(z(m)); exact = (x0 == single(x0)); if exact t = (f == 0.5); % ties else t = abs(f - 0.5) <= eps(z); % ties end x(t) = ceil(z(t)); switch tie case 'even' m = (mod(x(t),2) == 1); case 'odd' m = (mod(x(t),2) == 0); case 'plus' m = (s(t) < 0); case 'minus' m = (s(t) > 0); case 'zero' m = 1; case 'nan' m = NaN; otherwise m = 0; end x(t) = x(t) - m; x = s.*scale(x,-n);

#### parse

This parser handles the name-value pairs in a very simplistic manner. A more serious parser would be more discriminating.

disp(code(4))

function [x,n,sig,tie] = parse(varargin) x = varargin{1}; n = 0; sig = false; tie = 'from'; for k = 2:nargin vk = varargin{k}; if isnumeric(vk) n = vk; elseif vk == "significant" sig = true; elseif vk == "decimals" || ... vk == "roundTies" || ... vk == "digits" % ignore else tie = char(vk); caps = find(double(tie) < double('a')); if length(caps) > 1 && caps(2) > caps(1)+2 tie = lower(tie(caps(1):caps(2)-1)); elseif length(caps) > 0 && length(tie(caps:end)) <= 5 tie = lower(tie(caps(1):end)); else error(vk + " not recognized.") end end end end

#### scale

This scaling function always multiplies or divides by positive integer powers of 10, which are exact for `n <= 16`.

disp(code(5))

function x = scale(x,n) k = (n > 0); x(k) = x(k).*10.^n(k); k = (n < 0); x(k) = x(k)./10.^(-n(k)); end

#### graphic

Here is the graphic from previous posts, now labeled by the `paramValues`.

graphic

#### software

The prototype code is available here: Round.m

## 评论

要发表评论，请点击 此处 登录到您的 MathWorks 帐户或创建一个新帐户。