Float numbers in ERB/Rails - ruby-on-rails

I'm pretty new to Rails, I'm passing 2 variables to a view, in my controller have them defined like
#correct = 5
#total = 40
in my view I'm trying to mark them up like:
Score = <%=#score%>/<%=#total%> = <%=(#score/#total)%>
It outputs 0 for the division. Do I need to explicitly define that equation to output a float or something? How do I get it to output 0.125 instead of 0
Thanks guys

Decide whether you want to use #correct or #score. Also to use double division instead of integer multiply #score by 1.0:
<%=(1.0 * #score/#total)%>
Or alternatively cast #score to float:
<%=(#score.to_f/#total)%>

You need to explicitly convert your integers into floats:
1.9.3p0 :001 > a = 5
=> 5
1.9.3p0 :002 > b = 40
=> 40
1.9.3p0 :003 > a / b
=> 0
1.9.3p0 :005 > (a / b).to_f
=> 0.0
1.9.3p0 :006 > a.to_f / b.to_f
=> 0.125
In your case:
Score = <%=#score%>/<%=#total%> = <%=(#score.to_f/#total.to_f)%>

Related

How to round Decimals to the First Significant Figure in Ruby

I am attempting to solve an edge case to a task related to a personal project.
It is to determine the unit price of a service and is made up of the total_amount and cost.
Examples include:
# 1
unit_price = 300 / 1000 # = 0.3
# 2
unit_price = 600 / 800 # = 0.75
# 3
unit_price = 500 / 1600 # = 0.3125
For 1 and 2, the unit_prices can stay as they are. For 3, rounding to 2 decimal places will be sufficient, e.g. (500 / 1600).round(2)
The issue arises when the float becomes long:
# 4
unit_price = 400 / 56000 # = 0.007142857142857143
What's apparent is that the float is rather long. Rounding to the first significant figure is the aim in such instances.
I've thought about using a regular expression to match the first non-zero decimal, or to find the length of the second part and apply some logic:
unit_price.match ~= /[^.0]/
unit_price.to_s.split('.').last.size
Any assistance would be most welcome
One should use BigDecimal for this kind of computation.
require 'bigdecimal'
bd = BigDecimal((400.0 / 56000).to_s)
#⇒ 0.7142857142857143e-2
bd.exponent
#⇒ -2
Example:
[10_000.0 / 1_000, 300.0 / 1_000, 600.0 / 800,
500.0 / 1_600, 400.0 / 56_000].
map { |bd| BigDecimal(bd.to_s) }.
map do |bd|
additional = bd.exponent >= 0 ? 0 : bd.exponent + 1
bd.round(2 - additional) # THIS
end.
map(&:to_f)
#⇒ [10.0, 0.3, 0.75, 0.31, 0.007]
You can detect the length of the zeros string with regex. It's a bit ugly, but it works:
def significant_round(number, places)
match = number.to_s.match(/\.(0+)/)
return number unless match
zeros = number.to_s.match(/\.(0+)/)[1].size
number.round(zeros+places)
end
pry(main)> significant_round(3.14, 1)
=> 3.14
pry(main)> significant_round(3.00014, 1)
=> 3.0001
def my_round(f)
int = f.to_i
f -= int
coeff, exp = ("%e" % f).split('e')
"#{coeff.to_f.round}e#{exp}".to_f + int
end
my_round(0.3125)
#=> 0.3
my_round(-0.3125)
#=> -0.3
my_round(0.0003625)
#=> 0.0004
my_round(-0.0003625)
#=> -0.0004
my_round(42.0031)
#=> 42.003
my_round(-42.0031)
#=> -42.003
The steps are as follows.
f = -42.0031
int = f.to_i
#=> -42
f -= int
#=> -0.0031000000000034333
s = "%e" % f
#=> "-3.100000e-03"
coeff, exp = s.split('e')
#=> ["-3.100000", "-03"]
c = coeff.to_f.round
#=> -3
d = "#{c}e#{exp}"
#=> "-3e-03"
e = d.to_f
#=> -0.003
e + int
#=> -42.003
To instead keep only the most significant digit after rounding, change the method to the following.
def my_round(f)
coeff, exp = ("%e" % f).split('e')
"#{coeff.to_f.round}e#{exp}".to_f
end
If f <= 0 this returns the same as the earlier method. Here is an example when f > 0:
my_round(-42.0031)
#=> -40.0

Extracting numbers from a string

Consider i have a string like this:
"1 hour 7 mins"
I need to extract number of hour (1) and min (7). the problem is either hour or mins can be nill so in this case the string would be 1 hour ot just 7 mins
I am mostly interested in regular expression. I have already seen this and run this code
result = duration.gsub(/[^\d]/, '')
result[0]!= nil ? hour=result[0] : hour=0
result[1]!=nil ? mins=result[1] : mins=0
the problem is, when i have only 5 mins it gives me 5 and i do not know it is mins or hour
So how can i do it?
What do you think about something like this:
hours = duration.match(/[\d]* hour/).to_s.gsub(/[^\d]/, '')
minutes = duration.match(/[\d]* mins/).to_s.gsub(/[^\d]/, '')
You could do that :
a = duration[/(\d*)(\s*hour)?s?\s*(\d*)(\s*min)?s?/][0]
if a.include?("hour")
hour = a[0]
min = a[2]
else
min = a[0]
end
Improved, this is what I wanted :
capture = duration.match(/^((\d*) ?hour)?s? ?((\d*) ?min)?s?/)
hour = capture[2]
min = capture[4]
You can try the regex here :
http://rubular.com/r/ACwfzUIHBo
I couldn't resist a bit of code golf:
You can do:
hours,_,mins = (duration.match /^([\d]* h)?([^\d]*)?([\d]* m)?/)[1..3].map(&:to_i)
Explanation:
matches number then 'h', then anything not a number, then number then 'm'. Then gets the match data and does .to_i (which in ruby if it starts with a number uses this number). It then assigns 1st and third match to hours and minutes respectively:
Output:
2.2.1 :001 > duration = "5 hours 26 min"
=> "5 hours 26 min"
2.2.1 :002 > hours,_,mins = (duration.match /^([\d]* h)?([^\d]*)?([\d]* m)?/)[1..3].map(&:to_i)
=> [5, 0, 26]
2.2.1 :003 > hours
=> 5
2.2.1 :004 > mins
=> 26
2.2.1 :005 > duration = "5 hours"
=> "5 hours"
2.2.1 :006 > hours,_,mins = (duration.match /^([\d]* h)?([^\d]*)?([\d]* m)?/)[1..3].map(&:to_i)
=> [5, 0, 0]
2.2.1 :007 > duration = "54 mins"
=> "54 mins"
2.2.1 :008 > hours,_,mins = (duration.match /^([\d]* h)?([^\d]*)?([\d]* m)?/)[1..3].map(&:to_i)
=> [0, 0, 54]
2.2.1 :009 >

Map integers 0-6 to weekday names

I have a class with two attributes saving weekdays with numeric values. I had hoped to be able to use Enum, but appearantly you can not use the same value for two attributes with Enum.
How could I represent the integer value of an attribute to the corresponding weekday?
0 => "monday"
4 => "friday"
Date::DAYNAMES[(i + 1) % 7]
where i is your integer
2.0.0-p247 :001 > Date::DAYNAMES[(0 + 1) % 7]
=> "Monday"
2.0.0-p247 :002 > Date::DAYNAMES[(4 + 1) % 7]
=> "Friday"
2.0.0-p247 :003 > Date::DAYNAMES[(6 + 1) % 7]
=> "Sunday"
You cannot do a straight look up on the index because in DAYNAMES 0 is Sunday and you want 0 to be Monday.

A way to round Floats down

Float round rounds it up or down. I always need it to round down.
I have the solution but i dont really like it... Maybe there is a better way.
This is what i want:
1.9999.round_down(2)
#=> 1.99
1.9901.round_down(2)
#=> 1
I came up with this solution but i would like to know if there is a better solution(I dont like that i convert the float twice). Is there already a method for this? Because I found it pretty strange that I couldnt find it.
class Float
def round_down(n=0)
((self * 10**n).to_i).to_f/10**n
end
end
Thanks.
1.9999.to_i
#=> 1
1.9999.floor
#=> 1
answered 1 sec ago fl00r
"%.2f" % 1.93213
#=> 1.93
#kimmmo is right.
class Float
def round_down(n=0)
self.to_s[/\d+\.\d{#{n}}/].to_f
end
end
Based on answer from #kimmmo this should be a little more efficient:
class Float
def round_down n=0
s = self.to_s
l = s.index('.') + 1 + n
s.length <= l ? self : s[0,l].to_f
end
end
1.9991.round_down(3)
=> 1.999
1.9991.round_down(2)
=> 1.99
1.9991.round_down(0)
=> 1.0
1.9991.round_down(5)
=> 1.9991
or based on answer from #steenslag, probably yet more efficient as there is no string conversion:
class Float
def round_down n=0
n < 1 ? self.to_i.to_f : (self - 0.5 / 10**n).round(n)
end
end
Looks like you just want to strip decimals after n
class Float
def round_down(n=0)
int,dec=self.to_s.split('.')
"#{int}.#{dec[0...n]}".to_f
end
end
1.9991.round_down(3)
=> 1.999
1.9991.round_down(2)
=> 1.99
1.9991.round_down(0)
=> 1.0
1.9991.round_down(10)
=> 1.9991
(Edit: slightly more efficient version without the regexp)
You could use the floor method
http://www.ruby-doc.org/core/classes/Float.html#M000142
For anybody viewing this question in modern times (Ruby 2.4+), floor now accepts an argument.
> 1.9999.floor(1)
=> 1.9
> 1.9999.floor(2)
=> 1.99
> 1.9999.floor(3)
=> 1.999
> 1.9999.ceil(2)
=> 2.0
In Ruby 1.9:
class Float
def floor_with_prec(prec = 0)
(self - 0.5).round(prec)
end
end
class Float
def rownd_down(digits = 1)
("%.#{digits+1}f" % self)[0..-2].to_f
end
end
> 1.9991.rownd_down(3)
=> 1.999
> 1.9991.rownd_down(2)
=> 1.99
> 1.9991.rownd_down(10)
> 1.9991
Found a bug for the answers that try to calculate float in round_down method.
> 8.7.round_down(1)
=> 8.7
> 8.7.round_down(2)
=> 8.69
you can use bigdecimal, integer or maybe string to do all the math but float.
> 8.7 - 0.005
=> 8.694999999999999
Here is my solution:
require 'bigdecimal'
class Float
def floor2(n = 0)
BigDecimal.new(self.to_s).floor(n).to_f
end
end
> 8.7.floor2(1)
=> 8.7
> 8.7.floor2(2)
=> 8.7
> 1.9991.floor(3)
=> 1.999
> 1.9991.floor(2)
=> 1.99
> 1.9991.floor(1)
=> 1.9
class Float
def round_down(n)
num = self.round(n)
num > self ? (num - 0.1**n) : num
end
end
56.0.round_down(-1) = 50. Works with negative numbers as well, if you agree that rounding down makes a number smaller: -56.09.round_down(1) = -56.1.
Found this article helpful: https://richonrails.com/articles/rounding-numbers-in-ruby
Here are the round up and down methods:
class Float
def round_down(exp = 0)
multiplier = 10 ** exp
((self * multiplier).floor).to_f/multiplier.to_f
end
def round_up(exp = 0)
multiplier = 10 ** exp
((self * multiplier).ceil).to_f/multiplier.to_f
end
end
This worked for me.
> (1.999).to_i.to_f
For rounding up you could just use
> (1.999+1).to_i.to_f

How can I center truncate a string?

Does anybody have any code handy that center truncates a string in Ruby on Rails?
Something like this:
Ex: "Hello World, how are you?" => "Hel...you?"
If you want a certain fixed length irrespective of the length of the string, you can use Rails #truncate:
s.truncate(100, omission: "...#{s.last(50)}")
Here is a modified version of Mike Woodhouse's answer. It takes 2 optional params: a minimum length for the the string to be ellipsisized and the edge length.
class String
def ellipsisize(minimum_length=4,edge_length=3)
return self if self.length < minimum_length or self.length <= edge_length*2
edge = '.'*edge_length
mid_length = self.length - edge_length*2
gsub(/(#{edge}).{#{mid_length},}(#{edge})/, '\1...\2')
end
end
"abc".ellipsisize #=> "abc"
"abcdefghi".ellipsisize #=> "abcdefghi"
"abcdefghij".ellipsisize #=> "abc...hij"
"abcdefghij".ellipsisize(4,4) #=> "abcd...ghij"
"Testing all paramas and checking them!".ellipsisize(6,5) #=> "Testi...them!"
How about a Regex version:
class String
def ellipsisize
gsub(/(...).{4,}(...)/, '\1...\2')
end
end
"abc".ellipsisize #=> "abc"
"abcdefghi".ellipsisize #=> "abcdefghi"
"abcdefghij".ellipsisize #=> "abc...hij"
EDIT: as suggested in the comments, parameterised length (and using a different Regex notation, just for the heck of it)
class String
def ellipsisize(len = 9)
len = 9 unless len > 9 # assumes minimum chars at each end = 3
gsub(%r{(...).{#{len-5},}(...)}, '\1...\2')
end
end
so...
"abcdefghij".ellipsisize #=> "abc...hij"
but we can also:
"abcdefghij".ellipsisize(10) #=> "abcdefghij"
Here is my suggestion:
s[3...-4] = "..." if s.length > 9
Since you didn't specify how many characters you want to truncate, I'll assume (from your example) that you want to truncate strings whose length is greater than six. You can then use something like this:
s = "Hello World, how are you?"
s = s[0, 3] + "..." + s[-3, 3] if s.length > 9
=> "Hel...ou?"
Just adapt the ranges if you need to truncate more characters.
class String
# https://gist.github.com/1168961
# remove middle from strings exceeding max length.
def ellipsize(options={})
max = options[:max] || 40
delimiter = options[:delimiter] || "..."
return self if self.size <= max
remainder = max - delimiter.size
offset = remainder / 2
(self[0,offset + (remainder.odd? ? 1 : 0)].to_s + delimiter + self[-offset,offset].to_s)[0,max].to_s
end unless defined? ellipsize
end
Another way:
class String
def middle_truncate(len)
return self if len >= size
return self[0...len] unless len > 4
half = len / 2.0
(result = dup)[(half - 1.5).floor...(1.5 - half).floor] = '...'
result
end
end
This has the added benefit of just truncating if string size < 5.
e.g. for an even-sized string:
2.1.1 :001 > s = "12345678901234567890"
=> "12345678901234567890"
2.1.1 :002 > s.middle_truncate 21
=> "12345678901234567890"
2.1.1 :003 > s.middle_truncate 20
=> "12345678901234567890"
2.1.1 :004 > s.middle_truncate 19
=> "12345678...34567890"
2.1.1 :005 > s.middle_truncate 18
=> "1234567...34567890"
2.1.1 :006 > s.middle_truncate 5
=> "1...0"
2.1.1 :007 > s.middle_truncate 4
=> "1234"
and for an odd-sized string:
2.1.1 :012 > s = "123456789012345678901"
=> "123456789012345678901"
2.1.1 :013 > s.middle_truncate 22
=> "123456789012345678901"
2.1.1 :014 > s.middle_truncate 21
=> "123456789012345678901"
2.1.1 :015 > s.middle_truncate 20
=> "12345678...345678901"
2.1.1 :016 > s.middle_truncate 19
=> "12345678...45678901"
2.1.1 :017 > s.middle_truncate 5
=> "1...1"
2.1.1 :018 > s.middle_truncate 4
=> "1234"
Modified rails version that only truncated from the middle
def middle_truncate(str, total: 30, lead: 15, trail: 15)
str.truncate(total, omission: "#{str.first(lead)}...#{str.last(trail)}")
end
I added position option to grosser's method:
def ellipsize(str, options={})
max = options[:max] || 40
delimiter = options[:delimiter] || "..."
position = options[:position] || 0.8
return str if str.size <= max
remainder = max - delimiter.size
offset_left = remainder * position
offset_right = remainder * (1 - position)
(str[0,offset_left + (remainder.odd? ? 1 : 0)].to_s + delimiter + str[-offset_right,offset_right].to_s)[0,max].to_s
end
If you need to limit your string length by bytesize and you need to handle unicode then the solutions listed won't work correctly.
Here's a solution that uses byte size and keeps unicode text elements intact (which are not always the same as unicode characters). Tested in Ruby 1.8/1.9/2.0.
It could be improved to be more efficient for very long strings that need to be truncated down to much smaller lengths.
You must install the unicode gem.
require 'unicode'
# truncates a unicode string like:
# >> truncate_string_middle('12345678', 5)
# => "1...8"
def truncate_string_middle(str, limit, ellipsis='...')
raise "limit (#{limit}) must not be less than the ellipsis size (#{ellipsis.bytesize})" if limit < ellipsis.bytesize
return str if str.bytesize <= limit
chars = Unicode.text_elements(str)
split_point = (chars.size/2.0).ceil
front, back = chars[0...split_point], chars[split_point..-1]
pop_front = chars.size.odd?
# alternate between popping from the front and shifting from the back until it's small enough
while (front.join + ellipsis + back.join).bytesize > limit
if pop_front
front.pop
else
back.shift
end
pop_front = !pop_front
end
front.join + ellipsis + back.join
end
This is the easiest way for me:
def ellipsisize(text, minimum_length=12,edge_length=4)
leftover = text.length - minimum_length
edge_length = leftover if (edge_length > leftover && leftover >= 0)
edge_length = 0 if leftover < 0
return text.truncate(minimum_length) << text.last(edge_length)
end
Regards folks!
A combination of Benjamin Sullivan's and khelll's answers. Uses built-in Rails stuff and lets you define edge length.
With String#truncate, I don't think there's a need to define minimum length at all. This solution automatically shortens the string if the shortened version is shorter than the input string.
def ellipsize(string, edge_length, separator: '…')
string.truncate(
edge_length * 2 + separator.size, omission: "#{separator}#{string.last(edge_length)}"
)
end
Here's my version that lets you specify the maximum length instead, so, you can ensure that a string doesn't exceed the required length:
class String
def truncate(maximum_length = 3, separator = '…')
return '' if maximum_length.zero?
return self if self.length <= maximum_length
middle_length = self.length - maximum_length + separator.length
edges_length = (self.length - middle_length) / 2.0
left_length = edges_length.ceil
right_length = edges_length.floor
left_string = left_length.zero? ? '' : self[0, left_length]
right_string = right_length.zero? ? '' : self[-right_length, right_length]
return "#{left_string}#{separator}#{right_string}"
end
end
'123456'.truncate(0) # ""
'123456'.truncate(1) # "…"
'123456'.truncate(2) # "1…"
'123456'.truncate(3) # "1…6"
'123456'.truncate(4) # "12…6"
'123456'.truncate(5) # "12…56"
'123456'.truncate(6) # "123456"
'123456'.truncate(7) # "123456"

Resources