fraction
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//@version=5

// © kaigouthro

import kaigouthro/calc/5

//@description Fraction Creation and Basic Operations, To/From Decimal(float), Comparison ( < / == / >), Add / Sub / Mult / Div, Invert polarity +/-, and String output with 2 formats ..
library("fraction")

// function Helpers
// @function Greatet Common Denominator
_gcd(_numerator, _denominator)=>
    _greatestCommon = _numerator  
    _den  = _denominator       
    _swap = _den
    while _den != 0
        _greatestCommon %= _den
        _swap           := _den
        _den            := _greatestCommon
        _greatestCommon := _swap
    math.abs(_greatestCommon)

// @function array Get wrap for readability
_num (_array) => array.get(_array,0)

// @function array Get wrap for readability
_den (_array) => array.get(_array,1)

//@function Fraction Making and growing if desired to a multiple.
normTo(int _numerator, int _denominator, int _val = na) =>
    _arr = array.new<int>(2)
    if _denominator != 0
        if na(_val)
            _arr := array.from(_numerator, _denominator)
        else
            _arr := array.from(_numerator * _val, _denominator * _val)

// @param _numerator   (int) above the line integer ie: ____ of (___ / bottom )
// @param _denominator (int) below the line integer ie: ____ of (top / ______ )
// @param _val         (int) OPTIONAL (for no real reason including it) integer to multiply
// @returns array<int> where index 0 is Numerator, 1 is Denominator
export make       ( int  _numerator, int _denominator, int _val = na) =>
    switch
        _denominator == 1 => normTo( _numerator , 1)
        _numerator   == 0 => normTo( 0          , 1)
        _denominator == 0 => normTo( 1          , 1)
        =>
            _d =       _gcd( _numerator ,     _denominator             )
            switch _d
                1 => normTo( _numerator ,     _denominator      , _val )
                =>   normTo( _numerator / _d, _denominator / _d , _val )

// @function Perform add operation (left adds right onto )
// @param _fraction   (array<int>) left side  Fraction Object
// @param _fraction2  (array<int>) right side Fraction Object
// @returns array<int> where index 0 is Numerator, 1 is Denominator
export add        ( array<int> _fraction , array<int> _fraction2 ) =>  make ( _num(_fraction) * _den(_fraction2) + _den(_fraction) * _num(_fraction2), _den(_fraction) * _den(_fraction2))

// @function Perform subtract operation (left subtracts right from )
// @param _fraction   (array<int>) left side  Fraction Object
// @param _fraction2  (array<int>) right side Fraction Object
// @returns array<int> where index 0 is Numerator, 1 is Denominator
export subtract   ( array<int> _fraction , array<int> _fraction2 ) =>  make ( _num(_fraction) * _den(_fraction2) - _den(_fraction) * _num(_fraction2), _den(_fraction) * _den(_fraction2))

// @function Perform multiply operation (left multiplies by right )
// @param _fraction   (array<int>) left side  Fraction Object
// @param _fraction2  (array<int>) right side Fraction Object
// @returns array<int> where index 0 is Numerator, 1 is Denominator
export multiply   ( array<int> _fraction , array<int> _fraction2 ) =>  make ( _num(_fraction) * _num(_fraction2) , _den(_fraction) * _den(_fraction2))

// @function Perform divide operation (left divides by right )
// @param _fraction   (array<int>) left side  Fraction Object
// @param _fraction2  (array<int>) right side Fraction Object
// @returns array<int> where index 0 is Numerator, 1 is Denominator
export divide     ( array<int> _fraction , array<int> _fraction2 ) =>  make ( _num(_fraction) * _den(_fraction2) , _den(_fraction) * _num(_fraction2))

// @function Perform Negative number inversion ie: (-1/2 => 1/2) or (3/5 => -3/5)
// @param _fraction   (array<int>) Fraction Object to invert to/from negative
// @returns array<int> where index 0 is Numerator, 1 is Denominator
export negative   ( array<int> _fraction                         ) =>  make (-_num(_fraction) , _den(_fraction))

// @function Check if first fraction is smaller
// @param    _fraction   (array<int>) left side  Fraction Object
// @param    _fraction2  (array<int>) right side Fraction Object
// @returns True if smaller, false if bigger
export isSmaller  ( array<int> _fraction , array<int> _fraction2 ) => _num(_fraction) *_den(_fraction2) < _num(_fraction2) *_den(_fraction)

