<?php
/*! ----------------------------------------------- *
 *  Clasa LongNum (Long Numbers)
 *  
 *  @Author: Dumitru Uzun (DUzun)
 *  @Web   : http://duzun.teologie.net/
 *      
 * 
 *  ----------------------------------------------- */

/*------------------------------------------------------------------------*/
include_once(dirname(__FILE__).DIRECTORY_SEPARATOR.'PkInts.php');
/*------------------------------------------------------------------------*/
class LongNum extends PkInts
{
    /* $radix indica baza in care se reprezinta numarul la convertirea in/din STRING.
     * La convertirea in numar, $radix == 1 indica date binare (fara transformare).
     * Simbolurile cifrelor sunt cifrele zecimale si literele alfabetului Englez (36 simboluri).  
     */     

    //static $digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    static public $radix = 10; // baza implicitta
    static public $carry = 0;          // Valoarea de transport
  
/*------------------------------------------------------------------------*/
    /*! Obtine cifra pentru valoarea $nm (in baza 36) */
    static function val2digi($nm) 
    {
       //return self::$digits[$nm];
       return chr((($r=$nm+0x30)>0x39)?($r+0x07):$r) ;
    }
    
    /*! Obtine valoarea cifrei $ch (in baza 36) */
    static function digi2val($ch)
    {
       //return strpos(self::$digits, strtoupper($ch));
       $ch = ord($ch[0]);
       if(0x2F<$ch && $ch<0x3A) return $ch-0x30; // cifrele zecimale  
       $ch |= 0x20; // fortam minuscula
       if(0x60<$ch && $ch<0x7B) return $ch-0x57; // alfabetul latin  
       return false; // nu exista asa cifra
    }   
    
    /*! Incearca sa ghiceasca baza numarului din reprezentarea $str
     *  Daca sunt caractere straine, $str se considera binar.
     *  Daca incepe cu '0x', baza e 16.     
     *  Daca incepe numai cu '0', baza e 8.
     *  Daca incepe cu '$', baza e 2.
     *  Daca sunt numai cifre zecimale, baza e 10.          
     */
    static function guess_radix($str)
    {
       if(!strlen($str) || preg_match('#[^\s\+\-\$0-9A-Z]#i', $str)) return 1; // binarry data
       $p = 0;
       while(preg_match('#^\s\+\-#', $str[$p])) $p++;
       switch($str[$p])
       {
          case '0': if($str[$p+1] == 'x') return 16;
                    return 8;
          case '$': return 2;
          default:  if(!preg_match('#[^0-9]#', $str)) 
                   return 10;
       }
       return false;
    }      
/*------------------------------------------------------------------------*/
    /*! $val - valoarea, $radix - baza lui $val.
     *  $radix == 1 - pentru $val date binare
     */ 
    function __construct(/*[$val=0[, $radix]]*/)
    {
       parent::__construct();
       if(func_num_args())
       {
          if(func_num_args() > 1) {
            $radix = (int)func_get_arg(1);
            if( 2 <= $radix && $radix <= 36 ) $this->radix = $radix;
          }
          else $radix = 0;
      
          $val = func_get_arg(0);
          if($radix) $this->set_val($val, $radix); 
          else       $this->set_val($val);
       }
    }  
       
