rob-lozyniak 0 Newbie Poster

I am aware that JavaScript arithmetic does not handle either really large numbers or decimals well. This is why I have made this library of functions. I am posting it here rather than under "code snippets" because I would like to learn from your comments.

Along with the library is a demo.

<html>
<head>

</head>
<body>


<script language="JavaScript">
<!-- //

// JavaScript bignum / big decimal library
// by Robert Lozyniak - r07271368@hotmail.com
// this is version number 2
// this version 10 Sep 2009 at 07:47 UT


// generic polynomial functions
// here, we represent polynomials as arrays
// with the array indices being the coefficients
//
// it is expected that there be no gaps in these arrays!
// (i.e. there is no checking for missing elements or NaNs)

function figz(x,n) {
  if (n<0 || n>=x.length) return 0;
  return x[n]-0;
}

function polyAdd() { // polynomial addition
  var i=0;
  var j=0;
  var r=new Array();
  for (i=0; i<arguments.length; i++) {
    for (j=0; j<arguments[i].length; j++) {
      r[j]=figz(r,j)+figz(arguments[i],j);
    }
  }
  return r;
}  

function polyMul(x,y) { // polynomial multiplication
  var i=0;
  var j=0;
  var m;
  var r=new Array();
  for (i=0; i<x.length+y.length-1; i++) r[i]=0;
  for (i=0; i<x.length; i++) {
    m=figz(x,i);
    if (m!=0) { // why bother if m==0 ?
      for (j=0; j<y.length; j++) {
        r[i+j]+=(m*figz(y,j));
      }
    }
  }
  return r;
}  

function polyMulSc(x,n) { // polynomial multiplication by scalar
  var j=0;
  var r=new Array();
  for (j=0; j<x.length; j++) {
    r[j]=x[j]*n;
    }
  return r;
}  

function polyComp(x,y) { // polynomial comparison
  var i;
  var ml=x.length;
  if (y.length>ml) ml=y.length;
  for (i=ml-1; i>=0; i--) {
    if (figz(x,i)!=figz(y,i)) return figz(x,i)-figz(y,i);
  }
  return 0;
}

function polyLeftShift(x,n) { // polynomial left shift
  var j=0;
  var r=new Array();
  for (j=0; j<x.length+n; j++) {
    r[j]=figz(x,j-n);
  }
  return r;
} 


// now, on to chiliadic (base 1000) numbers and arithmetic

// define some useful "constants"

var CHIL_MINUS_ONE=new Array();
CHIL_MINUS_ONE[0]=0-1;

var CHIL_ZERO=new Array(); // zero is an empty array

var CHIL_ONE=new Array();
CHIL_ONE[0]=1;

var CHIL_TWO=new Array();
CHIL_TWO[0]=2;

var CHIL_TEN=new Array();
CHIL_TEN[0]=10;

var CHIL_ONE_HUNDRED=new Array();
CHIL_ONE_HUNDRED[0]=100;

var CHIL_ONE_THOUSAND=new Array();
CHIL_ONE_THOUSAND[0]=0;
CHIL_ONE_THOUSAND[1]=1;


function chilNorm(x) { // returns normalized form of chiliadic integer
  // a function such as this one is at the heart of all base-n arithmetic
  // here, the base is one thousand
  var i=0;
  var n=0;
  var w=0;
  var carry=0;
  var r=new Array();
  while (i<x.length || carry!=0) {
    n=carry+figz(x,i);
    carry=0;
    w=n%1000;
    // no negative digits except possibly most significant digit
    if (w<0) w+=1000;
    carry=Math.round((n-w)*0.001);
    if (i>=x.length && carry==0-1 && w>0) {
      w-=1000; carry=0;
    }
    r[i]=w;
    i++;
  }
  i=r.length-1;
  // shorten length as much as possible without changing numeric value
  while(i>0-1 && figz(r,i) == 0) i-- ;
  while(i>0 && figz(r,i) == 0-1 && figz(r,i-1) > 0) {
    i--;
    r[i]+=n*1000;
  }
  if (i!=r.length-1) {
    rr=new Array();
    for (n=0; n<=i; n++) rr[n]=r[n];
    return rr;
  }
  return r;
}


