index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. var bignum = require('bignum');
  2. var MoneyTight = function(opts) {
  3. var n = 0;
  4. // checks for opts being a bignum or moneytight
  5. if(typeof opts == 'object') {
  6. if(opts instanceof bignum) {
  7. this.amt = bignum(opts);
  8. return this;
  9. };
  10. if(opts instanceof MoneyTight) {
  11. this.amt = bignum(opts.amt);
  12. return this;
  13. };
  14. }
  15. else {
  16. // tricks to aviod rounding errors: try -123.456
  17. n = bignum(opts * 1000).div(10);
  18. }
  19. // need to include option of how many decimals are stored internally
  20. this.amt = bignum(n, 10); // stored in cents, for now
  21. return this;
  22. };
  23. // round to nearest precision in cents
  24. MoneyTight.prototype.roundUp = function(int) {
  25. var m = bignum(1).shiftLeft(int);
  26. var neg = this.amt.lt(0);
  27. var trimmed = this.amt.sub(this.amt.mod(m));
  28. };
  29. MoneyTight.prototype.roundDown = function(int) {
  30. };
  31. MoneyTight.prototype.roundTowardZero = function(int) {
  32. };
  33. MoneyTight.prototype.roundAwayFromZero = function(int) {
  34. };
  35. MoneyTight.prototype.roundNearest = function(int) {
  36. };
  37. MoneyTight.prototype.fromInt = function(int) {
  38. this.amt = bignum(int, 10).mul(100);
  39. return this;
  40. };
  41. MoneyTight.prototype.toInt = function(int) {
  42. return parseInt(this.amt.div(100));
  43. };
  44. MoneyTight.prototype.formatDollars = function() {
  45. var $ = this.toString();
  46. if(this.amt.lt(0)) {
  47. $ = '-$' + $.substr(1, $.length);
  48. }
  49. else {
  50. $ = '$' + $;
  51. }
  52. return $;
  53. };
  54. MoneyTight.prototype.toString = function(base) { // base is ignored; money is always decimal.
  55. var str = this.amt.toString();
  56. var x = 3 - str.length;
  57. if(x > 0) {
  58. str = (x > 1 ? '00' : '0') + str ;
  59. }
  60. return str.replace(/\B(\d\d)$/, '.$1').replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  61. }
  62. MoneyTight.prototype.inspect = function () {
  63. return '<MoneyTight ' + this.formatDollars() + '>';
  64. };
  65. MoneyTight.prototype.add = function(n) { // for now, assumes in cents
  66. this.amt = this.amt.add(n);
  67. return this;
  68. };
  69. MoneyTight.prototype.sub = function(n) { // for now, assumes in cents
  70. this.amt = this.amt.sub(n);
  71. return this;
  72. };
  73. // ratio is for dividing two monetary values to arrive at a unitless floating point number
  74. MoneyTight.prototype.ratio = function(n) { // for now, assumes in cents
  75. return parseFloat(this.amt) / parseFloat(n);
  76. };
  77. // ratio is for dividing two monetary values to arrive at a unitless floating point number
  78. // return value is in percentage points
  79. MoneyTight.prototype.ratioPercent = function(n) { // for now, assumes in cents
  80. return this.ratio(n) / 100;
  81. };
  82. // div is for dividing money with a number resulting in money
  83. // this version is not zero-sum, eg: 10 / 3 = 3.33 * 3 = 9.99 != 10
  84. MoneyTight.prototype.div = function(n) { // in units not cents
  85. };
  86. // this version of division does not leak cents. the remainder is distributed
  87. // among the divided parts which are returned as an array. largest ones first.
  88. // you probably shouldn't divide by large numbers since an array of that length is created.
  89. // integer divisors only. the divisor is rounded automatically.
  90. MoneyTight.prototype.div0sum = function(divisor) { // in units not cents
  91. divisor = Math.round(divisor);
  92. var part = this.amt.div(divisor);
  93. var rem = this.amt.mod(divisor);
  94. var ret = [];
  95. for(var i = 0; i < divisor; i++) {
  96. var n = new MoneyTight(part);
  97. if(rem-- > 0) {
  98. n.add(1);
  99. }
  100. ret.push(n);
  101. };
  102. return ret;
  103. };
  104. // this version returns the remainder too. [quotient, remainder]
  105. MoneyTight.prototype.divRem = function(divisor) { // in units not cents
  106. var quotient = this.amt.div(divisor);
  107. var remainder = this.amt.mod(divisor);
  108. return [new MoneyTight(quotient), new MoneyTight(remainder)];
  109. };
  110. // sums an array of values. cuts out all the needless conversions.
  111. // can handle MoneyTight and BigNum instances, as well as ints, strings, and floats
  112. // bignums are treated as cents. floats and strings are dollars.
  113. MoneyTight.prototype.sum = function(arr) { // in units not cents
  114. var sum = bignum(0);
  115. var len = arr.length;
  116. for(var i = 0; i < len; i++) {
  117. sum = sum.add(getNum(arr[i]));
  118. };
  119. return new MoneyTight(sum);
  120. };
  121. function monthlyPayment(loan_amount, monthly_interest, num_payments) {
  122. // BUG: sanitize and convert inputs
  123. // num_payments must be an integer
  124. // TODO: figure out how to do the exponentiation with bignum properly
  125. // NOTE: there shouldn't be any significant rounding errors here. even pawn shops only charge 1400%
  126. return (loan_amount * monthly_interest) / (1 - Math.pow(1 + monthly_interest, -num_payments));
  127. }
  128. function piRatios(balance, monthly_interest, payment_amount) {
  129. var i_p = balance * monthly_interest;
  130. return {
  131. principle: payment_amount - i_p,
  132. interest: i_p,
  133. new_balance: balance - payment_amount + i_p,
  134. };
  135. }
  136. // calculate an amortization table without losing any cents
  137. // currently, there might be rounding errors on very large numbers internally. don't calculate the national debt with this.
  138. MoneyTight.prototype.amortTable = function(loanAmount, APR, months) {
  139. // BUG need to sanitize and convert inputs
  140. var o = {
  141. principle: [],
  142. interest: [],
  143. payment: [],
  144. starting_balance: [],
  145. ending_balance: [],
  146. };
  147. APR /= 12; // you can do this with APR's
  148. var payment = monthlyPayment(loanAmount, APR, months);
  149. var plow = Math.floor(payment);
  150. var phigh = Math.ceil(payment);
  151. var acc = 0;
  152. for(var i = 0; i < months; i++) {
  153. var p_i = piRatios(loanAmount, APR, payment);
  154. o.starting_balance[i] = loanAmount;
  155. loanAmount = p_i.new_balance;
  156. o.payment[i] = payment;
  157. o.principle[i] = p_i.principle;
  158. o.interest[i] = p_i.interest;
  159. o.ending_balance[i] = p_i.new_balance;
  160. o.period[i] = i;
  161. }
  162. return o;
  163. };
  164. function forceNum(x) {
  165. if(typeof x == 'object') {
  166. if(x instanceof bignum) {
  167. return x;
  168. };
  169. if(x instanceof MoneyTight) {
  170. return x.amt;
  171. }
  172. }
  173. return bignum(parseFloat(x) * 100);
  174. }
  175. module.exports = MoneyTight;