    function __toString()
    {  
        return $this->get_str();
    }
    /*------------------------------------------------------------------------*/
    protected function get_radix()
    { // Se apeleaza automat prin getter
       return self::$radix;
    }
    /*------------------------------------------------------------------------*/
    /*!
     * Valoarea string a obiectului este reprezentarea numarului in baza $radix.
     * Daca $unsigned == TRUE, numrul se interpreteaza ca fara semn.     
     */ 
    public function get_str($radix=false, $unsigned=false)
    {
       if(!$radix) $radix = $this->radix;
       $rev = $radix < 0 ? $radix = -$radix : false;
       if($radix == 1) return $rev ? strrev($this->data) : $this->data;
       $l = strlen($this->data);
       if($l==0) return '0'; // '0' e zero in orice baza
       
       $data = $this->data; // copie de rezerva a datelor
       if($unsigned || !$this->get_sign()) $s = 0;
       else
       {
          $s = -1;
          $this->negative();
       }
       switch($radix)
       {
          case 2:
             $r = rtrim($this->get_bits(), '0 '); // ordinea inversata (din mem) 
             $rev = !$rev;
             if($s) { $r .= '-'; $s = 0; }
          break;

          case 16: 
             $r = '';
             $p = $this->not_null_r();   // stim la sigur ca numarul e pozitiv
             do $r .= bin2hex($this->data[$p]); while($p--);
             $r = ltrim($r, '0');
          break;
          
          default: 
             $r = '';
             //if(!( $radix & ($radix - 1) )) {} // baza 2^n ???
             while(!$this->is_null())
             {
               $this->div_int($radix);                 // impartim numarul la baza
               $r = self::val2digi(self::$carry) . $r; // restul impartirii este ultima cifra
             }
       }
       $this->data = $data; // restabilim datele
       if(empty($r)) return '0';
       if($s) $r = '-'.$r;
       return $rev ? strrev($r) : $r;
    }
    
    /*------------------------------------------------------------------------*/
    /*! 
     *  Daca $radix == 0, se foloseste baza $this->radix.
     *  Daca $radix == 1, $op sunt date binare.
     *  Daca $radix e o baza valida, $op reprezinta inscrierea 
     *  unui numar in baza $radix.           
     */
    public function set_str(&$op, $radix = 0)
    {
       if(!is_string($op)) $op = strval($op);
       if( $radix < 0 ) {
          $radix = -$radix;
          $op = strrev($op);
       }
       
       if($radix == 0) {
          $r = self::guess_radix($op);
          $radix = $r ? $r : self::default_radix;
       } 
       elseif($radix == 1) $this->data = $op; else
       {
          $this->data = '';
          if($op[0] == '-') $s = true;
          $op = ltrim(str_replace(' ', '', $op), '-+$');
          $l  = strlen($op);
          $v = 0;
          
          if(!( $radix & ($radix - 1) ))                   // Baza 2^n
          {
             $r = max_log2($radix);                        // nr de biti pu o cifra $radix
             $o = 0;
             while($l--)                                   // nr se parcurge invers, deoarece in memorie se reprezinta invers
             {
               $d = self::digi2val($op[$l]);
               if($d === false || $d >= $radix) continue;  // cifra inacceptabila
               $v += $d << $o;                             // acumulatorul valorii
               for($o+=$r; $o>=8; $o-=8, $v>>=8)           // $o - acumulatorul nr de biti
                  $this->data .= chr($v & BYTE_MASK);
             }
             if($v) $this->data .= chr($v);
          }
          else                                             // Baza oarecare, cazul general
          {
             $r = floor(log(INT_LO_MASK, $radix));         // nr de cifre in baza $radix ce incap in jumate de INT
             if($l < 2*$r)                                 // pentru numere mice nu are rost de pierdut timpul cu inmultiri
             {
               for($i=0; $i<$l; $i++) {
                 $d = self::digi2val($op[$i]);
                 if($d === false || $d >= $radix) continue;// cifra inacceptabila
                 $v *= $radix; $v += $d;                   // acumulatorul valorii
               }
               $this->set_int($v);
             }
             else
             {
               $m = pow($radix, $r);                       // baza pu $r cifre grupate
               $o = $r;                               
               for($i=0; $i<$l; $i++)
               {
                 $d = self::digi2val($op[$i]);
                 if($d === false || $d >= $radix) continue;// cifra inacceptabila
                 $v *= $radix; $v += $d;                   // acumulatorul valorii
                 $o--;                                     // numaratorul cifrelor bazei $radix
                 if(!$o) {
                   $this->mul_int($m);
                   $this->add_int($v);
                   $v = 0; 
                   $o = $r;
                 }
               }
               if($v) {                                    // nu a fost adaugata valoarea $v la numar
                 $this->mul_int(pow($radix, $r-$o));
                 $this->add_int($v);
               }
             }
          }
          $this->data .= NULL_CHAR;                        // reprezentarea totdeauna este nr pozitiv
          if(!empty($s)) $this->negative();
       }
       return $this->get_val();
    }
        
