version bump 1.0.1: cleanup and python

- demo page (gh-pages branch)
- updated year
- flow annotations in separate frac.flow.js file
- introduced build infrastructure from js-xlsx
- python version
This commit is contained in:
SheetJS 2015-04-21 21:14:03 -07:00
parent 5ed8c61d24
commit 2f54d2f5c1
21 changed files with 407 additions and 50 deletions

12
.flowconfig Normal file

@ -0,0 +1,12 @@
[ignore]
.*/node_modules/.*
.*/dist/.*
.*/test.js
.*/frac.js
[include]
frac.flow.js
[libs]
[options]

6
.jscs.json Normal file

@ -0,0 +1,6 @@
{
"requireCommaBeforeLineBreak": true,
"disallowTrailingWhitespace": true,
"disallowTrailingComma": true
}

4
.jshintrc Normal file

@ -0,0 +1,4 @@
{
"bitwise": false,
"curly": false
}

@ -2,3 +2,14 @@ test_files/*.tsv
.gitignore
node_modules/
coverage.html
.travis.yml
.jshintrc
.jscs.json
.flowconfig
misc/
*.sheetjs
*.pyc
build/
MANIFEST
*.gz
*.tgz

@ -1,5 +1,6 @@
language: node_js
node_js:
- "iojs"
- "0.10"
- "0.8"
before_install:
@ -7,4 +8,4 @@ before_install:
- "npm install blanket"
- "npm install coveralls mocha-lcov-reporter"
after_success:
- "make coveralls"
- "make coveralls-spin"

@ -1,4 +1,4 @@
Copyright (C) 2012-2014 SheetJS
Copyright (C) 2012-2015 SheetJS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

@ -1,29 +1,74 @@
LIB=frac
TARGET=$(LIB).js
.PHONY: frac
frac: frac.md
voc frac.md
REQS=
ADDONS=
AUXTARGETS=
.PHONY: test
test:
mocha -R spec
ULIB=$(shell echo $(LIB) | tr a-z A-Z)
DEPS=$(LIB).md
TARGET=$(LIB).js
.PHONY: all
all: $(TARGET) $(AUXTARGETS)
$(TARGET) $(AUXTARGETS): %.js : %.flow.js
node -e 'process.stdout.write(require("fs").readFileSync("$<","utf8").replace(/^\s*\/\*:[^*]*\*\/\s*(\n)?/gm,"").replace(/\/\*:[^*]*\*\//gm,""))' > $@
$(LIB).flow.js: $(DEPS)
voc $^
.PHONY: clean
clean:
rm -f $(TARGET)
.PHONY: test mocha
test mocha: test.js
mocha -R spec -t 20000
.PHONY: lint
lint:
jshint --show-non-errors frac.js
lint: $(TARGET) $(AUXTARGETS)
jshint --show-non-errors $(TARGET) $(AUXTARGETS)
jshint --show-non-errors package.json
jscs $(TARGET) $(AUXTARGETS)
.PHONY: cov
cov: coverage.html
.PHONY: flow
flow: lint
flow check --all --show-all-errors
coverage.html: frac
mocha --require blanket -R html-cov > coverage.html
.PHONY: cov cov-spin
cov: misc/coverage.html
cov-spin:
make cov & bash misc/spin.sh $$!
.PHONY: coveralls
misc/coverage.html: $(TARGET) test.js
mocha --require blanket -R html-cov -t 20000 > $@
.PHONY: coveralls coveralls-spin
coveralls:
mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js
mocha --require blanket --reporter mocha-lcov-reporter -t 20000 | ./node_modules/coveralls/bin/coveralls.js
coveralls-spin:
make coveralls & bash misc/spin.sh $$!
.PHONY: dist
dist: $(TARGET)
dist: dist-deps $(TARGET)
cp $(TARGET) dist/
cp LICENSE dist/
uglifyjs $(TARGET) -o dist/$(LIB).min.js --source-map dist/$(LIB).min.map --preamble "$$(head -n 1 frac.js)"
misc/strip_sourcemap.sh dist/$(LIB).min.js
.PHONY: aux
aux: $(AUXTARGETS)
.PHONY: dist-deps
dist-deps:
## Python
.PHONY: pylint
pylint: frac.py
pep8 $^
.PHONY: pypi
pypi: frac.py
python setup.py sdist upload

1
README Symbolic link

@ -0,0 +1 @@
README.md

@ -2,15 +2,14 @@
Rational approximation to a floating point number with bounded denominator.
Uses the Mediant Method <https://en.wikipedia.org/wiki/Mediant_(mathematics)>
Uses the [Mediant Method](https://en.wikipedia.org/wiki/Mediant_method).
This module also provides an implementation of the continued fraction method as
described by Aberth in "A method for exact computation with rational numbers",
which appears to be used by spreadsheet programs for displaying fractions
described by Aberth in "A method for exact computation with rational numbers".
## Setup
## Installation
In node:
With [npm](https://www.npmjs.org/package/frac):
$ npm install frac
@ -28,27 +27,28 @@ The exported `frac` function takes three arguments:
- `x` the number we wish to approximate
- `D` the maximum denominator
- `mixed` if true, return a mixed fraction (default); if false, improper
- `mixed` if true, return a mixed fraction; if false, improper
The return value is an array of the form `[quot, num, den]` where `quot==0`
for improper fractions.
for improper fractions. `quot <= x` for mixed fractions, which may lead to some
unexpected results when rendering negative numbers.
For example:
```
> // 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); // [ 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.cont` implements the Aberth algorithm (input and output specifications
match the original `frac` function)
## License
## Testing
Apache 2.0
## Tests
`make test` will run the node-based tests.
Tests generated from Excel have 4 columns. To produce a similar test:
@ -57,9 +57,15 @@ Tests generated from Excel have 4 columns. To produce a similar test:
- Column C format "Up to two digits (21/25)"
- Column D format "Up to three digits (312/943)"
## License
Please consult the attached LICENSE file for details. All rights not explicitly
granted by the Apache 2.0 license are reserved by the Original Author.
## Badges
[![Build Status](https://travis-ci.org/SheetJS/frac.svg?branch=master)](https://travis-ci.org/SheetJS/frac)
[![Coverage Status](https://coveralls.io/repos/SheetJS/frac/badge.png?branch=master)](https://coveralls.io/r/SheetJS/frac?branch=master)
[![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)

2
dist/LICENSE vendored

@ -1,4 +1,4 @@
Copyright (C) 2012-2014 SheetJS
Copyright (C) 2012-2015 SheetJS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

2
dist/frac.js vendored

@ -1,4 +1,4 @@
/* frac.js (C) 2012-2014 SheetJS -- http://sheetjs.com */
/* 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;

3
dist/frac.min.js vendored

@ -1,3 +1,2 @@
/* frac.js (C) 2012-2014 SheetJS -- http://sheetjs.com */
/* 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;
//# sourceMappingURL=dist/frac.min.map

43
frac.flow.js Normal file

@ -0,0 +1,43 @@
/* frac.js (C) 2012-2015 SheetJS -- http://sheetjs.com */
var frac = function(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array<number>*/ {
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/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array<number>*/ {
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) < 0.0000000005) 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];
};
/*:: declare var DO_NOT_EXPORT_FRAC: any; */
if(typeof module !== 'undefined' && typeof DO_NOT_EXPORT_FRAC === 'undefined') module.exports = frac;

