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>