    /*------------------------------------------------------------------------*/
    /* Important:
     *   $by trebuie sa fie atat de mare, incat ($by-1) * 256 + 255 <= PHP_INT_MAX,
     *   de unde avem: $by <= (INT_MAX-255) / 256 + 1 = (INT_MAX >> 8) + 1
     */
    public function div_int($by)
    {
       if($by == 0) { return self::$carry = -1; } 
       if($s = $by < 0)  // !!! exceptia!
       {
          $this->negative(); 
          $by = -$by; 
       }  
       self::$carry = 0;
       if($by > 1)
       {
          $o  = &self::$carry;
          $dt = &$this->data;
          $l  = strlen($dt);
          while($l--)
          {
             $g = ($o << 8) | ord($dt[$l]);  // transportul de la octetul precedent + octetul curent
             $o = $g % $by;                  // transportul pentru urmatorul octet
             $dt[$l] = chr(($g-$o) / $by);   // catul impartirii
          }
       }
       return $this->get_val();
    } // div_int
    
    /*------------------------------------------------------------------------*/
    /* Important:
     *   Analogic cu div_int, $o <= INT_MAX, de unde avem:
     *     $by <= (INT_MAX-(INT_MAX / 256)) / 255 = (INT_MAX >> 8) + 1
     */
    public function mul_int($by, $null_check=true)
    {
       // 0 * x = 0, for any x
       if($by == 0) { $this->data = ''; return 0; }
       if($null_check && $this->not_null() === false) return 0;

//        $this->reduce(); // sa presupunem ca algoritmii precedenti au grija de acest lucru
       $s = $by & INT_SIGN; 
       $o = $this->get_sign();
       if($o) {
          $this->negative();
          $s = ($s xor $o);
       }
       if($s) $by = -$by;      // de cazul -$by == $by are grija maska de mai jos
       if($by & INT_HI_MASK)   // Daca factorul depaseste jumate de INT, efectuam 2 inmultiri
       {
          $o = clone $this; // pentru fiecare jumatate de $by trebuie cate un factor
          $this->mul_int(($by >> INT_HALF_BIT_SIZE) & INT_LO_MASK, false);  // Inmultim partea de sus a $by cu $this
          $this->data = str_repeat(NULL_CHAR, INT_SIZE >> 1) . $this->data; // deplasam $this in sus cu jumatate de INT
          $o->mul_int($by & INT_LO_MASK, false);                            // Inmultim partea de jos a $by cu $this
          $this->add_obj($o);                                               // adaugam produsul pentru jumatatea de jos
          unset($dt); 
       }
       else
       {
          $o  = 0;
          $l  = strlen($this->data);
          $dt = &$this->data;
          for($i=0; $i<$l; $i++, $o >>= 8) {
             $o = ord($dt[$i]) * $by + $o;
             $dt[$i] = chr($o & BYTE_MASK);
          }
          for(; $o; $o >>= 8) $dt .= chr($o & BYTE_MASK);
          $dt .= NULL_CHAR; // Numarul trebuie sa fie pozitiv
       }
       if($s) $this->negative();
       return $this->get_val();
    } // mul_int
    
