version bump 1.0.2: cleanup

- cont: more direct reverse iteration
- README cleanup
- python linting and tests
This commit is contained in:
SheetJS 2015-05-04 23:19:23 -07:00
parent 2f54d2f5c1
commit 9a9ed4d1fa
13 changed files with 186 additions and 43 deletions

@ -65,10 +65,16 @@ dist-deps:
## Python
.PHONY: pylint
pylint: frac.py
pylint: frac.py $(wildcard test_*.py)
pep8 $^
.PHONY: pypi
pypi: frac.py
python setup.py sdist upload
.PHONY: pytest pypytest
pytest: pylint
py.test -v --durations=5
pypytest: pylint
pypy $$(which py.test) -v --durations=5

@ -9,6 +9,8 @@ described by Aberth in "A method for exact computation with rational numbers".
## Installation
### JS
With [npm](https://www.npmjs.org/package/frac):
$ npm install frac
@ -21,9 +23,15 @@ The script will manipulate `module.exports` if available (e.g. in a CommonJS
`require` context). This is not always desirable. To prevent the behavior,
define `DO_NOT_EXPORT_FRAC`
### Python
From [PyPI](https://pypi.python.org/pypi/frac):
$ pip install frac
## Usage
The exported `frac` function takes three arguments:
In all cases, the relevant function takes 3 arguments:
- `x` the number we wish to approximate
- `D` the maximum denominator
@ -33,18 +41,48 @@ The return value is an array of the form `[quot, num, den]` where `quot==0`
for improper fractions. `quot <= x` for mixed fractions, which may lead to some
unexpected results when rendering negative numbers.
### JS
The exported `frac` function implements the Mediant method.
`frac.cont` implements the Aberth algorithm
For example:
```
```js
> // var frac = require('frac'); // uncomment this line if in node
> frac(Math.PI,100); // [ 0, 22, 7 ]
> frac(Math.PI,100,true); // [ 3, 1, 7 ]
> frac(-Math.PI,100); // [ 0, -22, 7 ]
> frac(-Math.PI,100,true); // [ -4, 6, 7 ] // the approximation is (-4) + (6/7)
> frac(1.3, 9); // [ 0, 9, 7 ] // 1.3 ~ 9/7
> frac(1.3, 9, true); // [ 1, 2, 7 ] // 1.3 ~ 1 + 2/7
> frac(-1.3, 9); // [ 0, -9, 7 ] // -1.3 ~ -9/7
> frac(-1.3, 9, true); // [ -2, 5, 7 ] // -1.3 ~ -2 + 5/7
> frac.cont(1.3, 9); // [ 0, 4, 3 ] // 1.3 ~ 4/3
> frac.cont(1.3, 9, true); // [ 1, 1, 3 ] // 1.3 ~ 1 + 1/3
> frac.cont(-1.3, 9); // [ 0, -4, 3 ] // -1.3 ~ -4/3
> frac.cont(-1.3, 9, true); // [ -2, 2, 3 ] // -1.3 ~ -2 + 2/3
```
`frac.cont` implements the Aberth algorithm (input and output specifications
match the original `frac` function)
### Python
`frac.med` implements Mediant method.
`frac.cont` implements Aberth algorithm
For example:
```py
>>> import frac
>>> frac.med(1.3, 9) # [ 0, 9, 7 ]
>>> frac.med(1.3, 9, True) # [ 1, 2, 7 ]
>>> frac.med(-1.3, 9) # [ 0, -9, 7 ]
>>> frac.med(-1.3, 9, True) # [ -2, 5, 7 ]
>>> frac.cont(1.3, 9) # [ 0, 4, 3 ]
>>> frac.cont(1.3, 9, True) # [ 1, 1, 3 ]
>>> frac.cont(-1.3, 9) # [ 0, -4, 3 ]
>>> frac.cont(-1.3, 9, True) # [ -2, 2, 3 ]
```
## Testing
@ -68,4 +106,4 @@ granted by the Apache 2.0 license are reserved by the Original Author.
[![Coverage Status](http://img.shields.io/coveralls/SheetJS/frac/master.svg)](https://coveralls.io/r/SheetJS/frac?branch=master)
[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/731e31b3a26382ccd5d213b9e74ea552 "githalytics.com")](http://githalytics.com/SheetJS/frac)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/frac?pixel)](https://github.com/SheetJS/frac)

3
dist/frac.js vendored

@ -33,8 +33,7 @@ frac.cont = function cont(x, D, mixed) {
P_2 = P_1; P_1 = P;
Q_2 = Q_1; Q_1 = Q;
}
if(Q > D) { Q = Q_1; P = P_1; }
if(Q > D) { Q = Q_2; P = P_2; }
if(Q > D) { if(Q_1 > D) { Q = Q_2; P = P_2; } else { Q = Q_1; P = P_1; } }
if(!mixed) return [0, sgn * P, Q];
var q = Math.floor(sgn * P/Q);
return [q, sgn*P - q*Q, Q];

2
dist/frac.min.js vendored

@ -1,2 +1,2 @@
/* frac.js (C) 2012-2015 SheetJS -- http://sheetjs.com */
var frac=function(x,D,mixed){var n1=Math.floor(x),d1=1;var n2=n1+1,d2=1;if(x!==n1)while(d1<=D&&d2<=D){var m=(n1+n2)/(d1+d2);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}else if(x<m){n2=n1+n2;d2=d1+d2}else{n1=n1+n2;d1=d1+d2}}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]};frac.cont=function cont(x,D,mixed){var sgn=x<0?-1:1;var B=x*sgn;var P_2=0,P_1=1,P=0;var Q_2=1,Q_1=0,Q=0;var A=Math.floor(B);while(Q_1<D){A=Math.floor(B);P=A*P_1+P_2;Q=A*Q_1+Q_2;if(B-A<5e-10)break;B=1/(B-A);P_2=P_1;P_1=P;Q_2=Q_1;Q_1=Q}if(Q>D){Q=Q_1;P=P_1}if(Q>D){Q=Q_2;P=P_2}if(!mixed)return[0,sgn*P,Q];var q=Math.floor(sgn*P/Q);return[q,sgn*P-q*Q,Q]};if(typeof module!=="undefined"&&typeof DO_NOT_EXPORT_FRAC==="undefined")module.exports=frac;
var frac=function(x,D,mixed){var n1=Math.floor(x),d1=1;var n2=n1+1,d2=1;if(x!==n1)while(d1<=D&&d2<=D){var m=(n1+n2)/(d1+d2);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}else if(x<m){n2=n1+n2;d2=d1+d2}else{n1=n1+n2;d1=d1+d2}}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]};frac.cont=function cont(x,D,mixed){var sgn=x<0?-1:1;var B=x*sgn;var P_2=0,P_1=1,P=0;var Q_2=1,Q_1=0,Q=0;var A=Math.floor(B);while(Q_1<D){A=Math.floor(B);P=A*P_1+P_2;Q=A*Q_1+Q_2;if(B-A<5e-10)break;B=1/(B-A);P_2=P_1;P_1=P;Q_2=Q_1;Q_1=Q}if(Q>D){if(Q_1>D){Q=Q_2;P=P_2}else{Q=Q_1;P=P_1}}if(!mixed)return[0,sgn*P,Q];var q=Math.floor(sgn*P/Q);return[q,sgn*P-q*Q,Q]};if(typeof module!=="undefined"&&typeof DO_NOT_EXPORT_FRAC==="undefined")module.exports=frac;

2
dist/frac.min.map vendored

@ -1 +1 @@
{"version":3,"file":"dist/frac.min.js","sources":["frac.js"],"names":["frac","x","D","mixed","n1","Math","floor","d1","n2","d2","m","q","cont","sgn","B","P_2","P_1","P","Q_2","Q_1","Q","A","module","DO_NOT_EXPORT_FRAC","exports"],"mappings":";AACA,GAAIA,MAAO,SAASC,EAAGC,EAAGC,OACxB,GAAIC,IAAKC,KAAKC,MAAML,GAAIM,GAAK,CAC7B,IAAIC,IAAKJ,GAAG,EAAGK,GAAK,CACpB,IAAGR,IAAMG,GAAI,MAAMG,IAAML,GAAKO,IAAMP,EAAG,CACrC,GAAIQ,IAAKN,GAAKI,KAAOD,GAAKE,GAC1B,IAAGR,IAAMS,EAAG,CACV,GAAGH,GAAKE,IAAMP,EAAG,CAAEK,IAAIE,EAAIL,KAAII,EAAIC,IAAGP,EAAE,MACnC,IAAGK,GAAKE,GAAIA,GAAGP,EAAE,MACjBK,IAAGL,EAAE,CACV,WAEG,IAAGD,EAAIS,EAAG,CAAEF,GAAKJ,GAAGI,EAAIC,IAAKF,GAAGE,OAChC,CAAEL,GAAKA,GAAGI,EAAID,IAAKA,GAAGE,IAE7B,GAAGF,GAAKL,EAAG,CAAEK,GAAKE,EAAIL,IAAKI,GAC3B,IAAIL,MAAO,OAAQ,EAAGC,GAAIG,GAC1B,IAAII,GAAIN,KAAKC,MAAMF,GAAGG,GACtB,QAAQI,EAAGP,GAAKO,EAAEJ,GAAIA,IAExBP,MAAKY,KAAO,QAASA,MAAKX,EAAGC,EAAGC,OAC9B,GAAIU,KAAMZ,EAAI,GAAK,EAAI,CACvB,IAAIa,GAAIb,EAAIY,GACZ,IAAIE,KAAM,EAAGC,IAAM,EAAGC,EAAI,CAC1B,IAAIC,KAAM,EAAGC,IAAM,EAAGC,EAAI,CAC1B,IAAIC,GAAIhB,KAAKC,MAAMQ,EACnB,OAAMK,IAAMjB,EAAG,CACbmB,EAAIhB,KAAKC,MAAMQ,EACfG,GAAII,EAAIL,IAAMD,GACdK,GAAIC,EAAIF,IAAMD,GACd,IAAIJ,EAAIO,EAAK,MAAc,KAC3BP,GAAI,GAAKA,EAAIO,EACbN,KAAMC,GAAKA,KAAMC,CACjBC,KAAMC,GAAKA,KAAMC,EAEnB,GAAGA,EAAIlB,EAAG,CAAEkB,EAAID,GAAKF,GAAID,IACzB,GAAGI,EAAIlB,EAAG,CAAEkB,EAAIF,GAAKD,GAAIF,IACzB,IAAIZ,MAAO,OAAQ,EAAGU,IAAMI,EAAGG,EAC/B,IAAIT,GAAIN,KAAKC,MAAMO,IAAMI,EAAEG,EAC3B,QAAQT,EAAGE,IAAII,EAAIN,EAAES,EAAGA,GAE1B,UAAUE,UAAW,mBAAsBC,sBAAuB,YAAaD,OAAOE,QAAUxB"}
{"version":3,"file":"dist/frac.min.js","sources":["frac.js"],"names":["frac","x","D","mixed","n1","Math","floor","d1","n2","d2","m","q","cont","sgn","B","P_2","P_1","P","Q_2","Q_1","Q","A","module","DO_NOT_EXPORT_FRAC","exports"],"mappings":";AACA,GAAIA,MAAO,SAASC,EAAGC,EAAGC,OACxB,GAAIC,IAAKC,KAAKC,MAAML,GAAIM,GAAK,CAC7B,IAAIC,IAAKJ,GAAG,EAAGK,GAAK,CACpB,IAAGR,IAAMG,GAAI,MAAMG,IAAML,GAAKO,IAAMP,EAAG,CACrC,GAAIQ,IAAKN,GAAKI,KAAOD,GAAKE,GAC1B,IAAGR,IAAMS,EAAG,CACV,GAAGH,GAAKE,IAAMP,EAAG,CAAEK,IAAIE,EAAIL,KAAII,EAAIC,IAAGP,EAAE,MACnC,IAAGK,GAAKE,GAAIA,GAAGP,EAAE,MACjBK,IAAGL,EAAE,CACV,WAEG,IAAGD,EAAIS,EAAG,CAAEF,GAAKJ,GAAGI,EAAIC,IAAKF,GAAGE,OAChC,CAAEL,GAAKA,GAAGI,EAAID,IAAKA,GAAGE,IAE7B,GAAGF,GAAKL,EAAG,CAAEK,GAAKE,EAAIL,IAAKI,GAC3B,IAAIL,MAAO,OAAQ,EAAGC,GAAIG,GAC1B,IAAII,GAAIN,KAAKC,MAAMF,GAAGG,GACtB,QAAQI,EAAGP,GAAKO,EAAEJ,GAAIA,IAExBP,MAAKY,KAAO,QAASA,MAAKX,EAAGC,EAAGC,OAC9B,GAAIU,KAAMZ,EAAI,GAAK,EAAI,CACvB,IAAIa,GAAIb,EAAIY,GACZ,IAAIE,KAAM,EAAGC,IAAM,EAAGC,EAAI,CAC1B,IAAIC,KAAM,EAAGC,IAAM,EAAGC,EAAI,CAC1B,IAAIC,GAAIhB,KAAKC,MAAMQ,EACnB,OAAMK,IAAMjB,EAAG,CACbmB,EAAIhB,KAAKC,MAAMQ,EACfG,GAAII,EAAIL,IAAMD,GACdK,GAAIC,EAAIF,IAAMD,GACd,IAAIJ,EAAIO,EAAK,MAAc,KAC3BP,GAAI,GAAKA,EAAIO,EACbN,KAAMC,GAAKA,KAAMC,CACjBC,KAAMC,GAAKA,KAAMC,EAEnB,GAAGA,EAAIlB,EAAG,CAAE,GAAGiB,IAAMjB,EAAG,CAAEkB,EAAIF,GAAKD,GAAIF,QAAY,CAAEK,EAAID,GAAKF,GAAID,KAClE,IAAIb,MAAO,OAAQ,EAAGU,IAAMI,EAAGG,EAC/B,IAAIT,GAAIN,KAAKC,MAAMO,IAAMI,EAAEG,EAC3B,QAAQT,EAAGE,IAAII,EAAIN,EAAES,EAAGA,GAE1B,UAAUE,UAAW,mBAAsBC,sBAAuB,YAAaD,OAAOE,QAAUxB"}

@ -33,8 +33,7 @@ frac.cont = function cont(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Arra
P_2 = P_1; P_1 = P;
Q_2 = Q_1; Q_1 = Q;
}
if(Q > D) { Q = Q_1; P = P_1; }
if(Q > D) { Q = Q_2; P = P_2; }
if(Q > D) { if(Q_1 > D) { Q = Q_2; P = P_2; } else { Q = Q_1; P = P_1; } }
if(!mixed) return [0, sgn * P, Q];
var q = Math.floor(sgn * P/Q);
return [q, sgn*P - q*Q, Q];

@ -33,8 +33,7 @@ frac.cont = function cont(x, D, mixed) {
P_2 = P_1; P_1 = P;
Q_2 = Q_1; Q_1 = Q;
}
if(Q > D) { Q = Q_1; P = P_1; }
if(Q > D) { Q = Q_2; P = P_2; }
if(Q > D) { if(Q_1 > D) { Q = Q_2; P = P_2; } else { Q = Q_1; P = P_1; } }
if(!mixed) return [0, sgn * P, Q];
var q = Math.floor(sgn * P/Q);
return [q, sgn*P - q*Q, Q];

10
frac.md

@ -147,11 +147,10 @@ At the end of each iteration, advance `k` by one step:
}
```
In case we end up overstepping, walk back an iteration or two:
In case we end up overstepping, walk back to the last valid iteration:
```
if(Q > D) { Q = Q_1; P = P_1; }
if(Q > D) { Q = Q_2; P = P_2; }
if(Q > D) { if(Q_1 > D) { Q = Q_2; P = P_2; } else { Q = Q_1; P = P_1; } }
```
The final result is `r = (sgn x)p_k / q_k`:
@ -185,7 +184,7 @@ var xltestfiles=[
];
function xlline(o,j,m,w) {
it(j, function(done) {
it(j, function() {
var d, q, qq;
for(var i = j*w; i < m-3 && i < (j+1)*w; ++i) {
d = o[i].split("\t");
@ -202,7 +201,6 @@ function xlline(o,j,m,w) {
qq = (q[0]||q[1]) ? (q[0] || "") + " " + (q[1] ? (q[1] < 100 ? " " : "") + (q[1] < 10 ? " " : "") + q[1] + "/" + q[2] + (q[2]<10?" ":"") + (q[2]<100?" ":""): " ") : "0 ";
assert.equal(qq, d[3], d[3] + " 3");
}
done();
});
}
function parsexl(f,w) {
@ -241,7 +239,7 @@ xltestfiles.forEach(function(x) {
```json>package.json
{
"name": "frac",
"version": "1.0.1",
"version": "1.0.2",
"author": "SheetJS",
"description": "Rational approximation with bounded denominator",
"keywords": [ "math", "fraction", "rational", "approximation" ],

25
frac.py

@ -16,18 +16,16 @@ All functions take 3 arguments:
The return value is a list of 3 elements: [quotient, numerator, denominator]
"""
import math as Math
def med(x, D, mixed=False):
"""Generate fraction representation using Mediant method"""
n1, d1 = int(Math.floor(x)), 1
n1, d1 = int(x), 1
n2, d2 = n1+1, 1
m = 0.
if x != n1:
while d1 <= D and d2 <= D:
m = float(n1 + n2) / (d1 + d2)
if(x == m):
if x == m:
if d1 + d2 <= D:
n1, d1 = n1 + n2, d1 + d2
d2 = D + 1
@ -50,23 +48,24 @@ def med(x, D, mixed=False):
def cont(x, D, mixed=False):
"""Generate fraction representation using Aberth method"""
sgn = -1 if x < 0 else 1
B = abs(x)
P_2, P_1, P = 0, 1, 0
Q_2, Q_1, Q = 1, 0, 0
I = int
P_2, P_1, P, Q_2, Q_1, Q = 0, 1, 0, 1, 0, 0
while Q_1 < D:
A = int(Math.floor(B))
A = I(B)
P = A * P_1 + P_2
Q = A * Q_1 + Q_2
if ((B - A) < 0.0000000005):
if (B - A) < 0.0000000005:
break
B = 1. / (B-A)
P_2, P_1 = P_1, P
Q_2, Q_1 = Q_1, Q
if(Q > D):
P, Q = P_1, Q_1
if(Q > D):
P, Q = P_2, Q_2
if Q > D:
if Q_1 <= D:
P, Q = P_1, Q_1
else:
P, Q = P_2, Q_2
sgn = -1 if x < 0 else 1
if not mixed:
return [0, sgn * P, Q]
q = divmod(sgn * P, Q)

@ -1,6 +1,6 @@
{
"name": "frac",
"version": "1.0.1",
"version": "1.0.2",
"author": "SheetJS",
"description": "Rational approximation with bounded denominator",
"keywords": [ "math", "fraction", "rational", "approximation" ],

@ -1,7 +1,7 @@
from distutils.core import setup
setup(
name='frac',
version='0.0.1',
version='1.0.2',
author='SheetJS',
author_email='dev@sheetjs.com',
url='http://oss.sheetjs.com/frac',
@ -20,9 +20,10 @@ This module can generate fraction representations using:
license = "Apache-2.0",
keywords = ['frac', 'fraction', 'rational', 'approximation'],
classifiers = [
'Development Status :: 3 - Alpha',
'Development Status :: 5 - Production/Stable'
'License :: OSI Approved :: Apache Software License',
'Topic :: Office/Business',
'Operating System :: OS Independent',
'Topic :: Scientific/Engineering :: Mathematics',
'Topic :: Utilities',
]
)

@ -10,7 +10,7 @@ var xltestfiles=[
];
function xlline(o,j,m,w) {
it(j, function(done) {
it(j, function() {
var d, q, qq;
for(var i = j*w; i < m-3 && i < (j+1)*w; ++i) {
d = o[i].split("\t");
@ -27,7 +27,6 @@ function xlline(o,j,m,w) {
qq = (q[0]||q[1]) ? (q[0] || "") + " " + (q[1] ? (q[1] < 100 ? " " : "") + (q[1] < 10 ? " " : "") + q[1] + "/" + q[2] + (q[2]<10?" ":"") + (q[2]<100?" ":""): " ") : "0 ";
assert.equal(qq, d[3], d[3] + " 3");
}
done();
});
}
function parsexl(f,w) {

105
test_all.py Normal file

@ -0,0 +1,105 @@
import frac
import pytest
xltestfiles = [
['xl.00001.tsv', 10000],
['xl.0001.tsv', 10000],
['xl.001.tsv', 10000],
['xl.01.tsv', 10000],
['oddities.tsv', 25]
]
mediant_tests = [
[0.1, 9, False, 0, 1, 9],
[0.2, 9, False, 0, 1, 5],
[0.3, 9, False, 0, 2, 7],
[0.4, 9, False, 0, 2, 5],
[0.5, 9, False, 0, 1, 2],
[0.6, 9, False, 0, 3, 5],
[0.7, 9, False, 0, 5, 7],
[0.8, 9, False, 0, 4, 5],
[0.9, 9, False, 0, 8, 9],
[1.0, 9, False, 0, 1, 1],
[1.0, 9, True, 1, 0, 1],
[1.7, 9, True, 1, 5, 7],
[1.7, 9, False, 0, 12, 7]
]
def test_mediant_tenths():
for t in mediant_tests:
assert frac.med(t[0], t[1], t[2]) == [t[3], t[4], t[5]]
def make9(q):
S = str
if q[0] or q[1]:
qq = (S(q[0]) if q[0] else "")
qq += " "
qq += ("/".join([S(q[1]), S(q[2])]) if q[1] else " ")
return qq
else:
return "0 "
def make99(q):
S = str
if q[0] or q[1]:
qq = (S(q[0]) if q[0] else "")
qq += " "
if q[1]:
if q[1] < 10:
qq += " "
qq += ("/".join([S(q[1]), S(q[2])]))
if q[2] < 10:
qq += " "
else:
qq += " "
return qq
else:
return "0 "
def make999(q):
S = str
if q[0] or q[1]:
qq = (S(q[0]) if q[0] else "")
qq += " "
if q[1]:
if q[1] < 10:
qq += " "
elif q[1] < 100:
qq += " "
qq += "/".join([S(q[1]), S(q[2])])
if q[2] < 10:
qq += " "
elif q[2] < 100:
qq += " "
else:
qq += " "
return qq
else:
return "0 "
def xlline(o, j, m, w):
for i in xrange(j*w, min(m-3, (j+1) * w)):
d = o[i].split("\t")
dd = float(d[0])
assert make9(frac.cont(dd, 9, True)) == d[1]
assert make99(frac.cont(dd, 99, True)) == d[2]
assert make999(frac.cont(dd, 999, True)) == d[3]
def parsexl(f, w):
oo = open(f)
d = oo.readlines()
o = filter(lambda x: len(x) > 0, map(lambda x: x.strip("\n"), d))
m = len(o)-3
for j in xrange(int(m/w)):
xlline(o, j, m, w)
@pytest.mark.parametrize("filename,step", xltestfiles)
def test_file(filename, step):
parsexl('./test_files/' + filename, step)