// base-n arithmetic is just polynomial arithmetic with carries

function chilComp(x,y) { // compares two chiliadic integers
  return polyComp(chilNorm(x),chilNorm(y));
}

function chilIsZero(x) { // does this chiliadic integer equal zero?
  return (chilNorm(x).length==0);
}

function chilIsPos(x) { // is this chiliadic integer positive?
  var xn=chilNorm(x);
  return (xn.length>0 && figz(xn,xn.length-1)>0);
}

function chilIsNeg(x) { // is this chiliadic integer negative?
  var xn=chilNorm(x);
  return (xn.length>0 && figz(xn,xn.length-1)<0);
}

function chilAdd() { // adds chiliadic integers
  var t = polyAdd.apply(null, arguments);
  return chilNorm(t);
}

function chilNeg(x) { // negates a chiliadic integer
  return chilNorm(polyMulSc(x,0-1));
}

function chilSub(x,y) { // subtracts two chiliadic integers
  return chilNorm(polyAdd(x,polyMulSc(y,0-1)));
}

function chilMul(x,y) { // multiplies two chiliadic integers
  return chilNorm(polyMul(x,y));
}

function chilMulSc(x,n) { // multiplies a chiliadic integer by a scalar
  return chilNorm(polyMulSc(x,n));
}

// floats are useful for approximations
// and approximations can be refined by other means

function chilMantFloat(x) { // takes chiliadic integer ...
// hard to explain; helper for at least one other function
  var r = 0;
  r += figz(x,x.length-7)/1e18;
  r += figz(x,x.length-6)/1e15;
  r += figz(x,x.length-5)/1e12;
  r += figz(x,x.length-4)/1e9;
  r += figz(x,x.length-3)/1e6;
  r += figz(x,x.length-2)/1e3;
  r += figz(x,x.length-1)/1e0;
  return r;
}

function scaledFloatToChil (x,s) { // x = float,  s = scale
// x is JavaScript float, s = scale as JavaScript numeric
// returns chiliadic integer close to (x*(1000**s))
  var r = new Array();
  var xa;
  var xb = x;
  var i;
  for (i=0; i<7; i++) {
    xa = Math.round(xb);
    xb -= xa;
    r[i] = xa;
    xb *= 1000;
  }
  r = r.reverse();
  r = polyLeftShift(r,s-(r.length-1));
  return chilNorm(r);
}


function chilAbs(x) { // absolute value of chiliadic integer
  if (chilIsNeg(x)) return chilNeg(x);
  return chilNorm(x); 
}

function chilDiv(x,y) { // divides two chiliadic integers
  if (chilIsZero(y)) return false;
  if (chilIsNeg(y)) return chilDiv(chilNeg(x),chilNeg(y));
  // so if we're here, y must be positive
  // this simplifies the rounding
  if (chilIsZero(x)) return new Array();
  var qq;
  var r = new Array();
  var ymf = chilMantFloat(y);
  var xmf;
  var xres = chilNeg(x); // because easier to add than subtract
  // I could make this recursive but I don't think I should
  while (chilComp(chilAbs(xres),y)>=0) {
    xmf = chilMantFloat(xres);
    qq = scaledFloatToChil (0-(xmf/ymf),xres.length-y.length);
    r = polyAdd(r,qq);
    xres = chilAdd(xres,polyMul(qq,y));
  }
  if (chilIsPos(xres)) { // in case we overshot our quotient
    r = polyAdd(r,CHIL_MINUS_ONE);
  }
  return chilNorm(r);
}

