fraction 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|