toFrac.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. define("dojox/calc/toFrac", [
  2. "dojo/_base/lang",
  3. "dojox/calc/_Executor"
  4. ], function(lang, calc) {
  5. var multiples;
  6. function _fracHashInit(){
  7. var sqrts = [
  8. 5,6,7,10,11,13,14,15,17,19,21,22,23,26,29,
  9. 30,31,33,34,35,37,38,39,41,42,43,46,47,51,53,55,57,58,59,
  10. 61,62,65,66,67,69,70,71,73,74,77,78,79,82,83,85,86,87,89,91,93,94,95,97
  11. ];
  12. multiples = { "1":1, "\u221A(2)":Math.sqrt(2), "\u221A(3)":Math.sqrt(3), "pi":Math.PI };
  13. // populate the rest of the multiples array
  14. for(var i in sqrts){
  15. var n = sqrts[i];
  16. multiples["\u221A("+n+")"] = Math.sqrt(n);
  17. }
  18. multiples["\u221A(pi)"] = Math.sqrt(Math.PI);
  19. }
  20. function _fracLookup(number){
  21. function findSimpleFraction(fraction){
  22. var denom1Low = Math.floor(1 / fraction);
  23. // fraction <= 1/denom1Low
  24. var quotient = calc.approx(1 / denom1Low);
  25. if(quotient == fraction){ return { n:1, d:denom1Low }; }
  26. var denom1High = denom1Low + 1;
  27. // 1/denom1High <= fraction < 1/denom1Low
  28. quotient = calc.approx(1 / denom1High);
  29. if(quotient == fraction){ return { n:1, d:denom1High }; }
  30. if(denom1Low >= 50){ return null; } // only 1's in the numerator beyond this point
  31. // 1/denom1High < fraction < 1/denom1Low
  32. var denom2 = denom1Low + denom1High;
  33. quotient = calc.approx(2 / denom2);
  34. // 1/denom1High < 2/(denom1Low+denom1High) < 1/denom1Low
  35. if(quotient == fraction){ return { n:2, d:denom2 }; }
  36. if(denom1Low >= 34){ return null; } // only 1's and 2's in the numerator beyond this point
  37. var less2 = fraction < quotient;
  38. // if less2
  39. // 1/denom1High < fraction < 2/(denom1Low+denom1High)
  40. // else
  41. // 2/(denom1Low+denom1High) < fraction < 1/denom1Low
  42. var denom4 = denom2 * 2 + (less2 ? 1 : -1);
  43. quotient = calc.approx(4 / denom4);
  44. // 1/denom1High < 4/(2*denom1Low+2*denom1High+1) < 2/(denom1Low+denom1High) < 4/(2*denom1Low+2*denom1High-1) < 1/denom1Low
  45. if(quotient == fraction){ return { n:4, d:denom4 }; }
  46. var less4 = fraction < quotient;
  47. // we've already checked for 1, 2 and 4, but now see if we need to check for 3 in the numerator
  48. if((less2 && !less4) || (!less2 && less4)){
  49. var denom3 = (denom2 + denom4) >> 1;
  50. quotient = calc.approx(3 / denom3);
  51. // 1/denom1High < 4/(2*denom1Low+2*denom1High+1) < 3/((3*denom1Low+3*denom1High+1)/2) < 2/(denom1Low+denom1High) < 3/((3*denom1Low+3*denom1High-1)/2) < 4/(2*denom1Low+2*denom1High-1) < 1/denom1Low
  52. if(quotient == fraction){ return { n:3, d:denom3 }; }
  53. }
  54. if(denom1Low >= 20){ return null; } // only 1's, 2's, 3's, and 4's in the numerator beyond this point
  55. // if less2
  56. // if less4
  57. // 1/denom1High < fraction < 4/(2*denom1Low+2*denom1High+1)
  58. // else
  59. // 4/(2*denom1Low+2*denom1High+1) < fraction < 2/(denom1Low+denom1High)
  60. // else
  61. // if less4
  62. // 2/(denom1Low+denom1High) < fraction < 4/(2*denom1Low+2*denom1High-1)
  63. // else
  64. // 4/(2*denom1Low+2*denom1High-1) < fraction < 1/denom1Low
  65. var smallestDenom = denom2 + denom1Low * 2;
  66. var largestDenom = smallestDenom + 2;
  67. for(var numerator = 5; smallestDenom <= 100; numerator++){ // start with 5 in the numerator
  68. smallestDenom += denom1Low;
  69. largestDenom += denom1High;
  70. var startDenom = less2 ? ((largestDenom + smallestDenom + 1) >> 1) : smallestDenom;
  71. var stopDenom = less2 ? largestDenom : ((largestDenom + smallestDenom - 1) >> 1);
  72. startDenom = less4 ? ((startDenom + stopDenom) >> 1) : startDenom;
  73. stopDenom = less4 ? stopDenom : ((startDenom + stopDenom) >> 1);
  74. for(var thisDenom = startDenom; thisDenom <= stopDenom; thisDenom++){
  75. if(numerator & 1 == 0 && thisDenom & 1 == 0){ continue; } // skip where n and d are both even
  76. quotient = calc.approx(numerator / thisDenom);
  77. if(quotient == fraction){ return { n:numerator, d:thisDenom }; }
  78. if(quotient < fraction){ break; } // stop since the values will just get smaller
  79. }
  80. }
  81. return null;
  82. }
  83. number = Math.abs(number);
  84. for(var mt in multiples){
  85. var multiple = multiples[mt];
  86. var simpleFraction = number / multiple;
  87. var wholeNumber = Math.floor(simpleFraction);
  88. simpleFraction = calc.approx(simpleFraction - wholeNumber);
  89. if(simpleFraction == 0){
  90. return { mt:mt, m:multiple, n:wholeNumber, d:1 };
  91. }else{
  92. var a = findSimpleFraction(simpleFraction);
  93. if(!a){ continue; }
  94. return { mt:mt, m:multiple, n:(wholeNumber * a.d + a.n), d:a.d };
  95. }
  96. }
  97. return null;
  98. }
  99. // make the hash
  100. _fracHashInit();
  101. // add toFrac to the calculator
  102. return lang.mixin(calc, {
  103. toFrac: function(number){// get a string fraction for a decimal with a set range of numbers, based on the hash
  104. var f = _fracLookup(number);
  105. return f ? ((number < 0 ? '-' : '') + (f.m == 1 ? '' : (f.n == 1 ? '' : (f.n + '*'))) + (f.m == 1 ? f.n : f.mt) + ((f.d == 1 ? '' : '/' + f.d))) : number;
  106. //return f ? ((number < 0 ? '-' : '') + (f.m == 1 ? '' : (f.n == 1 ? '' : (f.n + '*'))) + (f.m == 1 ? f.n : f.mt) + '/' + f.d) : number;
  107. },
  108. pow: function(base, exponent){// pow benefits from toFrac because it can overcome many of the limitations set before the standard Math.pow
  109. // summary:
  110. // Computes base ^ exponent
  111. // Wrapper to Math.pow(base, exponent) to handle (-27) ^ (1/3)
  112. function isInt(n){
  113. return Math.floor(n) == n;
  114. }
  115. if(base>0||isInt(exponent)){
  116. return Math.pow(base, exponent);
  117. }else{
  118. var f = _fracLookup(exponent);
  119. if(base >= 0){
  120. return (f && f.m == 1)
  121. ? Math.pow(Math.pow(base, 1 / f.d), exponent < 0 ? -f.n : f.n) // 32 ^ (2/5) is much more accurate if done as (32 ^ (1/5)) ^ 2
  122. : Math.pow(base, exponent);
  123. }else{ // e.g. (1/3) root of -27 = -3, 1 / exponent must be an odd integer for a negative base
  124. return (f && f.d & 1) ? Math.pow(Math.pow(-Math.pow(-base, 1 / f.d), exponent < 0 ? -f.n : f.n), f.m) : NaN;
  125. }
  126. }
  127. }
  128. });
  129. /*
  130. function reduceError(number){
  131. var f = _fracLookup(number);
  132. if(!f){ f = _fracLookup(number); }
  133. return f ? ((number < 0 ? -1 : 1) * f.n * f.m / f.d) : number;
  134. }
  135. */
  136. });