function strToChil (s) { // converts string to chiliadic integer
  // I found this regex on the net
  var isInteger_re = /^\s*(\+|-)?\d+\s*$/;
  if ( String(s).search (isInteger_re) == -1) return false;
  var ssa = s.substring(0,1);
  var ssb = s.substring(1,s.length);
  if (ssa!="+" && ssa!="-") {
    ssb = ssa+ssb;
    ssa = "+";
  };
  ssb = "00"+ssb;
  ssb = ssb.substring(ssb.length%3,ssb.length);
  var r = new Array();
  var i;
  for (i=0; i*3<ssb.length; i++) {
    r[i]=parseInt(ssb.substring(i*3,i*3+3),10);
  }
  r = r.reverse();
  return (ssa=="+"?chilNorm(r):chilNeg(r));
}

function chilToStr (x) { // converts chiliadic integer to string
  if (chilIsZero(x)) return "0"; // must special-case this
  if (chilIsNeg(x)) return "-"+chilToStr(chilNeg(x));
  var xd=chilNorm(x).reverse();
  var r=""+xd[0];
  var i;
  var s;
  for (i=1; i<xd.length; i++) { // i should start as 1; we already have 0
    s="00"+xd[i];
    r+=s.substring(s.length-3,s.length);
  }
  return r;
}

function chilPowerOfTen (n) { // returns e.g. [0,100] for 5 
  if (n%3==0) return polyLeftShift(CHIL_ONE,Math.round(n/3));
  if (n%3==1) return polyLeftShift(CHIL_TEN,Math.round(n/3));
  if (n%3==2) return polyLeftShift(CHIL_ONE_HUNDRED,Math.round(n/3)-1);
}

function strToDec(s) { // converts string decimal to scaled integer form
// output is a two-element array
// element 0 is scale as an ordinary number
// element 1 is a chiliadic integer
// example: 3.1416 => [4,[416,31]]
//
// this regex is from the Internet
  var isDecimal_re     = /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/;
  if ( String(s).search (isDecimal_re) == -1) return false;
  var r=new Array();
  var dpi=s.indexOf(".");
  var ss=(s+((dpi==0-1)?".":"")).split(".");
  r[0]=ss[1].length;
  r[1]=strToChil(ss.join(""));
  return r;
}  

function decToStr(x) { // inverse of strToDec
// for decimal fractions less than 1, leading zero is used
  var sc=x[0];
  var mant=x[1];
  if (sc==0) return chilToStr(mant);
  var r=new Array();
  if(chilIsNeg(mant)) {
    r[0]=sc;
    r[1]=chilNeg(mant);
    return "-"+decToStr(r);
  } 
// this is really inefficient but I will use it anyway
  var p=chilPowerOfTen(sc);
  var ri=chilDiv(mant,p);
  r[0]=chilToStr(ri);
  r[1]=chilToStr(chilAdd(p,chilSub(mant,chilMul(ri,p))));
  r[1]=r[1].substring(1,r[1].length);
  return r.join(".");
}

function decScaleTo(x,s) { // changes x to scale s
// where x is a decimal as a scaled chiliadic integer
// for ease of reuse, all rounding is toward negative infinity
  if (s==x[0]) return x;
  r=new Array();
  if (s>x[0]) {
    r[0] = s;
    r[1] = chilMul(x[1],chilPowerOfTen(s-x[0]));
    return r;
  }
  // if we're here, then s<x[0]
  r[0]=s;
  r[1]=chilDiv(x[1],chilPowerOfTen(x[0]-s));
  return r;
}

function decAdd() { // adds decimals as scaled chiliadic integers
  var i;
  var smax = 0;
  for (i=0; i<arguments.length; i++) {
    if (smax<arguments[i][0]) smax = arguments[i][0];
  }
  var mnta = new Array();
  for (i=0; i<arguments.length; i++) {
    mnta[i] = decScaleTo(arguments[i],smax)[1];
  }
  var mnts = chilAdd.apply(null, mnta);
  var r = new Array();
  r[0] = smax;
  r[1] = mnts;
  return r;
}

function decNeg(x) { // negates a scaled chiliadic integer
  var r=new Array();
  r[0] = x[0];
  r[1] = chilNeg(x[1]);
  return r;
}

