From 2f54d2f5c1ba345b08c75f6101b6599630a2d4df Mon Sep 17 00:00:00 2001 From: SheetJS Date: Tue, 21 Apr 2015 21:14:03 -0700 Subject: [PATCH] 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 --- .flowconfig | 12 +++++++ .jscs.json | 6 ++++ .jshintrc | 4 +++ .npmignore | 11 ++++++ .travis.yml | 3 +- LICENSE | 2 +- Makefile | 77 ++++++++++++++++++++++++++++++--------- README | 1 + README.md | 36 +++++++++++-------- dist/LICENSE | 2 +- dist/frac.js | 2 +- dist/frac.min.js | 3 +- frac.flow.js | 43 ++++++++++++++++++++++ frac.js | 2 +- frac.md | 46 ++++++++++++++++++------ frac.py | 73 +++++++++++++++++++++++++++++++++++++ index.html | 79 +++++++++++++++++++++++++++++++++++++++++ misc/spin.sh | 14 ++++++++ misc/strip_sourcemap.sh | 11 ++++++ package.json | 2 +- setup.py | 28 +++++++++++++++ 21 files changed, 407 insertions(+), 50 deletions(-) create mode 100644 .flowconfig create mode 100644 .jscs.json create mode 100644 .jshintrc create mode 120000 README create mode 100644 frac.flow.js create mode 100644 frac.py create mode 100644 index.html create mode 100755 misc/spin.sh create mode 100755 misc/strip_sourcemap.sh create mode 100644 setup.py diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..b7e9ace --- /dev/null +++ b/.flowconfig @@ -0,0 +1,12 @@ +[ignore] +.*/node_modules/.* +.*/dist/.* +.*/test.js +.*/frac.js + +[include] +frac.flow.js + +[libs] + +[options] diff --git a/.jscs.json b/.jscs.json new file mode 100644 index 0000000..5965004 --- /dev/null +++ b/.jscs.json @@ -0,0 +1,6 @@ +{ + "requireCommaBeforeLineBreak": true, + "disallowTrailingWhitespace": true, + "disallowTrailingComma": true +} + diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..5cd205b --- /dev/null +++ b/.jshintrc @@ -0,0 +1,4 @@ +{ + "bitwise": false, + "curly": false +} diff --git a/.npmignore b/.npmignore index ebd49bf..9c37b08 100644 --- a/.npmignore +++ b/.npmignore @@ -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 diff --git a/.travis.yml b/.travis.yml index dc9e596..2853f70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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" diff --git a/LICENSE b/LICENSE index d3d2837..9f00b94 100644 --- a/LICENSE +++ b/LICENSE @@ -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. diff --git a/Makefile b/Makefile index fe73517..4f5333e 100644 --- a/Makefile +++ b/Makefile @@ -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 + diff --git a/README b/README new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/README @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/README.md b/README.md index 0a633d5..74a1ee3 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,14 @@ Rational approximation to a floating point number with bounded denominator. -Uses the Mediant Method +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) - diff --git a/dist/LICENSE b/dist/LICENSE index d3d2837..9f00b94 100644 --- a/dist/LICENSE +++ b/dist/LICENSE @@ -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. diff --git a/dist/frac.js b/dist/frac.js index 87ffdab..4412674 100644 --- a/dist/frac.js +++ b/dist/frac.js @@ -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; diff --git a/dist/frac.min.js b/dist/frac.min.js index 40db049..fb64e84 100644 --- a/dist/frac.min.js +++ b/dist/frac.min.js @@ -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(xD){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_1D){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 \ No newline at end of file diff --git a/frac.flow.js b/frac.flow.js new file mode 100644 index 0000000..b60e8a9 --- /dev/null +++ b/frac.flow.js @@ -0,0 +1,43 @@ +/* frac.js (C) 2012-2015 SheetJS -- http://sheetjs.com */ +var frac = function(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array*/ { + 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*/ { + 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; diff --git a/frac.js b/frac.js index 87ffdab..4412674 100644 --- a/frac.js +++ b/frac.js @@ -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; diff --git a/frac.md b/frac.md index 2a44a4c..fc9a89e 100644 --- a/frac.md +++ b/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*/ { ``` 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*/ { ``` > 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 ``` diff --git a/frac.py b/frac.py new file mode 100644 index 0000000..af8d5d0 --- /dev/null +++ b/frac.py @@ -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] diff --git a/index.html b/index.html new file mode 100644 index 0000000..b596064 --- /dev/null +++ b/index.html @@ -0,0 +1,79 @@ + + + + + + Fraction Live Demo + + + + Fraction Live Demo
+ Source Code
+ Issues? Something look weird? Click here and report an issue
+
+ + + + + + + +
Number:
Denominator Digits:
Use Mixed Fraction:
 
Mediant Fraction:
Aberth Fraction:
+ + + + + diff --git a/misc/spin.sh b/misc/spin.sh new file mode 100755 index 0000000..d57fe41 --- /dev/null +++ b/misc/spin.sh @@ -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 diff --git a/misc/strip_sourcemap.sh b/misc/strip_sourcemap.sh new file mode 100755 index 0000000..c331104 --- /dev/null +++ b/misc/strip_sourcemap.sh @@ -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 diff --git a/package.json b/package.json index 34ae1ee..6d39806 100644 --- a/package.json +++ b/package.json @@ -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" ], diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..84fc761 --- /dev/null +++ b/setup.py @@ -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', + ] +)