How do I control the decimals in my float calculations? - ruby-on-rails

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"

Related

How to format a price correctly regardless of decimal format

I want it to be able to read in JSON and save it correctly regardless whether the value is 44.5, 44 or 44.99. The price attributes are a decimal format.
The error is happening in the convert_price method. The price in the JSON response can be 44, 44.50 or 44.99. However, I noticed that sometimes the last decimal is cut off, like in the error 44.5.
I'm receiving this error:
undefined method 'match' for float 74.5:Float
My code is:
# read in JSON and create books
def create_item
job_items_url = "https://foobar.com&format=json"
response = open(job_items_url).read.to_s
books = JSON.parse(response)
Book.create(reg_price: convert_price(item['reg_price']),
sale_price: convert_price(item['sale_price']))
end
# format the price
def convert_price(price)
return nil if price.blank? || price.to_f.zero?
price = "#{price}.00" unless price.match(/[,.]\d{2}\z/)
price.delete(',.').to_f / 100
end
You can use number_to_currency without a unit:
>> number_to_currency(45,unit:"")
=> "45.00"
>> number_to_currency(45.5,unit:"")
=> "45.50"
>> number_to_currency(45.55,unit:"")
=> "45.55"
>>
See number_to_currency for more information.
It looks like price is already a Numeric object. Check out sprintf for simple Type-aware padding, for example:
sprintf('%.2f', 44.5) # => "44.50"
# so you should do something like this:
sprintf('%.2f', price.to_f)
A suggestion:
def try_format_currency(price)
sprintf('%.2f', Float(price))
rescue => ex
# log error if you want
nil
end
Use Float() conversion, which can raise, which will clearly express that price is "untrustworthy" input, and might not be a proper number
Express the same thing in the naming of the method

Ruby/Rails - Converting an integer into a float excluding existing zeros

There must be a simple way to achieve this, I have an DB field containing an integer and I want to reformat it into a float to display.
As an integer my value looks like 6500 and I want it to display as 65.00
Within my model I have attempted to achieve this by creating the following method
def get_payment_amount_as_number
amount = self.payment_amount
return '%.02f' % self.payment_amount.to_f
end
Which results in the following being displayed: 6500.00
What would the best approach be to either strip the initial zeroes or to simply insert a decimal point?
Whilst I imagine this a ruby related question, I am not sure if rails has a handy helper already in place?
Thank you.
You could divide the number by 100:
payment_amount = 6595
'%.02f' % payment_amount.fdiv(100)
#=> "65.95"
'%.02f' % (payment_amount / 100.0)
#=> "65.95"
Or you could convert the number to a string and insert a decimal point:
payment_amount.to_s.rjust(3, '0').insert(-3, '.')
#=> "65.95"
Rails also provides several helpers to format numbers:
number_to_currency(65.95)
#=> "$65.95"
number_to_currency(1000)
#=> "$1,000.00"
And you might want to take a look at the money-rails gem which provides a mapping from cents to money objects.
You do this simply ...
def get_payment_amount_as_number
amount = self.payment_amount / 100
#to convert amount to float
amount.to_f
end
I find another one
amount = self.payment_amount
# => 6500
ans = '%.2f' % (amount/100)
# => "65.00"
int_value = 6500
float_value = float_value = '%.2f' % (int_value / 100.0)
puts int_value: int_value, float_value: float_value
it's all!

Represent repeating decimals in Rails model

What's a good way to represent repeating decimals in the database?
Example 2.818181, the 81 repeats
Idea 1
Separate 2.818181 into non-repeating and repeating parts, then non_repeat = 2.0 and repeat = .007
class Decimal < ActiveRecord::Base
attr_accessible :non_repeat, :repeat #floats
def to_f
to_s.to_f
end
def to_s
"#{non_repeat + repeat}#{repeat.to_s.gsub(/0\./, '') * 3}" #approximation
end
def self.random_new
a = rand(100)
b = rand(100) / 100.0
self.new(non_repeat: a, repeat: b)
end
end
Idea 2
Use a fraction, which means turn 2.818181 into 31/11, save two integers 31 and 11
class Decimal < ActiveRecord::Base
attr_accessible :numerator, :denominator #integers
def to_f
numerator / denominator
end
def to_s
to_f.to_s
end
def self.random_new
a = rand(100)
b = random_prime(...) # like 7, 9, 11
self.new(numerator: a, denominator: b)
end
end
For the purpose of randomly generating repeating decimals, which idea is better? Or is there another way?
Your second approach won't always generate a repeating decimal number, just think what happens if a is a multiple of b.
The idea of using fractions tho is the best one. You need to slightly change your approach:
Randomly generate the integer part of your repeating number
Generate another random integer, rapresenting the repetition
Transform those 2 numbers into a fraction using the usual formula
rand = rand(100)
3.times { print rand.to_s }

Rails: How To Deal With Many Subsets of Data From One Model?

Rails 3.0.3 application (stuck on a Dreamhost shared server).
I have a page that displays averages calculated from subsets of data from one model.
Right now, each average is calculated individually, like this:
From the view, I'm using the current_user helper provided by Devise authentication to call the average methods that are located in the user model, like so:
<%= current_user.seven_day_weight_average %>
<%= current_user.fourteen_day_weight_average %>
<%= current_user.thirty_day_weight_average %>
Here's the public methods and the averaging method in the user model:
def seven_day_weight_average
calculate_average_weight(7)
end
def fourteen_day_weight_average
calculate_average_weight(14)
end
def thirty_day_weight_average
calculate_average_weight(30)
end
. . .
private
def calculate_average_weight(number_days)
temp_weight = 0
weights_array = self.weights.find_all_by_entry_date(number_days.days.ago..Date.today)
unless weights_array.count.zero?
weights_array.each do |weight|
temp_weight += weight.converted_weight
end
return (temp_weight/weights_array.count).round(1).to_s
else
return '0.0'
end
end
This doesn't seem very efficient - the database is queried for every average calculated.
How can I calculate and make these averages available to the page with one database query?
You could cache an array of converted weights for the last 30 days (presuming 30 is the maximum days back), something like this:
def calculate_average_weight(number_days)
#converted_weights ||= weights.where("entry_date > ?", 30.days.ago).group_by(&:entry_date).sort_by do |date,weights|
date
end.collect do |date,weights|
weights.collect(&:converted_weight)
end
weights_during_period = #converted_weights[0..number_days-1].flatten
weights_during_period.sum / weights_during_period.length
end
Explanation:
Firstly, ||= gets or sets #converted_weights (ie don't bother setting it unless it's nil or false). This ensures only one db hit. Next, we find all weights from 30 days ago and group by date. This returns an array of [date, weights], which we sort by date. Then we collect the converted weights for each date, so we end up with: [weights on day 1], [weights on day 2], ....
Now, the calculation: we store values spanning the number of days from the array in weights_during_period. We flatten the values and calculate the average value.

How to get a variable to have 2 decimals

I have a variable i would like to force to have 2 and always 2 decimals. Im comparing to a currency. Often i get a comparison looking like the following.
if self.price != price
#do something
end
Where self.price = 120.00 and price = 120.0. The self.price is set with a :precision => 2 in the model, but how do i do the same with a variable, cause this seems to fail in comparison
Use integers for storing currency, for example, use store 100 cents for 1 dollar. It reduces headaches and may improve performance if it matters.
class Numeric
def round_to( decimals=0 )
factor = 10.0**decimals
(self*factor).round / factor
end
end
if self.price.round_to(2) != price.round_to(2)
#do something
end

Resources