function decSub(x,y) { // subtracts two scaled chiliadic integers
  return decAdd(x,decNeg(y));
}

function decIsZero(x) { // tests for zero
  return chilIsZero(x[1]);
}

function decIsPos(x) { // tests for positive
  return chilIsPos(x[1]);
}

function decIsNeg(x) { // tests for negative
  return chilIsNeg(x[1]);
}

function decComp(x,y) { // compares two scaled chiliadic integers
  // method: compare mantissa of (x-y) with 0
  return chilComp(decSub(x,y)[1],new Array());
}

function decMul(x,y) { // multiplies two scaled chiliadic integers
  var r = new Array();
  r[0] = x[0] + y[0];
  r[1] = chilMul(x[1],y[1]);
  return r;
}

function decDiv(x,y,s) { // divides x by y with scale s
// x and y are scaled chiliadic integers
  var r = new Array();
  r[0] = s;
  r[1] = chilDiv(decScaleTo(x,y[0]+s)[1],y[1]);
  if (r[1]===false) return false;
  return r;
}



// library ends here; now we have the functions specific to this form

function doTheMath() {
  var n1s = document.calcForm.n1.value;
  var n2s = document.calcForm.n2.value;
  var n1d = strToDec(n1s);
  var n2d = strToDec(n2s);
  var opnum = 0;
  var outtext = "Hmmm... if I've done the math right...\n";
  while (document.calcForm.mathradio[opnum].checked == false) opnum++;
  if (n1d===false || n2d===false) {
    alert("I need two valid numbers.\nDigits and decimal points only.\nNegative numbers are OK too.");  
  }
  else if ((opnum==3 || opnum==4) && decIsZero(n2d)) {
    alert("Can't divide by zero.")
  }
  else if (opnum==0) {
    outtext += n1s +" plus "+ n2s +" equals "+decToStr(decAdd(n1d,n2d));
    alert(outtext); 
  }
  else if (opnum==1) {
    outtext += n1s +" minus "+ n2s +" equals "+decToStr(decSub(n1d,n2d));
    alert(outtext); 
  }
  else if (opnum==2) {
    outtext += n1s +" times "+ n2s +" equals "+decToStr(decMul(n1d,n2d));
    alert(outtext); 
  }
  else if (opnum==3) {
    outtext += n1s +" divided by "+ n2s +" equals "+decToStr(decDiv(n1d,n2d,2));
    outtext += "\n(to 2 decimal places)"
    alert(outtext); 
  }
  else if (opnum==4) {
    outtext += n1s +" divided by "+ n2s +" equals "+decToStr(decDiv(n1d,n2d,20));
    outtext += "\n(to 20 decimal places)"
    alert(outtext); 
  }
}
// -->
</script>

<BODY>
<FORM NAME="calcForm" ACTION="" METHOD="GET">
Give me a number: <BR>
<INPUT TYPE="text" NAME="n1" SIZE="50" VALUE=""><BR>
Give me another number: <BR>
<INPUT TYPE="text" NAME="n2" SIZE="50" VALUE=""><BR>
What to do with these numbers?<BR>
<INPUT TYPE="radio" NAME="mathradio" VALUE="add" checked> Add them<BR>
<INPUT TYPE="radio" NAME="mathradio" VALUE="sub"> Subtract them<BR>
<INPUT TYPE="radio" NAME="mathradio" VALUE="mul"> Multiply them<BR>
<INPUT TYPE="radio" NAME="mathradio" VALUE="div2"> Divide them (2 decimal places)<BR>
<INPUT TYPE="radio" NAME="mathradio" VALUE="div20"> Divide them (20 decimal places)<BR>
<INPUT TYPE="button" NAME="mathbutton" Value="Do the math" onClick="doTheMath()">
</FORM>
<br><br>
This page and arithmetic library by Robert Lozyniak on 10 Sep 2009<br>
e-mail: letter arr zero seven deuce seven one trey halfdozen eight at hotmail dot you know the rest

</body></html>