Consistent rounding of floating points in Ruby - ruby

I understand due to the inexact representation of floating points, the following code 'feels' inconsistent.
"%.1f" % 1.14 # => 1.1
"%.1f" % 1.15 # => 1.1
"%.1f" % 1.16 # => 1.2
"%.0f" % 1.4 # => 1
"%.0f" % 1.5 # => 2
"%.0f" % 1.6 # => 2
However, is there an easy way of doing consistent floating points rounding by 5? One way might be to do string manipulation explicitly. Is there an easier way or existent library?

If you want decimal precision, use BigDecimal instead of floats.
Edit: You will have to manually round the number to the desired length before passing it to %, otherwise it gets converted to a normal float before being rounded.
"%.1f" % BigDecimal('1.15').round(1) => "1.2"
"%.0f" % BigDecimal('1.5').round(0) => "2"

Just add a tiny pertubation, to ensure things that are just under 0.5 in floating-point become just over.
For example,
x = 1.15
"%.1f" % (1.000001*x) # include correction for imprecise floating-point.
this will be enough to deal with the formatting problems, while very unlikely to cause a relevant error.
also: an obvious follow-on to my earlier question here, which is fine, but included for completeness.

The function roundthis() in this example shows how to round numbers in a controllable, consistent way. Note the small fudge value. Try running this example without the fudge to see what happens.
def roundthis(x, m)
return (x/m+0.50001).floor*m
end
for x in [1.14, 1.15, 1.16]
print "#{x} #{roundthis(x, 0.1)} \n"
end
for x in [1.4, 1.5, 1.6]
print "#{x} #{roundthis(x, 1.0)} \n"
end
This, put into a file named roundtest.rb and executed prints
bash> ruby roundtest.rb
1.14 1.1
1.15 1.2
1.16 1.2
1.4 1.0
1.5 2.0
1.6 2.0
Note the ease of rounding to the nearest 2, 15, 0.005, or whatever.

Multiply by 100, then round, then divide by 100:
(1.15 * 100).round / 100.0 # => 1.15
It's not exactly elegant, but it avoids using strings.

Related

How do I rounding of zeros in Ruby

When I round of 12121.232323 to 2 digit decimal point like below
p 12121.232323.round(2)
it is printing
12121.23
But when I try to round()
211211.00000.round(2)
it's printing
211211.0
But I want
211211.00
How do I do that?
'%.2f' % 12121.232323
or
include ActionView::Helpers::NumberHelper
number_with_precision(value, :precision => 2)
What you seek is not exactly rounding but formatting.
You can select your float formatting like this:
p "%.2f" % 12121.0000
where the %.2f part means "show 2 decimal points"

Float Rounding Changes in Ruby 2.4

Ruby 2.4 uses Gaussian rounding to round off floating point numbers.
According to Wikipedia:
A tie-breaking rule that is less biased (even when the original numbers are positive or negative with unequal probability) is round half to even. By this convention, if the fraction of y is 0.5, then q is the even integer nearest to y. Thus, for example, +23.5 becomes +24, as does +24.5; while −23.5 becomes −24, as does −24.5.
However, executing the following code in Ruby 2.4 produces a different output than what is expected.
[1.5, 2.5, 3.5, 4.5, 5.5].each { | num | puts num.round }
# output:
2
3
4
5
6
# expected output(based on Gaussian rounding):
2
2
4
4
6
Can someone explain why is this so or what am I missing?
In order to apply Gaussian rounding, you have to pass the keyword argument :half.
The keyword argument :half can take either :down or :even and the default behavior is still to round up, just as it was before.
# ruby 2.4.0-rc1
irb(main):001:0> (2.5).round
# => 3
irb(main):008:0> (2.5).round(half: :down)
# => 2
irb(main):009:0> (2.5).round(half: :even)
# => 2
The background to this decision is in this blog post.

Unable to understand the subtraction result

