# Rounding to X digits

Discussion in 'Ruby' started by Eric Anderson, Oct 14, 2004.

1. ### Eric AndersonGuest

This seems like such a basic question yet I can't really find the answer
anywhere in the docs (I'm probably looking in the wrong place).

Anyway I need to compute a percentage and output it. The percentage
should be in the form 39.45% (i.e. round to the nearest two decimal
places). So I have

top = 68
bottom = 271

percentage = (top.to_f / bottom * 100 * 100).round.to_f / 100

The above works but it does not seem very intuitive. What I would rather is:

percentage = (top.to_f / bottom).round(4) * 100

But round does not take any arguments on how many decimal places I want.
It just assumes I don't want any. Perhaps there is another function that
I am not seeing that will round while keeping a certain number of
decimal places. Obviously I could also enhance round to take an optional
argument but I wanted to see if there was an already existing function
in the Ruby std library that will do it for me.

Thanks,

Eric

Eric Anderson, Oct 14, 2004

2. ### Eric AndersonGuest

Eric Anderson wrote:
> Obviously I could also enhance round to take an optional
> argument but I wanted to see if there was an already existing function
> in the Ruby std library that will do it for me.

To follow up my own post. If there isn't a function like what I am
looking for in the standard library, I am using the following to make it
like I want.

class Float
alias ldround :round
def round( sd=0 )
return (self * (10 ** sd)).oldround.to_f / (10**sd)
end
end

Eric Anderson, Oct 14, 2004

3. ### James Edward Gray IIGuest

On Oct 14, 2004, at 8:19 AM, Eric Anderson wrote:

> This seems like such a basic question yet I can't really find the
> answer anywhere in the docs (I'm probably looking in the wrong place).
>
> Anyway I need to compute a percentage and output it. The percentage
> should be in the form 39.45% (i.e. round to the nearest two decimal
> places).

You're looking for sprintf():

sprintf "%.2f", 39.456789 # => 39.46

The ri docs for sprintf() will explain the format string options.

It also has a cousin, printf() that can used as the data is sent to
some IO object.

Finally, the String class allows a shortcut:

"%.2f" % 39.456789 # => 39.46

Hope that helps.

James Edward Gray II

James Edward Gray II, Oct 14, 2004
4. ### Eric AndersonGuest

James Edward Gray II wrote:
> You're looking for sprintf():

I didn't even think about sprintf. I guess I was so fixed on looking for
a way to round numbers in the various Numeric objects that it didn't
occur to me to look in the string functionality.

sprintf() still seems a bit to C-ish for me. I kind of like my round
extension that allows for an optional arg. The question is my
implementation more efficient or using sprintf more efficient? Benchmark
to the rescue:

My implementation through 1 million iterations:

user system total real
No Decimal: 5.410000 0.000000 5.410000 ( 5.413982)
5 Decimal Places: 13.260000 0.000000 13.260000 ( 13.259163)
13 Decimal Places: 21.490000 0.000000 21.490000 ( 21.481821)

Sprintf implementation through 1 million iterations:

user system total real
No Decimal: 12.400000 0.000000 12.400000 ( 12.362547)
5 Decimal Places: 15.310000 0.000000 15.310000 ( 15.549580)
13 Decimal Places: 14.700000 0.020000 14.720000 ( 16.145235)

Looks like my implementation leads for the case where there are less
decimal places remaining (the more common case) and sprintf leads when
there are more. Of course at this point we are really just splitting
hairs.

For reference here are the implementations and benchmark script:

# My implementation
class Float
alias ldround :round
def round( sd=0 )
return (self * (10 ** sd)).oldround.to_f / (10**sd)
end
end

# Sprintf implementation
class Float
alias ldround :round
def round( sd=0 )
return ("%.#{sd}f" % self).to_f
end
end

Benchmark script:

#!/usr/bin/ruby

require 'benchmark'
include Benchmark

require 'sprintf_round'

n = 1000000
pi = 3.14159265358979
bm(17) do |test|
test.report('No Decimal:') do
n.times do |x|
pi.round
end
end
test.report('5 Decimal Places:') do
n.times do |x|
pi.round(5)
end
end
test.report('13 Decimal Places:') do
n.times do |x|
pi.round(13)
end
end
end

Eric Anderson, Oct 14, 2004
5. ### Robert KlemmeGuest

"Eric Anderson" <> schrieb im Newsbeitrag
news:GZvbd.111\$...
> James Edward Gray II wrote:
> > You're looking for sprintf():

>

*Hands over some cooling ice*

> I didn't even think about sprintf. I guess I was so fixed on looking for
> a way to round numbers in the various Numeric objects that it didn't
> occur to me to look in the string functionality.
>
> sprintf() still seems a bit to C-ish for me. I kind of like my round
> extension that allows for an optional arg. The question is my
> implementation more efficient or using sprintf more efficient? Benchmark
> to the rescue:

The difference in efficiency is one issue. But there is a conceptual
difference: it depends whether you want to align printed output or round a
numeric value in a calculation. For the first task sprintf is probably
better because the amount of places printed is guaranteed (by the format
string), while it's not if you just round the number and print it.

Kind regards

robert

Robert Klemme, Oct 14, 2004
6. ### MarkusGuest

Here's mine (with a few extras):

class Numeric
def iff(cond)
cond ? self : self-self
end
def to_the_nearest(n)
n*(self/n).round
end
def aprox(x,n=0.001)
(to_the_nearest n) == (x.to_the_nearest n)
end
def at_least(x); (self >= x) ? self : x; end
def at_most(x); (self <= x) ? self : x; end
end

-- Markus

On Thu, 2004-10-14 at 06:19, Eric Anderson wrote:
> This seems like such a basic question yet I can't really find the answer
> anywhere in the docs (I'm probably looking in the wrong place).
>
> Anyway I need to compute a percentage and output it. The percentage
> should be in the form 39.45% (i.e. round to the nearest two decimal
> places). So I have
>
> top = 68
> bottom = 271
>
> percentage = (top.to_f / bottom * 100 * 100).round.to_f / 100
>
> The above works but it does not seem very intuitive. What I would rather is:
>
> percentage = (top.to_f / bottom).round(4) * 100
>
> But round does not take any arguments on how many decimal places I want.
> It just assumes I don't want any. Perhaps there is another function that
> I am not seeing that will round while keeping a certain number of
> decimal places. Obviously I could also enhance round to take an optional
> argument but I wanted to see if there was an already existing function
> in the Ruby std library that will do it for me.
>
> Thanks,
>
> Eric
>

Markus, Oct 14, 2004
7. ### trans. (T. Onoma)Guest

On Thursday 14 October 2004 01:55 pm, Markus wrote:
| Here's mine (with a few extras):
|
| class Numeric
| def iff(cond)
| cond ? self : self-self
| end
| def to_the_nearest(n)
| n*(self/n).round
| end
| def aprox(x,n=0.001)
| (to_the_nearest n) == (x.to_the_nearest n)
| end
| def at_least(x); (self >= x) ? self : x; end
| def at_most(x); (self <= x) ? self : x; end
| end
|

| def to_the_nearest(n)
| n*(self/n).round
| end
def round_to_nearest( n=0.01 )
Â Â  (self * (1/n)).round.to_f / (1/n)
Â Â end

For some reason they don't give the exact same answer. Try an edge case.

irb(main):066:0> 0.1 * (134.45/0.1).round
=> 134.4
irb(main):067:0> (134.45 * (1/0.1)).round.to_f / (1/0.1)
=> 134.5

T.

trans. (T. Onoma), Oct 15, 2004
8. ### MarkusGuest

Interesting. I hope to heck you used a script to find that edge
case. What made you suspect there would be a difference, given that
they are mathematically equivalent?

I'll definitely come back to this after the coffee hits.

-- Markus

On Fri, 2004-10-15 at 07:28, trans. (T. Onoma) wrote:
> On Thursday 14 October 2004 01:55 pm, Markus wrote:
> | Here's mine (with a few extras):
> |
> | class Numeric
> | def iff(cond)
> | cond ? self : self-self
> | end
> | def to_the_nearest(n)
> | n*(self/n).round
> | end
> | def aprox(x,n=0.001)
> | (to_the_nearest n) == (x.to_the_nearest n)
> | end
> | def at_least(x); (self >= x) ? self : x; end
> | def at_most(x); (self <= x) ? self : x; end
> | end
> |
>
> | def to_the_nearest(n)
> | n*(self/n).round
> | end
> def round_to_nearest( n=0.01 )
> (self * (1/n)).round.to_f / (1/n)
> end
>
> For some reason they don't give the exact same answer. Try an edge case.
>
> irb(main):066:0> 0.1 * (134.45/0.1).round
> => 134.4
> irb(main):067:0> (134.45 * (1/0.1)).round.to_f / (1/0.1)
> => 134.5
>
> T.

Markus, Oct 15, 2004
9. ### Guest

On Fri, 15 Oct 2004, trans. (T. Onoma) wrote:

> On Thursday 14 October 2004 01:55 pm, Markus wrote:
> | Here's mine (with a few extras):
> |
> | class Numeric
> | def iff(cond)
> | cond ? self : self-self
> | end
> | def to_the_nearest(n)
> | n*(self/n).round
> | end
> | def aprox(x,n=0.001)
> | (to_the_nearest n) == (x.to_the_nearest n)
> | end
> | def at_least(x); (self >= x) ? self : x; end
> | def at_most(x); (self <= x) ? self : x; end
> | end
> |
>
> | def to_the_nearest(n)
> | n*(self/n).round
> | end
> def round_to_nearest( n=0.01 )
> Â Â  (self * (1/n)).round.to_f / (1/n)
> Â Â end
>
> For some reason they don't give the exact same answer. Try an edge case.
>
> irb(main):066:0> 0.1 * (134.45/0.1).round
> => 134.4
> irb(main):067:0> (134.45 * (1/0.1)).round.to_f / (1/0.1)
> => 134.5

jib:~/eg/ruby > cat truncate.rb
class Float
def truncate sd = 2
i = self.to_i
prec = 10 ** sd
fraction = (i.zero? ? self : (self / i) - 1)
i + (fraction * prec).to_i / prec.to_f
end
end

p(0.1 * (134.45/0.1).truncate) #=> 134.4
p((134.45 * (1/0.1)).truncate.to_f / (1/0.1)) #=> 134.4

jib:~/eg/ruby > ruby truncate.rb
134.4
134.4

regards.

-a
--
===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| When you do something, you should burn yourself completely, like a good
| bonfire, leaving no trace of yourself. --Shunryu Suzuki
===============================================================================

, Oct 15, 2004
10. ### MarkusGuest

On Fri, 2004-10-15 at 07:28, trans. (T. Onoma) wrote:
> On Thursday 14 October 2004 01:55 pm, Markus wrote:
> | Here's mine (with a few extras):
> |
> | class Numeric
> | def to_the_nearest(n)
> | n*(self/n).round
> | end
> | end
> |
>
> def round_to_nearest( n=0.01 )
> (self * (1/n)).round.to_f / (1/n)
> end
>
> For some reason they don't give the exact same answer. Try an edge case.
>
> irb(main):066:0> 0.1 * (134.45/0.1).round
> => 134.4
> irb(main):067:0> (134.45 * (1/0.1)).round.to_f / (1/0.1)
> => 134.5

Peeling one layer of the onion, they differ because:

irb(main):033:0> printf "%20.15f",(134.45 * (1.0/0.1))
1344.500000000000000=> nil
irb(main):034:0> printf "%20.15f",(134.45/0.1)
1344.499999999999773=> nil

I suppose this is not unexpected (my mama warned me 'bout floats) but it
is a little unexpected--no, I'm wrong, 1/5 is a repeating decimal base
2, so it's perfectly expected. The tricky bit is, which form (if either
of them) will always (or at least, more generally) give the correct
result? I can't see off hand that either will be intrinsically "better"
but I could be missing a point. I suspect (SWAG) that the hybrid:

def to_the_nearest(n)
if self.abs < 1.0
(self/n).round/n
else
m = 1.0/m
(self*m).round/m
end
end

would do better than both, but I haven't tested it.

-- Markus

P.S. Mine has mostly been tested on values < 1.0, thus my suspicion that
it works well in that domain. Typing this though, I realize that it was
tested FOR CONFORMANCE WITH A PRE-EXISTING SYSTEM* which itself may have
been buggy.

* Think 25 year old spaghetti FORTRAN, then sigh in bliss realizing that
your imagination is much nicer than the ugly facts.

Markus, Oct 15, 2004
11. ### trans. (T. Onoma)Guest

On Friday 15 October 2004 11:14 am, wrote:| Â  Â jib:~/eg/ruby > cat truncate.rb| Â  Â class Float| Â  Â  Â def truncate sd = 2| Â  Â  Â  Â i = self.to_i| Â  Â  Â  Â prec = 10 ** sd| Â  Â  Â  Â fraction = (i.zero? ? self : (self / i) - 1)| Â  Â  Â  Â i + (fraction * prec).to_i / prec.to_f| Â  Â  Â end| Â  Â end|| Â  Â p(0.1 * (134.45/0.1).truncate) Â  Â  Â  Â  Â  Â  Â  Â  Â  #=> 134.4| Â  Â p((134.45 * (1/0.1)).truncate.to_f / (1/0.1)) Â  Â #=> 134.4|| Â  Â jib:~/eg/ruby > ruby truncate.rb| Â  Â 134.4| Â  Â 134.4
Sorry, I don't follow. Isn't that answer incorrect? The mid-point should round up, not down. At least, that's what I was taught in school.
T.

trans. (T. Onoma), Oct 15, 2004
12. ### trans. (T. Onoma)Guest

On Friday 15 October 2004 12:02 pm, Markus wrote:
| Peeling one layer of the onion, they differ because:
|
| irb(main):033:0> printf "%20.15f",(134.45 * (1.0/0.1))
| 1344.500000000000000=> nil
| irb(main):034:0> printf "%20.15f",(134.45/0.1)
| 1344.499999999999773=> nil

That isn't too... er... reassuring.

| I suppose this is not unexpected (my mama warned me 'bout floats) but it
| is a little unexpected--no, I'm wrong, 1/5 is a repeating decimal base
| 2, so it's perfectly expected.

Expected, really? I'm not all that familiar with floating point standards, but
I would have though this kind of thing would have been worked out long ago.
It's a pretty substantiate miscalculation. --And the fact that simply using
'*(1.0/0.1)' fixes it, makes me think so even more.

T.

trans. (T. Onoma), Oct 15, 2004
13. ### MarkusGuest

On Friday 15 October 2004 11:14 am, wrote:
| jib:~/eg/ruby > ruby truncate.rb
| 134.4
| 134.4

On Fri, 2004-10-15 at 10:27, trans. (T. Onoma) wrote:
> Sorry, I don't follow. Isn't that answer incorrect? The mid-point should
> round up, not down. At least, that's what I was taught in school.

But this is truncate, not round.

On Fri, 2004-10-15 at 10:27, trans. (T. Onoma) wrote:
> On Friday 15 October 2004 12:02 pm, Markus wrote:
> | Peeling one layer of the onion, they differ because:
> |
> | irb(main):033:0> printf "%20.15f",(134.45 * (1.0/0.1))
> | 1344.500000000000000=> nil
> | irb(main):034:0> printf "%20.15f",(134.45/0.1)
> | 1344.499999999999773=> nil
>
> That isn't too... er... reassuring.

Yeah.

> | I suppose this is not unexpected (my mama warned me 'bout floats) but it
> | is a little unexpected--no, I'm wrong, 1/5 is a repeating decimal base
> | 2, so it's perfectly expected.
>
> Expected, really? I'm not all that familiar with floating point standards, but
> I would have though this kind of thing would have been worked out long ago.
> It's a pretty substantiate miscalculation. --And the fact that simply using
> '*(1.0/0.1)' fixes it, makes me think so even more.

My internal jury is still out. Since 0.1 (base ten) in binary is a
repeating decimal (binimal?), there has to be some rounding assumptions
made somewhere. By their very nature, such assumptions can't always be
right...but one might hope that they were never wrong in a case one

I'll think more about it, but may not come to any conclusions
before my one-week-in-Costa-Rica-with-no-internet.

-- Markus

Markus, Oct 15, 2004
14. ### Hal FultonGuest

trans. (T. Onoma) wrote:
> Expected, really? I'm not all that familiar with floating point standards, but
> I would have though this kind of thing would have been worked out long ago.

If you can work out how to store an infinite number of bits in a finite
storage location, you can solve this problem and become famous.

> It's a pretty substantiate miscalculation. --And the fact that simply using
> '*(1.0/0.1)' fixes it, makes me think so even more.

You're multiplying one inexact quantity by another. Who knows how the answer
will be represented?

Hal

Hal Fulton, Oct 15, 2004
15. ### Guest

On Sat, 16 Oct 2004, trans. (T. Onoma) wrote:

> On Friday 15 October 2004 11:14 am, wrote:| Â  Â jib:~/eg/ruby > cat truncate.rb| Â  Â class Float| Â  Â  Â def truncate sd = 2| Â  Â  Â  Â i = self.to_i| Â  Â  Â  Â prec = 10 ** sd| Â  Â  Â  Â fraction = (i.zero? ? self : (self / i) - 1)| Â  Â  Â  Â i + (fraction * prec).to_i / prec.to_f| Â  Â  Â end| Â  Â end|| Â  Â p(0.1 * (134.45/0.1).truncate) Â  Â  Â  Â  Â  Â  Â  Â  Â  #=> 134.4| Â  Â p((134.45 * (1/0.1)).truncate.to_f / (1/0.1)) Â  Â #=> 134.4|| Â  Â jib:~/eg/ruby > ruby truncate.rb| Â  Â 134.4| Â  Â 134.4
> Sorry, I don't follow. Isn't that answer incorrect? The mid-point should round up, not down. At least, that's what I was taught in school.
> T.

sorry-

not only did i post the wrong version, but mine clobbered the existing
truncate in Float, this ought to work:

jib:~/eg/ruby > cat roundn.rb
class Float
def roundn sd = 2
f = self.floor
prec = 10.0 ** sd.to_i.abs
frac = (f == 0 ? self : self - f)
f + (frac * prec).round / prec
end
end

p(0.1 * (134.45/0.1).roundn) #=> 134.45
p((134.45 * (1/0.1)).roundn.to_f / (1/0.1)) #=> 134.45

jib:~/eg/ruby > ruby roundn.rb
134.45
134.45

regards.

-a

ps. i'm cc'ing you directly to let you know your message ends up as one big
line in my mailer and i'm wondering if it's on my end or yours?

--
===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| When you do something, you should burn yourself completely, like a good
| bonfire, leaving no trace of yourself. --Shunryu Suzuki
===============================================================================

, Oct 15, 2004
16. ### GOTO KentaroGuest

In message Re: Rounding to X digits
on Thu, 14 Oct 2004 23:49:31 +0900, wrote "Robert Klemme":
> The difference in efficiency is one issue. But there is a conceptual
> difference: it depends whether you want to align printed output or round a
> numeric value in a calculation. For the first task sprintf is probably
> better because the amount of places printed is guaranteed (by the format
> string), while it's not if you just round the number and print it.

And there is an algorithmic difference: many implementation of
sprintf(3) conform to ISO 31-0 but Float#round doesn't.

puts 0.5.round #=> 1
puts sprintf("%.0f", 0.5) #=> 0

Gotoken

GOTO Kentaro, Oct 15, 2004
17. ### trans. (T. Onoma)Guest

On Friday 15 October 2004 02:23 pm, wrote:
| ps. Â i'm cc'ing you directly to let you know your message ends up as one
| big line in my mailer and i'm wondering if it's on my end or yours?

Strange. Hope you can read this!

Your messages come to me as type: multipart/mixed; encoding: 7-bit, with a
body of type: Plain Text Document; encoding: QUOTED-PRINTABLE, according to
my client which is KMail.

Also I have my client set to try to respond to messages in the same encoding
as they are received --that may sometimes be problematic, too.

Other's have reported different problems as well --even when I used pure
ASCII. Seems ASCII isn't the same for all parts of the world. I am going to
try using utf-8 for all outgoing messages from now on. See if that helps.

Thanks, T.

---

KEYBOARD CHARACTERS
01234567890abcdefghijklmnopABCDEFGHIJKLMNOP
!@#\$%^&*()_+|-=\`~[]{};':",./<>?
/*-+0123456789

trans. (T. Onoma), Oct 15, 2004
18. ### Guest

On Sat, 16 Oct 2004, GOTO Kentaro wrote:

> In message Re: Rounding to X digits
> on Thu, 14 Oct 2004 23:49:31 +0900, wrote "Robert Klemme":
> > The difference in efficiency is one issue. But there is a conceptual
> > difference: it depends whether you want to align printed output or round a
> > numeric value in a calculation. For the first task sprintf is probably
> > better because the amount of places printed is guaranteed (by the format
> > string), while it's not if you just round the number and print it.

>
> And there is an algorithmic difference: many implementation of
> sprintf(3) conform to ISO 31-0 but Float#round doesn't.
>
> puts 0.5.round #=> 1
> puts sprintf("%.0f", 0.5) #=> 0
>

Both are (IIRC) ISO 31-0 conformant, but one uses ISO 31-0 B.3 rule A
and the other uses ISO 31-0 B.3 rule B.

That said, one of the rules (the one sprintf uses, to round down
following even digits is an abomination and should be eliminated from the
face of the earth. nnn.nn5 should ALWAYS round up and rounded values
should not be re-rounded (which is what the contrary rule assumes is
happening).

-- Markus

, Oct 16, 2004
19. ### trans. (T. Onoma)Guest

On Friday 15 October 2004 07:23 pm, wrote:
| > puts 0.5.round #=> 1
| > puts sprintf("%.0f", 0.5) #=> 0
|
| Both are (IIRC) ISO 31-0 conformant, but one uses ISO 31-0 B.3 rule A
| and the other uses ISO 31-0 B.3 rule B.
|
| That said, one of the rules (the one sprintf uses, to round down
| following even digits is an abomination and should be eliminated from the
| face of the earth. nnn.nn5 should ALWAYS round up and rounded values
| should not be re-rounded (which is what the contrary rule assumes is
| happening).

Apparently the idea of even vs. odd rounding was to help prevent "inflational"
rounding --successive rounding pushing values upward. I think K&R actually
supported the idea. I'm not sure how I feel about it.

T.

trans. (T. Onoma), Oct 16, 2004
20. ### MarkusGuest

On Fri, 2004-10-15 at 17:58, trans. (T. Onoma) wrote:
> On Friday 15 October 2004 07:23 pm, wrote:
> | > puts 0.5.round #=> 1
> | > puts sprintf("%.0f", 0.5) #=> 0
> |
> | Both are (IIRC) ISO 31-0 conformant, but one uses ISO 31-0 B.3 rule A
> | and the other uses ISO 31-0 B.3 rule B.
> |
> | That said, one of the rules (the one sprintf uses, to round down
> | following even digits is an abomination and should be eliminated from the
> | face of the earth. nnn.nn5 should ALWAYS round up and rounded values
> | should not be re-rounded (which is what the contrary rule assumes is
> | happening).
>
> Apparently the idea of even vs. odd rounding was to help prevent "inflational"
> rounding --successive rounding pushing values upward. I think K&R actually
> supported the idea. I'm not sure how I feel about it.

Yes, that's exactly why it's there. But there are several reasons
(e.g. Benford's Law http://mathworld.wolfram.com/BenfordsLaw.html) why
it doesn't even work for its intended purpose. The real answer is to
not re-round the data.

As for developing strong feelings about it, all you need to do is
work downstream from someone who swears by it and try to do some valid
numerical analysis. The only person I ever knew with a worse lament was
a friend who worked downstream of an astronomy professor (who should
have been emeritusized years before) that routinely converted his images
to jpeg "to save disk space."

-- Markus

Markus, Oct 16, 2004