JavaScript decimal arithmetic

Please support our JavaScript / DHTML / AJAX advertiser: PostgreSQL or MySQL? Compare and contrast the two most popular open source databases
Reply

Join Date: Sep 2009
Posts: 5
Reputation: rob-lozyniak is an unknown quantity at this point 
Solved Threads: 0
rob-lozyniak rob-lozyniak is offline Offline
Newbie Poster

JavaScript decimal arithmetic

 
0
  #1
Sep 10th, 2009
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.

JavaScript / DHTML / AJAX Syntax (Toggle Plain Text)
  1.  
  2.  
  3. <html>
  4. <head>
  5.  
  6. </head>
  7. <body>
  8.  
  9.  
  10. <script language="JavaScript">
  11. <!-- //
  12.  
  13. // JavaScript bignum / big decimal library
  14. // by Robert Lozyniak - r07271368@hotmail.com
  15. // this is version number 2
  16. // this version 10 Sep 2009 at 07:47 UT
  17.  
  18.  
  19. // generic polynomial functions
  20. // here, we represent polynomials as arrays
  21. // with the array indices being the coefficients
  22. //
  23. // it is expected that there be no gaps in these arrays!
  24. // (i.e. there is no checking for missing elements or NaNs)
  25.  
  26. function figz(x,n) {
  27. if (n<0 || n>=x.length) return 0;
  28. return x[n]-0;
  29. }
  30.  
  31. function polyAdd() { // polynomial addition
  32. var i=0;
  33. var j=0;
  34. var r=new Array();
  35. for (i=0; i<arguments.length; i++) {
  36. for (j=0; j<arguments[i].length; j++) {
  37. r[j]=figz(r,j)+figz(arguments[i],j);
  38. }
  39. }
  40. return r;
  41. }
  42.  
  43. function polyMul(x,y) { // polynomial multiplication
  44. var i=0;
  45. var j=0;
  46. var m;
  47. var r=new Array();
  48. for (i=0; i<x.length+y.length-1; i++) r[i]=0;
  49. for (i=0; i<x.length; i++) {
  50. m=figz(x,i);
  51. if (m!=0) { // why bother if m==0 ?
  52. for (j=0; j<y.length; j++) {
  53. r[i+j]+=(m*figz(y,j));
  54. }
  55. }
  56. }
  57. return r;
  58. }
  59.  
  60. function polyMulSc(x,n) { // polynomial multiplication by scalar
  61. var j=0;
  62. var r=new Array();
  63. for (j=0; j<x.length; j++) {
  64. r[j]=x[j]*n;
  65. }
  66. return r;
  67. }
  68.  
  69. function polyComp(x,y) { // polynomial comparison
  70. var i;
  71. var ml=x.length;
  72. if (y.length>ml) ml=y.length;
  73. for (i=ml-1; i>=0; i--) {
  74. if (figz(x,i)!=figz(y,i)) return figz(x,i)-figz(y,i);
  75. }
  76. return 0;
  77. }
  78.  
  79. function polyLeftShift(x,n) { // polynomial left shift
  80. var j=0;
  81. var r=new Array();
  82. for (j=0; j<x.length+n; j++) {
  83. r[j]=figz(x,j-n);
  84. }
  85. return r;
  86. }
  87.  
  88.  
  89. // now, on to chiliadic (base 1000) numbers and arithmetic
  90.  
  91. // define some useful "constants"
  92.  
  93. var CHIL_MINUS_ONE=new Array();
  94. CHIL_MINUS_ONE[0]=0-1;
  95.  
  96. var CHIL_ZERO=new Array(); // zero is an empty array
  97.  
  98. var CHIL_ONE=new Array();
  99. CHIL_ONE[0]=1;
  100.  
  101. var CHIL_TWO=new Array();
  102. CHIL_TWO[0]=2;
  103.  
  104. var CHIL_TEN=new Array();
  105. CHIL_TEN[0]=10;
  106.  
  107. var CHIL_ONE_HUNDRED=new Array();
  108. CHIL_ONE_HUNDRED[0]=100;
  109.  
  110. var CHIL_ONE_THOUSAND=new Array();
  111. CHIL_ONE_THOUSAND[0]=0;
  112. CHIL_ONE_THOUSAND[1]=1;
  113.  
  114.  
  115. function chilNorm(x) { // returns normalized form of chiliadic integer
  116. // a function such as this one is at the heart of all base-n arithmetic
  117. // here, the base is one thousand
  118. var i=0;
  119. var n=0;
  120. var w=0;
  121. var carry=0;
  122. var r=new Array();
  123. while (i<x.length || carry!=0) {
  124. n=carry+figz(x,i);
  125. carry=0;
  126. w=n%1000;
  127. // no negative digits except possibly most significant digit
  128. if (w<0) w+=1000;
  129. carry=Math.round((n-w)*0.001);
  130. if (i>=x.length && carry==0-1 && w>0) {
  131. w-=1000; carry=0;
  132. }
  133. r[i]=w;
  134. i++;
  135. }
  136. i=r.length-1;
  137. // shorten length as much as possible without changing numeric value
  138. while(i>0-1 && figz(r,i) == 0) i-- ;
  139. while(i>0 && figz(r,i) == 0-1 && figz(r,i-1) > 0) {
  140. i--;
  141. r[i]+=n*1000;
  142. }
  143. if (i!=r.length-1) {
  144. rr=new Array();
  145. for (n=0; n<=i; n++) rr[n]=r[n];
  146. return rr;
  147. }
  148. return r;
  149. }
  150.  
  151.  
  152. // base-n arithmetic is just polynomial arithmetic with carries
  153.  
  154. function chilComp(x,y) { // compares two chiliadic integers
  155. return polyComp(chilNorm(x),chilNorm(y));
  156. }
  157.  
  158. function chilIsZero(x) { // does this chiliadic integer equal zero?
  159. return (chilNorm(x).length==0);
  160. }
  161.  
  162. function chilIsPos(x) { // is this chiliadic integer positive?
  163. var xn=chilNorm(x);
  164. return (xn.length>0 && figz(xn,xn.length-1)>0);
  165. }
  166.  
  167. function chilIsNeg(x) { // is this chiliadic integer negative?
  168. var xn=chilNorm(x);
  169. return (xn.length>0 && figz(xn,xn.length-1)<0);
  170. }
  171.  
  172. function chilAdd() { // adds chiliadic integers
  173. var t = polyAdd.apply(null, arguments);
  174. return chilNorm(t);
  175. }
  176.  
  177. function chilNeg(x) { // negates a chiliadic integer
  178. return chilNorm(polyMulSc(x,0-1));
  179. }
  180.  
  181. function chilSub(x,y) { // subtracts two chiliadic integers
  182. return chilNorm(polyAdd(x,polyMulSc(y,0-1)));
  183. }
  184.  
  185. function chilMul(x,y) { // multiplies two chiliadic integers
  186. return chilNorm(polyMul(x,y));
  187. }
  188.  
  189. function chilMulSc(x,n) { // multiplies a chiliadic integer by a scalar
  190. return chilNorm(polyMulSc(x,n));
  191. }
  192.  
  193. // floats are useful for approximations
  194. // and approximations can be refined by other means
  195.  
  196. function chilMantFloat(x) { // takes chiliadic integer ...
  197. // hard to explain; helper for at least one other function
  198. var r = 0;
  199. r += figz(x,x.length-7)/1e18;
  200. r += figz(x,x.length-6)/1e15;
  201. r += figz(x,x.length-5)/1e12;
  202. r += figz(x,x.length-4)/1e9;
  203. r += figz(x,x.length-3)/1e6;
  204. r += figz(x,x.length-2)/1e3;
  205. r += figz(x,x.length-1)/1e0;
  206. return r;
  207. }
  208.  
  209. function scaledFloatToChil (x,s) { // x = float, s = scale
  210. // x is JavaScript float, s = scale as JavaScript numeric
  211. // returns chiliadic integer close to (x*(1000**s))
  212. var r = new Array();
  213. var xa;
  214. var xb = x;
  215. var i;
  216. for (i=0; i<7; i++) {
  217. xa = Math.round(xb);
  218. xb -= xa;
  219. r[i] = xa;
  220. xb *= 1000;
  221. }
  222. r = r.reverse();
  223. r = polyLeftShift(r,s-(r.length-1));
  224. return chilNorm(r);
  225. }
  226.  
  227.  
  228. function chilAbs(x) { // absolute value of chiliadic integer
  229. if (chilIsNeg(x)) return chilNeg(x);
  230. return chilNorm(x);
  231. }
  232.  
  233. function chilDiv(x,y) { // divides two chiliadic integers
  234. if (chilIsZero(y)) return false;
  235. if (chilIsNeg(y)) return chilDiv(chilNeg(x),chilNeg(y));
  236. // so if we're here, y must be positive
  237. // this simplifies the rounding
  238. if (chilIsZero(x)) return new Array();
  239. var qq;
  240. var r = new Array();
  241. var ymf = chilMantFloat(y);
  242. var xmf;
  243. var xres = chilNeg(x); // because easier to add than subtract
  244. // I could make this recursive but I don't think I should
  245. while (chilComp(chilAbs(xres),y)>=0) {
  246. xmf = chilMantFloat(xres);
  247. qq = scaledFloatToChil (0-(xmf/ymf),xres.length-y.length);
  248. r = polyAdd(r,qq);
  249. xres = chilAdd(xres,polyMul(qq,y));
  250. }
  251. if (chilIsPos(xres)) { // in case we overshot our quotient
  252. r = polyAdd(r,CHIL_MINUS_ONE);
  253. }
  254. return chilNorm(r);
  255. }
  256.  
  257. function strToChil (s) { // converts string to chiliadic integer
  258. // I found this regex on the net
  259. var isInteger_re = /^\s*(\+|-)?\d+\s*$/;
  260. if ( String(s).search (isInteger_re) == -1) return false;
  261. var ssa = s.substring(0,1);
  262. var ssb = s.substring(1,s.length);
  263. if (ssa!="+" && ssa!="-") {
  264. ssb = ssa+ssb;
  265. ssa = "+";
  266. };
  267. ssb = "00"+ssb;
  268. ssb = ssb.substring(ssb.length%3,ssb.length);
  269. var r = new Array();
  270. var i;
  271. for (i=0; i*3<ssb.length; i++) {
  272. r[i]=parseInt(ssb.substring(i*3,i*3+3),10);
  273. }
  274. r = r.reverse();
  275. return (ssa=="+"?chilNorm(r):chilNeg(r));
  276. }
  277.  
  278. function chilToStr (x) { // converts chiliadic integer to string
  279. if (chilIsZero(x)) return "0"; // must special-case this
  280. if (chilIsNeg(x)) return "-"+chilToStr(chilNeg(x));
  281. var xd=chilNorm(x).reverse();
  282. var r=""+xd[0];
  283. var i;
  284. var s;
  285. for (i=1; i<xd.length; i++) { // i should start as 1; we already have 0
  286. s="00"+xd[i];
  287. r+=s.substring(s.length-3,s.length);
  288. }
  289. return r;
  290. }
  291.  
  292. function chilPowerOfTen (n) { // returns e.g. [0,100] for 5
  293. if (n%3==0) return polyLeftShift(CHIL_ONE,Math.round(n/3));
  294. if (n%3==1) return polyLeftShift(CHIL_TEN,Math.round(n/3));
  295. if (n%3==2) return polyLeftShift(CHIL_ONE_HUNDRED,Math.round(n/3)-1);
  296. }
  297.  
  298. function strToDec(s) { // converts string decimal to scaled integer form
  299. // output is a two-element array
  300. // element 0 is scale as an ordinary number
  301. // element 1 is a chiliadic integer
  302. // example: 3.1416 => [4,[416,31]]
  303. //
  304. // this regex is from the Internet
  305. var isDecimal_re = /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/;
  306. if ( String(s).search (isDecimal_re) == -1) return false;
  307. var r=new Array();
  308. var dpi=s.indexOf(".");
  309. var ss=(s+((dpi==0-1)?".":"")).split(".");
  310. r[0]=ss[1].length;
  311. r[1]=strToChil(ss.join(""));
  312. return r;
  313. }
  314.  
  315. function decToStr(x) { // inverse of strToDec
  316. // for decimal fractions less than 1, leading zero is used
  317. var sc=x[0];
  318. var mant=x[1];
  319. if (sc==0) return chilToStr(mant);
  320. var r=new Array();
  321. if(chilIsNeg(mant)) {
  322. r[0]=sc;
  323. r[1]=chilNeg(mant);
  324. return "-"+decToStr(r);
  325. }
  326. // this is really inefficient but I will use it anyway
  327. var p=chilPowerOfTen(sc);
  328. var ri=chilDiv(mant,p);
  329. r[0]=chilToStr(ri);
  330. r[1]=chilToStr(chilAdd(p,chilSub(mant,chilMul(ri,p))));
  331. r[1]=r[1].substring(1,r[1].length);
  332. return r.join(".");
  333. }
  334.  
  335. function decScaleTo(x,s) { // changes x to scale s
  336. // where x is a decimal as a scaled chiliadic integer
  337. // for ease of reuse, all rounding is toward negative infinity
  338. if (s==x[0]) return x;
  339. r=new Array();
  340. if (s>x[0]) {
  341. r[0] = s;
  342. r[1] = chilMul(x[1],chilPowerOfTen(s-x[0]));
  343. return r;
  344. }
  345. // if we're here, then s<x[0]
  346. r[0]=s;
  347. r[1]=chilDiv(x[1],chilPowerOfTen(x[0]-s));
  348. return r;
  349. }
  350.  
  351. function decAdd() { // adds decimals as scaled chiliadic integers
  352. var i;
  353. var smax = 0;
  354. for (i=0; i<arguments.length; i++) {
  355. if (smax<arguments[i][0]) smax = arguments[i][0];
  356. }
  357. var mnta = new Array();
  358. for (i=0; i<arguments.length; i++) {
  359. mnta[i] = decScaleTo(arguments[i],smax)[1];
  360. }
  361. var mnts = chilAdd.apply(null, mnta);
  362. var r = new Array();
  363. r[0] = smax;
  364. r[1] = mnts;
  365. return r;
  366. }
  367.  
  368. function decNeg(x) { // negates a scaled chiliadic integer
  369. var r=new Array();
  370. r[0] = x[0];
  371. r[1] = chilNeg(x[1]);
  372. return r;
  373. }
  374.  
  375. function decSub(x,y) { // subtracts two scaled chiliadic integers
  376. return decAdd(x,decNeg(y));
  377. }
  378.  
  379. function decIsZero(x) { // tests for zero
  380. return chilIsZero(x[1]);
  381. }
  382.  
  383. function decIsPos(x) { // tests for positive
  384. return chilIsPos(x[1]);
  385. }
  386.  
  387. function decIsNeg(x) { // tests for negative
  388. return chilIsNeg(x[1]);
  389. }
  390.  
  391. function decComp(x,y) { // compares two scaled chiliadic integers
  392. // method: compare mantissa of (x-y) with 0
  393. return chilComp(decSub(x,y)[1],new Array());
  394. }
  395.  
  396. function decMul(x,y) { // multiplies two scaled chiliadic integers
  397. var r = new Array();
  398. r[0] = x[0] + y[0];
  399. r[1] = chilMul(x[1],y[1]);
  400. return r;
  401. }
  402.  
  403. function decDiv(x,y,s) { // divides x by y with scale s
  404. // x and y are scaled chiliadic integers
  405. var r = new Array();
  406. r[0] = s;
  407. r[1] = chilDiv(decScaleTo(x,y[0]+s)[1],y[1]);
  408. if (r[1]===false) return false;
  409. return r;
  410. }
  411.  
  412.  
  413.  
  414. // library ends here; now we have the functions specific to this form
  415.  
  416. function doTheMath() {
  417. var n1s = document.calcForm.n1.value;
  418. var n2s = document.calcForm.n2.value;
  419. var n1d = strToDec(n1s);
  420. var n2d = strToDec(n2s);
  421. var opnum = 0;
  422. var outtext = "Hmmm... if I've done the math right...\n";
  423. while (document.calcForm.mathradio[opnum].checked == false) opnum++;
  424. if (n1d===false || n2d===false) {
  425. alert("I need two valid numbers.\nDigits and decimal points only.\nNegative numbers are OK too.");
  426. }
  427. else if ((opnum==3 || opnum==4) && decIsZero(n2d)) {
  428. alert("Can't divide by zero.")
  429. }
  430. else if (opnum==0) {
  431. outtext += n1s +" plus "+ n2s +" equals "+decToStr(decAdd(n1d,n2d));
  432. alert(outtext);
  433. }
  434. else if (opnum==1) {
  435. outtext += n1s +" minus "+ n2s +" equals "+decToStr(decSub(n1d,n2d));
  436. alert(outtext);
  437. }
  438. else if (opnum==2) {
  439. outtext += n1s +" times "+ n2s +" equals "+decToStr(decMul(n1d,n2d));
  440. alert(outtext);
  441. }
  442. else if (opnum==3) {
  443. outtext += n1s +" divided by "+ n2s +" equals "+decToStr(decDiv(n1d,n2d,2));
  444. outtext += "\n(to 2 decimal places)"
  445. alert(outtext);
  446. }
  447. else if (opnum==4) {
  448. outtext += n1s +" divided by "+ n2s +" equals "+decToStr(decDiv(n1d,n2d,20));
  449. outtext += "\n(to 20 decimal places)"
  450. alert(outtext);
  451. }
  452. }
  453. // -->
  454. </script>
  455.  
  456. <BODY>
  457. <FORM NAME="calcForm" ACTION="" METHOD="GET">
  458. Give me a number: <BR>
  459. <INPUT TYPE="text" NAME="n1" SIZE="50" VALUE=""><BR>
  460. Give me another number: <BR>
  461. <INPUT TYPE="text" NAME="n2" SIZE="50" VALUE=""><BR>
  462. What to do with these numbers?<BR>
  463. <INPUT TYPE="radio" NAME="mathradio" VALUE="add" checked> Add them<BR>
  464. <INPUT TYPE="radio" NAME="mathradio" VALUE="sub"> Subtract them<BR>
  465. <INPUT TYPE="radio" NAME="mathradio" VALUE="mul"> Multiply them<BR>
  466. <INPUT TYPE="radio" NAME="mathradio" VALUE="div2"> Divide them (2 decimal places)<BR>
  467. <INPUT TYPE="radio" NAME="mathradio" VALUE="div20"> Divide them (20 decimal places)<BR>
  468. <INPUT TYPE="button" NAME="mathbutton" Value="Do the math" onClick="doTheMath()">
  469. </FORM>
  470. <br><br>
  471. This page and arithmetic library by Robert Lozyniak on 10 Sep 2009<br>
  472. e-mail: letter arr zero seven deuce seven one trey halfdozen eight at hotmail dot you know the rest
  473.  
  474. </body></html>
Reply With Quote Quick reply to this message  
Reply

Tags
decimal, javascript, math

This thread is more than three months old.
Perhaps start a new thread instead?
Message:




Views: 1088 | Replies: 0
Thread Tools Search this Thread



Tag cloud for decimal, javascript, math
About Us | Contact Us | Advertise | DaniWeb | Acceptable Use Policy | RSS Feed

©2003 - 2009 DaniWeb® LLC