fraction 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +20 -16
  2. data/fraction.c +57 -42
  3. 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.fraction # num==1, den==3
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 also get the error
25
+ You can get the error:
25
26
 
26
- num,den,err = 0.33.fraction #=> [1, 3, -0.0033333333333333]
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
- double x = NUM2DBL(self);
73
- double startx = x;
74
- m11 = m22 = 1;
75
- m12 = m21 = 0;
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
- VALUE numer1 = INT2NUM(m11);
90
- VALUE denom1 = INT2NUM(m21);
91
- VALUE err1 = rb_float_new(startx - ((double) m11 / (double) m21));
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
- // rb_define_singleton_method(res, "to_s", method_string_form_for, 0);
97
- // rb_define_singleton_method(res, "to_html", method_html_form_for, 0);
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
- int maxden = 10; // the default
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
- 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));
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);
metadata CHANGED
@@ -4,8 +4,8 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- version: "0.2"
7
+ - 3
8
+ version: "0.3"
9
9
  platform: ruby
10
10
  authors:
11
11
  - Christopher Lord