Search code examples
rubyaveragereduceinfinity

Infinity is returned when calculating average in array


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


Solution

  • 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.