I'm displaying star ratings based on a review, using conditionals like the following:
- if page.page_reviews.average('rating') == 5
%p Do something...
The problem is... with a 1-5 review system, not all averages equate to whole numbers. How can I round each average to the nearest whole number in Rails?
Use Ruby's Float#round
Without arguments, the Float#round method will convert a Float to the nearest Integer. For example:
(2.5).round
#=> 3
avg = 3.2
avg.round
#=> 3
Here you go:
2.0.0-p247 :002 > (1.2).ceil
=> 2
2.0.0-p247 :003 > (1.2).floor
=> 1
2.0.0-p247 :004 > (1.2).round
=> 1
2.0.0-p247 :005 > (1.8).round
=> 2
You can use
Math.floor(number + 0.5)
to round the nearest integer value (4.7 rounds to 5). Do not include the addition of 0.5 if you just want to round to the integer part of the value (e.g. 4.7 rounds to 4).
Related
If I compare a, b and c like so
[a,b,c].min
where
a = BigDecimal.new("NaN")
b = BigDecimal.new("NaN")
c = BigDecimal.new("0.0")
I get:
ArgumentError: comparison of BigDecimal with BigDecimal failed
But if I was to use the comparison operator that ruby's Enumerable min uses then I get this:
irb(main):001:0> a <=> b
=> nil
irb(main):002:0> a <=> c
=> nil
And no errors are rendered. Is this an issue within Ruby or am I misunderstanding min, is there something else I can use to achieve the same effect as enumerable's min that will not explode?
From the Ruby language documentation:
NaN is never considered to be the same as any other value, even NaN
itself:
n = ::new(‘NaN’)
n == 0.0 -> nil
n == n -> nil
As I understand it, any instance of the NaN value is unique and incomparable.
Consider this code snippet:
require 'bigdecimal'
a = BigDecimal('NaN')
b = BigDecimal('NaN')
puts a == b
puts a > b
puts a < b
You'll get false for each of these comparisons. min and max depend sorting to produce a result, and as #phoffer pointed out, sort produces values of 0, 1 and -1 based on whether the second number is equal to, greater than or less than the first number.
TL;DR: Any time you're using NaN in an operation, you can't expect meaningful results.
If the API you're working with returns NaN, you can at least protect yourself against this corner case by detecting it. NaN is actually an instance of Float, so you can test it by using the nan? method.
2.2.0 :002 > require 'bigdecimal'
=> true
2.2.0 :003 > a = BigDecimal('NaN')
=> #<BigDecimal:7fae1b3bd050,'NaN',9(9)>
2.2.0 :004 > a.nan?
=> true
I'm dealing with currencies and I want to round down the number to 2 decimal places. Even if the number is 500.0, I would like it to be 500.00 to be consistent. When I do "500.00".to_d it converts it to 500.0.
Whats a good way of changing this behavior? I also use this method to round down to 2 digits and make sure it always has 2 decimals.
def self.round_down(x, n=2)
s = x.to_s
l = s.index('.') ? s.index('.') + 1 + n : s.length
s = s[0, l]
s = s.index('.') ? s.length - (s.index('.') + 1) == 1 ? s << '0' : s : s << '.00'
s.to_f
end
In addition to mcfinnigan's answer, you can also use the following to get 2 decimal places
'%.2f' % 500 # "500.00"
This use case is known as the string format operator
Since you are using Rails and this seems to be related to a view, there's number_with_precision:
number_with_precision(500, precision: 2)
#=> "500.00"
I18n.locale = :de
number_with_precision(500, precision: 2)
#=> "500,00"
For currencies I'd suggest number_to_currency:
number_to_currency(500)
#=> "$500.00"
Here's a hint. 500.00 is a representation of the number 500.0
Specifically, sprintf will help you:
irb(main):004:0> sprintf "%.2f", 500.0
=> "500.00"
Do not use floating point numbers to represent money. See this question for a good overview of why this is a bad idea.
Instead, store monetary values as integers (representing cents), or have a look at the money gem that provides lots of useful functionality for dealing with such values.
There was a requirement to round DOWN.
Most other answers round 500.016 UP to 500.02
Try:
def self.round_down(x, n = 2)
"%.#{n}f" % x.to_d.truncate(n)
end
irb(main):024:0> x=500.0; '%.2f' % x.to_d.truncate(2)
=> "500.00"
irb(main):025:0> x=500.016; '%.2f' % x.to_d.truncate(2)
=> "500.01"
I'm storing a decimal in rails and I need to have it display as a currency as such:
11.1230 => "$11.123"
11.1000 => "$11.10"
11.0100 => "$11.01"
11.1234 => "$11.1234"
Any easy way to do this?
def pad_number( number, min_decimals=2 )
s = "%g" % number
decimals = (s[/\.(\d+)/,1] || "").length
s << "." if decimals == 0
s << "0"*[0,min_decimals-decimals].max
end
puts [ 11.123, 11.1, 11.01, 11.1234, 11 ].map{ |n| pad_number(n) }
#=> 11.123
#=> 11.10
#=> 11.01
#=> 11.1234
#=> 11.00
Edit: Looks like this is Rails 3 specific, as Rails 2's number_with_precision method doesn't include the strip_insignificant_zeros option:
You can pass some options to number_to_currency (a standard Rails helper):
number_to_currency(11.1230, :precision => 10, :strip_insignificant_zeros => true)
# => "$11.123"
You need to provide a precision in order for the strip_insignificant_zeros option to work, though, otherwise the underlying number_with_precision method never gets called.
If you want to store as a float, you can use the number_to_currency(value) method in yours views for printing something that looks like $.
Correct me if I'm wrong (as I've rarely dealt with currency) but I think the conventional wisdom is to store dollar values as integers. That way you won't have to deal with funky float math.
So, convert it to three decimal fraction digits and then remove the final one if and only if it's a zero.
s.sub(/0$/, '')
Thoughout our app we use number_to_currency(value, :precision => 2). However, we now have a requirement whereby the value may need displaying to three or more decimal places, e.g.
0.01 => "0.01"
10 => "10.00"
0.005 => "0.005"
In our current implementation, the third example renders as:
0.005 => "0.01"
What's the best approach for me to take here? Can number_to_currency be made to work for me? If not, how do I determine how many decimal places a given floating point value should be displayed to? sprintf("%g", value) comes close, but I can't figure out how to make it always honour a minimum of 2dp.
The following will not work with normal floats, because of precision problems, but if you're using BigDecimal it should work fine.
def variable_precision_currency(num, min_precision)
prec = (num - num.floor).to_s.length - 2
prec = min_precision if prec < min_precision
number_to_currency(num, :precision => prec)
end
ruby-1.8.7-p248 > include ActionView::Helpers
ruby-1.8.7-p248 > puts variable_precision_currency(BigDecimal.new("10"), 2)
$10.00
ruby-1.8.7-p248 > puts variable_precision_currency(BigDecimal.new("0"), 2)
$0.00
ruby-1.8.7-p248 > puts variable_precision_currency(BigDecimal.new("12.45"), 2)
$12.45
ruby-1.8.7-p248 > puts variable_precision_currency(BigDecimal.new("12.045"), 2)
$12.045
ruby-1.8.7-p248 > puts variable_precision_currency(BigDecimal.new("12.0075"), 2)
$12.0075
ruby-1.8.7-p248 > puts variable_precision_currency(BigDecimal.new("-10"), 2)
$-10.00
ruby-1.8.7-p248 > puts variable_precision_currency(BigDecimal.new("-12.00075"), 2)
$-12.00075
I have a calculation that generates what appears to be the Float 22.23, and a literal 22.23 like so:
some_object.total => 22.23
some_object.total.class => Float
22.23 => 22.23
22.23.class => Float
But for some reason, the following is false:
some_object.total == 22.23 ? true : false
Wacky, right?
Is there some kind of precision mechanism being used that maybe isn't completely transparent through the some_object.total call?
Floating-point numbers cannot precisely represent all decimal numbers within their range. For example, 0.9 is not exactly 0.9, it's a number really close to 0.9 that winds up being printed as it in most cases. As you do floating-point calculations, these errors can accumulate and you wind up with something very close to the right number but not exactly equal to it. For example, 0.3 * 3 == 0.9 will return false. This is the case in every computer language you will ever use — it's just how binary floating-point math works. See, for example, this question about Haskell.
To test for floating point equality, you generally want to test whether the number is within some tiny range of the target. So, for example:
def float_equal(a, b)
if a + 0.00001 > b and a - 0.00001 < b
true
else
false
end
end
You can also use the BigDecimal class in Ruby to represent arbitrary decimal numbers.
If this is a test case, you can use assert_in_delta:
def test_some_object_total_is_calculated_correctly
assert_in_delta 22.23, some_object.total, 0.01
end
Float#to_s and Float#inspect round. Try "%.30f" % some_object.total and you will see that it's not quite 22.23.
there is something else going on here. this is from a 1.8.7 irb
irb(main):001:0> class Test
irb(main):002:1> attr_accessor :thing
irb(main):003:1> end
=> nil
irb(main):004:0> t = Test.new
=> #<Test:0x480ab78>
irb(main):005:0> t.thing = 22.5
=> 22.5
irb(main):006:0> t.thing == 22.5
=> true