[racket] Decimal rounding problem

1,882 views
Skip to first unread message

Greg Graham

unread,
Nov 29, 2012, 9:44:38 AM11/29/12
to us...@racket-lang.org
I am trying to report GPA calculation results to 2 decimal places, so I thought real->decimal-string would do the trick. However, the following behavior surprised me:

> (real->decimal-string 3.225 2)
"3.23"
> (real->decimal-string 4.225 2)
"4.22"

I would like the second answer to be "4.23", which is what a student would expect to see if they did the calculations themselves. The documentation for real->decimal-string says that it first converts the argument to an exact number. I suspect the problem has something to do with this:

> (inexact->exact 4.225)
4 126663739519795/562949953421312
> (/ 126663739519795.0 562949953421312.0)
0.22499999999999964

Is there another rounding function that would just round the floating point without going through the conversion to exact?

-Greg


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

Matthew Flatt

unread,
Nov 29, 2012, 9:55:49 AM11/29/12
to Greg Graham, us...@racket-lang.org
There are a couple of issues here.

First, the inexact number 4.225 is actually slightly smaller than the
exact value 4.225:

> (< 4.225 #e4.225)
#t
> (pretty-print-exact-as-decimal #t)
> (inexact->exact 4.225)
4.2249999999999996447286321199499070644378662109375

So, that's one reason to argue that "4.22" is the right answer.


Even if you use the exact number 4.225, though, you get a "2" as the
last digit:

> (real->decimal-string #e4.225 2)
"4.22"

That's because rounding in Racket is to "even" --- a different
convention than the one taught to most students, but one that it often
preferred in computing (because it often reduces accumulated error, as I
understand it).

Robby Findler

unread,
Nov 29, 2012, 10:48:31 AM11/29/12
to Matthew Flatt, us...@racket-lang.org
But why does 3.225 round differently than 4.225?

I see that this is different:

> (> 3.225 #e3.225)
#t

but I would have thought that the integer part wouldn't affect
anything here since floats have those parts separate? I thought?

Robby

Neil Toronto

unread,
Nov 29, 2012, 10:53:54 AM11/29/12
to us...@racket-lang.org
On 11/29/2012 07:55 AM, Matthew Flatt wrote:
> At Thu, 29 Nov 2012 14:44:38 +0000, Greg Graham wrote:
> There are a couple of issues here.
>
> First, the inexact number 4.225 is actually slightly smaller than the
> exact value 4.225:
>
>[...]

>
> Even if you use the exact number 4.225, though, you get a "2" as the
> last digit:
>
> > (real->decimal-string #e4.225 2)
> "4.22"
>
> That's because rounding in Racket is to "even" --- a different
> convention than the one taught to most students, but one that it often
> preferred in computing (because it often reduces accumulated error, as I
> understand it).

Right. If you want to implement rounding with ties rounded away from
zero, you've got to do it yourself. Here's an implementation, which
takes an optional `scale' argument:

(define (round/ties-away x [scale 1])
(cond [(not (= scale 1)) (/ (round/ties-away (* x scale) 1) scale)]
[(x . < . 0) (truncate (- x 1/2))]
[else (truncate (+ x 1/2))]))

> (real->decimal-string (round/ties-away #e4.225 100) 2)
"4.23"

You'd still need to compute using exact numbers, though, because as
Matthew said, 4.225 is a little smaller than #e4.225:

> (real->decimal-string (round/ties-away 4.225 100) 2)
"4.22"

Also, multiplying and dividing inexact numbers by non-powers-of-two
introduces a little bit of error, which can make scaling before rounding
produce the wrong result. With exact numbers, though, it's, uh, exact.

Neil ⊥

Neil Toronto

unread,
Nov 29, 2012, 11:01:42 AM11/29/12
to us...@racket-lang.org
This is because numbers in [4.0,8.0) have one fewer bit with which to
represent the fractional part than numbers in [2.0,4.0). That bit is
needed to represent the larger integer part.

Alternatively, you can understand it in terms of how many flonums there
are between each integer. That number decreases as the numbers get
farther from zero:

> (require unstable/flonum) ; (require math/flonum) on the nightlies
> (flonums-between 2.0 3.0)
2251799813685248
> (flonums-between 4.0 5.0)
1125899906842624

It gets really sparse, eventually:

> (flonums-between (expt 2.0 52.0) (+ (expt 2.0 52.0) 1.0))
1

Neil ⊥

Stephen Bloch

unread,
Nov 29, 2012, 11:06:17 AM11/29/12
to Greg Graham, us...@racket-lang.org
Another rounding function won't help, because by the time the READER has finished with the character string "4.225", the internal form is already slightly less than 4225/1000. And if you're getting the inexact 4.225 from somewhere else, again it's not really 4225/1000 so it's already too late to round it the way you want.

You could work in a student language, where the reader reads decimals as exact by default :-)

Seriously, I'm sure there's a way to tell the reader in other languages to read decimals as exact; I just don't know what it is.


Stephen Bloch
sbl...@adelphi.edu

Stephen Bloch

unread,
Nov 29, 2012, 11:17:26 AM11/29/12
to Neil Toronto, us...@racket-lang.org

On Nov 29, 2012, at 11:01 AM, Neil Toronto wrote:

> This is because numbers in [4.0,8.0) have one fewer bit with which to represent the fractional part than numbers in [2.0,4.0). That bit is needed to represent the larger integer part.

Cute! I want to assign this as an exam problem for my computer-architecture students.


Stephen Bloch
sbl...@adelphi.edu

Pierpaolo Bernardi

unread,
Nov 29, 2012, 11:00:51 AM11/29/12
to Robby Findler, Matthew Flatt, us...@racket-lang.org
On Thu, Nov 29, 2012 at 4:48 PM, Robby Findler
<ro...@eecs.northwestern.edu> wrote:
> But why does 3.225 round differently than 4.225?

Neither 3.225 nor 4.225 can be represented exactly in binary floating-point.

The numbers actually parsed are:

> (write-rational-expansion (inexact->exact 4.225))
4.2249999999999996447286321199499070644378662109375(0)
> (write-rational-expansion (inexact->exact 3.225))
3.225000000000000088817841970012523233890533447265625(0)

Stephen Bloch

unread,
Nov 29, 2012, 11:47:40 AM11/29/12
to Neil Toronto, us...@racket-lang.org

On Nov 29, 2012, at 11:01 AM, Neil Toronto wrote:

> This is because numbers in [4.0,8.0) have one fewer bit with which to represent the fractional part than numbers in [2.0,4.0). That bit is needed to represent the larger integer part.

I'm trying to replicate this in Java, using the DecimalFormat class, and I'm getting

0.225 -> "0.22"
1.225 -> "1.22"
2.225 -> "2.22"
3.225 -> "3.22"
4.225 -> "4.22"
9.225 -> "9.22"
17.225 -> "17.22"
35.225 -> "35.22"
68.225 -> "68.22"
130.225 -> "130.22"
250.225 -> "250.22"

What gives? If Java and Racket are both using IEEE standard double-precision floating point, and both rounding-to-even (as the DecimalFormat class doc says it does by default), why am I NEVER getting round-up behavior in Java?

I tried the negations of the same numbers, and they still all end in ".22", as though it were in round-towards-zero mode, but I queried the rounding mode and it says it's HALF_EVEN.

The corresponding results in Racket, BTW, are
("0.23" "1.23" "2.23" "3.23" "4.22" "9.22" "17.23" "35.23" "68.22" "130.22" "250.22")



Stephen Bloch
sbl...@adelphi.edu

Robby Findler

unread,
Nov 29, 2012, 12:17:55 PM11/29/12
to Neil Toronto, Racket Users
Oh, of course! Thank you!

Robby

Ryan Culpepper

unread,
Nov 29, 2012, 12:21:53 PM11/29/12
to us...@racket-lang.org
Prefix the number with "#e", as in

> #e4.225
169/40

As an alternative, you could create a language that sets the
'read-decimal-as-inexact' reader parameter to #f before reading the
module contents. Try saving the following as s-exp-exact/lang/reader.rkt
(adapted from s-exp/lang/reader.rkt with some help from Carl):

(module reader syntax/module-reader
#:language (lambda (p) (read-syntax (object-name p) p))
#:wrapper1 (lambda (go)
(parameterize ((read-decimal-as-inexact #f)) (go))))

Then run

#lang s-exp-exact racket
(exact? 4.225)

Ryan

Greg Graham

unread,
Nov 29, 2012, 12:53:59 PM11/29/12
to us...@racket-lang.org
Thank you to everyone who weighed in on the topic; the discussion has been very informative and interesting.

I've decided to not do the rounding in Racket, but to store all of the digits in the database. The rounding will occur at the time of display by either Crystal Reports or Excel, which is what the current system does anyway. That way we won't see any unexpected changes in anyone's GPAs.

Now, the unanswered question is why do Crystal Reports and Excel round 4.225 to 4.23? I don't think I'll find as helpful of a forum to answer that question as I have found for Racket. Thanks again for being such a great community!

-Greg

Neil Toronto

unread,
Nov 29, 2012, 1:42:11 PM11/29/12
to us...@racket-lang.org
On 11/29/2012 10:53 AM, Greg Graham wrote:
> Thank you to everyone who weighed in on the topic; the discussion has been very informative and interesting.
>
> I've decided to not do the rounding in Racket, but to store all of the digits in the database. The rounding will occur at the time of display by either Crystal Reports or Excel, which is what the current system does anyway. That way we won't see any unexpected changes in anyone's GPAs.
>
> Now, the unanswered question is why do Crystal Reports and Excel round 4.225 to 4.23? I don't think I'll find as helpful of a forum to answer that question as I have found for Racket. Thanks again for being such a great community!

Aw, you buttered us up so nicely that I have to respond. They're
probably rounding twice: once to get a decimal number close to the
original, and then to the specified number of digits. This'll do it:

(define (smart-donkey-round x [scale 1])
(cond [(inexact? x)
(smart-donkey-round (smart-donkey-round (inexact->exact x)
(expt 10 14))
scale)]
[(not (= scale 1))
(/ (smart-donkey-round (* x scale) 1) scale)]


[(x . < . 0)
(truncate (- x 1/2))]
[else
(truncate (+ x 1/2))]))

> (real->decimal-string (smart-donkey-round 4.225 100) 2)
"4.23"

The magic number (expt 10 14) is the reciprocal of the assumed error
when `x' is inexact. Flonums have about 15 digits precision.

Neil ⊥

Pierpaolo Bernardi

unread,
Nov 29, 2012, 1:37:29 PM11/29/12
to Greg Graham, us...@racket-lang.org
Excel is infamous for its incorrect roundings. Google "excel roundings quirks".

I don't know about crystal rep.


2012/11/29, Greg Graham <GGr...@cistercian.org>:
--
Inviato dal mio dispositivo mobile

Hendrik Boom

unread,
Nov 29, 2012, 1:59:44 PM11/29/12
to us...@racket-lang.org
All these problems that would have been avoided if we had just started
out counting on our fingers instead of out fingers and thumbs!

-- hendrik
k

Stephan Houben

unread,
Nov 29, 2012, 2:28:33 PM11/29/12
to Hendrik Boom, us...@racket-lang.org

In octal you have the same issue, how do you round octal 0.4 ?

No, we should be using an odd base, say base-5. Fingers and thumb of one hand. No central digit to cause rounding ambiguity.

Stephan

Stephan Houben

Op 29 nov. 2012 20:02 schreef "Hendrik Boom" <hen...@topoi.pooq.com> het volgende:

Hendrik Boom

unread,
Nov 29, 2012, 4:36:49 PM11/29/12
to us...@racket-lang.org
But you don't have the problem of misrounding because of a conversion
from octal to beinary before you start.

-- hendrik

Pierpaolo Bernardi

unread,
Nov 30, 2012, 4:02:05 AM11/30/12
to Neil Toronto, us...@racket-lang.org
On Thu, Nov 29, 2012 at 7:42 PM, Neil Toronto <neil.t...@gmail.com> wrote:
> On 11/29/2012 10:53 AM, Greg Graham wrote:

>> Now, the unanswered question is why do Crystal Reports and Excel round
>> 4.225 to 4.23? I don't think I'll find as helpful of a forum to answer that
>> question as I have found for Racket. Thanks again for being such a great
>> community!
>
> Aw, you buttered us up so nicely that I have to respond. They're probably
> rounding twice: once to get a decimal number close to the original, and then
> to the specified number of digits.

Looks like that's it. This paper by William Kahan contains a nice
example of the problems of excel rounding:

http://www.cs.berkeley.edu/~wkahan/Mindless.pdf

(the paper is long, the excel example is on page 3)

P.

Jos Koot

unread,
Nov 30, 2012, 2:55:05 PM11/30/12
to Pierpaolo Bernardi, Neil Toronto, us...@racket-lang.org
As 4.225 is inexact, it may be somewhat smaller that #e4.225 or somewhat
greater than #e4.225. Therefore, whether rounding an inexact number very
close to the edge of the rounding point goes up or down, seems irrelevant to
me. BTW, explicit rounding normally is the last operation in an inexact
calculation. Implicit rounding as in a basic operation (+, -, *, /) with
inexact numbers does occur, of course, and here rounding even up and odd
down (or reversely, I don't recall) may be usefull such as to have implicit
roundings cancel each other as much as possible, though I imagine their are
examples of calculations where this strategy accumulates inaccuracy rather
than reducing it.

Just some thoughts,
Jos
Reply all
Reply to author
Forward
0 new messages