fraction 0.1 → 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +45 -0
- data/fraction.c +68 -6
- metadata +18 -8
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
For a [cooking website][3] I recently worked on,
|
2
|
+
I needed to display decimal floating point numbers as fractions. I needed to
|
3
|
+
come up with the fraction closest to what the user typed. For example, 0.33
|
4
|
+
should resolve to ⅓. When I googled for a solution, most of the code I found
|
5
|
+
was slow, buggy, and worst of all, too precise (returning 33/100 for the above example.)
|
6
|
+
|
7
|
+
I decided to widen my search to C, and [found][1] a piece of code on Stack Overflow
|
8
|
+
written by David Eppstein in 1993.
|
9
|
+
It uses the [theory of continued fractions][2] to approach the correct value,
|
10
|
+
but stops when the denominator reaches some value. The limitation of such an
|
11
|
+
algorithm is that we can't choose to leave out unnatural denominators
|
12
|
+
|
13
|
+
So became `fraction`: it's Eppstein's code in a Ruby gem.
|
14
|
+
|
15
|
+
Install it with `gem`:
|
16
|
+
|
17
|
+
gem install fraction
|
18
|
+
|
19
|
+
Using this gem is easy:
|
20
|
+
|
21
|
+
require 'fraction'
|
22
|
+
num, den = 0.33.fraction # num==1, den==3
|
23
|
+
|
24
|
+
You can also get the error
|
25
|
+
|
26
|
+
num,den,err = 0.33.fraction #=> [1, 3, -0.0033333333333333]
|
27
|
+
|
28
|
+
you can choose a different maximum denominator than the default value of 10:
|
29
|
+
|
30
|
+
num, den = 0.51.fraction(100) #[51, 100, 0.0]
|
31
|
+
|
32
|
+
|
33
|
+
The best part of this gem over others is the speed:
|
34
|
+
|
35
|
+
% ruby test.rb
|
36
|
+
I'm Feeling Lucky: 19.145s
|
37
|
+
'fraction' gem: 2.090s
|
38
|
+
|
39
|
+
subtracting the time required for an empty ruby loop, we can conclude the
|
40
|
+
algorithm itself requires only ½ of a second for 1,000,000 iterations on my Mac Pro.
|
41
|
+
|
42
|
+
|
43
|
+
[1]: http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions
|
44
|
+
[2]: http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/cfINTRO.html#termdecs
|
45
|
+
[3]: http://freshslowcooking.com
|
data/fraction.c
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
**
|
7
7
|
** Made into a ruby module by Christopher Lord, Nov 2009
|
8
8
|
**
|
9
|
-
** usage: require '
|
9
|
+
** usage: require 'fraction'
|
10
10
|
**
|
11
11
|
** n,d,err = 0.33.fraction
|
12
12
|
**
|
@@ -24,10 +24,12 @@
|
|
24
24
|
|
25
25
|
#include "ruby.h"
|
26
26
|
#include <stdio.h>
|
27
|
+
|
28
|
+
|
27
29
|
VALUE method_html_form_for(VALUE self)
|
28
30
|
{
|
29
31
|
VALUE res = rb_str_new2("<span class='fraction'><span class='above'>");
|
30
|
-
// This is an array. to make a string form, we want to
|
32
|
+
// This is an array. to make a string form, we want to
|
31
33
|
VALUE num = rb_obj_as_string(rb_ary_entry(self, 0));
|
32
34
|
VALUE den = rb_obj_as_string(rb_ary_entry(self, 1));
|
33
35
|
rb_str_concat(res, rb_obj_as_string(num));
|
@@ -39,19 +41,23 @@ VALUE method_html_form_for(VALUE self)
|
|
39
41
|
// font-family: Verdana, Arial, sans-serif; }
|
40
42
|
// .above { vertical-align: 0.7ex; }
|
41
43
|
// .below { vertical-align: -0.3ex; }
|
42
|
-
return res;
|
44
|
+
return res;
|
43
45
|
}
|
46
|
+
|
47
|
+
|
44
48
|
VALUE method_string_form_for(VALUE self)
|
45
49
|
{
|
46
50
|
VALUE res = rb_str_new2("");
|
47
|
-
// This is an array. to make a string form, we want to
|
51
|
+
// This is an array. to make a string form, we want to
|
48
52
|
VALUE num = rb_obj_as_string(rb_ary_entry(self, 0));
|
49
53
|
VALUE den = rb_obj_as_string(rb_ary_entry(self, 1));
|
50
54
|
rb_str_concat(res, rb_obj_as_string(num));
|
51
55
|
rb_str_cat2(res, "/");
|
52
56
|
rb_str_concat(res, rb_obj_as_string(den));
|
53
|
-
return res;
|
57
|
+
return res;
|
54
58
|
}
|
59
|
+
|
60
|
+
|
55
61
|
VALUE method_fraction_for(int argc, VALUE * argv, VALUE self)
|
56
62
|
{
|
57
63
|
long m11, m12,
|
@@ -86,10 +92,10 @@ VALUE method_fraction_for(int argc, VALUE * argv, VALUE self)
|
|
86
92
|
rb_ary_store(res, 0, numer1);
|
87
93
|
rb_ary_store(res, 1, denom1);
|
88
94
|
rb_ary_store(res, 2, err1);
|
95
|
+
// Although the below is very cool, it's also quite slow to execute.
|
89
96
|
// rb_define_singleton_method(res, "to_s", method_string_form_for, 0);
|
90
97
|
// rb_define_singleton_method(res, "to_html", method_html_form_for, 0);
|
91
98
|
|
92
|
-
|
93
99
|
/* We can go one more step to find another candidate:
|
94
100
|
m11 = m11 * ai + m12;
|
95
101
|
m21 = m21 * ai + m22;
|
@@ -98,8 +104,64 @@ VALUE method_fraction_for(int argc, VALUE * argv, VALUE self)
|
|
98
104
|
return res;
|
99
105
|
}
|
100
106
|
|
107
|
+
|
108
|
+
/// Same as above, but implements whole fraction simplification.
|
109
|
+
/// the return value is: h, n, d, e = 3.5.whole_fraction
|
110
|
+
VALUE method_whole_fraction_for(int argc, VALUE * argv, VALUE self)
|
111
|
+
{
|
112
|
+
long m11, m12,
|
113
|
+
m21, m22;
|
114
|
+
VALUE res = rb_ary_new2(4);
|
115
|
+
VALUE maxdenr;
|
116
|
+
int maxden = 10; // the default
|
117
|
+
rb_scan_args(argc, argv, "01", &maxdenr);
|
118
|
+
if (!NIL_P(maxdenr))
|
119
|
+
maxden = NUM2INT(maxdenr);
|
120
|
+
int ai;
|
121
|
+
double x = NUM2DBL(self);
|
122
|
+
double startx = x;
|
123
|
+
m11 = m22 = 1;
|
124
|
+
m12 = m21 = 0;
|
125
|
+
|
126
|
+
// loop finding terms until denom gets too big
|
127
|
+
while (m21 * ( ai = (long)x ) + m22 <= maxden) {
|
128
|
+
long t = m11 * ai + m12;
|
129
|
+
m12 = m11;
|
130
|
+
m11 = t;
|
131
|
+
t = m21 * ai + m22;
|
132
|
+
m22 = m21;
|
133
|
+
m21 = t;
|
134
|
+
if(x==(double)ai) break;
|
135
|
+
x = 1/(x - (double) ai);
|
136
|
+
if(x>(double)0x7FFFFFFF) break;
|
137
|
+
}
|
138
|
+
VALUE wholen = INT2NUM(m11 / m21);
|
139
|
+
VALUE numer1 = INT2NUM(m11 % m21);
|
140
|
+
VALUE denom1 = INT2NUM(m21);
|
141
|
+
VALUE err1 = rb_float_new(startx - ((double) m11 / (double) m21));
|
142
|
+
|
143
|
+
rb_ary_store(res, 0, wholen);
|
144
|
+
rb_ary_store(res, 1, numer1);
|
145
|
+
rb_ary_store(res, 2, denom1);
|
146
|
+
rb_ary_store(res, 3, err1);
|
147
|
+
return res;
|
148
|
+
}
|
149
|
+
|
150
|
+
|
101
151
|
void Init_fraction() {
|
152
|
+
|
153
|
+
rb_define_method(rb_cNumeric, "to_whole_fraction", method_whole_fraction_for, -1);
|
154
|
+
rb_define_method(rb_cFloat, "to_whole_fraction", method_whole_fraction_for, -1);
|
155
|
+
|
156
|
+
rb_define_method(rb_cNumeric, "to_fraction", method_fraction_for, -1);
|
157
|
+
rb_define_method(rb_cFloat, "to_fraction", method_fraction_for, -1);
|
158
|
+
|
159
|
+
// The following are legacy support methods. they are named a bit badly
|
102
160
|
rb_define_method(rb_cNumeric, "fraction", method_fraction_for, -1);
|
103
161
|
rb_define_method(rb_cFloat, "fraction", method_fraction_for, -1);
|
162
|
+
|
163
|
+
rb_define_method(rb_cNumeric, "whole_fraction", method_whole_fraction_for, -1);
|
164
|
+
rb_define_method(rb_cFloat, "whole_fraction", method_whole_fraction_for, -1);
|
165
|
+
|
104
166
|
}
|
105
167
|
|
metadata
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fraction
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
version: "0.2"
|
5
9
|
platform: ruby
|
6
10
|
authors:
|
7
11
|
- Christopher Lord
|
@@ -11,11 +15,11 @@ autorequire:
|
|
11
15
|
bindir: bin
|
12
16
|
cert_chain: []
|
13
17
|
|
14
|
-
date:
|
18
|
+
date: 2011-05-23 00:00:00 -04:00
|
15
19
|
default_executable:
|
16
20
|
dependencies: []
|
17
21
|
|
18
|
-
description: Provides
|
22
|
+
description: Provides "to_fraction" and to_whole_fraction methods on all ruby floats and numerics.
|
19
23
|
email: christopherlord+fractiongem@gmail.com
|
20
24
|
executables: []
|
21
25
|
|
@@ -25,8 +29,10 @@ extra_rdoc_files: []
|
|
25
29
|
|
26
30
|
files:
|
27
31
|
- fraction.c
|
32
|
+
- README.md
|
33
|
+
- extconf.rb
|
28
34
|
has_rdoc: true
|
29
|
-
homepage:
|
35
|
+
homepage: https://github.com/clord/fraction
|
30
36
|
licenses: []
|
31
37
|
|
32
38
|
post_install_message:
|
@@ -35,23 +41,27 @@ rdoc_options: []
|
|
35
41
|
require_paths:
|
36
42
|
- lib
|
37
43
|
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
38
45
|
requirements:
|
39
46
|
- - ">="
|
40
47
|
- !ruby/object:Gem::Version
|
48
|
+
segments:
|
49
|
+
- 0
|
41
50
|
version: "0"
|
42
|
-
version:
|
43
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
44
53
|
requirements:
|
45
54
|
- - ">="
|
46
55
|
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
47
58
|
version: "0"
|
48
|
-
version:
|
49
59
|
requirements: []
|
50
60
|
|
51
61
|
rubyforge_project:
|
52
|
-
rubygems_version: 1.3.
|
62
|
+
rubygems_version: 1.3.7
|
53
63
|
signing_key:
|
54
64
|
specification_version: 3
|
55
|
-
summary: Provides a "
|
65
|
+
summary: Provides a "to_fraction" method on all ruby floats.
|
56
66
|
test_files: []
|
57
67
|
|