<?php

/**
 * Math
 *
 * @author Alix Axel
 */

class Math
{
    public 
$bcmath_enabled false;
    public 
$gmp_enabled false;

    
/**
     * Returns the absolute value of an arbitrary precision number.
     *
     * @param mixed $number
     * @return mixed
     */
    
function Absolute($number)
    {
        if (
$number[0] == '-')
        {
            
$number substr($number1strlen($number));
        }

        return 
$number;
    }

    
/**
     * Returns the sum of two arbitrary precision numbers.
     *
     * @param mixed $number1
     * @param mixed $number2
     * @return mixed
     */
    
function Add($number1$number2)
    {
        if (
$this->gmp_enabled === true)
        {
            if ((
$this->Is_Integer($number1)) && ($this->Is_Integer($number2)))
            {
                return 
gmp_strval(gmp_add($number1$number2));
            }
        }

        if (
$this->bcmath_enabled === true)
        {
            return 
$this->Clean_Decimals(bcadd($number1$number2));
        }

        return 
$number1 $number2;
    }

    
/**
     * Retuns the average of all values contained in an array.
     *
     * @param array $array
     * @return mixed
     */
    
function Average($array)
    {
        if (!
is_array($array))
        {
            return 
false;
        }

        
$result 0;

        foreach (
$array as $element)
        {
            
$result $this->Add($result$element);
        }

        return 
$this->Divide($resultcount($array));
    }

    
/**
     * Rounds up the value, if necessary.
     *
     * @param mixed $number
     * @param int $precision
     * @return mixed
     */
    
function Ceil($number$precision 0)
    {
        return 
$this->Round($number$precision'ceil');
    }

    
/**
     * Drops all consecutive zeros at the end of a float.
     *
     * @param mixed $number
     * @return mixed
     */
    
function Clean_Decimals($number)
    {
        
$number explode('.'$number);

        if (
count($number) > 1)
        {
            
$number[1] = preg_replace('/0*$/'''$number[1]);

            if (empty(
$number[1]))
            {
                unset(
$number[1]);
            }
        }

        return 
implode('.'$number);
    }

    
/**
     * Compares two arbitrary precision numbers.
     *
     * @param mixed $number1
     * @param string $operator
     * @param mixed $number2
     * @return bool
     */
    
function Compare($number1$operator$number2)
    {
        
settype($number1'string');
        
settype($number2'string');

        if (!
in_array($operator, array('===''==''=''!==''!=''<>''>=''<=''>''<')))
        {
            return 
false;
        }

        if ((
$operator == '===') || ($operator == '==') || ($operator == '='))
        {
            return (
$number1 === $number2);
        }

        else if ((
$operator == '!==') || ($operator == '!=') || ($operator == '<>'))
        {
            return (
$number1 !== $number2);
        }

        else if (
$operator == '>=')
        {
            if ((
$this->Compare($number1'>'$number2)) || ($this->Compare($number1'=='$number2)))
            {
                return 
true;
            }

            return 
false;
        }

        else if (
$operator == '<=')
        {
            if ((
$this->Compare($number1'<'$number2)) || ($this->Compare($number1'=='$number2)))
            {
                return 
true;
            }

            return 
false;
        }

        else
        {
            if (
$this->Compare($number1'=='$number2))
            {
                return 
false;
            }

            if ((
$this->Is_Positive($number1)) && ($this->Is_Negative($number2)))
            {
                switch (
$operator)
                {
                    case 
'>':
                        return 
true;
                    break;

                    case 
'<':
                        return 
false;
                    break;
                }
            }

            else if ((
$this->Is_Negative($number1)) && ($this->Is_Positive($number2)))
            {
                switch (
$operator)
                {
                    case 
'>':
                        return 
false;
                    break;

                    case 
'<':
                        return 
true;
                    break;
                }
            }

            
$number1 explode('.'$this->Absolute($number1));
            
$number2 explode('.'$this->Absolute($number2));

            if (
strlen($number1[0]) > strlen($number2[0]))
            {
                switch (
$operator)
                {
                    case 
'>':
                        return 
true;
                    break;

                    case 
'<':
                        return 
false;
                    break;
                }
            }

            else if (
strlen($number1[0]) < strlen($number2[0]))
            {
                switch (
$operator)
                {
                    case 
'>':
                        return 
false;
                    break;

                    case 
'<':
                        return 
true;
                    break;
                }
            }

            
$float_precision 0;
            
$max_integer_lenght strlen(PHP_INT_MAX) - 1;

            if (
$this->Compare($number1[0], '=='$number2[0]) && $this->Compare($number1[1], '!='$number2[1]))
            {
                
$index 1;

                
$float_precision min(strlen($number1[1]), strlen($number2[1]));

                
$number1 explode('.'$this->Round(implode('.'$number1), $float_precision));
                
$number2 explode('.'$this->Round(implode('.'$number2), $float_precision));
            }

            else
            {
                
$index 0;
            }

            
$number1[$index] = str_split($number1[$index], $max_integer_lenght);
            
$number2[$index] = str_split($number2[$index], $max_integer_lenght);

            if (
$index === 0)
            {
                
$segments max(count($number1[$index]), count($number2[$index]));
            }

            else
            {
                
$segments min(count($number1[$index]), count($number2[$index]));
            }

            
$result = array();

            if (
$operator == '>')
            {
                for (
$i 0$i $segments$i++)
                {
                    if (
$number1[$index][$i] > $number2[$index][$i])
                    {
                        
$result[] = true;
                    }

                    else if (
$number1[$index][$i] < $number2[$index][$i])
                    {
                        
$result[] = false;
                    }
                }
            }

            else if (
$operator == '<')
            {
                for (
$i 0$i $segments$i++)
                {
                    if (
$number1[$index][$i] < $number2[$index][$i])
                    {
                        
$result[] = true;
                    }

                    else if (
$number1[$index][$i] > $number2[$index][$i])
                    {
                        
$result[] = false;
                    }
                }
            }

            if (
count($result) > 0)
            {
                return 
$result[0];
            }

            return 
false;
        }
    }

    
/**
     * Returns the division of two arbitrary precision numbers.
     *
     * @param mixed $number1
     * @param mixed $number2
     * @return mixed
     */
    
function Divide($number1$number2)
    {
        if (
$this->gmp_enabled === true)
        {
            if ((
$this->Is_Integer($number1)) && ($this->Is_Integer($number2)))
            {
                return 
gmp_strval(gmp_div_q($number1$number2)) . $this->Float($this->Modulo($number1$number2) / $number2true);
            }
        }

        if (
$this->bcmath_enabled === true)
        {
            return 
$this->Clean_Decimals(bcdiv($number1$number2));
        }

        return 
$number1 $number2;
    }

    
/**
     * Returns the factorial of the number.
     *
     * @param mixed $number
     * @return mixed
     */
    
function Factorial($number)
    {
        if (
$this->Is_Float($number))
        {
            return 
false;
        }

        if (
$this->gmp_enabled === true)
        {
            
$result gmp_strval(gmp_fact($number));
        }

        else
        {
            
$result 1;

            for (
$i 2$this->Compare($i'<='$number); $i $this->Add($i1))
            {
                
$result $this->Multiply($result$i);
            }
        }

        return 
$result;
    }

    
/**
     * Returns the float part of the number, if exists.
     *
     * @param mixed $number
     * @param bool $include_dot
     * @return mixed
     */
    
function Float($number$include_dot false)
    {
        
$result explode('.'$number);

        if ((isset(
$result[1])) && (!empty($result[1])))
        {
            if (
$include_dot === true)
            {
                
$result[1] = '.' $result[1];
            }

            return 
$result[1];
        }

        return 
false;
    }

    
/**
     * Rounds down the value, if necessary.
     *
     * @param mixed $number
     * @param int $precision
     * @return mixed
     */
    
function Floor($number$precision 0)
    {
        return 
$this->Round($number$precision'floor');
    }

    
/**
     * Returns the integer part of the number.
     *
     * @param mixed $number
     * @return mixed
     */
    
function Integer($number)
    {
        
$result explode('.'$number);

        return 
$result[0];
    }

    
/**
     * Finds whether the number is a float.
     *
     * @param mixed $number
     * @return bool
     */
    
function Is_Float($number)
    {
        if (
strpos($number'.') === false)
        {
            return 
false;
        }

        return 
true;
    }

    
/**
     * Finds whether the number is a integer.
     *
     * @param mixed $number
     * @return bool
     */
    
function Is_Integer($number)
    {
        if (
strpos($number'.') === false)
        {
            return 
true;
        }

        return 
false;
    }

    
/**
     * Finds whether the number is negative.
     *
     * @param mixed $number
     * @return bool
     */
    
function Is_Negative($number)
    {
        if (
$number[0] == '-')
        {
            return 
true;
        }

        return 
false;
    }

    
/**
     * Finds whether the number is positive.
     *
     * @param mixed $number
     * @return bool
     */
    
function Is_Positive($number)
    {
        if (
$number[0] == '-')
        {
            return 
false;
        }

        return 
true;
    }

    
/**
     * Math Constructor.
     *
     * @return Math
     */
    
function Math()
    {
        if (
extension_loaded('gmp'))
        {
            
$this->gmp_enabled true;
        }

        if (
extension_loaded('bcmath'))
        {
            
$this->bcmath_enabled true;

            
bcscale(ini_get('precision'));
        }
    }

    
/**
     * Returns the modulos of two arbitrary precision numbers.
     *
     * @param mixed $number1
     * @param mixed $number2
     * @return mixed
     */
    
function Modulo($number1$number2)
    {
        if (
$this->gmp_enabled === true)
        {
            if ((
$this->Is_Integer($number1)) && ($this->Is_Integer($number2)))
            {
                return 
gmp_strval(gmp_mod($number1$number2));
            }
        }

        if (
$this->bcmath_enabled === true)
        {
            return 
$this->Clean_Decimals(bcmod($number1$number2));
        }

        return 
$number1 $number2;
    }

    
/**
     * Returns the product of two arbitrary precision numbers.
     *
     * @param mixed $number1
     * @param mixed $number2
     * @return mixed
     */
    
function Multiply($number1$number2)
    {
        if (
$this->gmp_enabled === true)
        {
            if ((
$this->Is_Integer($number1)) && ($this->Is_Integer($number2)))
            {
                return 
gmp_strval(gmp_mul($number1$number2));
            }
        }

        if (
$this->bcmath_enabled === true)
        {
            return 
$this->Clean_Decimals(bcmul($number1$number2));
        }

        return 
$number1 $number2;
    }

    
/**
     * Format a number with grouped thousands.
     *
     * @param mixed $number
     * @param int $decimal_precision
     * @param string $decimals_separator
     * @param string $thousands_separator
     * @return mixed
     */
    
function Number_Format($number$decimal_precision 0$decimals_separator '.'$thousands_separator ',')
    {
        
$is_negative false;

        
$number $this->Round($number$decimal_precision);

        if (
$this->Is_Negative($number))
        {
            
$is_negative true;

            
$number str_replace('-'''$number);
        }

        
$number explode('.'str_replace(' '''$number));

        if (
function_exists('str_split'))
        {
            
$number[0] = str_split(strrev($number[0]), 3);
        }

        else
        {
            
$number[0] = chunk_split(strrev($number[0]), 3);
        }

        
$number[0] = strrev(implode($thousands_separator$number[0]));

        if (
$is_negative === true)
        {
            
$number[0] = '-' $number[0];
        }

        
$number implode($decimals_separator$number);

        return 
$number;
    }

    
/**
     * Returns an arbitrary precision base raised to power.
     *
     * @param mixed $base
     * @param mixed $power
     * @return mixed
     */
    
function Power($base$power 2)
    {
        if (
$this->gmp_enabled === true)
        {
            if ((
$this->Compare($power'>'0)) && ($this->Is_Integer($base)) && ($this->Is_Integer($power)))
            {
                return 
gmp_strval(gmp_pow($base$power));
            }
        }

        if (
$this->bcmath_enabled === true)
        {
            if (
$this->Is_Integer($power))
            {
                return 
bcpow($base$power);
            }
        }

        if (
$base 0)
        {
            return 
pow($base$power);
        }

        else
        {
            
$base $this->Absolute($base);

            if (
$this->Compare($this->Modulo($power2), '=='0))
            {
                return 
pow($base$power);
            }

            else
            {
                return 
'-' pow($base$power);
            }
        }
    }

    
/**
     * Returns a random number.
     *
     * @param int $digits
     * @return mixed
     */
    
function Random($digits 8)
    {
        if (
$this->Compare($digits'<='strlen(PHP_INT_MAX)))
        {
            return 
mt_rand(str_repeat(0$digits 1), str_repeat(9$digits));
        }

        else
        {
            if (
$this->gmp_enabled === true)
            {
                
$i 1;

                
$result gmp_strval(gmp_random(pow(2$i)));

                while (
$this->Compare(strlen($result), '<'$digits))
                {
                    
$result gmp_strval(gmp_random(pow(2$i $this->Add($i1))));
                }

                return 
substr($result0$digits);
            }

            return 
false;
        }
    }

    
/**
     * Returns the index root of a number.
     *
     * @param mixed $number
     * @param mixed $index
     * @return mixed
     */
    
function Root($number$index 2)
    {
        if (
$this->Is_Integer($index))
        {
            
$index $this->Divide(1$index);
        }

        return 
$this->Power($number$index);
    }

    
/**
     * Rounds the number, if necessary.
     *
     * @param mixed $number
     * @param int $precision
     * @param string $type
     * @return mixed
     */
    
function Round($number$precision 0$type null)
    {
        if (
$this->Is_Float($number))
        {
            
$number explode('.'$number);

            if (
$precision 0)
            {
                
$precision 0;
            }

            if (
$precision == 0)
            {
                if (
$type == 'ceil')
                {
                    return 
$this->Add($number[0], 1);
                }

                else if (
$type == 'floor')
                {
                    return 
$number[0];
                }

                else
                {
                    if (
$number[1][0] >= 5)
                    {
                        return 
$this->Add($number[0], 1);
                    }

                    else
                    {
                        return 
$number[0];
                    }
                }
            }

            else
            {
                if (
$type == 'ceil')
                {
                    return 
$number[0] . '.' $this->Add(substr($number[1], 0$precision), 1);
                }

                else if (
$type == 'floor')
                {
                    return 
$number[0] . '.' substr($number[1], 0$precision);
                }

                else
                {
                    if (
$number[1][$precision] >= 5)
                    {
                        return 
$number[0] . '.' $this->Add(substr($number[1], 0$precision), 1);
                    }

                    else
                    {
                        return 
$number[0] . '.' substr($number[1], 0$precision);
                    }
                }
            }
        }

        return 
$this->Clean_Decimals($number);
    }

    
/**
     * Returns the difference of two arbitrary precision numbers.
     *
     * @param mixed $number1
     * @param mixed $number2
     * @return mixed
     */
    
function Subtract($number1$number2)
    {
        if (
$this->gmp_enabled === true)
        {
            if ((
$this->Is_Integer($number1)) && ($this->Is_Integer($number2)))
            {
                return 
gmp_strval(gmp_sub($number1$number2));
            }
        }

        if (
$this->bcmath_enabled === true)
        {
            return 
$this->Clean_Decimals(bcsub($number1$number2));
        }

        return 
$number1 $number2;
    }
}

highlight_file(__FILE__);

?>