[racket] Rounding

810 views
Skip to first unread message

Eric Nadeau

unread,
Mar 16, 2013, 10:21:57 AM3/16/13
to us...@racket-lang.org
Hi,

I was having problems with a mathematical application I'm writing and narrowed it down to:

(round 0.5)
(round (/ 1 2))
(round 1.5)
(round (/ 3 2))

giving me

0.0
0
2.0
2

Since when is round 0.5 not giving 1?!

Cheers,
Eric
____________________
Racket Users list:
http://lists.racket-lang.org/users

Nadeem Abdul Hamid

unread,
Mar 16, 2013, 8:31:21 PM3/16/13
to Eric Nadeau, us...@racket-lang.org
> Since when is round 0.5 not giving 1?!

http://en.wikipedia.org/wiki/Rounding#Round_half_to_even

--- nadeem

Eric Dobson

unread,
Mar 16, 2013, 8:32:00 PM3/16/13
to Eric Nadeau, us...@racket-lang.org
The docs say that it is round to even, which is the standard for
floating point numbers [1]. You can write your own rounding operator
that rounds .5 up if need be.

;; Untested
(define (my-round n)
(let ((r (round n))) (if (= .5 (- n r)) (add1 r) r)))

[1] http://en.wikipedia.org/wiki/Rounding#Round_half_to_even

Danny Yoo

unread,
Mar 16, 2013, 8:33:16 PM3/16/13
to Eric Nadeau, us...@racket-lang.org
>
> Since when is round 0.5 not giving 1?!

According to the documentation:

http://docs.racket-lang.org/reference/generic-numbers.html#%28def._%28%28quote._~23~25kernel%29._round%29%29

"Returns the integer closest to x, resolving ties in favor of an
even number... "

