126 lines
2.9 KiB
Markdown
126 lines
2.9 KiB
Markdown
|
# Target
|
||
|
|
||
|
In all languages, the target is a function that takes 3 parameters:
|
||
|
|
||
|
- `x` the number we wish to approximate
|
||
|
- `D` the maximum denominator
|
||
|
- `mixed` if true, return a mixed fraction (default); if false, improper
|
||
|
|
||
|
The JS implementation walks through the algorithm.
|
||
|
|
||
|
# JS Implementation
|
||
|
|
||
|
In this version, the return value is `[quotient, numerator, denominator]`,
|
||
|
where `quotient == 0` for improper fractions. The interpretation is
|
||
|
`x ~ quotient + numerator / denominator` where `0 <= numerator < denominator`
|
||
|
and `quotient <= x` for negative `x`.
|
||
|
|
||
|
```js>frac.js
|
||
|
var frac = function(x, D, mixed) {
|
||
|
```
|
||
|
|
||
|
The goal is to maintain a feasible fraction (with bounded denominator) below
|
||
|
the target and another fraction above the target. The lower bound is
|
||
|
`floor(x) / 1` and the upper bound is `(floor(x) + 1) / 1`. We keep track of
|
||
|
the numerators and denominators separately:
|
||
|
|
||
|
```
|
||
|
var n1 = Math.floor(x), d1 = 1;
|
||
|
var n2 = n1+1, d2 = 1;
|
||
|
```
|
||
|
|
||
|
If `x` is not integral, we bisect using mediants until a denominator exceeds
|
||
|
our target:
|
||
|
|
||
|
```
|
||
|
if(x !== n1) while(d1 <= D && d2 <= D) {
|
||
|
```
|
||
|
|
||
|
The mediant is the sum of the numerators divided by the sum of demoninators:
|
||
|
|
||
|
```
|
||
|
var m = (n1 + n2) / (d1 + d2);
|
||
|
```
|
||
|
|
||
|
If we happened to stumble upon the exact value, then we choose the closer one
|
||
|
(the mediant if the denominator is within bounds, or the bound with the larger
|
||
|
denominator)
|
||
|
|
||
|
```
|
||
|
if(x === m) {
|
||
|
if(d1 + d2 <= D) d1+=d2, n1+=n2, d2=D+1;
|
||
|
else if(d1 > d2) d2=D+1;
|
||
|
else d1=D+1;
|
||
|
break;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Otherwise shrink the range:
|
||
|
|
||
|
```
|
||
|
else if(x < m) n2 = n1+n2, d2 = d1+d2;
|
||
|
else n1 = n1+n2, d1 = d1+d2;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
At this point, `d1 > D` or `d2 > D` (but not both -- keep track of how `d1` and
|
||
|
`d2` change). So we merely return the desired values:
|
||
|
|
||
|
```
|
||
|
if(d1 > D) d1 = d2, n1 = n2;
|
||
|
if(!mixed) return [0, n1, d1];
|
||
|
var q = Math.floor(n1/d1);
|
||
|
return [q, n1 - q*d1, d1];
|
||
|
};
|
||
|
```
|
||
|
|
||
|
Finally we put some export jazz:
|
||
|
|
||
|
```
|
||
|
if(typeof module !== undefined) module.exports = frac;
|
||
|
```
|
||
|
|
||
|
# Tests
|
||
|
|
||
|
```js>test.js
|
||
|
var frac;
|
||
|
describe('source', function() { it('should load', function() { frac = require('./'); }); });
|
||
|
```
|
||
|
|
||
|
# Miscellany
|
||
|
|
||
|
```make>Makefile
|
||
|
frac.js: frac.md
|
||
|
voc frac.md
|
||
|
|
||
|
.PHONY: test
|
||
|
test:
|
||
|
mocha -R spec
|
||
|
```
|
||
|
|
||
|
## Node Ilk
|
||
|
|
||
|
```json>package.json
|
||
|
{
|
||
|
"name": "frac",
|
||
|
"version": "0.1.0",
|
||
|
"author": "SheetJS",
|
||
|
"description": "Rational approximation with bounded denominator",
|
||
|
"keywords": [ "math", "fraction", "rational", "approximation" ],
|
||
|
"main": "./frac.js",
|
||
|
"dependencies": {},
|
||
|
"devDependencies": {"mocha":""},
|
||
|
"repository": {
|
||
|
"type":"git",
|
||
|
"url": "git://github.com/SheetJS/frac.git"
|
||
|
},
|
||
|
"scripts": {
|
||
|
"test": "make test"
|
||
|
},
|
||
|
"bugs": { "url": "https://github.com/SheetJS/frac/issues" },
|
||
|
"engines": { "node": ">=0.8" }
|
||
|
}
|
||
|
```
|
||
|
|
||
|
|