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.