fraction 0.2 → 0.3
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 +20 -16
- data/fraction.c +57 -42
- metadata +2 -2
data/README.md
CHANGED
@@ -1,14 +1,14 @@
|
|
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
|
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
5
|
was slow, buggy, and worst of all, too precise (returning 33/100 for the above example.)
|
6
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
|
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
12
|
|
13
13
|
So became `fraction`: it's Eppstein's code in a Ruby gem.
|
14
14
|
|
@@ -19,24 +19,28 @@ Install it with `gem`:
|
|
19
19
|
Using this gem is easy:
|
20
20
|
|
21
21
|
require 'fraction'
|
22
|
-
num, den = 0.33.
|
22
|
+
num, den = 0.33.to_fraction # num==1, den==3
|
23
|
+
num, den = 0.33.fraction # legacy name also works
|
23
24
|
|
24
|
-
You can
|
25
|
+
You can get the error:
|
25
26
|
|
26
|
-
num,den,err = 0.33.
|
27
|
+
num,den,err = 0.33.to_fraction #=> [1, 3, -0.0033333333333333]
|
27
28
|
|
28
29
|
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
30
|
|
31
|
+
num, den = 0.51.to_fraction(100) #=> [51, 100, 0.0]
|
32
|
+
|
33
|
+
There is also whole fraction support, which factors out any whole numbers:
|
34
|
+
|
35
|
+
whole, num, den, error = 3.5.to_whole_fraction #=> [3, 1, 2, 0]
|
32
36
|
|
33
37
|
The best part of this gem over others is the speed:
|
34
38
|
|
35
|
-
% ruby test.rb
|
39
|
+
% ruby test.rb
|
36
40
|
I'm Feeling Lucky: 19.145s
|
37
41
|
'fraction' gem: 2.090s
|
38
42
|
|
39
|
-
subtracting the time required for an empty ruby loop, we can conclude the
|
43
|
+
subtracting the time required for an empty ruby loop, we can conclude the
|
40
44
|
algorithm itself requires only ½ of a second for 1,000,000 iterations on my Mac Pro.
|
41
45
|
|
42
46
|
|
data/fraction.c
CHANGED
@@ -57,25 +57,35 @@ VALUE method_string_form_for(VALUE self)
|
|
57
57
|
return res;
|
58
58
|
}
|
59
59
|
|
60
|
-
|
61
|
-
VALUE method_fraction_for(int argc, VALUE * argv, VALUE self)
|
60
|
+
void core_fraction(double val, long maxden, long * n, long * d, double * e)
|
62
61
|
{
|
63
|
-
long m11, m12,
|
64
|
-
m21, m22;
|
65
|
-
VALUE res = rb_ary_new2(3);
|
66
|
-
VALUE maxdenr;
|
67
|
-
int maxden = 10; // the default
|
68
|
-
rb_scan_args(argc, argv, "01", &maxdenr);
|
69
|
-
if (!NIL_P(maxdenr))
|
70
|
-
maxden = NUM2INT(maxdenr);
|
71
62
|
int ai;
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
63
|
+
long sign = 1;
|
64
|
+
long m11 = 1, m22 = 1;
|
65
|
+
long m12 = 0, m21 = 0;
|
66
|
+
if (val == NAN) {
|
67
|
+
*n = NAN;
|
68
|
+
*d = NAN;
|
69
|
+
*e = NAN;
|
70
|
+
return;
|
71
|
+
}
|
72
|
+
if (val == INFINITY) {
|
73
|
+
*n = INFINITY;
|
74
|
+
*d = 1.0;
|
75
|
+
*e = 0.0;
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
if (val < 0.0) {
|
79
|
+
// work in positive space, it seems we can get confused by negatives
|
80
|
+
sign = -1;
|
81
|
+
val *= -1.0;
|
82
|
+
}
|
83
|
+
double x = val;
|
84
|
+
long count = 0;
|
76
85
|
|
77
86
|
// loop finding terms until denom gets too big
|
78
87
|
while (m21 * ( ai = (long)x ) + m22 <= maxden) {
|
88
|
+
if (++count > 50000000) break; // break after 'too many' iterations
|
79
89
|
long t = m11 * ai + m12;
|
80
90
|
m12 = m11;
|
81
91
|
m11 = t;
|
@@ -86,15 +96,34 @@ VALUE method_fraction_for(int argc, VALUE * argv, VALUE self)
|
|
86
96
|
x = 1/(x - (double) ai);
|
87
97
|
if(x>(double)0x7FFFFFFF) break;
|
88
98
|
}
|
89
|
-
|
90
|
-
|
91
|
-
|
99
|
+
*n = m11 * sign;
|
100
|
+
*d = m21;
|
101
|
+
*e = val - ((double)m11 / (double)m21);
|
102
|
+
}
|
103
|
+
|
104
|
+
|
105
|
+
VALUE method_fraction_for(int argc, VALUE * argv, VALUE self)
|
106
|
+
{
|
107
|
+
VALUE res = rb_ary_new2(3);
|
108
|
+
VALUE maxdenr;
|
109
|
+
long maxden = 10; // the default
|
110
|
+
rb_scan_args(argc, argv, "01", &maxdenr);
|
111
|
+
if (!NIL_P(maxdenr))
|
112
|
+
maxden = NUM2INT(maxdenr);
|
113
|
+
double x = NUM2DBL(self);
|
114
|
+
long n, d;
|
115
|
+
double e;
|
116
|
+
core_fraction(x, maxden, &n, &d, &e);
|
117
|
+
|
118
|
+
VALUE numer1 = INT2NUM(n);
|
119
|
+
VALUE denom1 = INT2NUM(d);
|
120
|
+
VALUE err1 = rb_float_new(e);
|
92
121
|
rb_ary_store(res, 0, numer1);
|
93
122
|
rb_ary_store(res, 1, denom1);
|
94
123
|
rb_ary_store(res, 2, err1);
|
95
|
-
// Although the below is very cool, it's also quite slow to execute.
|
96
|
-
|
97
|
-
|
124
|
+
// Although the below is very cool, it's also quite slow to execute.
|
125
|
+
//rb_define_singleton_method(res, "to_s", method_string_form_for, 0);
|
126
|
+
//rb_define_singleton_method(res, "to_html", method_html_form_for, 0);
|
98
127
|
|
99
128
|
/* We can go one more step to find another candidate:
|
100
129
|
m11 = m11 * ai + m12;
|
@@ -113,32 +142,18 @@ VALUE method_whole_fraction_for(int argc, VALUE * argv, VALUE self)
|
|
113
142
|
m21, m22;
|
114
143
|
VALUE res = rb_ary_new2(4);
|
115
144
|
VALUE maxdenr;
|
116
|
-
|
145
|
+
long maxden = 10; // the default
|
117
146
|
rb_scan_args(argc, argv, "01", &maxdenr);
|
118
147
|
if (!NIL_P(maxdenr))
|
119
148
|
maxden = NUM2INT(maxdenr);
|
120
|
-
int ai;
|
121
149
|
double x = NUM2DBL(self);
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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));
|
150
|
+
long n, d;
|
151
|
+
double e;
|
152
|
+
core_fraction(x, maxden, &n, &d, &e);
|
153
|
+
VALUE wholen = INT2NUM(n / d);
|
154
|
+
VALUE numer1 = INT2NUM(n % d);
|
155
|
+
VALUE denom1 = INT2NUM(d);
|
156
|
+
VALUE err1 = rb_float_new(e);
|
142
157
|
|
143
158
|
rb_ary_store(res, 0, wholen);
|
144
159
|
rb_ary_store(res, 1, numer1);
|