Skip to content

Commit

Permalink
Avoid adding unintended precision to numbers.
Browse files Browse the repository at this point in the history
There are numbers who's decimal places, when converted, expand to the point of
giving the #path_to method issues. For example:

(7/6r).to_f => 1.1666666666666667

The #path_to method can handle 7/6r fine, but it doesn't handle its floating
point equivalent well, taking too long to compute.

Another example is 1.247265. Ruby's #to_r method converts this float to
2808591094616131/2251799813685248r, although 1247265/1000000r is simpler and
matches the float's precision accurately. The former takes a long time to
compute the #path_to, the latter takes considerably less time.

This commit adds the Bigdecimal method #to_d to covert all floating point
numbers to big decimals. Doing this conversion irons out the issues mentioned
above.
  • Loading branch information
jolohaga committed Jan 5, 2024
1 parent 458e76c commit 61a2e87
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 13 deletions.
3 changes: 2 additions & 1 deletion fraction_tree.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ require "date"

Gem::Specification.new do |spec|
spec.name = "fraction-tree"
spec.version = "1.0.2"
spec.version = "1.0.3"
spec.summary = "Fraction tree"
spec.description = "A collection of Stern-Brocot based models and methods"
spec.authors = ["Jose Hales-Garcia"]
Expand All @@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "rspec", ["~> 3.2"]
spec.add_development_dependency "byebug", ["~> 11.1"]
spec.add_development_dependency "yard", ["~> 0.9"]
spec.add_development_dependency "rspec-benchmark", ["~> 0.6"]
spec.license = "MIT"
spec.date = Date.today.to_s
end
20 changes: 12 additions & 8 deletions lib/fraction_tree.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "bigdecimal/util"
require "continued_fractions"

# @author Jose Hales-Garcia
Expand Down Expand Up @@ -66,6 +67,8 @@ def sequence(depth=5, segment: base_segment)
#
def path_to(number, find_parents: false, segment: base_segment)
return Node.new(number.numerator, number.denominator) if number.zero?
number = number.kind_of?(Float) ? number.to_d : number

q = Node.new(number.numerator, number.denominator)
l = segment.first
h = segment.last
Expand Down Expand Up @@ -220,13 +223,6 @@ def farey_neighbors?(num1, num2)
end
end

# @attr_reader numerator [Integer]
# The numerator of the node
# @attr_reader denominator [Integer]
# The denominator of the node
# @attr_reader weight [Rational|Infinity]
# The value of the node
#
class Node
include Comparable

Expand Down Expand Up @@ -256,8 +252,16 @@ def ==(rhs)
# Needed for intersection operations to work.
# https://blog.mnishiguchi.com/ruby-intersection-of-object-arrays
# https://shortrecipes.blogspot.com/2006/10/ruby-intersection-of-two-arrays-of.html
# Also, allows using with Set, which uses Hash as storage and equality of its elements is determined according to Object#eql? and Object#hash.
#
def eql?(rhs)
rhs.kind_of?(self.class) && weight == rhs.weight
rhs.instance_of?(self.class) && weight == rhs.weight
end

def hash
p, q = 17, 37
p = q * @id.hash
p = q * @name.hash
end

def +(rhs)
Expand Down
36 changes: 32 additions & 4 deletions spec/fraction_tree/fraction_tree_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,30 @@
end

describe ".path_to" do
let(:expected_nodes) { [described_class::Node.new(1,1), described_class::Node.new(2,1), described_class::Node.new(3,2), described_class::Node.new(4,3), described_class::Node.new(5,4), described_class::Node.new(6,5), described_class::Node.new(7,6), described_class::Node.new(8,7), described_class::Node.new(9,8), described_class::Node.new(10,9), described_class::Node.new(11,10)] }
let(:number) { 11/10r }
context "with Rational" do
let(:expected_nodes) { [described_class::Node.new(1,1), described_class::Node.new(2,1), described_class::Node.new(3,2), described_class::Node.new(4,3), described_class::Node.new(5,4), described_class::Node.new(6,5), described_class::Node.new(7,6), described_class::Node.new(8,7), described_class::Node.new(9,8), described_class::Node.new(10,9), described_class::Node.new(11,10)] }
let(:number) { 11/10r }

it "returns the list of nodes leading to n" do
expect(described_class.path_to(number)).to eq expected_nodes
it "returns the list of nodes leading to n" do
expect(described_class.path_to(number)).to eq expected_nodes
end
end

context "with Float" do
let(:number) { Math::PI }

it "returns a large list reasonably quickly" do
expect(described_class.path_to(number).count).to eq 412
expect{ described_class.path_to(number) }.to perform_under(0.3).ms
end

context "with a Float of finite length" do
let(:number) { 1.247265 }

it "returns a list of reasonable length" do
expect(described_class.path_to(number).count).to eq 60
end
end
end
end

Expand Down Expand Up @@ -101,6 +120,15 @@
it "returns the parents of the fraction tree node" do
expect(described_class.parents_of(number)).to eq expected_nodes
end

context "with Float of finite length" do
let(:expected_nodes) { [ described_class::Node.new(47542,38117), described_class::Node.new(201911,161883)] }
let(:number) { 1.247265 }

it "returns the parents of 1247265/1000000 and not of 2808591094616131/2251799813685248" do
expect(described_class.parents_of(number).inject(:+)).to eq expected_nodes.inject(:+)
end
end
end

describe ".common_ancestors_between" do
Expand Down
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "rubygems"
require "rspec"
require "byebug"
require "rspec-benchmark"
require "fraction_tree"

RSpec.configure do |config|
Expand All @@ -15,4 +16,5 @@
config.formatter = :documentation
config.filter_run focus: true
config.run_all_when_everything_filtered = true
config.include RSpec::Benchmark::Matchers
end

0 comments on commit 61a2e87

Please sign in to comment.