Ruby how to round only higher? - ruby-on-rails

How can i round in ruby only higher, and so, that 2 last numbers are null?
For exapmle
4233.000001 to 4300
52825 to 52900
627444 to 627500
111999 to 112000
?
Now i can round only in mathematic-rules via round(-2), but how to do only higher, and only with 2 null's on the end?

You should use ceil
def my_round a
(a / 100.0).ceil * 100
end
my_round 4233.000001 # => 4300
my_round 52825 # => 52900
my_round 627444 # => 627500
my_round 111999 # => 112000

working off Sergio's answer, you could mix a module in to the actual Numeric object for a more general solution:
module RoundsUp
def round_up(ndigits)
pow_ten = 10 ** -ndigits
(self / pow_ten.to_f).ceil * pow_ten
end
end
then
mynumeric = 262.33
mynumeric.extend(RoundsUp)
mynumeric.round_up(-2) #=> 300
and you've got a method that behaves like the normal round for any number of digits

Related

Ruby Rounding Convention

I'm trying to define a function that respects the following rounding conditions (round to the closest integer or tenth):
The main issue I found out, was around rounding negative numbers.
Here is my implementation (sorry for the conditional check but its just for this example):
def convention_round(number, to_int = false)
if to_int
number.round
else
number.round(1)
end
end
convention_round(1.2234) # 1.2
convention_round(1.2234, true) # 1
convention_round(1.896) # 1.9
convention_round(1.896, true) # 2
convention_round(1.5) # 1.5
convention_round(1.5, true) # 2
convention_round(1.55) # 1.6
convention_round(1.55, true) # 2
convention_round(-1.2234) # -1.2
convention_round(-1.2234, true) # -1
convention_round(-1.896) # -1.9
convention_round(-1.2234, true) # -2
convention_round(-1.5) # -1.5
convention_round(-1.5, true) # -2 (Here I want rounded to -1)
convention_round(-1.55) # -1.6 (Here I want rounded to -1.5)
convention_round(-1.55, true) # -2
I'm not 100% sure what is the best approach for rounding the negative numbers.
Thank you!
From the docs, you can use Integer#round (and Float#round) for this, as follows:
def convention_round(number, precision = 0)
number.round(
precision,
half: (number.positive? ? :up : :down)
)
end
convention_round(1.4) #=> 1
convention_round(1.5) #=> 2
convention_round(1.55) #=> 2
convention_round(1.54, 1) #=> 1.5
convention_round(1.55, 1) #=> 1.6
convention_round(-1.4) #=> -1
convention_round(-1.5) #=> -1 # !!!
convention_round(-1.55) #=> -2
convention_round(-1.54, 1) #=> -1.55
convention_round(-1.55, 1) #=> -1.5 # !!!
This isn't quite the method signature you asked for, but it's a more generic form - since you can supply an arbitrary precision.
However, I would like to point out the irony that (despite the method name) this is not a conventional way of rounding numbers.
There are a few different conventions, all(?) of which are supported by the ruby core library (see above link to docs), but this is not one of them.

How do I control the decimals in my float calculations?