I executed the following line in Ruby (Aptana IDE)
puts 3.3 - 2.7 == 0.6 #which should be true
and I got the result as
false
Then I executed
puts 3.3 - 2.7
and got the result as
0.5999999999999996
Can anybody please explain about whats going on? Why I got 0.5999999999999996 instead of 0.6?
Floating-point numbers cannot precisely represent all real numbers, and floating-point operations cannot precisely represent true arithmetic operations, this leads to many surprising situations.
I advise to read: https://en.wikipedia.org/wiki/Floating_point#Accuracy_problems
The right way to handle this in Ruby is to use the BigDecimal class
> require 'bigdecimal'
true
> a = BigDecimal.new('3.3')
3.3
> b = BigDecimal.new('2.7')
2.7
> c = BigDecimal.new('0.6')
0.6
> a - b == c
true
It's floating point math. Computers can't represent some values exactly. You can use Ruby's String format to get a string version:
'%0.1f' % (3.3 - 2.7) # => "0.6"
Or adjust your math to multiply your values by the precision you want, then remove that offset:
(3.3 * 10 - 2.7 * 10) / 10 # => 0.6
The question you should ask yourself (or yourinterpreter) is
how is 0.6 different from 0.5999999999999996 in my machine?
and the answer is that it is not different. Floating point number 0.5999999999999996 is 0.6 in your machine because the precision your interpreter is using for floating point numbers can not distinguish the two.
If this representation causes problems you can use explicit rounding Float#round
(3.3 - 2.7).round(1)
#=> 0.6
or special formatting (check Kernel#sprintf for more opts)
"%0.1f" % (3.3 - 2.7)
#=> "0.6"

multiplying floating point numbers produces zero

the code below outputs 0.0. is this because of the overflow? how to avoid it? if not, why?
p ((1..100000).map {rand}).reduce :*
I was hoping to speed up this code:
p r.reduce(0) {|m, v| m + (Math.log10 v)}
and use this instead:
p Math.log10 (r.reduce :*)
but apparently this is not always possible...
The values produced by rand are all between 0.0 and 1.0. This means that on each multiplication, your number gets smaller. So by the time you have multiplied 1000 of them, it is probably indistinguishable from 0.
At some point, ruby will take your number to be so small that it is 0. for instance: 2.0e-1000 # => 0
Every multiplication reduces your number by about 1/21, so after about 50 of them, you are down 1/250, and after 100000 (actually, after about 700) you have underflowed the FP format itself, see here.
Ruby provides the BigDecimal class, which implements accurate floating point arithmetic.
require 'bigdecimal'
n = 100
decimals = n.times.map { BigDecimal.new rand.to_s }
result = decimals.reduce :*
result.nonzero?.nil? # returns nil if zero, self otherwise
# => false
result.precs # [significant_digits, maximum_significant_digits]
# => [1575, 1764]
Math.log10 result
# => -46.8031931083014
It is a lot slower than native floating point numbers, however. With n = 100_000, the decimals.reduce :* call went on for minutes on my computer before I finally interrupted it.

Issue with precision of Ruby math operations

Do you know how to fix the following issue with math precision?
p RUBY_VERSION # => "1.9.1"
p 0.1%1 # => 0.1
p 1.1%1 # => 0.1
p 90.0%1 # => 0.0
p 90.1%1 # => 0.0999999999999943
p 900.1%1 # => 0.100000000000023
p RUBY_VERSION # => "1.9.2"
p 0.1%1 # => 0.1
p 1.1%1 # => 0.10000000000000009
p 90.0%1 # => 0.0
p 90.1%1 # => 0.09999999999999432
p 900.1%1 # => 0.10000000000002274
Big Decimal
As the man said;
Squeezing infinitely many real numbers into a finite number of bits requires an approximate representation.
I have however had great success using the BigDecimal class. To quote its intro
Ruby provides built-in support for arbitrary precision integer arithmetic. For example:
42**13 -> 1265437718438866624512
BigDecimal provides similar support for very large or very accurate floating point numbers.
Taking one of your examples;
>> x = BigDecimal.new('900.1')
=> #<BigDecimal:101113be8,'0.9001E3',8(8)>
>> x % 1
=> #<BigDecimal:10110b498,'0.1E0',4(16)>
>> y = x % 1
=> #<BigDecimal:101104760,'0.1E0',4(16)>
>> y.to_s
=> "0.1E0"
>> y.to_f
=> 0.1
As you can see, ensuring decent precision is possible but it requires a little bit of effort.
This is true of all computer languages, not just Ruby. It's a feature of representing floating point numbers on binary computers:
What Every Computer Scientist Should Know About Floating Point Arithmetic
Writing 0.1 into a floating point will always result in rounding errors. If you want 'precise' decimal representation, you should use the Decimal type.

Resources