    /*------------------------------------------------------------------------*/
    function mul_obj($op)
    {
       $a  = &$op->data;   // se presupune ca $op este mai scurt decat $this
       $b  = &$this->data;
       $al = strlen($a);
       $bl = strlen($b);
       $s = ord($a[$al-1]) & BYTE_SIGN;
       $m = ord($b[$bl-1]) & BYTE_SIGN;
       if($s) {
          $r = $a;         // backup $op
          $op->negative();
          $op->data = &$r; // restore $op
          if(self::$carry < 0) $al++;
       }   
       if($m) { 
          $this->negative();
          $s ^= $m; // result sign
       }
       if(self::$carry < 0) $bl++;
       if($bl < $al) { // $b trebuie sa fie numarul mai lung 
         $r = &$a; $a  = &$b; $b  = &$r;
         $o = $al; $al = $bl; $bl = $o;
       }
       unset($r); 
       
       $o = 0;
       $r = ''; // rezultatul inmultirii
       $l = $al + $bl - 1;
       global $mxln;
       for($k=0; $k<$l; $k++)
       {
           $m = min($k+1, $al);
           $sum = 0;
           for($i=max(0, $k+1-$bl); $i<$m; $i++)
           {
              $sum++;
      $j = $k-$i;
              $o += ord($a[$i]) * ord($b[$j]); // length($o) <= (2*$al) bytes
           }
           if($sum > $mxln) $mxln = $sum;
           $r .= chr($o & BYTE_MASK);
           $o >>= 8;
       }
       while($o) {  // transportul prelungeste numarul
          $r .= chr($o & BYTE_MASK);
          $o >>= 8;
       }
       
       $this->data = &$r;
       if($s) $this->negative();
       self::$carry = $o;
       return $this->get_val();
    } // mul_obj

    /*------------------------------------------------------------------------*/
    public function set_val($val)
    {
       $this->data = '';
       if(is_int($val) || is_bool($val)) return $this->set_int((int)$val); // datele constituie un singur INT
       if(is_float($val))  $this->set_float($val); else
       if(is_array($val))  parent::set_ints($val); else // un array de numere intregi pentru un numar lung
       if($val instanceof parent) return $this->set_obj($val);
       else
       {
          $radix = func_num_args() > 1 ? func_get_arg(1) : $this->radix;
          $this->set_str($val, $radix);
       }
       return $this->get_val();     
    } 
    
    protected function get_val()
    { 
       $this->reduce();
       return $this->reduce() <= PHP_INT_SIZE ? $this->get_int_s() : $this; 
    }   

    /*------------------------------------------------------------------------*/
    public function add($op)
    {
       if(is_int($op) || is_bool($op)) return $this->add_int((int)$op);
       if(is_float($op)) return $this->add_float($op);
       if(is_array($op)) { 
          $radix = func_num_args() > 1 ? func_get_arg(1) : 0;
          foreach($op as &$v) $this->add($v, $radix); 
          return $this->get_val(); 
       }
       if( !($op instanceof self) ) {
      $o = new self();
          $o->set_str($op, func_num_args() > 1 ? $radix=func_get_arg(1) : 0);
          $op = $o;
       } 
       return $this->add_obj($op);
    }
    /*------------------------------------------------------------------------*/
    public function mul($op)
    {
       if(is_int($op) || is_bool($op)) return $this->mul_int((int)$op);
       if(is_float($op)) return $this->mul_float($op);
       if(is_array($op)) { 
          $radix = func_num_args() > 1 ? func_get_arg(1) : 0;
          foreach($op as &$v) $this->mul($v, $radix); 
          return $this->get_val(); 
       }
       if( !($op instanceof self) ) {
      $o = new self();
          $o->set_str($op, func_num_args() > 1 ? $radix=func_get_arg(1) : 0);
          $op = $o;
       } 
       return $this->mul_obj($op);
    }
    /*------------------------------------------------------------------------*/
    /*!
     * Obtine opusul numarului
     *     
     * Numerele intregi se reprezinta ca complementi fata de 2^n, 
     * de aceea pentru a obtine valoarea negativa a numarului z, 
     * putem aplica formula:
     *       -z == ~z + 1,
     * unde ~z este complementul fata de 2^n a numarului z.
     *       ~z = 2^n - 1 - z
     *      
     * Observatie: ~z - 1 = ~(z + 1)                              
     */
    function negative()
    {
       $dt = &$this->data;
       $l  = strlen($dt);
       for($i=0; $i<$l && $dt[$i] == NULL_CHAR; $i++) ; // cat timp exista transport si nu s-a ajuns la sfarsit
       if($i < $l) 
       {
          self::$carry = 0; // nu exista transport
          $dt[$i] = chr(BYTE_MASK & ~ord($dt[$i]) + 1);
          $i++;
          if($i == $l && ord($dt[$i-1]) == BYTE_SIGN) // cazul special, cand -n == n !
          {
            $dt .= NULL_CHAR;     // numarul a fost negativ si trebuie sa devina pozitiv
            self::$carry = -1; // pentru cazul de exceptie!
          } else
          for(; $i<$l; $i++) $dt[$i] = chr(BYTE_MASK & ~ord($dt[$i])); // cat timp nu s-a ajuns la sfarsit
       }
       else self::$carry = 1; // exista transport (doar pentru valoarea 0)
       return $this;
    }
    /*------------------------------------------------------------------------*/
    /*!
     * Reduce octetii superiori, daca e posibil, fara a skimba valoarea numarului.
     * @return: Nr de octeti ramasi     
     */     
    protected function reduce() // sign
    {
       $l = $this->get_size();
       if(!$l) return 0;
       $g = $this->get_byte($l-1);
       if($g === 0 || $g === BYTE_MASK) // "cifra" superioara e 0 sau ~0
       {
         $l = $this->ltrim_byte($g);
         if(!$l) // s-au redus toti octetii
         {
           $this->push_byte($g); // adaugam octetul purtator de semn
           $l = 1;               // a ramas un singur octet
         }
         else
         {
           $v = $this->get_byte($l-1); // ultimul octet ramas
           if( BYTE_SIGN & ($v ^ $g) ) {
              $this->push_byte($g); // ultima "cifra" trebuie sa pastreze semnul 
              $l++; 
           }
         } 
       }
       return $l; 
    }

