diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ce6a72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.gem +*.rbc +*.bundle +*.o +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..6699d3c --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in standard_deviation.gemspec +gemspec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..789277c --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 Rodrigo Navarro + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a4a66e --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Native Standard Deviation + +An implementation of the standard deviation calculation in C, with much better performance (50x-100x) than using pure ruby. + +## Installation + +Add this line to your application's Gemfile: + + gem 'standard_deviation' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install standard_deviation + +## Usage + +Just call `standard_deviation` or `stdev` on a array with numbers. + +``` ruby +[1, 2, 3, 4, 5].stdev => 1.5811388300841898 + +[521.0, BigDecimal("1234.45"), 1_120].standard_deviation => 383.168958598336 +``` + +## Benchmarks + +``` ruby +# Basic ruby implementation +class Array + def stdev_ruby + total = inject :+ + mean = total.to_f / size + variance = inject(0) { |variance, value| variance + (value - mean) ** 2 } + + Math.sqrt(variance / (size - 1)) + end +end + +# Running on a 2.4 GHz MacBook Pro +n = 10000 +values = (1..10_000).map(&:to_f) + +Benchmark.bm do |b| + b.report do + n.times { values.stdev } + end + + b.report do + n.times { values.stdev_ruby } + end +end + +# user system total real +# 1.070000 0.000000 1.070000 ( 1.065246) +# 88.180000 0.550000 88.730000 ( 93.835144) +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..62fb7c0 --- /dev/null +++ b/Rakefile @@ -0,0 +1,19 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" +require "rake/extensiontask" +require "rspec" +require "rspec/core/rake_task" + +Rake::ExtensionTask.new("standard_deviation") do |ext| + ext.lib_dir = File.join "lib", "standard_deviation" +end + + +RSpec::Core::RakeTask.new("spec") do |t| + t.verbose = true +end + +Rake::Task[:spec].prerequisites << :compile + +task :test => :spec +task :default => :test diff --git a/ext/standard_deviation/extconf.rb b/ext/standard_deviation/extconf.rb new file mode 100644 index 0000000..e2fb840 --- /dev/null +++ b/ext/standard_deviation/extconf.rb @@ -0,0 +1,2 @@ +require "mkmf" +create_makefile "standard_deviation/standard_deviation" diff --git a/ext/standard_deviation/standard_deviation.c b/ext/standard_deviation/standard_deviation.c new file mode 100644 index 0000000..752e700 --- /dev/null +++ b/ext/standard_deviation/standard_deviation.c @@ -0,0 +1,28 @@ +#include +#include + +static VALUE stdev(VALUE self) { + int i, size; + double total, mean, variance; + + size = RARRAY_LEN(self); + total = variance = 0; + VALUE *array = RARRAY_PTR(self); + + for (i = 0; i < size; i++) { + total += NUM2DBL(array[i]); + } + + mean = total / size; + + for (i = 0; i < size; i++) { + variance += pow((NUM2DBL(array[i]) - mean), 2); + } + + return rb_float_new(sqrt(variance / (size - 1))); +} + +void Init_standard_deviation() { + rb_define_method(rb_cArray, "stdev", stdev, 0); + rb_define_alias(rb_cArray, "standard_deviation", "stdev"); +} diff --git a/lib/standard_deviation.rb b/lib/standard_deviation.rb new file mode 100644 index 0000000..460487b --- /dev/null +++ b/lib/standard_deviation.rb @@ -0,0 +1,5 @@ +require "standard_deviation/version" +require "standard_deviation/standard_deviation" + +module StandardDeviation +end diff --git a/lib/standard_deviation/version.rb b/lib/standard_deviation/version.rb new file mode 100644 index 0000000..153f1f1 --- /dev/null +++ b/lib/standard_deviation/version.rb @@ -0,0 +1,3 @@ +module StandardDeviation + VERSION = "0.0.1" +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..77b2e46 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require "standard_deviation" +require "bigdecimal" diff --git a/spec/standard_deviation/standard_deviation_spec.rb b/spec/standard_deviation/standard_deviation_spec.rb new file mode 100644 index 0000000..f18341d --- /dev/null +++ b/spec/standard_deviation/standard_deviation_spec.rb @@ -0,0 +1,22 @@ +require "spec_helper" + +describe Array do + describe "#stdev" do + subject { values.stdev } + + context "with integer values" do + let(:values) { [1, 2, 6, 3, -4, 23] } + it { should == 9.32559202767667 } + end + + context "with float values" do + let(:values) { [1.0, 2.0, 6.0, 3.0, -4.0, 23.0] } + it { should == 9.32559202767667 } + end + + context "with bigdecimal values" do + let(:values) { [BigDecimal("1.0"), BigDecimal("2.0"), BigDecimal("6.0"), BigDecimal("3.0"), BigDecimal("-4.0"), BigDecimal("23.0")] } + it { should == 9.32559202767667 } + end + end +end diff --git a/standard_deviation.gemspec b/standard_deviation.gemspec new file mode 100644 index 0000000..6cb2392 --- /dev/null +++ b/standard_deviation.gemspec @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/standard_deviation/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["Rodrigo Navarro"] + gem.email = ["rnavarro1@gmail.com"] + gem.description = %q{An implementation of the standard deviation calculation in C, + with much better performance (50x-100x) than using pure ruby.} + gem.summary = %q{Native extension for Ruby to calculate standard deviation.} + gem.homepage = "https://github.com/reu/standard_deviation" + + gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + gem.files = `git ls-files`.split("\n") + gem.extensions = ["ext/standard_deviation/extconf.rb"] + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.name = "standard_deviation" + gem.require_paths = ["lib", "ext"] + gem.version = StandardDeviation::VERSION + + gem.add_development_dependency "rake-compiler" + gem.add_development_dependency "rspec" +end