@ -1,4 +1,4 @@
/* frac.js (C) 2012-2014 SheetJS -- http://sheetjs.com */
/* 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;

46
frac.md

@ -4,20 +4,26 @@ 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
- `mixed` if true, return a mixed fraction; 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`.
In this version, the return value is `[quotient, numerator, denominator]`. The
interpretation is `x ~ quotient + numerator / denominator`
```js>frac.js
/* frac.js (C) 2012-2014 SheetJS -- http://sheetjs.com */
var frac = function(x, D, mixed) {
- For improper fractions, `quotient == 0`.
- For proper fractions, `0 <= numerator < denominator` and `quotient <= x`
For negative `x`, this deviates from how some people and systems interpret mixed
fractions. Some interpret `a b/c` as `sgn(a) * [abs(a) + b/c]`. To replicate
that behavior, pass the absolute value to frac and prepend a "-" if negative.
```js>frac.flow.js
/* frac.js (C) 2012-2015 SheetJS -- http://sheetjs.com */
var frac = function(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array<number>*/ {
```
The goal is to maintain a feasible fraction (with bounded denominator) below
@ -81,8 +87,8 @@ The continued fraction technique is employed by various spreadsheet programs.
Note that this technique is inferior to the mediant method (at least, according
to the desired goal of most accurately approximating the floating point number)
```
frac.cont = function cont(x, D, mixed) {
```js>frac.flow.js
frac.cont = function cont(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array<number>*/ {
```
> Record the sign of x, take b0=|x|, p_{-2}=0, p_{-1}=1, q_{-2}=1, q_{-1}=0
@ -160,6 +166,7 @@ The final result is `r = (sgn x)p_k / q_k`:
Finally we put some export jazz:
```
/*:: declare var DO_NOT_EXPORT_FRAC: any; */
if(typeof module !== 'undefined' && typeof DO_NOT_EXPORT_FRAC === 'undefined') module.exports = frac;
```
@ -234,7 +241,7 @@ xltestfiles.forEach(function(x) {
```json>package.json
{
"name": "frac",
"version": "1.0.0",
"version": "1.0.1",
"author": "SheetJS",
"description": "Rational approximation with bounded denominator",
"keywords": [ "math", "fraction", "rational", "approximation" ],
@ -267,6 +274,17 @@ test_files/*.tsv
.gitignore
node_modules/
coverage.html
.travis.yml
.jshintrc
.jscs.json
.flowconfig
misc/
*.sheetjs
*.pyc
build/
MANIFEST
*.gz
*.tgz
```
Don't include the node modules in git:
@ -275,4 +293,10 @@ Don't include the node modules in git:
.gitignore
node_modules/
coverage.html
*.sheetjs
*.pyc
build/
MANIFEST
*.gz
*.tgz
```

73
frac.py Normal file

@ -0,0 +1,73 @@
# frac.py (C) 2015 SheetJS -- http://sheetjs.com
# vim: set fileencoding=utf-8 :
"""
Rational approximations to numbers
This module can generate fraction representations using:
- Mediant method (akin to fractions.Fraction#limit_denominator)
- Aberth method (as used by spreadsheet software)
All functions take 3 arguments:
- x: the number to be approximated
- D: the max denominator
- mixed: if True, generate a mixed representation
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
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 d1 + d2 <= D:
n1, d1 = n1 + n2, d1 + d2
d2 = D + 1
elif d1 > d2:
d2 = D+1
else:
d1 = D+1
break
elif x < m:
n2, d2 = n1+n2, d1+d2
else:
n1, d1 = n1+n2, d1+d2
if d1 > D:
n1, d1 = n2, d2
if not mixed:
return [0, n1, d1]
q = divmod(n1, d1)
return [q[0], q[1], d1]
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
while Q_1 < D:
A = int(Math.floor(B))
P = A * P_1 + P_2
Q = A * Q_1 + Q_2
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 not mixed:
return [0, sgn * P, Q]
q = divmod(sgn * P, Q)
return [q[0], q[1], Q]

79
index.html Normal file

@ -0,0 +1,79 @@
<!DOCTYPE html>
<!-- frac.js (C) 2015 SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<title>Fraction Live Demo</title>
<script src="frac.js"></script>
</head>
<body>
<b>Fraction Live Demo</b><br />
<a href="https://github.com/SheetJS/frac">Source Code</a><br />
<a href="https://github.com/SheetJS/frac/issues">Issues? Something look weird? Click here and report an issue</a><br />
<br />
<table>
<tr><td><b>Number:</b></td><td><input type="text" id="val" value="1.3"></td></tr>
<tr><td><b>Denominator Digits:</b></td><td><input type="text" id="fmt" value="1"></td></tr>
<tr><td><b>Use Mixed Fraction:</b></td><td><input type="checkbox" id="mix" name="mixd" checked></td></tr>
<tr><td colspan="2">&nbsp;</td></tr>
<tr><td><b>Mediant Fraction:</b></td><td><pre id="frm"></pre></td></tr>
<tr><td><b>Aberth Fraction:</b></td><td><pre id="fra"></pre></td></tr>
</table>
</body>
<script>
var V = document.getElementById('val');
var F = document.getElementById('fmt');
var X = document.getElementById('mix');
var M = document.getElementById('frm');
var A = document.getElementById('fra');
function update() {
var x, D, mixed, oM, oA;
x = Number(V.value);
D = Math.pow(10, Number(F.value))-1;
mixed = X.checked;
if(x !== x) { M.innerHTML=A.innerHTML = V.value + " not valid"; return; }
if(D !== D) { M.innerHTML=A.innerHTML = F.value + " not valid"; return; }
try {
oM = frac(x, D, mixed);
M.innerHTML = oM[0] + " " + oM[1] + "/" + oM[2];
} catch(e) { M.innerHTML = e.message; }
try {
oA = frac.cont(x, D, mixed);
A.innerHTML = oA[0] + " " + oA[1] + "/" + oA[2];
} catch(e) { A.innerHTML = e.message; }
}
/* Bind to relevant events */
if(F.addEventListener) {
F.addEventListener('keyup', update);
V.addEventListener('keyup', update);
M.addEventListener('change', update);
X.addEventListener('change', update);
} else if(F.attachEvent) {
F.attachEvent('onkeyup', update);
V.attachEvent('onkeyup', update);
M.attachEvent('onchange', update);
X.attachEvent('onchange', update);
} else {
F.oninput = update;
V.oninput = update;
M.onchange = update;
X.onchange = update;
}
update();
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36810333-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</html>

14
misc/spin.sh Executable file

@ -0,0 +1,14 @@
#!/bin/bash
# spin.sh -- show a spinner (for coverage test)
# Copyright (C) 2014-2015 SheetJS
wpid=$1
delay=1
str="|/-\\"
while [ $(ps -a|awk '$1=='$wpid' {print $1}') ]; do
t=${str#?}
printf " [%c]" "$str"
str=$t${str%"$t"}
sleep $delay
printf "\b\b\b\b"
done

11
misc/strip_sourcemap.sh Executable file

@ -0,0 +1,11 @@
#!/bin/bash
# strip_sourcemap.sh -- strip sourcemaps from a JS file (missing from uglifyjs)
# Copyright (C) 2014-2015 SheetJS
if [ $# -gt 0 ]; then
if [ -e "$1" ]; then
sed -i .sheetjs '/sourceMappingURL/d' "$1"
fi
else
cat - | sed '/sourceMappingURL/d'
fi

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

28
setup.py Normal file

@ -0,0 +1,28 @@
from distutils.core import setup
setup(
name='frac',
version='0.0.1',
author='SheetJS',
author_email='dev@sheetjs.com',
url='http://oss.sheetjs.com/frac',
description='Rational approximations to numbers',
long_description = """
Rational approximations to numbers
This module can generate fraction representations using:
- Mediant method (akin to fractions.Fraction#limit_denominator)
- Aberth method (as used by spreadsheet software)
""",
platforms = ["any"],
requires=[],
py_modules=['frac'],
scripts = [],
license = "Apache-2.0",
keywords = ['frac', 'fraction', 'rational', 'approximation'],
classifiers = [
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: Apache Software License',
'Topic :: Office/Business',
'Topic :: Utilities',
]
)