diff --git a/HISTORY.md b/HISTORY.md index 03be86083..7053c71ac 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,7 @@ ## not yet released, version 2.0.0-SNAPSHOT +- Implemented support for fractions, powered by the library `fraction.js`. - Implemented matrix LU decomposition with partial pivoting and a LU based linear equations solver (functions `lup` and `lusolve`). Thanks @rjbaucells. - Large internal refactoring, allowing to create custom bundles of math.js. diff --git a/README.md b/README.md index 8aea6d1ba..b5fe97555 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Powerful and easy to use. ## Features -- Supports numbers, big numbers, complex numbers, units, strings, arrays, and matrices. +- Supports numbers, big numbers, complex numbers, fractions, units, strings, arrays, and matrices. - Is compatible with JavaScript's built-in Math library. - Contains a flexible expression parser. - Supports chained operations. diff --git a/docs/configuration.md b/docs/configuration.md index d48902615..500bad05e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -24,12 +24,14 @@ The following configuration options are available: inputs, a matrix will be returned always. - `number`. The default type of numbers. This setting is used by functions - like `eval `which cannot determine the correct type of output from the + like `eval` which cannot determine the correct type of output from the functions input. For most functions though, the type of output is determined from the the input: a number as input will return a number as output, a BigNumber as input returns a BigNumber as output. - Available values are: `'number'` (default) or `'bignumber'`. - BigNumbers have higher precision than the default numbers of JavaScript. + Available values are: `'number'` (default), `'bignumber'`, or `'fraction'`. + [BigNumbers](./datatypes/bignumbers.js) have higher precision than the default + numbers of JavaScript, and [`Fractions`](./datatypes/fractions.js) store + values in terms of a numerator and denominator. - `precision`. The maximum number of significant digits for bigNumbers. This setting only applies to BigNumbers, not to numbers. @@ -76,7 +78,7 @@ math2.range(0, 4); // Matrix [0, 1, 2, 3] // create an instance of math.js with bignumber configuration var bigmath = math.create({ - number: 'bignumber', // Choose 'number' (default) or 'bignumber' + number: 'bignumber', // Choose 'number' (default), 'bignumber', or 'fraction' precision: 32 // 64 by default, only applicable for BigNumbers }); @@ -110,7 +112,7 @@ bigmath.eval('1 / 3'); // BigNumber, 0.33333333333333333333333333333333 // create a new instance of math.js with bignumber configuration var bigmath = math.create({ - number: 'bignumber', // Choose 'number' (default) or 'bignumber' + number: 'bignumber', // Choose 'number' (default), 'bignumber', or 'fraction' precision: 32 // 64 by default, only applicable for BigNumbers }); diff --git a/docs/datatypes/bignumbers.md b/docs/datatypes/bignumbers.md index f36474cde..aedd98570 100644 --- a/docs/datatypes/bignumbers.md +++ b/docs/datatypes/bignumbers.md @@ -21,7 +21,8 @@ BigNumbers instead of [numbers](numbers.md) by default, configure math.js like: ```js math.config({ - number: 'bignumber', // Default type of number: 'number' (default) or 'bignumber' + number: 'bignumber', // Default type of number: + // 'number' (default), 'bignumber', or 'fraction' precision: 64 // Number of significant digits for BigNumbers }); @@ -83,21 +84,21 @@ console.log(ans.toString()); ## Conversion BigNumbers can be converted to numbers and vice versa using the functions -`number` and `bignumber`. When converting a BigNumber to a Number, the high +`number` and `bignumber`. When converting a BigNumber to a number, the high precision of the BigNumber will be lost. When a BigNumber is too large to be represented as Number, it will be initialized as `Infinity`. ```js // converting numbers and BigNumbers -var a = math.number(0.3); // Number, 0.3 +var a = math.number(0.3); // number, 0.3 var b = math.bignumber(a); // BigNumber, 0.3 -var c = math.number(b); // Number, 0.3 +var c = math.number(b); // number, 0.3 // exceeding the maximum of a number var d = math.bignumber('1.2e500'); // BigNumber, 1.2e+500 -var e = math.number(d); // Number, Infinity +var e = math.number(d); // number, Infinity // loosing precision when converting to number var f = math.bignumber('0.2222222222222222222'); // BigNumber, 0.2222222222222222222 -var g = math.number(f); // Number, 0.2222222222222222 +var g = math.number(f); // number, 0.2222222222222222 ``` diff --git a/docs/datatypes/fractions.md b/docs/datatypes/fractions.md new file mode 100644 index 000000000..5c424af75 --- /dev/null +++ b/docs/datatypes/fractions.md @@ -0,0 +1,66 @@ +# Fractions + +For calculations with fractions, math.js supports a `Fraction` data type. +Fraction support is powered by [fraction.js](https://github.com/infusion/Fraction.js). +Unlike [numbers](numbers.md) and [BigNumbers](./bignumbers.md), fractions can +store numbers with infinitely repeating decimals, for example `1/3 = 0.3333333...`, +which can be represented as `0.(3)`, or `2/7` which can be represented as `0.(285714)`. + + +## Usage + +A Fraction can be created using the function `fraction`: + +```js +math.fraction('1/3'); // Fraction, 0.(3) +math.fraction(1, 3); // Fraction, 0.(3) +math.fraction('0.(3)'); // Fraction, 0.(3) +``` + +And can be used in functions like `add` and `multiply` like: + +```js +math.add(fraction('1/3'), fraction('1/6')); // Fraction, 0.5 +math.multiply(fraction('1/4'), fraction('1/2')); // Fraction, 0.125 +``` + +Note that not all functions support fractions. For example trigonometric +functions doesn't support fractions. When not supported, the functions +will convert the input to numbers and return a number as result. + +Most functions will determine the type of output from the type of input: +a number as input will return a number as output, a Fraction as input returns +a Fraction as output. Functions which cannot determine the type of output +from the input (for example `math.eval`) use the default number type `number`, +which can be configured when instantiating math.js. To configure the use of +fractions instead of [numbers](numbers.md) by default, configure math.js like: + +```js +// Configure the default type of number: 'number' (default), 'bignumber', or 'fraction' +math.config({ + number: 'fraction' +}); + +// use math +math.eval('0.1 + 0.2'); // Fraction, 0.3 +``` + + +## Conversion + +Fractions can be converted to numbers and vice versa using the functions +`number` and `fraction`. When converting a Fraction to a number, precision +may be lost when the value cannot represented in 16 digits. + +```js +// converting numbers and fractions +var a = math.number(0.3); // number, 0.3 +var b = math.fraction(a); // Fraction, 0.3 +var c = math.number(b); // number, 0.3 + +// loosing precision when converting to number: a fraction can represent +// a number with an infinite number of repeating decimals, a number just +// stores about 16 digits and cuts consecutive digits. +var d = math.fraction('1/3'); // Fraction, 0.(3) +var e = math.number(f); // number, 0.3333333333333333 +``` diff --git a/docs/datatypes/index.md b/docs/datatypes/index.md index 13e5b64c7..5c686c23b 100644 --- a/docs/datatypes/index.md +++ b/docs/datatypes/index.md @@ -11,6 +11,7 @@ The supported data types are: - [Number](numbers.md) - [BigNumber](bignumbers.md) - [Complex](complex_numbers.md) +- [Fraction](fractions.md) - [Array](matrices.md) - [Matrix](matrices.md) - [Unit](units.md) @@ -27,6 +28,9 @@ math.sqrt(4.41e2); // 21 // use BigNumbers math.add(math.bignumber(0.1), math.bignumber(0.2)); // BigNumber, 0.3 +// use Fractions +math.add(math.fraction(1), math.fraction(3)); // Fraction, 0.(3) + // use strings math.add('hello ', 'world'); // 'hello world' math.max('A', 'D', 'C'); // 'D' diff --git a/docs/datatypes/numbers.md b/docs/datatypes/numbers.md index 18a086995..487621e79 100644 --- a/docs/datatypes/numbers.md +++ b/docs/datatypes/numbers.md @@ -16,7 +16,8 @@ can be configured when instantiating math.js: ```js math.config({ - number: 'number' // Default type of number: 'number' (default) or 'bignumber' + number: 'number' // Default type of number: + // 'number' (default), 'bignumber', or 'fraction' }); ``` diff --git a/docs/expressions/syntax.md b/docs/expressions/syntax.md index af0158a4a..0c5ce4d75 100644 --- a/docs/expressions/syntax.md +++ b/docs/expressions/syntax.md @@ -272,7 +272,7 @@ The default number type of the expression parser can be changed at instantiation of math.js. The expression parser parses numbers as BigNumber by default: ```js -// Configure the type of number: 'number' (default) or 'bignumber' +// Configure the type of number: 'number' (default), 'bignumber', or 'fraction' math.config({number: 'bignumber'}); // all numbers are parsed as BigNumber diff --git a/examples/bignumbers.js b/examples/bignumbers.js index 06f69f42c..958805329 100644 --- a/examples/bignumbers.js +++ b/examples/bignumbers.js @@ -5,18 +5,11 @@ var math = require('../index'); // configure the default type of numbers as BigNumbers math.config({ - number: 'bignumber', // Default type of number: 'number' (default) or 'bignumber' + number: 'bignumber', // Default type of number: + // 'number' (default), 'bignumber', or 'fraction' precision: 20 // Number of significant digits for BigNumbers }); -/** - * Helper function to output a value in the console. Value will be formatted. - * @param {*} value - */ -function print (value) { - console.log(math.format(value)); -} - console.log('round-off errors with numbers'); print(math.add(0.1, 0.2)); // Number, 0.30000000000000004 print(math.divide(0.3, 0.2)); // Number, 1.4999999999999998 @@ -38,3 +31,11 @@ console.log('use BigNumbers in the expression parser'); print(math.eval('0.1 + 0.2')); // BigNumber, 0.3 print(math.eval('0.3 / 0.2')); // BigNumber, 1.5 console.log(); + +/** + * Helper function to output a value in the console. Value will be formatted. + * @param {*} value + */ +function print (value) { + console.log(math.format(value)); +} diff --git a/examples/fractions.js b/examples/fractions.js new file mode 100644 index 000000000..316b270b5 --- /dev/null +++ b/examples/fractions.js @@ -0,0 +1,42 @@ +// Fractions + +// load math.js +var math = require('../index'); + +// configure the default type of numbers as Fractions +math.config({ + number: 'fraction' // Default type of number: + // 'number' (default), 'bignumber', or 'fraction' +}); + +console.log('round-off errors with numbers'); +print(math.add(0.1, 0.2)); // Number, 0.30000000000000004 +print(math.divide(0.3, 0.2)); // Number, 1.4999999999999998 +console.log(); + +console.log('no round-off errors with Fractions'); +print(math.add(math.fraction(0.1), math.fraction(0.2))); // Fraction, 0.3 +print(math.divide(math.fraction(0.3), math.fraction(0.2))); // Fraction, 1.5 +console.log(); + +console.log('Represent an infinite number of repeating digits'); +print(math.fraction('1/3')); // Fraction, 0.(3) +print(math.fraction('2/7')); // Fraction, 0.(285714) +print(math.fraction('23/11')); // Fraction, 2.(09) +console.log(); + +// one can work conveniently with fractions using the expression parser. +// note though that Fractions are only supported by basic arithmetic functions +console.log('use fractions in the expression parser'); +print(math.eval('0.1 + 0.2')); // Fraction, 0.3 +print(math.eval('0.3 / 0.2')); // Fraction, 1.5 +print(math.eval('23 / 11')); // Fraction, 2.(09) +console.log(); + +/** + * Helper function to output a value in the console. Value will be formatted. + * @param {*} value + */ +function print (value) { + console.log(math.format(value)); +} diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index f03fdcf3f..5972978b9 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -87,9 +87,14 @@ function factory (type, config, load, typed) { ConstantNode.prototype._compile = function (defs) { switch (this.valueType) { case 'number': - if (defs.math.config().number === 'bignumber') { + // TODO: replace this with using config.number + var numConfig = defs.math.config().number; + if (numConfig === 'bignumber') { return 'math.bignumber("' + this.value + '")'; } + else if (numConfig === 'fraction') { + return 'math.fraction("' + this.value + '")'; + } else { // remove leading zeros like '003.2' which are not allowed by JavaScript return this.value.replace(/^(0*)[0-9]/, function (match, zeros) { diff --git a/test/expression/parse.test.js b/test/expression/parse.test.js index 2c47636fc..66299650e 100644 --- a/test/expression/parse.test.js +++ b/test/expression/parse.test.js @@ -203,6 +203,20 @@ describe('parse', function() { }); + describe('fraction', function () { + + it('should output fractions if default number type is fraction', function() { + var fmath = math.create({ + number: 'fraction' + }); + + assert(parse('0.1').compile(fmath).eval() instanceof math.type.Fraction); + assert.equal(parse('1/3').compile(fmath).eval().toString(), '0.(3)'); + assert.equal(parse('0.1+0.2').compile(fmath).eval().toString(), '0.3'); + }); + + }); + describe('string', function () { it('should parse a string', function() {