Skip to content
This repository was archived by the owner on May 11, 2021. It is now read-only.

Commit fc32705

Browse files
author
Jack Toole
committed
Fix toFraction
Summary: Evidentally 283.33 % 1 = .3299999999999841 Also, using our previous method, 1/1000 took 1000 steps to get to. This adds (a) a fix to that, (b) a worst case running time of 100 steps Anything beyond that is a useless fraction. It accounts for all fractions where n < d < 100 ``` for (var x = 1; x < 100; x++) { for (var y = x+1; y < 100; y++) { var z = KhanUtil.toFraction(x/y); if(x/y !== z[0]/z[1]) { console.log(x, y, z); } } } ``` The first non-exact fraction is for d >= 141 Test Plan: Try out: (1) 283.333333333333333 (2) 283.33 (3) .0001 Reviewers: brianmerlob Reviewed By: brianmerlob Subscribers: alpert Differential Revision: http://phabricator.khanacademy.org/D7519
1 parent bd8e92e commit fc32705

File tree

2 files changed

+95
-2
lines changed

2 files changed

+95
-2
lines changed

test/knumber.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,54 @@
3535
start();
3636
});
3737

38+
asyncTest('isInteger(-2.8) should be false', 1, function() {
39+
strictEqual(number.isInteger(-2.8), false);
40+
start();
41+
});
42+
43+
asyncTest('isInteger(-2) should be true', 1, function() {
44+
strictEqual(number.isInteger(-2), true);
45+
start();
46+
});
47+
48+
asyncTest('toFraction(-2) should be -2/1', 1, function() {
49+
deepEqual(number.toFraction(-2), [-2, 1]);
50+
start();
51+
});
52+
53+
asyncTest('toFraction(-2.5) should be -5/2', 1, function() {
54+
deepEqual(number.toFraction(-2.5), [-5, 2]);
55+
start();
56+
});
57+
58+
asyncTest('toFraction(2/3) should be 2/3', 1, function() {
59+
deepEqual(number.toFraction(2/3), [2, 3]);
60+
start();
61+
});
62+
63+
asyncTest('toFraction(283.33...) should be 850/3', 1, function() {
64+
deepEqual(number.toFraction(283 + 1/3), [850, 3]);
65+
start();
66+
});
67+
68+
asyncTest('toFraction(0) should be 0/1', 1, function() {
69+
deepEqual(number.toFraction(0), [0, 1]);
70+
start();
71+
});
72+
73+
asyncTest('toFraction(pi) should be pi/1', 1, function() {
74+
deepEqual(number.toFraction(Math.PI), [Math.PI, 1]);
75+
start();
76+
});
77+
78+
asyncTest('toFraction(0.66) should be 33/50', 1, function() {
79+
deepEqual(number.toFraction(0.66), [33, 50]);
80+
start();
81+
});
82+
83+
asyncTest('toFraction(0.66, 0.01) should be 2/3', 1, function() {
84+
deepEqual(number.toFraction(0.66, 0.01), [2, 3]);
85+
start();
86+
});
87+
3888
})();

utils/knumber.js

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
define(function(require) {
66

77
var DEFAULT_TOLERANCE = 1e-9;
8-
var MACHINE_EPSILON = Math.pow(2, -42);
8+
var EPSILON = Math.pow(2, -42);
99

1010
var knumber = KhanUtil.knumber = {
1111

1212
DEFAULT_TOLERANCE: DEFAULT_TOLERANCE,
13-
MACHINE_EPSILON: MACHINE_EPSILON,
13+
EPSILON: EPSILON,
1414

1515
is: function(x) {
1616
return _.isNumber(x) && !_.isNaN(x);
@@ -50,6 +50,49 @@ var knumber = KhanUtil.knumber = {
5050

5151
ceilTo: function(num, increment) {
5252
return Math.ceil(num / increment) * increment;
53+
},
54+
55+
isInteger: function(num, tolerance) {
56+
return knumber.equal(Math.round(num), num, tolerance);
57+
},
58+
59+
/**
60+
* toFraction
61+
*
62+
* Returns a [numerator, denominator] array rational representation
63+
* of `decimal`
64+
*
65+
* See http://en.wikipedia.org/wiki/Continued_fraction for implementation
66+
* details
67+
*
68+
* toFraction(4/8) => [1, 2]
69+
* toFraction(0.66) => [33, 50]
70+
* toFraction(0.66, 0.01) => [2/3]
71+
* toFraction(283 + 1/3) => [850, 3]
72+
*/
73+
toFraction: function(decimal, tolerance, max_denominator) {
74+
max_denominator = max_denominator || 1000;
75+
tolerance = tolerance || EPSILON; // can't be 0
76+
77+
// Initialize everything to compute successive terms of
78+
// continued-fraction approximations via recurrence relation
79+
var n = [1, 0], d = [0, 1];
80+
var a = Math.floor(decimal), t;
81+
var rem = decimal - a;
82+
83+
while (d[0] <= max_denominator) {
84+
if (knumber.equal(n[0] / d[0], decimal, tolerance)) {
85+
return [n[0], d[0]];
86+
}
87+
n = [a*n[0] + n[1], n[0]];
88+
d = [a*d[0] + d[1], d[0]];
89+
a = Math.floor(1 / rem);
90+
rem = 1/rem - a;
91+
}
92+
93+
// We failed to find a nice rational representation,
94+
// so return an irrational "fraction"
95+
return [decimal, 1];
5396
}
5497
};
5598

0 commit comments

Comments
 (0)