TPA - Total Precision Arithmetic
tpa.js performs basic arithmetic operations with total precision.
The main features are:
- Simplicity - one library, add/subtract/multiply/divide
- Performance - optimised to perform operations reasonably fast (see below)
- Limitless - represents and operates on rational numbers of any size and precision
- Expressive - inputs/outputs numbers using decimal or fractional forms
- Quality - comprehensively tested and documented
For a terse list of methods go to the end of this readme. The usage section below is more descriptive.
There are many similar libraries available. I wrote this more as an exercise than anything else. Enjoy.
To install it:
npm install TPA
To see how to use it:
npm docs TPA
To code with it:
var Tpa = ;var n='3 1/3';console; // Outputs '3.'
To install it:
- Download tpa.min.js from GitHub or use their CDN for my latest version: v1.0.14
tpa.min.jsis a UMD (Universal Module Definition) bundle with an export name of
To see how to use it: http://dthwaite.github.io/tpa/
To code with it:
<script src ="tpa.min.js"></script><script>var n='3 1/3';console; // Outputs '3.'</script>
Review Coverage (upon successful test):
npm run coverage
Lint (to ensure no
npm run lint
Build minified version for browser into lib/tpa.min.js:
npm run build
A note about performance
How fast is this library compared to others? Good question. And tricky to answer. It all depends on the operation, the size of the numbers, whether they are fractional or not (many libraries just do integers), whether you call static or instance methods and your run time environment. I spent some time comparing and contrasting and there's no straight answer. Most of the time this library performs quite well in comparison. Sometimes wildy better than most, sometimes not so good and it's difficult to summarise. If performance is really important then you must do your own analysis specific to your environment and needs to then choose the fastest in your circumstance. If it's not that important then you could do a lot worse than choosing this one. I've focussed on delivering a healthy mix of the features listed earlier. It's not slow, by any means.
var n1=; // new integer set to zerovar n2=123; // new integer set to 123var n3=2135; // new fraction set to 123.5var n4='123'; // new integer set to 123var n5='123.3'; // new fraction set to 123 1/3var n6='123 1/3'; // new fraction set to 123 1/3var n7='-4 538/1284'; // new fraction set to to -4.41900311...var n8='-.2'; // new fractionn8; // Sets an existing number to a new valuen8; // resets an existing number to zeron8; // resets an existing number 4.41900311...n8; // Sets an existing number to equal another (takes a copy)
As can be seen above, setting a number with a string representation is the best way as you can express any rational number with complete accuracy using either a decimal form (with optional recurring digits) or a fractional form.
Numbers can be output in decimal (
toDecimal()) or fractional (
toFraction()) form. Decimal places are limited to 100 unless specified in the
console; // '0'console; // '123'console; // 123.0console; // '123.5'console; // '123.5' (alias for toString())console; // '123 5/10'n3;console; // '123 1/2'console; // '123.console; // '123 30/90'console; // '-4 538/1284'n7;console; // '-4 269/642'console; // '-4.4'console; // '-4.41900311526479750778...' (limit dp's to 20)
Note that there is a
divide() all operate in-situ on the number on which they are called. They return the number to allow for chaining of operations. Each takes a parameter that may either be an existing
Tpa object (which is not changed) or a number or string that is a valid representation. Aliases for the above are:
console; // '246'console; // '123'n2;console; // '-77'n2;console; // '123'console; // '26331.' (123 3/9 * 123.5)n5;console; // '123.'n5;console; // '-1'
Integer and Fractions
Tpa numbers are declared either integer or fractional. If integer then all operations performed on them will only use the integer part of their operands. Whether a number is integer or fractional is inferred from its initialisation. But you can force the issue by passing
true (for integer) or
false (for fractional) as an additional parameter to the
var a=3; // Constructs a to be integervar b=78; // Constructs b to be fractionala;console; // '10' (a is an integer and ignores fractional operands)var c=3false; // Explicitly set a to be fractionalc;console; // '10.8' (c is fractional and so operates on fractional operands)var d=btrue; // Explicitly set d to be integerconsole; // '7' (d was constructed to ignore any fractional part)var e='23 100/23'true; // Explicitly set e to be integerconsole; // 27 (e took on the integer evaluation of the initialising string)console; // Sets an existing number to a new value and to be fractional
You can find out what type a number is with the
isFractional() methods and you can convert a number to one or other representation with the
var a='33 2/3';console; // falseconsole; // 33console; // truevar b=10;console; // -1.5console; // trueconsole; // '-1'
The reason Tpa makes a distinction is that a common requirement is simply to deal with integers. Processing fractions for the four main operations is a significant overhead. In fact it can quite quickly lead to massive numerators and denominators in fractional parts of numbers in order to maintain total precision. Keep this in mind.
Fractions are never automatically simplified. However, the
simplify() method makes its best attempt to simplify the fractional part of a number. Reducing a very large fraction is compute intensive (just as well because most encryption mechanisms rely on this fact!) as it essentially involves trying to find common prime factors.
var n='1/3';n;console; // '0.0164924626838031038186599...'console; // '0 67626900/4100473125'console; // true - indicates that simplification was fully achievedconsole; // '0 11132/674975'n='234789789167435342333343/4239123411142533478912';console; // false - defaults to 100 ms which is probably not enough timeconsole; // '55 1638001554596000993183/4239123411142533478912'console; // true - achieved full simplificationconsole; // '55 1638001554596000993183/4239123411142533478912'
It is often the case that the fractional form is more terse than a decimal read out. The decimal form of the number resulting from that chain of computations has a recurring decimal section of 252 digits while the simplified fraction involves considerably less digits.
simplify() method does not look for any common prime factors above 33,554,393. It also limits its computation to 100 milliseconds. You can bypass this by passing in the number of milliseconds you are prepared to wait or 0 to indicate no limit. The system builds up its own inventory of prime numbers and it may take several seconds to simplify the first time (assuming you permit it) as it creates this inventory. But subsequent simplifications will generally be achieved within one second.
simplify() returns true if the fraction has been fully simplified otherwise it may or may not have been fully simplified as either the fraction may have a common factor above 33,554,393 or time has run out.
There are a selection of comparison methods, namely:
var a=;var b=;var c=;var d=;var f=;console; // falseconsole; // trueconsole; // false (it's zero)console; // falseconsole; // false (a is an integer and ignores fractional operands)console; // trueconsole; // true (they are equal)console; // true (ditto)console; // falseconsole; // true
sign()returns -1, 0 or 1 if the number is negative, zero or positive respectively
hasFraction()return true if the number has a non zero fractional part
frac()removes the integer value from the number
int()removes the fractional value from the number
modulus()set this number to the modulus of the number passed in
abs()set this number to its absolute value
console; // -1console; // trueconsole; // '-0 1/3'console; // '-3'console; // '1'console; // 33.5
Typically the arithmetical operations change the number on which they are called. Alternatively you can choose to not mutate existing numbers to return a new number which is the result of the operation. This is achieved with static functions as follows:
Tpa.add(a,b)adds a and b and returns the result in a new number (aliases:
Tpa.subtract(a,b)subtracts b from a and returns the result in a new number (aliases:
Tpa.multiply(a,b)multiplies two numbers and returns the result in a new number (aliases:
Tpa.divide(a,b)divides a by b and returns the result in a new number (aliases:
Tpa.modulus(a,b)performs a modulus b and returns the result in a new number (aliases:
Tpa.frac(a)takes the fractional part of a and returns it in a new number
Tpa.int(a)takes the integer part of a and returns it in a new number
Tpa.abs(a)takes the absolute value of a and returns it in a new number
var a=;var b=;console; // 17console; // -7console; // 60console;// '2 25/50'console; // 5console; // 0.5console; // 12console; // 23
Note that for methods that take two arguments the first one dictates whether the result is integer or fractional. Thus the subtraction of 12.5 from 5 yields 7 as the operation is only working on integer parts. As opposed to the division that takes the type of b which is fractional.
Construction and mutators take numbers as parameters in the following forms:
- Tpa object
Construction & setting
Note that all mutators are available as static methods to preserve the original value as per this example
var x=;var y=;Tpa; // Returns a new number = x/y, x and y remain unchangedx; // Returns x having been divided by y, only y remains unchanged