I'm tasked with making an interest calculator with class arguments, but I'm having a hard time applying variables/arguments. I keep getting an argument error (4 for 0). Moreover, how can I properly refer to the amount argument result in my `statement' argument? Any suggestions? Can anyone offer insight to help me understand scoping better within this context?
class InterestCalculator
attr_accessor :principal, :rate, :years, :times_compounded
def intitialize(principal, rate, years, times_compounded)
#principal, #rate, #years, #times_compounded = principal, rate, years, times_compounded
end
def amount
amount = #principal * (1 + #rate / #times_compounded) ** (#times_compounded * #years)
end
def statement
"After #{#years} I'll have #{amount} dollars!"
end
end
These are the specs:
describe InterestCalculator do
before { #calc = InterestCalculator.new(500, 0.05, 4, 5) }
describe "#amount" do
it "calculates correctly" do
expect( #calc.amount ).to eq(610.1)
end
end
describe "#statement" do
it "calls amount" do
#calc.stub(:amount).and_return(100)
expect( #calc.statement ).to eq("After 4 years I'll have 100 dollars!")
end
end
end
You've typoed your initialize method ("intitialize") so ruby thinks you are still using the default initialize method which takes no arguments, hence the error.
Frederick Cheung is correct however they are also looking for the number to be rounded "eq(610.1)"
def amount
amount = #principal * (1 + #rate / #times_compounded) ** (#times_compounded * #years)
end
Should be...
def amount
amount = #principal * (1 + #rate / #times_compounded) ** (#times_compounded * #years)
amount.round(2)
end
Related
Why does the following method return infinity when trying to find the average volume of a stock:
class Statistics
def self.averageVolume(stocks)
values = Array.new
stocks.each do |stock|
values.push(stock.volume)
end
values.reduce(:+).to_f / values.size
end
end
class Stock
attr_reader :date, :open, :high, :low, :close, :adjusted_close, :volume
def initialize(date, open, high, low, close, adjusted_close, volume)
#date = date
#open = open
#high = high
#low = low
#close = close
#adjusted_close = adjusted_close
#volume = volume
end
def close
#close
end
def volume
#volume
end
end
CSV.foreach(fileName) do |stock|
entry = Stock.new(stock[0], stock[1], stock[2], stock[3], stock[4], stock[5], stock[6])
stocks.push(entry)
end
Here is how the method is called:
Statistics.averageVolume(stocks)
Output to console using a file that has 251 rows:
stock.rb:32: warning: Float 23624900242507002003... out of range
Infinity
Warning is called on the following line: values.reduce(:+).to_f / values.size
When writing average functions you'll want to pay close attention to the possibility of division by zero.
Here's a fixed and more Ruby-like implementation:
def self.average_volume(stocks)
# No data in means no data out, can't calculate.
return if (stocks.empty?)
# Pick out the `volume` value from each stock, then combine
# those with + using 0.0 as a default. This forces all of
# the subsequent values to be floating-point.
stocks.map(&:volume).reduce(0.0, &:+) / values.size
end
In Ruby it's strongly recommended to keep variable and method names in the x_y form, like average_volume here. Capitals have significant meaning and indicate constants like class, module and constant names.
You can test this method using a mock Stock:
require 'ostruct'
stocks = 10.times.map do |n|
OpenStruct.new(volume: n)
end
average_volume(stocks)
# => 4.5
average_volume([ ])
# => nil
If you're still getting infinity it's probably because you have a broken value somewhere in there for volume which is messing things up. You can try and filter those out:
stocks.map(&:value).reject(&:nan?)...
Where testing vs. nan? might be what you need to strip out junk data.
I have a piece of code that uses class variables. I've read that class variables should generally be avoided in Ruby.
The class variables are ##cost and ##kwh.
How can I rewrite the following without using class variables?
class Device
attr_accessor :name, :watt
##cost = 0.0946
def initialize(name, watt)
#name = name
#watt = watt
end
def watt_to_kwh(hours)
##kwh = (watt / 1000) * hours
end
def cost_of_energy
puts "How many hours do you use the #{self.name} daily?"
hours = gets.chomp.to_i
self.watt_to_kwh(hours)
daily_cost = ##kwh * ##cost
montly_cost = daily_cost * 30
puts "Dayly cost: #{daily_cost}€"
puts "montly_cost: #{montly_cost}€"
end
end
##cost behaves more like a constant (i.e. it won't change during runtime), so you should use one instead:
COST = 0.0946
##kwh should be an instance variable, since it is used only within the instantiated object, so you could use #kwh instead:
#kwh = (watt / 1000) * hours
And daily_cost = ##kwh * ##cost will become:
daily_cost = #kwh * COST
That will avoid the use of class variables, but you could also eliminate #kwh altogether since you don't use it anywhere else.
So, instead of:
def watt_to_kwh(hours)
#kwh = (watt / 1000) * hours
end
You could just do:
def watt_to_kwh(hours)
(watt / 1000) * hours
end
And use it like this in cost_of_energy method:
def cost_of_energy
puts "How many hours do you use the #{self.name} daily?"
hours = gets.chomp.to_i
daily_cost = watt_to_kwh(hours) * COST
montly_cost = daily_cost * 30
puts "Dayly cost: #{daily_cost}€"
puts "montly_cost: #{montly_cost}€"
end
Try this.
class Device
singleton_class.send(:attr_accessor, :cost_per_kwh)
def initialize(name, watts)
#name = name
#watts = watts
end
def daily_cost(hours_per_day)
self.class.cost_per_kwh * kwh_per_day(hours_per_day)
end
def monthly_cost(hours_per_day)
30 * daily_cost(hours_per_day)
end
private
def kwh_per_day(hours_per_day)
hours_per_day * #watts / 1000
end
end
singleton_class.send(:attr_accessor, :cost_per_kwh) creates a setter and getter for the class instance variable #cost_per_kwh.
First, obtain and save the cost per kwh, which will be used in the calculation of cost for all devices of interest.
puts "Please enter the cost per kwh in $"
Device.cost_per_kwh = gets.chomp.to_f
Suppose
Device.cost_per_kwh = 0.0946
Calculate the costs for each device of interest.
puts "What is the name of the device?"
name = gets.chomp
puts "How many watts does it draw?"
watts = gets.chomp.to_f
Suppose
name = "chair"
watts = 20000.0
We may now create a class instance.
device = Device.new(name, watts)
#=> #<Device:0x007f9d530206f0 #name="chair", #watts=20000.0>
Lastly, obtain hours per days, the only variable likely to change in future calculations of costs for the given device.
puts "How many hours do you use the #{name} daily?"
hours_per_day = gets.chomp.to_f
Lastly, suppose
hours_per_day = 0.018
then we may compute the costs.
puts "Daily cost: $#{ device.daily_cost(hours_per_day)}"
Daily cost: $0.034056€
puts "Monthly_cost (30 days/month): $#{ 30 * device.daily_cost(hours_per_day) }"
Monthly_cost (30 days/month): $1.0216800000000001
Suppose circumstances change1 and use of the device increases. We need only update hours per day. For example,
puts "How many hours do you use the #{name} daily?"
hours_per_day = gets.chomp.to_f
Suppose now
hours_per_day = 1.5
Then
puts "Daily cost: $#{ device.daily_cost(hours_per_day)}"
Daily cost: $2.838
puts "Monthly_cost (30 days/month): $#{ 30 * device.daily_cost(hours_per_day) }"
Monthly_cost (30 days/month): $85.14
1 The election of a new president, for example.
I'm testing a small and simple library I made in Ruby. The goal is to convert from EUR to CNY and vice versa. Simple.
I tested it to be sure everything works but I got an unexpected issue. When I use to_euro followed by to_yuan it should go back to the original amount ; it doesn't happen. I tried to .to_f or round(2) the amount variable which fix some tests, raise new ones, but it's never equal to what I expect globally ; I'm running out of idea to fix this :(
class Currency
attr_reader :amount, :currency
def initialize(amount, currency='EUR')
#amount = amount
#currency = currency
end
def to_yuan
update_currency!('CNY', amount * Settings.instance.exchange_rate_to_yuan)
end
def to_euro
update_currency!('EUR', amount / Settings.instance.exchange_rate_to_yuan)
end
def display
"%.2f #{current_symbol}" % amount
end
private
def current_symbol
if currency == 'EUR'
symbol = Settings.instance.supplier_currency.symbol
elsif currency == 'CNY'
symbol = Settings.instance.platform_currency.symbol
end
end
def update_currency!(new_currency, new_amount)
unless new_currency == currency
#currency = new_currency
#amount = new_amount
end
self
end
end
Tests
describe Currency do
let(:rate) { Settings.instance.exchange_rate_to_yuan.to_f }
context "#to_yuan" do
it "should return Currency object" do
expect(Currency.new(20).to_yuan).to be_a(Currency)
end
it "should convert to yuan" do
expect(Currency.new(20).to_yuan.amount).to eql(20.00 * rate)
end
it "should convert to euro and back to yuan" do
# state data test
currency = Currency.new(150, 'CNY')
expect(currency.to_euro).to be_a(Currency)
expect(currency.to_yuan).to be_a(Currency)
expect(currency.amount).to eql(150.00)
end
end
context "#to_euro" do
it "should convert to euro" do
expect(Currency.new(150, 'CNY').to_euro.amount).to eql(150 / rate)
end
end
context "#display" do
it "should display euros" do
expect(Currency.new(10, 'EUR').display).to eql("10.00 €")
end
it "should display yuan" do
expect(Currency.new(60.50, 'CNY').display).to eql("60.50 ¥")
end
end
end
And here's my RSpec result
I'm pretty sure this problem is very common, any idea how to solve it easily ?
Float isn't an exact number representation, as stated in the ruby docs:
Float objects represent inexact real numbers using the native architecture's double-precision floating point representation.
This not ruby fault, as floats can only be represented by a fixed number of bytes and therefor cannot store decimal numbers correctly.
Alternatively, you can use ruby Rational or BigDecimal
Its is also fairly common to use the money gem when dealing with currency and money conversion.
I have two small numbers that I'd like to find the percentage of.
First number: 0.683789473684211
Second number: 0.678958333333333
I want to find out what percentage of the number is bigger or smaller. These happen to be small numbers, but they could be bigger. The first number COULD be 250, and the second number could be 0.3443435. What I'm TRYING to do is detect whether the first number is 25% bigger than the second number.
I tried using this:
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
But it kept saying I was dividing by zero
How would you do it?
Why not shoot straight for what you say you want to do?
class Numeric
def sufficiently_bigger?(n, proportion = 1.25)
self >= proportion * n
end
end
p 5.sufficiently_bigger? 4 # => true
p 5.sufficiently_bigger? 4.00001 # => false
This will default to a 25% larger check, but you can override the proportionality by supplying a different value as the second argument.
It's generally easier and avoids the need for an explicit zero-denominator check if you express ratios in product form rather than using division.
The basic implementation of your code looks correct to me. Can you provide the specific example and expected output that is producing that error?
Just because I was curious I took your code and executed it with a small test suite and had 3 passing tests.
require 'rubygems'
require 'test/unit'
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.00
end
end
class PercentageTeset < Test::Unit::TestCase
def test_25_is_50_percent_of_50
assert_equal (25.percent_of(50)), 50.0
end
def test_50_is_100_percent_of_50
assert_equal (50.percent_of(50)), 100.0
end
def test_75_is_150_percent_of_50
assert_equal (75.percent_of(50)), 150.0
end
end
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
p 0.683789473684211.percent_of(0.678958333333333)
--output:--
100.71155181602376
p 250.percent_of(0.3443435)
--output:--
72601.9222084924
p 0.000_001.percent_of(0.000_000_5)
--output:--
200.0
p 0.000_000_000_01.percent_of(0.000_000_000_01)
--output:--
100.0
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
numbers = [ 0.683789473684211, 0.678958333333333 ]
min_max = {min: numbers.min, max: numbers.max}
puts "%<min>f is #{min_max[:min].percent_of(min_max[:max])} of %<max>f" % min_max
This program has opinions in that it shows what percentage the minimal number is of the maximal number, and shows the numbers.
If you use %d for the String#format method, you will show 0's. Perhaps that was what you were referring to, not sure.
Edit: Using minmax as suggested.
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
numbers = [ 0.683789473684211, 0.678958333333333 ]
min_max = Hash.new
min_max[:min], min_max[:max] = numbers.minmax
puts "%<min>f is #{min_max[:min].percent_of(min_max[:max])} of %<max>f" % min_max
I like the first version as the hash is built as it is needed, rather than initalized and then built.
I can't for the life of me figure out why my: generate_receipt method is returning 54.9 for my "imported dinner plates item" when it should equal 54.65. I wrote RSpec tests to confirm that the array is indeed returning the correct values.
47.50 + 4.75 + 2.40 = 54.65
Why is it returning 54.9 instead of 54.65?! Where is this rounding up occurring? How do I get it to return the correct value? I'm stumped.
describe :calcualte_sales_tax do
it "should return the correct array" do
calculate_sales_tax(#receipt).should eq([0, 4.75])
end
end
describe :calculate_import_tax do
it "should return the correct array" do
calculate_import_tax(#receipt).should eq([0.50, 2.40])
end
end
#receipt = {
"1"=>{:item=>"imported chocolates", :price=>10.00, :quantity=>1},
"2"=>{:item=>"imported dinner plates", :price=>47.50, :quantity=>1}
}
def generate_receipt(receipt)
n = 0
while n < receipt.length
receipt["#{n+1}"][:price]+=calculate_sales_tax(receipt)[n]
receipt["#{n+1}"][:price]+=calculate_import_tax(receipt)[n]
n+=1
end
receipt
end
def calculate_import_tax(receipt)
taxes = []
receipt.each do |k,v|
if (v[:item] =~ /imported/)
subtotal = v[:price]
# v[:price]+=(((((5 * subtotal)/100)*20.ceil) / 20.0))
# taxes<<(5 * subtotal)/100
taxes<<((5 * subtotal/100)*20).ceil/20.0.round(2)
else
taxes<<0
end
end
taxes
end
def calculate_sales_tax(receipt)
tax_free_items = ["book", "chocolate bar", "chocolates", "pills"]
taxes = []
receipt.each do |k,v|
if (v[:item] =~ /chocolate\s/) ||
(v[:item] =~ /chocolates/) ||
(v[:item] =~ /book/) ||
(v[:item] =~ /pills/)
taxes<<0
else
subtotal = v[:price]
# v[:price]+=(((((10 * subtotal)/100)*20.ceil) / 20.0))
# taxes<<(10 * subtotal)/100
taxes<<((10 * subtotal/100)*20).ceil/20.0
end
end
taxes
end
def generate_receipt(receipt)
n = 0
while n < receipt.length
puts receipt["#{n+1}"][:price].inspect
receipt["#{n+1}"][:price]+=calculate_sales_tax(receipt)[n].round(2)
puts receipt["#{n+1}"][:price].inspect
puts calculate_import_tax(receipt)[n]
receipt["#{n+1}"][:price]+=calculate_import_tax(receipt)[n].round(2)
puts receipt["#{n+1}"][:price].inspect
puts "-----"
n+=1
end
receipt
end
Returns:
47.5
52.25
2.65
54.9
The bug is in your calculate_import_tax method. It's returning 2.65, not 2.40.
EDIT:
Got it :).
receipt["#{n+1}"][:price]+=calculate_sales_tax(receipt)[n]
receipt["#{n+1}"][:price]+=calculate_import_tax(receipt)[n]
Those rows are updating the price of the receipt. Hence, your tests are running independently, but the sales tax is modifying the raw price before the import tax is calculated...
1) Ruby treat numbers without decimal part as INTEGERS. And WILL NOT create result that have decimal part unless computation include floating point number.
2) 10 * X / 100 * 20 == X * 2
3) LEGAL COUNTING may require specified accuracy, and so you may need to use specialized library for such number crunching.
Fast solution would be to change that code to:
10.0 * subtotal / 100.0 * 20.0
Now Ruby will treat all those numbers as float's.