    /// Nr de octeti nenuli din numar -1
    function grad()
    {
       return $this->not_null_r();
    }
    /*------------------------------------------------------------------------*/
    protected function set_float($op)
    { 
      /* Reprezentarea pe float difera calitativ, de aceea vom 
       * folosi metode numerice (nu reprezentarea numarului) 
       * de convertire a valorii in formatul intern LongNum. 
       * float == double (mantisa are 52 bitsi)       */
       
       if(PHP_INT_SIZE == 8) return $this->set_int($op); // in acest caz INT il poare contine pe float
       
       // semnul numarului se prelucreaza separat
       if($op<0) {$op = -$op; $s = -1; }
       else $s = 0;
       
       $d   = INT_LO_MASK + 1;   // sqrt(MAX_INT) - jumate din capacitatea INT (ex. pe 32 biti - 0x00010000)
       $isz = PHP_INT_SIZE >> 1; // nr de octeti necesari pentru jumate de INT
       while($op) 
       {
          $f  = floor($op / $d);                      // jumate de INT (binar)
          $this->data .= int2bs($op - $f * $d, $isz); // restul impartirii
          $op = floor($f / $d);                       // a doua jumate de INT
          $this->data .= int2bs($f - $op * $d, $isz); // al doilea rest al impartirii
       }
       if($s) $this->negative();
       return $this->get_val();
    }

    protected function set_obj($op)
    {
        if($op instanceof parent)   // daca obiectul atribuit este de acelasi tip,
           $this->data = $op->data; // pur si simplu se copie datele
        else                        // in caz contrar
           $this->set_str($op);     // se incearca atribuirea valorii STRING a $op 
        return $this->get_val();
    }
    protected function get_obj() { return clone $this; }
    
    /// Un simplu alias pentru set_int($op, 0)
    protected function set_integer($op)
    {
       return $this->set_int($op);
    }
     
    protected function get_integer()
    {
       return $this->get_int_s();
    }
    /*------------------------------------------------------------------------*/
    protected function add_float(&$op)
    { // float == double (mantisa are 52 bitsi)
       if(PHP_INT_SIZE == 8) return $this->add_int($op); // in acest caz INT il poare contine pe float
       return $this->add_obj(new self($op));
    }

    protected function add_int($op)
    {
       $o = new self();
       $o->set_int($op);
       return $this->add_obj($o);
    }