And zero is an even number. (http://www.bbc.co.uk/news/magazine-20559052)

So although it may be surprising, at least the behavior is documented.



Mathworld and Wikipedia say that this is done to combat statistical biasing:

http://mathworld.wolfram.com/NearestIntegerFunction.html
http://en.wikipedia.org/wiki/Rounding#Round_half_to_even

Carl Eastlund

unread,
Mar 16, 2013, 9:04:34 PM3/16/13
to Eric Dobson, us...@racket-lang.org, Eric Nadeau
That's an overly specific solution.  I believe the more general rounding function taught in grade-school math would be:

(define (my-round n)
  (floor (+ n 1/2)))

This is also untested.  And it might need tweaking for negative numbers if they are supposed to round away from zero, I forget how that works.

Carl Eastlund

mi...@goblin.punk.net

unread,
Mar 16, 2013, 9:26:13 PM3/16/13
to us...@racket-lang.org
On Sat, Mar 16, 2013 at 10:21:57AM -0400, Eric Nadeau wrote:
> Since when is round 0.5 not giving 1?!

I believe that this is standard in physics and engineering in order to avoid statistical bias. For example, see the NIST "Guide for the Use of the International System of Units", section B.7.
(http://physics.nist.gov/cuu/Units/rules.html)

Eli Barzilay

unread,
Mar 16, 2013, 9:46:08 PM3/16/13
to Carl Eastlund, Eric Nadeau, us...@racket-lang.org
40 minutes ago, Carl Eastlund wrote:
> That's an overly specific solution.  I believe the more general rounding
> function taught in grade-school math would be:
>
> (define (my-round n)
>   (floor (+ n 1/2)))

If you want 0.5 to round down to 0, then

(define (my-round n) (ceiling (- n 1/2)))

--
((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!

Neil Toronto

unread,
Mar 17, 2013, 1:45:25 AM3/17/13
to us...@racket-lang.org
On 03/16/2013 05:33 PM, Danny Yoo wrote:
>>
>> Since when is round 0.5 not giving 1?!
>
> According to the documentation:
>
> http://docs.racket-lang.org/reference/generic-numbers.html#%28def._%28%28quote._~23~25kernel%29._round%29%29
>
> "Returns the integer closest to x, resolving ties in favor of an
> even number... "
>
> And zero is an even number. (http://www.bbc.co.uk/news/magazine-20559052)
>
> So although it may be surprising, at least the behavior is documented.
>
>
>
> Mathworld and Wikipedia say that this is done to combat statistical biasing:
>
> http://mathworld.wolfram.com/NearestIntegerFunction.html
> http://en.wikipedia.org/wiki/Rounding#Round_half_to_even

This is the reason the *low-order bit* of a floating-point number (or
any finite-precision number) is rounded with ties toward even. Assuming
that the unrepresented bits are distributed uniformly, it tends to make
sums of values that are rounded multiple times more accurate.

IMO, the reason floating-point hardware rounds toward even for integer
rounding is code reuse. Its bias correction is miniscule to nonexistent
at that magnitude, unless you round after nearly every operation.

Here's a demo:


#lang typed/racket

(require math)

(: round/ties-toward-zero (Real -> Real))
;; Like the rounding we learned in school
(define (round/ties-toward-zero x)
(if (x . < . 0)
(- (round/ties-toward-zero (- x)))
(floor (+ x 1/2))))

(: double-rounding-error ((Real -> Real) Real (Listof Real) -> Real))
;; Round every x once to the nearest `low-bit', then compare the sum
;; of those with the sum of them rounded to the nearest integer
(define (double-rounding-error round low-bit xs)
(let ([xs (map (λ: ([x : Real])
(* low-bit (round (/ x low-bit))))
xs)])
(absolute-error (sum xs) (sum (map round xs)))))

;; Get some random numbers
(define xs (sample (uniform-dist 0 10) 10000))

;; Wow, this really helps if the second rounding is near the magnitude
;; of the low-order bit
(double-rounding-error round 1/2 xs)
(double-rounding-error round/ties-toward-zero 1/2 xs)

;; If the low-order bit is much smaller, though, it doesn't help at all
(double-rounding-error round 1/512 xs)
(double-rounding-error round/ties-toward-zero 1/512 xs)

;; Also, if we violate assumptions, it doesn't help as much (in this
;; case, the violated assumption is that there are as many values near
;; odd numbers as near even numbers)
(define ys (sample (uniform-dist 0 3) 10000))
(double-rounding-error round 1/2 ys)
(double-rounding-error round/ties-toward-zero 1/2 ys)

;; Violated assumption: missing bits are approximately uniformly
;; distributed (still does okay, though)
(define zs (map flsqrt (sample (uniform-dist 0 100) 10000)))
(double-rounding-error round 1/2 zs)
(double-rounding-error round/ties-toward-zero 1/2 zs)


Neil ⊥

Eric Nadeau

unread,
Mar 16, 2013, 11:47:55 PM3/16/13
to us...@racket-lang.org

The point of rounding is coming up with the best possible integer approximation for a decimal and this nearest even number rule does not qualify. This "logic" was used by my grandparents' generation because odd numbers were seen as less pure than even ones.

I'm ditching Scheme altogether based on this, but thanks all for the good explanations, it can be confirmed by running the following (I thought it was only a fluke with 0.5):

(round 0.5) (round 1.5) (round 2.5) (round 3.5)

Robby Findler

unread,
Mar 17, 2013, 5:31:54 PM3/17/13
to Eric Nadeau, Racket Users
Racket is not Scheme. Please do not let our decisions influence your opinion of other languages. Go try Scheme for real.

Robby

Danny Yoo

unread,
Mar 17, 2013, 10:11:36 PM3/17/13
to Eric Nadeau, us...@racket-lang.org
> The point of rounding is coming up with the best possible integer approximation for a decimal and this nearest even number rule does not qualify. This "logic" was used by my grandparents' generation because odd numbers were seen as less pure than even ones.

This is a strawman argument. That is not the argument for the choice
of rounding toward even. Read again.


> I'm ditching Scheme altogether based on this, but thanks all for the good explanations, it can be confirmed by running the following (I thought it was only a fluke with 0.5):

The citations to Wikipedia and Mathworld have references to help you
see that the rounding behavior in Racket is not arbitrary, nor
exclusive to Racket alone. Your comment seems to suggest that you
believe otherwise, but I have no idea why. Did you read what people
have sent you?

It would be useful to know more of what you expected. Are you
expecting rounding toward odd? You omitted to say much. Did you want
the ceiling function? Did you want round-half-up? What did you want?

Stephen Bloch

unread,
Mar 17, 2013, 10:46:43 PM3/17/13
to Eric Nadeau, users@racket-lang.org list

On Mar 16, 2013, at 11:47 PM, Eric Nadeau <nad...@gmail.com> wrote:

> The point of rounding is coming up with the best possible integer approximation for a decimal and this nearest even number rule does not qualify.

Why not? In these cases there is no "the" best possible integer approximation, but two equally-close integer approximations, so either one would qualify. In fact, choosing one or the other at random would meet the above criterion, but would be non-deterministic and annoy a lot of programmers.

> This "logic" was used by my grandparents' generation because odd numbers were seen as less pure than even ones.

I've never heard that "logic". Citation?

On the contrary, my impression is that our grandparents' generation chose "round halves up" because the most common source of decimal numbers that needed rounding was long division, which generates decimal digits one at a time. If you generated the first digit after the decimal point and you got a "5", it meant the remainder was AT LEAST half of the divisor, but possibly more, so rounding halves up meant that you'd never round in the wrong direction, and most of the time (1 - 1/divisor) you'd be rounding in the right direction. (The remaining 1/divisor of the time would be the "exact half" case in which either direction would be equally close.)

In computer arithmetic, by contrast, typically all the bits are generated (including guard, round, and sticky bits) before you even think about rounding, so you don't need to make your rounding decision based on only the first digit after the point.

> I'm ditching Scheme altogether based on this, but thanks all for the good explanations, it can be confirmed by running the following (I thought it was only a fluke with 0.5):
>
> (round 0.5) (round 1.5) (round 2.5) (round 3.5)

Oh, there's no dispute that it happens consistently. If it didn't, a bug report would be in order.

BTW, before you blame this on Scheme or Racket, see http://docs.oracle.com/javase/1.5.0/docs/api/java/math/RoundingMode.html and http://en.wikipedia.org/wiki/IEEE_floating_point#Rounding_rules


Stephen Bloch
sbl...@adelphi.edu

Neil Toronto

unread,
Mar 18, 2013, 12:00:09 AM3/18/13
to us...@racket-lang.org
On 03/16/2013 08:47 PM, Eric Nadeau wrote:
> I'm ditching Scheme altogether based on this...

In case you're not trolling: changing languages won't help. This is
standard behavior. And for goodness sake, you can always write your own
function!

Neil ⊥

Robby Findler

unread,
Mar 18, 2013, 7:49:28 AM3/18/13
to Neil Toronto, Racket Users
FWIW: it appears that both ruby and python round n.5 to n+1.

Robby

Matthew Flatt

unread,
Mar 18, 2013, 8:05:37 AM3/18/13
to Robby Findler, Racket Users
But Python 3 switched to round-to-even, FWIW.

> > http://lists.racket-lang.org/**users <http://lists.racket-lang.org/users>

Reply all
Reply to author
Forward
0 new messages