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.
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