    protected function add_obj($op)
    {
       $al = strlen($this->data);
       $bl = strlen($op->data);
       if($al < $bl) { // $a trebuie sa fie numarul mai lung 
         $a = &$op->data;
         $b = &$this->data;
         $r = $al; $al = $bl; $bl = $r;
       } else {        // $a este numarul cel mai lung
         $a = &$this->data;
         $b = &$op->data;
       }
       $r = '';
       $carry = &self::$carry;
       $carry = 0; // transportul initial lipseste
       
       for($i=0; $i<$bl; $i++) // parcurgem partea din lungimea comuna a ambelor numere
       {
          $carry += ord($a[$i]) + ord($b[$i]); // adunarea "cifrelor" curente + transportul
          $rb = BYTE_MASK & $carry;            // ultima "cifra" din suma curenta 
          $r .= chr($rb);                      // scrie "cifra" in rezultat
          $carry >>= 8;                        // obtinem transportul
       } 
       
       $gb = (ord($b[$bl-1]) & BYTE_SIGN) >> 7; // bitul de semn se plaseaza pe prima pozitie
       for(; $i<$al && ($gb ^ $carry); $i++) // $gb este bitul de semn (0 sau 1), iar $carry poate fi 0 sau 1
       {
          $carry += ord($a[$i]) + $gb;         // $gb reprezinta "cifrele" lui $b de dupa $bl
          $rb = BYTE_MASK & $carry;            // ultima "cifra" din suma curenta
          $r .= chr($rb);                      // scrie "cifra" in rezultat
          $carry >>= 8;                        // obtinem transportul (max 1)
       }

       /* Cand apare depasirea capacitatii curente a numerelor?
        * 1. $a > 0 && $b > 0 && $r < 0, no carry
        * 2. $a < 0 && $b < 0 && $r > 0, carry   
        * 
        * Nota: Daca $a si $b au semne diferite, depasirea nu poate avea loc!                    
        */ 
       
       if($i<$al) // $ga == $gr
       {
          $r .= substr($a, $i, $al-$i);
       }
       else
       {
          $gr = ($rb & BYTE_SIGN) ? BYTE_MASK : 0;
          if($gr != $gb)
          {
            $ga = ord($a[$al-1]) & BYTE_SIGN ? BYTE_MASK : 0;
            if($ga == $gb) $r .= chr($ga);
          }
       }
       
       $this->data = &$r;
       return $this->get_val();
       
       // Cream obiectul numarului din $r
       $ro = new self();
       $ro->data = &$r;
       return $ro;
       return $ro->get_val();
    } // add_obj
    
    /*------------------------------------------------------------------------*/
    /// Obtine semnul numarului: !0 pentru negativ si 0 pentru pozitiv
    protected function get_sign()
    {
       $l = parent::get_size();
       $b = parent::get_byte($l-1);
       return ($b & BYTE_SIGN); 
    } 
   