// @function Check if first fraction is larger
// @param    _fraction   (array<int>) left side  Fraction Object
// @param    _fraction2  (array<int>) right side Fraction Object
// @returns True if smaller, false if bigger
export isLarger   ( array<int> _fraction , array<int> _fraction2 ) => _num(_fraction) *_den(_fraction2) > _num(_fraction2) *_den(_fraction)

// @function Check if first fraction is equal
// @param    _fraction   (array<int>) left side  Fraction Object
// @param    _fraction2  (array<int>) right side Fraction Object
// @returns True if smaller, false if bigger
export isEqual    ( array<int> _fraction , array<int> _fraction2 ) => _num(_fraction) *_den(_fraction2) == _num(_fraction2) *_den(_fraction)

// @function Convert Decimal to Fractioin array
// note :   this is my own Negative Number Capable (tiny speed loss)
//          adaptation of the fastest algo out there
//          Exclusive for Tradingview.
// @param  _input (float)    Decimal Input
// @param  _epsilon (int)    (OPTIONAL) to precision 0's after dec 0.0000 -> epsilon 0's
// @param  _iterations (int) (OPTIONAL) Maximum iterations Till give up
// @returns array<int> where index 0 is Numerator, 1 is Denominator
export fromDec    ( float _source, int _epsilon = 9, int _iterations = 20) =>
    switch 
        int (_source) == _source => make(int(_source), 1)
        na  (_source)  => make(0 , 1)
        =>
            _num = _source
            _pol = math.sign(_num)
            _z = math.abs(_num), _n = 1, _t = 1
            _d = array.new<int>(_iterations+2,0)
            array.set(_d,1,1)
            while _t < _iterations and calc.gapSize(_n/array.get(_d,_t), _num) > math.pow(10, -_epsilon)
                _t  += 1
                _z  := math.sign(_z)/calc.gapSize(_z,int(_z))
                array.set(_d,_t   , array.get(_d,_t-1) * math.abs(int(_z)) + array.get(_d,_t-2))
                _n := int(_num * array.get(_d,_t  ) + _pol/2)
            make (_n,array.get(_d,_t))

// @function Convert Fraction to Decimal Output
// @returns Float of fration
export toDec      ( array<int> _fraction) =>
    _num(_fraction) / _den(_fraction)

// @function Create "A/B" or "A and B/C" String Value of Fraction.
// @param   _fraction  (array<int>) Fraction Object to invert to/from negative
// @returns String as  (-)? A and B/C format
export toString   ( array<int>      _fraction , bool _wholenum = false) =>
    _num       =  _num   ( _fraction )
    _den       =  _den   ( _fraction )
    switch _den
        0  =>  str.format  ('{0} / {1}', 1    , 0    )
        1  =>  str.format  ('{0}'      , _num        )
        => 
            if _wholenum
                _dec       =  _num / _den
                _whole     =  int    ( na )
                if math.abs(_dec)    >  1 
                    _whole := int    ( _num  / _den   )
                    _f     =  fromDec( _dec  - _whole )
                    _num   := math.abs(_num   ( _f  ) )
                    _den   := math.abs(_den   ( _f  ) )
                (na(_whole) ? '' : str.tostring(_whole) + ' and ') + str.format  ('{0} / {1}', _num , _den )
            else 
                str.format  ('{0} / {1}', _num , _den )

// frac from ints
a = make   ( input ( 20 ) , input (11)  )
// frac from dec
b = fromDec( input ( 5  ) / input (14)  )

// check all and string
_str = " Fractions =>  op \/  A XXX B \n"   +
       "\n A ="+toString(a) + " ||   B ="  +  toString   (b     ) +
       "\n\n\n A add B   ||| => "   + toString    (add        ( a ,b)) +
       "\n A subtract B  ||| => "   + toString    (subtract   ( a ,b)) +
       "\n A multiply B  ||| => "   + toString    (multiply   ( a ,b), true) +
       "\n A divide B    ||| => "   + toString    (divide     ( a ,b), true) +
       "\n A negative    ||| => "   + toString    (negative   ( a   )) +
       "\n A is Smaller than B  "  + str.tostring(isSmaller  ( a ,b)) +
       "\n A is Larger than B  "  + str.tostring(isLarger   ( a ,b)) +
       "\n A is Equal to B   "  + str.tostring(isEqual    ( a ,b)) 

if barstate.islastconfirmedhistory
    label.new(last_bar_index, 0, _str , size = size.huge, color = color.white)
    