I'm trying to run a few calculations in order to represent a particular price (ie 20.30).
I have tried the Float#round method, but the instance variables holding these values eventually start representing numbers that look like 24.43418 after a few calculations.
This is just a method I created to turn a users input into a percentage
class Fixnum
def percentage
self.to_f / 100
end
end
The #bankroll_amount and #risk_amount values should be evaluating to two decimal points
class Client
def initialize(bankroll, unit)
#bankroll_amount = bankroll.to_i.round(2)
#unit_percentage = unit.to_i.percentage
default_risk_amount.round(2)
evaluate_default_unit!.round(2)
end
def default_risk_amount
#risk_amount = #unit_percentage * #bankroll_amount
#risk_amount.round(2)
end
# simulates what an updated bankroll looks like after a win based on clients "unit" amount
def risk_win
#bankroll_amount = #bankroll_amount + #risk_amount
#bankroll_amount.round(2)
evaluate_default_unit!.round(2)
end
# simulates what a clients updated bankroll looks like after a loss based on clients "unit" amount
def risk_loss
#bankroll_amount = #bankroll_amount - #risk_amount
evaluate_default_unit!
end
def evaluate_default_unit!
#risk_amount = #unit_percentage * #bankroll_amount.round(2)
end
end
Im not sure if this has anything to do with the fact that I am initializing these instance variables or not, but the #risk_amount returns the correct two decimal value, but when I return the object, the instance variable inside has running decimals.
c = Client.new 2000, 1
<Client:0x000001018956a0 #bankroll_amount=2000.0, #unit_percentage=0.01, #risk_amount=20.0>
c.risk_win
=> 20.2
When I run c.risk_win enough, it eventually returns
c
<Client:0x000001018956a0 #bankroll_amount=2440.3802, #unit_percentage=0.01, #risk_amount=24.4038>
This is one way to show only two decimal points.
price = 20.21340404
"%.2f" % price
# => 20.23
Also see RAILS number_to_currency helpers ActionView::Helpers::NumberHelper
http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_currency
Use number_with_precision to format the display of your floating point numbers to 2 decimal places:
number_with_precision(#bankroll_amount.to_f, precision: 2)
Usage in rails console:
[1] pry(main)> include ActionView::Helpers::NumberHelper
=> Object
[2] pry(main)> number_with_precision(2440.3802, precision: 2)
=> "2440.38"

Rails Specifying the order of validations

I have a validator class that i am writing that has three validations, that are run when calling MyVariableName.valid?
validates_length_of :id_number, :is => 13, :message => "A SA ID has to be 13 digits long"
validates_format_of :id_number, :with => /^[0-9]+$/, :message => "A SA ID cannot have any symbols or letters"
validate :sa_id_validator
The third one is a custom validator. The thing is that my validator sa_id_validator requires that the data that is passed in is a 13 digit number, or I will get errors. How can I make sure that the validate :sa_id_validator is only considered after the first two have run?
Sorry if this is a very simple question I have tried figuring this out all of yesterday afternoon.
Note: this validator has to run over a couple thousand entries and is also run on a spreadsheet upload so I need it to be fast..
I saw a way of doing this but it potentially runs the validations twice, which in my case would be bad.
EDIT:
my custom validator looks like this
def sa_id_validator
#note this is specific to South African id's
id_makeup = /(\d{6})(\d{4})(\d{1})(\d{1})(\d{1})/.match(#id_number)
birthdate = /(\d{2})(\d{2})(\d{2})/.match(id_makeup[1])
citizenship = id_makeup[3]
variable = id_makeup[4]
validator_key = id_makeup[5]
birthdate_validator(birthdate) && citizenship_validator(citizenship) && variable_validator(variable) && id_algorithm(id_makeup[0], validator_key)
end
private
def birthdate_validator(birthdate)
Date.valid_date?(birthdate[1].to_i,birthdate[2].to_i,birthdate[3].to_i)
end
def citizenship_validator(citizenship)
/[0]|[1]/.match(citizenship)
end
def variable_validator(variable)
/[8]|[9]/.match(variable)
end
def id_algorithm(id_num, validator_key)
odd_numbers = digits_at_odd_positions
even_numbers = digits_at_even_positions
# step1: the sum off all the digits in odd positions excluding the last digit.
odd_numbers.pop
a = odd_numbers.inject {|sum, x| sum + x}
# step2: concate all the digits in the even positions.
b = even_numbers.join.to_i
# step3: multiply step2 by 2 then add all the numbers in the result together
b_multiplied = (b*2)
b_multiplied_array = b_multiplied.to_s.split('')
int_array = b_multiplied_array.collect{|i| i.to_i}
c = int_array.inject {|sum, x| sum + x}
# step4: add the result from step 1 and 3 together
d = a + c
# step5: the last digit of the id must equal the result of step 4 mod 10, subtracted from 10
return false unless
validator_key == 10 - (d % 10)
end
def digits_at_odd_positions
id_num_as_array.values_at(*id_num_as_array.each_index.select(&:even?))
end
def digits_at_even_positions
id_num_as_array.values_at(*id_num_as_array.each_index.select(&:odd?))
end
def id_num_as_array
id_number.split('').map(&:to_i)
end
end
if i add the :calculations_ok => true attribute to my validation, and then pass in a 12 digit number instead i get this error:
i.valid?
NoMethodError: undefined method `[]' for nil:NilClass
from /home/ruberto/work/toolkit_3/toolkit/lib/id_validator.rb:17:in `sa_id_validator'
so you can see its getting to the custom validation even though it should have failed the validates_length_of :id_number??
I am not quite sure but i have read at some blog that Rails always runs all validations even if the first one is invalid.
What you can do is to make your custom method in such a way that it would become flexible or bouncy in such a way that i would handle all the cases.
This answer would definitely help you.
Hope it would answer your question

Ruby: Strip significant digits from number?

Suppose I have the number 1.29999. I want to just have 1.2 without the trailing 9s. Note, I don't want it to round to 1.3. How do I do this? I know there's the number helper, but I can't seem to get this working outside of a view. Any ideas?
For instance, number_with_precision 111.2345, :precision => 2 does not work if I just put it in a normal model function.
Thanks!
Another approach is to multiply by 100, truncate, then divide by 100.0:
$ irb --simple-prompt
>> (1.29999999*100).truncate/100.0
=> 1.29
Making it a method:
>> def truncate_to_two (x)
>> (x * 100).truncate/100.0
>> end
=> nil
>> truncate_to_two 6342.899
=> 6342.89
>> truncate_to_two -322.11892
=> -322.11
>> truncate_to_two 244.9342
=> 244.93
It's rudimentary, but you can do use string manipulation instead of math to do it. Example:
x = 1.29999
truncated = x.to_s.match(/(\d+\.\d{2})/)[0] # assumes the format "n.nn" with 2 or more digits of precision; the regex can be expanded to handle more cases
You can always include ActionView::Helpers::NumberHelper in your model to get access to the helpers.

Formatting a float to a minimum number of decimal places

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$/, '')

Resources