    /// Seteaza semnul numarului: TRUE <-> -1; FALSE <-> 0;
    protected function set_sign($sgn)
    {
       if(!is_numeric($sng)) $sng = $sng     ? BYTE_SIGN : 0;
       else                  $sng = $sgn < 0 ? BYTE_SIGN : 0;
       $osgn = $this->get_sign();
       if($sgn ^ $osgn)      // daca semnul cerut difera ce cel al numarului
          $this->negative(); // se obtine opusul numarului curent
       return $sgn;
    } 
    /*------------------------------------------------------------------------*/
    /// Verifica daca numarul este o putere a lui 2 (2^n).
    function is_pow2()
    {
      if($l = strlen($this->data)) 
      {
         $dt = &$this->data;
         while($l--) if($dt[$l] != NULL_CHAR) 
         {
            $i = ord($dt[$l]);
            if($i & ($i - 1)) return false;
            $i = $l;
            while($l--) if($dt[$l] != NULL_CHAR) return false;
            return $i+1; // pozitia octetului nenul
         }
      }
      return true; 
    }
// /*------------------------------------------------------------------------*/
//     function negative_i()
//     {
//        $o  = &self::$carry;
//        $v  = $this->reset;
//        $o  = 1;
//        if(!$v)  // ~0 + 1 = 2^n-0 + 1 => exista transport
//        { 
//          while($o && $v!==false) // cat timp exista transport si nu s-a ajuns la sfarsit
//          {
//             $v = ~$v;
//             // LO of $v
//             $o  += INT_LO_MASK & $v; 
//             $f   = INT_LO_MASK & $o;
//             $o >>= INT_HALF_BIT_SIZE;
//             
//             // HI of $v
//             $o  += INT_LO_MASK & ($v >> INT_HALF_BIT_SIZE);
//             $g   = INT_LO_MASK & $o;
//             $o >>= INT_HALF_BIT_SIZE;
//              
//             $this->current = ($g << INT_HALF_BIT_SIZE) | $f;
//             $v = $this->next;
//          }
//        } else { $v -= $o; $o = 0; }      
//        while($v!==false) // cat timp nu s-a ajuns la sfarsit
//        {
//           $this->current = ~$v;
//           $v = $this->next;
//        }
//        return $this;
//     }
// /*------------------------------------------------------------------------*/
//     protected function add_int_i($op)
//     {
//        self::$carry = 0; // transportul initial lipseste
//        $v = $this->reset;
//        $this->current = add_carry($v, $op, self::$carry);
//        $g = ($op & INT_SIGN) >> INT_BIT_SIZE-1;
//        $v = $this->next;       
//        while($v !== false && ((self::$carry ^ $g) & 1)) 
//        {
//           $this->current = add_carry($v, $g, self::$carry);  
//           $v = $this->next;       
//        }
//        unset($this->cursor);
//        return $this->get_val();
//     }
// /*------------------------------------------------------------------------*/
//     function add_obj_i($op)
//     {
//        if($this->size > $op->size) {
//           $ho = $this;
//           $lo = $op;
//        }
//        else {
//           $lo = $this;
//           $ho = $op;
//        }
//        
//        self::$carry = 0; // transportul initial lipseste
//        $lv = $lo->reset;
//        $hv = $ho->reset;
//        while($lv !== false) // partea din lungimea comuna a ambelor numere
//        {
//           $this->current = add_carry($hv, $lv, self::$carry);
//           $lv = $lo->next;
//           $hv = $ho->next;
//        } 
//        $g = $lo->sign;
//        while($hv !== false && (self::$carry ^ $g & 1)) 
//        {
//           $this->set_int(add_carry($hv, $g, self::$carry), $ho->cursor);  
//           $hv = $ho->next;       
//        }
//        if($hv !== false && $ho != $this)
//        {
//           $l = $ho->len;
//           do $this->append_bin($ho->get_bin($ho->cursor++)); 
//           while($ho->cursor < $l);
//        }
//        return $this->get_val();
//     }
// /*------------------------------------------------------------------------*/
//     public function div_int_i($by)
//     {
//        if($by == 0) { return self::$carry = -1; } 
//        self::$carry = 0;
//        if($by == 1) return $this;
//        $o  = &self::$carry;
//        $v  = $this->end;
//        while($v !== false)
//        {
//           // HI of $v
//           $w = ($o << INT_HALF_BIT_SIZE) | INT_LO_MASK & ($v >> INT_HALF_BIT_SIZE);
//           $o  = $w % $by;
//           $f = (int)(($w-$o) / $by); // Scadem restul pentru a obtine int la impartire
//           
//           // LO of $v
//           $w = ($o << INT_HALF_BIT_SIZE) | INT_LO_MASK & $v; 
//           $o  = $w % $by;
//           $g = (int)(($w-$o) / $by); // Scadem restul pentru a obtine int la impartire
//           
//           $this->current = ($f << INT_HALF_BIT_SIZE) | $g; 
//           $v = $this->prev;
//        }
//        return $this->get_val();
//     } // div_int_i    
// /*------------------------------------------------------------------------*/
};

/*------------------------------------------------------------------------------------------*/
function ln_str($val, $radix=false)
{
   if( ! $val instanceof LongNum ) $val = new LongNum($val);
   return $val->get_str($radix); 
}

function ln_add($op1, $op2)
{
   $r = new LongNum($op1);
   return $r->add($op2);
}

function ln_sub($op1, $op2)
{
   $r = new LongNum($op2);
   $r->negative();
   return $r->add($op1);
}

function ln_mul($op1, $op2)
{
   $r = new LongNum($op1);
   return $r->mul($op2);
}
/*------------------------------------------------------------------------------------------*/
?>
