TPA

    1.0.14 • Public • Published

    TPA - Total Precision Arithmetic

    Join the chat at https://gitter.im/dthwaite/TPA

    npm Build Status Coverage Status bitHound Overall Score bitHound Code

    tpa.js performs basic arithmetic operations with total precision.

    Available on GitHub, details on JSDocs. See it working: Demonstration

    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.

    Node.js:

    To install it: npm install TPA

    To see how to use it: npm docs TPA

    To code with it:

    var Tpa = require('Tpa');
     
    var n=new Tpa('3 1/3');
    console.log(n.toString()); // Outputs '3.[3]'
    Browser:

    To install it:

    • Download tpa.min.js from GitHub or use their CDN for my latest version: v1.0.14
    • tpa.min.js is a UMD (Universal Module Definition) bundle with an export name of Tpa

    To see how to use it: http://dthwaite.github.io/tpa/

    To code with it:

    <script src ="tpa.min.js"></script>
    <script>
    var n=new Tpa('3 1/3');
    console.log(n.toString()); // Outputs '3.[3]'
    </script>
    Development:

    Test: npm test

    Review Coverage (upon successful test): npm run coverage

    Lint (to ensure no eslint issues): 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.

    Usage

    Set up

    var n1=new Tpa();                // new integer set to zero
    var n2=new Tpa(123);             // new integer set to 123
    var n3=new Tpa(213.5);           // new fraction set to 123.5
    var n4=new Tpa('123');           // new integer set to 123
    var n5=new Tpa('123.3[3]');      // new fraction set to 123 1/3
    var n6=new Tpa('123 1/3');       // new fraction set to 123 1/3
    var n7=new Tpa('-4 538/1284');   // new fraction set to to -4.41900311...
    var n8=new Tpa('-.2[512]');      // new fraction
    n8.set(-9);                      // Sets an existing number to a new value
    n8.set();                        // resets an existing number to zero
    n8.set('-4 538/1284');           // resets an existing number 4.41900311...
    n8.set(n2);                      // 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.

    Outputs

    Numbers can be output in decimal (toDecimal()) or fractional (toFraction()) form. Decimal places are limited to 100 unless specified in the toString() or toDecimal() methods.

    console.log(n1.toString());      // '0'
    console.log(n2.toString());      // '123'
    console.log(n2.value());         // 123.0
    console.log(n3.toString());      // '123.5'
    console.log(n3.toDecimal());     // '123.5' (alias for toString())
    console.log(n3.toFraction());    // '123 5/10'
    n3.simplify();
    console.log(n3.toFraction());    // '123 1/2'
    console.log(n5.toDecimal());     // '123.[3]
    console.log(n5.toFraction());    // '123 30/90'
    console.log(n7.toFraction());    // '-4 538/1284'
    n7.simplify();
    console.log(n7.toFraction());    // '-4 269/642'
    console.log(n7.toDecimal());     // '-4.4[19003115264797507788161993769470404984423676012461059]'
    console.log(n7.toDecimal(20));   // '-4.41900311526479750778...' (limit dp's to 20)

    Note that there is a value() method that gives the number as a javascript floating point number. This will clearly be an approximation in many cases.

    Operations

    Methods add(), subtract(), multiply() and 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: plus(), sub(), minus(), mult(), times(), div().

    console.log(n2.add(n2).toString());         // '246'
    console.log(n2.subtract(123).toString());   // '123'
    n2.subtract('200');
    console.log(n2.toDecimal());                // '-77'
    n2.add(new Tpa(200));
    console.log(n2.toDecimal());                // '123'
    console.log(n5.multiply(n3).toString());    // '26331.[6]' (123 3/9 * 123.5)
    n5.divide(n3);
    console.log(n5.toString());                 // '123.[3]'
    n5.subtract('23 1/3').divide(2).add('48 2/1').divide(-100);
    console.log(n5.toString());                 // '-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 Tpa constructor

    var a=new Tpa(3);                           // Constructs a to be integer
    var b=new Tpa(7.8);                         // Constructs b to be fractional
    a.add(b);
    console.log(a.toString());                  // '10' (a is an integer and ignores fractional operands)
    var c=new Tpa(3,false);                     // Explicitly set a to be fractional
    c.add(b);
    console.log(c.toString());                  // '10.8' (c is fractional and so operates on fractional operands)
    var d=new Tpa(b,true);                      // Explicitly set d to be integer
    console.log(d.toString());                  // '7' (d was constructed to ignore any fractional part)
    var e=new Tpa('23 100/23',true);            // Explicitly set e to be integer
    console.log(e.value());                     // 27 (e took on the integer evaluation of the initialising string)
    console.log(e.set(3,false).value());        // Sets an existing number to a new value and to be fractional

    You can find out what type a number is with the isInteger() and isFractional() methods and you can convert a number to one or other representation with the makeInteger() and makeFractional() methods:

    var a=new Tpa('33 2/3');
    console.log(a.isInteger());                 // false
    console.log(a.makeInteger().value());       // 33
    console.log(a.isInteger());                 // true
    var b=new Tpa(10);
    console.log(b.makeFractional().subtract(11.5).value()); // -1.5
    console.log(b.isFractional());                 // true
    console.log(b.makeInteger().toDecimal());    // '-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=new Tpa('1/3');
    n.multiply('3/5').multiply('9/7').multiply('23/45').multiply('12 45/87').divide('99.75');
    console.log(n.toString(25));                // '0.0164924626838031038186599...'
    console.log(n.toFraction());                // '0 67626900/4100473125'
    console.log(n.simplify());                  // true - indicates that simplification was fully achieved
    console.log(n.toFraction());                // '0 11132/674975'
    n=new Tpa('234789789167435342333343/4239123411142533478912');
    console.log(n.simplify());                  // false - defaults to 100 ms which is probably not enough time
    console.log(n.toFraction());                // '55 1638001554596000993183/4239123411142533478912'
    console.log(n.simplify(0));                 // true - achieved full simplification
    console.log(n.toFraction());                // '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.

    The 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.

    Comparisons

    There are a selection of comparison methods, namely: isZero(), isPositive(), isNegative(), lt(), lte(), gt(), gte() and eq()

    var a=Tpa(3);
    var b=Tpa(3.5);
    var c=Tpa('4 1/4');
    var d=Tpa('3 5/4');
    var f=Tpa();
    console.log(a.isZero());            // false
    console.log(a.isPositive());        // true
    console.log(f.isPositive());        // false (it's zero)
    console.log(b.isNegative());        // false
    console.log(a.lt(b));               // false (a is an integer and ignores fractional operands)
    console.log(a.lt(c));               // true
    console.log(d.lte(c));              // true (they are equal)
    console.log(d.gte(c));              // true (ditto)
    console.log(d.gt(c));               // false
    console.log(d.eq(c));               // true

    Other methods

    • 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.log(Tpa(-3).sign());                        // -1
    console.log(Tpa(3.3).hasFraction());                // true
    console.log(Tpa('-3 1/3').frac().toFraction());     // '-0 1/3'
    console.log(Tpa('-3 1/3').int().toFraction());      // '-3'
    console.log(Tpa(22).modulus(3).toString());         // '1'
    console.log(Tpa(-33.5).abs().value());              // 33.5

    Static methods

    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: plus())
    • Tpa.subtract(a,b) subtracts b from a and returns the result in a new number (aliases: sub() & minus())
    • Tpa.multiply(a,b) multiplies two numbers and returns the result in a new number (aliases: times() & mult())
    • Tpa.divide(a,b) divides a by b and returns the result in a new number (aliases: div())
    • Tpa.modulus(a,b) performs a modulus b and returns the result in a new number (aliases: mod())
    • 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=Tpa(5);
    var b=Tpa(12.5,false);
    console.log(Tpa.add(a,b).value());        // 17
    console.log(Tpa.subtract(a,b).value());   // -7
    console.log(Tpa.multiply(a,b).value());   // 60
    console.log(Tpa.divide(b,a).toFraction());// '2 25/50'
    console.log(Tpa.modulus(a,b).value());    // 5
    console.log(Tpa.frac(b).value());         // 0.5
    console.log(Tpa.int(b).value());          // 12
    console.log(Tpa.abs(-23).value());        // 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.

    Method index

    Construction and mutators take numbers as parameters in the following forms:

    1. Tpa object
    2. Javascript number
    3. Javascript string (decimal or fractional format)
    Construction & setting
    • new Tpa() or Tpa()
    • set()
    Mutators
    • Unary

      • frac()
      • int()
      • abs()
      • makeInteger()
      • makeFractional()
    • Binary

      • add() or plus()
      • subtract() or sub() or minus()
      • multiply() or mult() or times()
      • divide() or div()
      • modulus() or mod()
    Enquirers
    • Unary
      • sign()
      • hasFraction()
      • isZero()
      • isPositive()
      • isNegative()
      • isInteger()
      • isFractional()
    • Binary
      • lt()
      • lte()
      • gt()
      • gte()
      • eq()
    Output
    • toDecimal() or toString()
    • toFraction()
    • value()
    Miscellaneous
    • simplify()

    Note that all mutators are available as static methods to preserve the original value as per this example

    var x=Tpa(100);
    var y=Tpa(50);
    Tpa.divide(x,y);         // Returns a new number = x/y, x and y remain unchanged
    x.divide(y);             // Returns x having been divided by y, only y remains unchanged

    Install

    npm i TPA

    DownloadsWeekly Downloads

    16

    Version

    1.0.14

    License

    MIT

    Last publish

    Collaborators

    • dthwaite