UNPKG

paypal-invoice

Version:
1,449 lines (1,200 loc) 80.6 kB
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Invoice=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ module.exports = _dereq_('./lib/invoice'); },{"./lib/invoice":4}],2:[function(_dereq_,module,exports){ 'use strict'; var InvoiceContactInfo = function (details) { var t = this; details = details || {}; 'firstName lastName businessName phoneNumber faxNumber website customValue'.split(' ').forEach(function (p) { if (details[p]) { t[p] = details[p]; } }); if (details.address) { t.address = {}; 'country line1 line2 city state postalCode'.split(' ').forEach(function (p) { t.address[p] = details.address[p]; }); } }; module.exports = InvoiceContactInfo; },{}],3:[function(_dereq_,module,exports){ 'use strict'; var BigNum = _dereq_('bignumber.js'); var currencyProperties = { USD: { symbol: '$', decimals: 2, round: BigNum.ROUND_HALF_UP, iso4217: 840 }, GBP: { symbol: '£', decimals: 2, round: BigNum.ROUND_HALF_UP, iso4217: 826 }, AUD: { symbol: '$', decimals: 2, round: BigNum.ROUND_HALF_UP, iso4217: 36 } }; module.exports = { round: function (currency, amountAsBigNum) { return new BigNum(amountAsBigNum.round(currencyProperties[currency].decimals, currencyProperties[currency].round)); }, isSupported: function (currency) { return currencyProperties.hasOwnProperty(currency); }, Number: BigNum, newNumber: function (v) { return new BigNum(v); }, toCents: function (currency, amount) { var decimals = currencyProperties[currency].decimals; return new BigNum(10).pow(decimals).times(amount); }, properties: currencyProperties }; },{"bignumber.js":6}],4:[function(_dereq_,module,exports){ 'use strict'; var currency = _dereq_('./currency'); var $$ = currency.newNumber; var InvoiceContactInfo = _dereq_('./contactInfo'); var InvoiceTotals = _dereq_('./totals'); var ZERO = $$(0); var ONE = $$(1); var Invoice = function (currencyOrDetails) { if (!currencyOrDetails || typeof(currencyOrDetails) === 'string') { this.initWithCurrency(currencyOrDetails); } else { this.initWithDetails(currencyOrDetails); } }; Invoice.STATUS = { CANCELLED: 'cancelled', CANCELED: 'cancelled', DRAFT: 'draft', PAYMENT_PENDING: 'payment_pending' }; Invoice.Number = currency.newNumber; Invoice.Currencies = currency.properties; Invoice.Item = function (quantity, unitPrice, itemId, detailId) { if (!quantity) { throw new Error('You must specify a quantity when adding an item.'); } this.quantity = $$(quantity); this.unitPrice = $$(unitPrice||0); this.itemId = itemId; this.detailId = detailId || null; // Other properties: name, description, imageUrl, taxRate, taxName // Not sure how to tell WebStorm/JS about these w/o making them null and showing up in JSON unnecessarily }; Invoice.Item.prototype = { totalForInvoice: function (inv) { var itemPrice = this.quantity.times(this.unitPrice || ZERO); if (this.discountPercentage) { var discount = currency.round(inv.currencyCode, itemPrice.times(this.discountPercentage)); itemPrice = itemPrice.minus(discount); } else if (this.discountAmount) { itemPrice = itemPrice.minus(this.discountAmount); } return currency.round(inv.currencyCode, itemPrice); } }; Invoice.prototype = { initWithCurrency: function (currencyCode) { this.currencyCode = currencyCode || 'USD'; if (!currency.isSupported(this.currencyCode)) { throw new Error('Unsupported currency: ' + this.currencyCode); } this.payerEmail = 'noreply@here.paypal.com'; this.paymentTerms = 'DueOnReceipt'; this.taxInclusive = false; this.taxCalculatedAfterDiscount = true; this.merchantInfo = new InvoiceContactInfo(); this.billingInfo = new InvoiceContactInfo(); this.shippingInfo = new InvoiceContactInfo(); this.items = []; this.status = Invoice.STATUS.DRAFT; }, initWithDetails: function (json) { this.currencyCode = json.currencyCode; if(!currency.isSupported(this.currencyCode)) { throw new Error('Unsupported currency: ' + this.currencyCode); } this.taxInclusive = json.taxInclusive ? true : false; this.taxCalculatedAfterDiscount = json.taxCalculatedAfterDiscount ? true : false; var t = this; 'status payerEmail number invoiceId paymentTerms referrerCode receiptDetails merchantMemo shippingAmount shippingTaxName customAmountName customAmountValue discountAmount discountPercentage gratuityAmount' .split(' ').forEach(function (p) { if (json[p]) { t[p] = json[p]; } }); if (json.shippingTaxRate) { this.shippingTaxRate = $$($$(json.shippingTaxRate).dividedBy(100)); } this.readMetadata(json); this.readItems(json.items); }, readMetadata: function (json) { this.merchantInfo = new InvoiceContactInfo(json.merchantInfo); this.billingInfo = new InvoiceContactInfo(); this.shippingInfo = new InvoiceContactInfo(); }, readItems: function (itemsJson) { this.items = []; var items = this.items; if (itemsJson) { itemsJson.forEach(function (itemJson) { // Item ids are not stored on the server now so we just make one up var item = new Invoice.Item(itemJson.quantity, itemJson.unitPrice, itemJson.itemId || ('ItemId'+new Date().getTime())); item.name = itemJson.name; item.description = itemJson.description; if (itemJson.taxRate) { item.taxName = itemJson.taxName; item.taxRate = $$(itemJson.taxRate); } item.imageUrl = itemJson.imageUrl; if (itemJson.discountAmount) { item.discountAmount = itemJson.discountAmount; } if (itemJson.discountPercentage) { item.discountPercentage = itemJson.discountPercentage; } items.push(item); }); } }, calculate: function () { return new InvoiceTotals(this); }, findItem: function (itemOrItemId, detailId) { if (typeof(itemOrItemId) !== 'string') { detailId = itemOrItemId.detailId; itemOrItemId = itemOrItemId.itemId; } for (var i = 0, len = this.items.length; i < len; i++) { var candidate = this.items[i]; if (candidate.itemId === itemOrItemId && candidate.detailId === detailId) { return candidate; } } return null; }, addItem: function (item) { var existing = this.findItem(item); if (existing) { existing.quantity = existing.quantity.plus(item.quantity); return existing; } else { if (item.taxRate && !(item.taxRate instanceof currency.Number)) { item.taxRate = $$(item.taxRate); } this.items.push(item); return item; } }, _sortedInvoiceItems: function () { // Sort the items so that the highest tax rate items are first just so we have some logic to the application // of pre-sales tax discounts. var copy = this.items.slice(0); // equivalent to array copy copy.sort(function (a, b) { if (!a.taxRate) { if (!b.taxRate) { return compare(a.name, b.name); } // a should go first return 1; } else { if (!b.taxRate) { // b should go first return -1; } if (b.taxRate.equals(a.taxRate)) { return compare(a.name, b.name); } return -1 * a.taxRate.comparedTo(b.taxRate); } }); return copy; } }; function compare(a, b) { if (a === b) { return 0; } else if (a < b) { return -1; } return 1; } module.exports = Invoice; },{"./contactInfo":2,"./currency":3,"./totals":5}],5:[function(_dereq_,module,exports){ 'use strict'; var currency = _dereq_('./currency'); var $$ = currency.newNumber; var ZERO = $$(0); var ONE = $$(1); function nonZero(amt) { return amt && !ZERO.equals(amt); } function valOrZero(amt) { return amt || ZERO; } var InvoiceTotals = function (invoice) { this.invoice = invoice; this.shippingTax = ZERO; this.itemSubTotal = ZERO; this.itemTax = ZERO; this.discountTotal = ZERO; this.refundTotal = ZERO; var sortedItems = invoice._sortedInvoiceItems(); this.itemSubTotal = this.calculateItemSubtotals(sortedItems); if (invoice.taxCalculatedAfterDiscount || invoice.taxInclusive) { this.discountTotal = this.calculatePreTaxDiscountForItemSubtotal(this.itemSubTotal); if (invoice.taxCalculatedAfterDiscount) { this.taxes = this.calculateTaxesForItemsWithPreTaxDiscountTotal(sortedItems, this.discountTotal, this.itemSubTotal); } else { this.taxes = this.calculateTaxesForItemsWithDiscountAfterTax(sortedItems); } this.shippingTax = this.calculateShippingTax(this.taxes); this.itemTax = this.calculateTotalTaxFromTaxes(this.taxes); this.itemTax = $$(this.itemTax.minus(this.shippingTax)); this.taxes = this.generateRoundedTaxDetailsFromTaxes(this.taxes); } else { this.taxes = this.calculateTaxesForItemsWithDiscountAfterTax(sortedItems); this.shippingTax = this.calculateShippingTax(this.taxes); this.itemTax = this.calculateTotalTaxFromTaxes(this.taxes); // MPTODO: This is possibly a rounding issue. this.itemTax = $$(this.itemTax.minus(this.shippingTax)); this.taxes = this.generateRoundedTaxDetailsFromTaxes(this.taxes); this.discountTotal = this.calculateDiscountWithItemSubTotal(this.itemSubTotal, this.itemTax); } this.setDerivedTotals(); }; InvoiceTotals.prototype = { _round: function (amt) { return currency.round(this.invoice.currencyCode, amt); }, toCents: function (amt) { return currency.toCents(this.invoice.currencyCode, amt); }, generateRoundedTaxDetailsFromTaxes: function (taxes) { var roundedTaxDetails = {}; for (var key in taxes) { roundedTaxDetails[key] = this._round(taxes[key]); } return roundedTaxDetails; }, calculateTotalTaxFromTaxes: function (taxes) { var itemTax = ZERO; for (var key in taxes) { itemTax = itemTax.plus(taxes[key]); } return itemTax; }, calculateTaxesForItemsWithPreTaxDiscountTotal: function (items, discountTotal, itemSubTotal) { var taxes = {}, self = this; items.forEach(function (i) { var itemDiscount = self.calculateItemDiscount(i, discountTotal, itemSubTotal); var itemContribution = ZERO; if (nonZero(i.unitPrice)) { itemContribution = self.calculateItemPrice(i); } if (nonZero(i.taxRate)) { var key = [i.taxName, ' (', i.taxRate.toString(), '%)'].join(''); // Rounding is complicated. We have to match invoicing, and invoicing rounds per line item. var taxAmount; if (self.invoice.taxInclusive) { var taxSub = itemContribution.minus(itemDiscount); taxAmount = self._round(taxSub.dividedBy(ONE.plus(i.taxRate.dividedBy(100)))); } else { taxAmount = self._round(itemContribution.minus(itemDiscount).times(i.taxRate.dividedBy(100))); } if (taxAmount && !ZERO.equals(taxAmount)) { taxes[key] = (taxes[key] || ZERO).plus(taxAmount); } } }); return taxes; }, calculateItemDiscount: function (i, discountTotal, itemSubTotal) { if (nonZero(discountTotal)) { var itemTotal = this.calculateItemPrice(i); if (nonZero(itemSubTotal)) { if (this.discountPercentage && !ZERO.equals(this.discountPercentage)) { return itemTotal.times(this.discountPercentage); } else { // This may still be wrong. We're basically reverse engineering the discount percentage for this item return itemTotal.dividedBy(itemSubTotal).times(discountTotal); } } } return ZERO; }, calculateTaxesForItemsWithDiscountAfterTax: function (items) { var taxes = {}, self = this; items.forEach(function (i) { var itemContribution = ZERO; if (nonZero(i.unitPrice)) { // In this mode (discounts after tax), even line item discounts don't matter for tax calculations itemContribution = currency.round(self.invoice.currencyCode, i.quantity.times(i.unitPrice)); } if (nonZero(i.taxRate)) { var key = [i.taxName, ' (', i.taxRate.toString(), '%)'].join(''); var taxAmount; if (self.invoice.taxInclusive) { var taxSub = itemContribution.dividedBy(ONE.plus(i.taxRate.dividedBy(100))); taxAmount = self._round(itemContribution.minus(taxSub)); } else { taxAmount = self._round(itemContribution.times(i.taxRate.dividedBy(100))); } if (taxAmount && !ZERO.equals(taxAmount)) { taxes[key] = (taxes[key] || ZERO).plus(taxAmount); } } }); return taxes; }, calculateShippingTax: function (taxes) { if (this.shippingTaxRate && this.shippingAmount && !ZERO.equals(this.shippingTaxRate) && !ZERO.equals(this.shippingAmount)) { // Rounding is complicated. For best results, we're going to round at the "tax class" level. // If you have one tax rate, this means "order level." If you have as many tax rates as line items, this means line level. // The advantage is with this rounding I can display valid tax per tax rate and have consistent totals. var key = [this.shippingTaxName, ' (', this.shippingTaxRate.toString(), '%)'].join(''); var shippingTax = this.shippingAmount.times(this.shippingTaxRate.dividedBy(100)); if (nonZero(shippingTax)) { taxes[key] = (taxes[key] || ZERO).plus(shippingTax); } return shippingTax; } return ZERO; }, calculateItemSubtotals: function (items) { var subtotal = ZERO; for (var i = 0, len = items.length; i < len; i++) { subtotal = subtotal.plus(this.calculateItemPrice(items[i])); } return $$(subtotal); }, calculateDiscountWithItemSubTotal: function (itemSubTotal, itemTax) { if (nonZero(this.invoice.discountAmount)) { return $$(this.discountAmount); } else if (nonZero(this.invoice.discountPercentage)) { return currency.round(this.currencyCode, itemSubTotal.plus(itemTax).times(this.discountPercentage)); } return ZERO; }, calculatePreTaxDiscountForItemSubtotal: function (subtotal) { if (nonZero(this.invoice.discountAmount)) { return $$(this.invoice.discountAmount); } else if (nonZero(this.invoice.discountPercentage)) { return currency.round(this.currencyCode, $$(this.invoice.discountPercentage).times(subtotal)); } return ZERO; }, calculateItemPrice: function (item) { var itemPrice = item.quantity.times(item.unitPrice || ZERO); if (item.discountPercentage) { var discount = currency.round(this.invoice.currencyCode, itemPrice.times(item.discountPercentage)); itemPrice = itemPrice.minus(discount); } else if (item.discountAmount) { itemPrice = itemPrice.minus(item.discountAmount); } return this._round(itemPrice); }, /** * Once the component values of the total have been calculated, this is used to set * various derived totals such as the grand total. Rather than using properties, which * in theory won't work on all browsers, we just spend the cycles to do various additions * every time the invoice is recalculated. */ setDerivedTotals: function () { this.total = valOrZero(this.itemSubTotal) .plus(valOrZero(this.invoice.gratuityAmount)) .plus(valOrZero(this.invoice.shippingAmount)); if (!this.invoice.taxInclusive) { this.total = this.total.plus(valOrZero(this.itemTax)) .plus(valOrZero(this.shippingTax)); } if (nonZero(this.discountTotal)) { this.total = this.total.minus(this.discountTotal); } this.total = $$(this.total); }, toJSON: function () { var stringify = {}; for (var s in this) { if (this.hasOwnProperty(s) && s !== 'invoice') { stringify[s] = this[s]; } } return stringify; } }; module.exports = InvoiceTotals; },{"./currency":3}],6:[function(_dereq_,module,exports){ /*! bignumber.js v1.5.0 https://github.com/MikeMcl/bignumber.js/LICENCE */ ;(function ( global ) { 'use strict'; /* bignumber.js v1.5.0 A JavaScript library for arbitrary-precision arithmetic. https://github.com/MikeMcl/bignumber.js Copyright (c) 2012 Michael Mclaughlin <M8ch88l@gmail.com> MIT Expat Licence */ /*********************************** DEFAULTS ************************************/ /* * The default values below must be integers within the stated ranges (inclusive). * Most of these values can be changed during run-time using BigNumber.config(). */ /* * The limit on the value of DECIMAL_PLACES, TO_EXP_NEG, TO_EXP_POS, MIN_EXP, * MAX_EXP, and the argument to toFixed, toPrecision and toExponential, beyond * which an exception is thrown (if ERRORS is true). */ var MAX = 1E9, // 0 to 1e+9 // Limit of magnitude of exponent argument to toPower. MAX_POWER = 1E6, // 1 to 1e+6 // The maximum number of decimal places for operations involving division. DECIMAL_PLACES = 20, // 0 to MAX /* * The rounding mode used when rounding to the above decimal places, and when * using toFixed, toPrecision and toExponential, and round (default value). * UP 0 Away from zero. * DOWN 1 Towards zero. * CEIL 2 Towards +Infinity. * FLOOR 3 Towards -Infinity. * HALF_UP 4 Towards nearest neighbour. If equidistant, up. * HALF_DOWN 5 Towards nearest neighbour. If equidistant, down. * HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour. * HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity. * HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity. */ ROUNDING_MODE = 4, // 0 to 8 // EXPONENTIAL_AT : [TO_EXP_NEG , TO_EXP_POS] // The exponent value at and beneath which toString returns exponential notation. // Number type: -7 TO_EXP_NEG = -7, // 0 to -MAX // The exponent value at and above which toString returns exponential notation. // Number type: 21 TO_EXP_POS = 21, // 0 to MAX // RANGE : [MIN_EXP, MAX_EXP] // The minimum exponent value, beneath which underflow to zero occurs. // Number type: -324 (5e-324) MIN_EXP = -MAX, // -1 to -MAX // The maximum exponent value, above which overflow to Infinity occurs. // Number type: 308 (1.7976931348623157e+308) MAX_EXP = MAX, // 1 to MAX // Whether BigNumber Errors are ever thrown. // CHANGE parseInt to parseFloat if changing ERRORS to false. ERRORS = true, // true or false parse = parseInt, // parseInt or parseFloat /***********************************************************************************/ P = BigNumber.prototype, DIGITS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_', outOfRange, id = 0, isValid = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i, trim = String.prototype.trim || function () {return this.replace(/^\s+|\s+$/g, '')}, ONE = BigNumber(1); // CONSTRUCTOR /* * The exported function. * Create and return a new instance of a BigNumber object. * * n {number|string|BigNumber} A numeric value. * [b] {number} The base of n. Integer, 2 to 64 inclusive. */ function BigNumber( n, b ) { var e, i, isNum, digits, valid, orig, x = this; // Enable constructor usage without new. if ( !(x instanceof BigNumber) ) { return new BigNumber( n, b ) } // Duplicate. if ( n instanceof BigNumber ) { id = 0; // e is undefined. if ( b !== e ) { n += '' } else { x['s'] = n['s']; x['e'] = n['e']; x['c'] = ( n = n['c'] ) ? n.slice() : n; return; } } // If number, check if minus zero. if ( typeof n != 'string' ) { n = ( isNum = typeof n == 'number' || Object.prototype.toString.call(n) == '[object Number]' ) && n === 0 && 1 / n < 0 ? '-0' : n + ''; } orig = n; if ( b === e && isValid.test(n) ) { // Determine sign. x['s'] = n.charAt(0) == '-' ? ( n = n.slice(1), -1 ) : 1; // Either n is not a valid BigNumber or a base has been specified. } else { // Enable exponential notation to be used with base 10 argument. // Ensure return value is rounded to DECIMAL_PLACES as with other bases. if ( b == 10 ) { return setMode( n, DECIMAL_PLACES, ROUNDING_MODE ); } n = trim.call(n).replace( /^\+(?!-)/, '' ); x['s'] = n.charAt(0) == '-' ? ( n = n.replace( /^-(?!-)/, '' ), -1 ) : 1; if ( b != null ) { if ( ( b == (b | 0) || !ERRORS ) && !( outOfRange = !( b >= 2 && b < 65 ) ) ) { digits = '[' + DIGITS.slice( 0, b = b | 0 ) + ']+'; // Before non-decimal number validity test and base conversion // remove the `.` from e.g. '1.', and replace e.g. '.1' with '0.1'. n = n.replace( /\.$/, '' ).replace( /^\./, '0.' ); // Any number in exponential form will fail due to the e+/-. if ( valid = new RegExp( '^' + digits + '(?:\\.' + digits + ')?$', b < 37 ? 'i' : '' ).test(n) ) { if ( isNum ) { if ( n.replace( /^0\.0*|\./, '' ).length > 15 ) { // 'new BigNumber() number type has more than 15 significant digits: {n}' ifExceptionsThrow( orig, 0 ); } // Prevent later check for length on converted number. isNum = !isNum; } n = convert( n, 10, b, x['s'] ); } else if ( n != 'Infinity' && n != 'NaN' ) { // 'new BigNumber() not a base {b} number: {n}' ifExceptionsThrow( orig, 1, b ); n = 'NaN'; } } else { // 'new BigNumber() base not an integer: {b}' // 'new BigNumber() base out of range: {b}' ifExceptionsThrow( b, 2 ); // Ignore base. valid = isValid.test(n); } } else { valid = isValid.test(n); } if ( !valid ) { // Infinity/NaN x['c'] = x['e'] = null; // NaN if ( n != 'Infinity' ) { // No exception on NaN. if ( n != 'NaN' ) { // 'new BigNumber() not a number: {n}' ifExceptionsThrow( orig, 3 ); } x['s'] = null; } id = 0; return; } } // Decimal point? if ( ( e = n.indexOf('.') ) > -1 ) { n = n.replace( '.', '' ); } // Exponential form? if ( ( i = n.search( /e/i ) ) > 0 ) { // Determine exponent. if ( e < 0 ) { e = i; } e += +n.slice( i + 1 ); n = n.substring( 0, i ); } else if ( e < 0 ) { // Integer. e = n.length; } // Determine leading zeros. for ( i = 0; n.charAt(i) == '0'; i++ ) { } b = n.length; // Disallow numbers with over 15 significant digits if number type. if ( isNum && b > 15 && n.slice(i).length > 15 ) { // 'new BigNumber() number type has more than 15 significant digits: {n}' ifExceptionsThrow( orig, 0 ); } id = 0; // Overflow? if ( ( e -= i + 1 ) > MAX_EXP ) { // Infinity. x['c'] = x['e'] = null; // Zero or underflow? } else if ( i == b || e < MIN_EXP ) { // Zero. x['c'] = [ x['e'] = 0 ]; } else { // Determine trailing zeros. for ( ; n.charAt(--b) == '0'; ) { } x['e'] = e; x['c'] = []; // Convert string to array of digits (without leading and trailing zeros). for ( e = 0; i <= b; x['c'][e++] = +n.charAt(i++) ) { } } } // CONSTRUCTOR PROPERTIES/METHODS BigNumber['ROUND_UP'] = 0; BigNumber['ROUND_DOWN'] = 1; BigNumber['ROUND_CEIL'] = 2; BigNumber['ROUND_FLOOR'] = 3; BigNumber['ROUND_HALF_UP'] = 4; BigNumber['ROUND_HALF_DOWN'] = 5; BigNumber['ROUND_HALF_EVEN'] = 6; BigNumber['ROUND_HALF_CEIL'] = 7; BigNumber['ROUND_HALF_FLOOR'] = 8; /* * Configure infrequently-changing library-wide settings. * * Accept an object or an argument list, with one or many of the following * properties or parameters respectively: * [ DECIMAL_PLACES [, ROUNDING_MODE [, EXPONENTIAL_AT [, RANGE [, ERRORS ]]]]] * * E.g. * BigNumber.config(20, 4) is equivalent to * BigNumber.config({ DECIMAL_PLACES : 20, ROUNDING_MODE : 4 }) * Ignore properties/parameters set to null or undefined. * * Return an object with the properties current values. */ BigNumber['config'] = function () { var v, p, i = 0, r = {}, a = arguments, o = a[0], c = 'config', inRange = function ( n, lo, hi ) { return !( ( outOfRange = n < lo || n > hi ) || parse(n) != n && n !== 0 ); }, has = o && typeof o == 'object' ? function () {if ( o.hasOwnProperty(p) ) return ( v = o[p] ) != null} : function () {if ( a.length > i ) return ( v = a[i++] ) != null}; // [DECIMAL_PLACES] {number} Integer, 0 to MAX inclusive. if ( has( p = 'DECIMAL_PLACES' ) ) { if ( inRange( v, 0, MAX ) ) { DECIMAL_PLACES = v | 0; } else { // 'config() DECIMAL_PLACES not an integer: {v}' // 'config() DECIMAL_PLACES out of range: {v}' ifExceptionsThrow( v, p, c ); } } r[p] = DECIMAL_PLACES; // [ROUNDING_MODE] {number} Integer, 0 to 8 inclusive. if ( has( p = 'ROUNDING_MODE' ) ) { if ( inRange( v, 0, 8 ) ) { ROUNDING_MODE = v | 0; } else { // 'config() ROUNDING_MODE not an integer: {v}' // 'config() ROUNDING_MODE out of range: {v}' ifExceptionsThrow( v, p, c ); } } r[p] = ROUNDING_MODE; /* * [EXPONENTIAL_AT] {number|number[]} Integer, -MAX to MAX inclusive or * [ integer -MAX to 0 inclusive, 0 to MAX inclusive ]. */ if ( has( p = 'EXPONENTIAL_AT' ) ) { if ( inRange( v, -MAX, MAX ) ) { TO_EXP_NEG = -( TO_EXP_POS = ~~( v < 0 ? -v : +v ) ); } else if ( !outOfRange && v && inRange( v[0], -MAX, 0 ) && inRange( v[1], 0, MAX ) ) { TO_EXP_NEG = ~~v[0]; TO_EXP_POS = ~~v[1]; } else { // 'config() EXPONENTIAL_AT not an integer or not [integer, integer]: {v}' // 'config() EXPONENTIAL_AT out of range or not [negative, positive: {v}' ifExceptionsThrow( v, p, c, 1 ); } } r[p] = [ TO_EXP_NEG, TO_EXP_POS ]; /* * [RANGE][ {number|number[]} Non-zero integer, -MAX to MAX inclusive or * [ integer -MAX to -1 inclusive, integer 1 to MAX inclusive ]. */ if ( has( p = 'RANGE' ) ) { if ( inRange( v, -MAX, MAX ) && ~~v ) { MIN_EXP = -( MAX_EXP = ~~( v < 0 ? -v : +v ) ); } else if ( !outOfRange && v && inRange( v[0], -MAX, -1 ) && inRange( v[1], 1, MAX ) ) { MIN_EXP = ~~v[0], MAX_EXP = ~~v[1]; } else { // 'config() RANGE not a non-zero integer or not [integer, integer]: {v}' // 'config() RANGE out of range or not [negative, positive: {v}' ifExceptionsThrow( v, p, c, 1, 1 ); } } r[p] = [ MIN_EXP, MAX_EXP ]; // [ERRORS] {boolean|number} true, false, 1 or 0. if ( has( p = 'ERRORS' ) ) { if ( v === !!v || v === 1 || v === 0 ) { parse = ( outOfRange = id = 0, ERRORS = !!v ) ? parseInt : parseFloat; } else { // 'config() ERRORS not a boolean or binary digit: {v}' ifExceptionsThrow( v, p, c, 0, 0, 1 ); } } r[p] = ERRORS; return r; }; // PRIVATE FUNCTIONS // Assemble error messages. Throw BigNumber Errors. function ifExceptionsThrow( arg, i, j, isArray, isRange, isErrors) { if ( ERRORS ) { var error, method = ['new BigNumber', 'cmp', 'div', 'eq', 'gt', 'gte', 'lt', 'lte', 'minus', 'mod', 'plus', 'times', 'toFr' ][ id ? id < 0 ? -id : id : 1 / id < 0 ? 1 : 0 ] + '()', message = outOfRange ? ' out of range' : ' not a' + ( isRange ? ' non-zero' : 'n' ) + ' integer'; message = ( [ method + ' number type has more than 15 significant digits', method + ' not a base ' + j + ' number', method + ' base' + message, method + ' not a number' ][i] || j + '() ' + i + ( isErrors ? ' not a boolean or binary digit' : message + ( isArray ? ' or not [' + ( outOfRange ? ' negative, positive' : ' integer, integer' ) + ' ]' : '' ) ) ) + ': ' + arg; outOfRange = id = 0; error = new Error(message); error['name'] = 'BigNumber Error'; throw error; } } /* * Convert a numeric string of baseIn to a numeric string of baseOut. */ function convert( nStr, baseOut, baseIn, sign ) { var e, dvs, dvd, nArr, fracArr, fracBN; // Convert string of base bIn to an array of numbers of baseOut. // Eg. strToArr('255', 10) where baseOut is 16, returns [15, 15]. // Eg. strToArr('ff', 16) where baseOut is 10, returns [2, 5, 5]. function strToArr( str, bIn ) { var j, i = 0, strL = str.length, arrL, arr = [0]; for ( bIn = bIn || baseIn; i < strL; i++ ) { for ( arrL = arr.length, j = 0; j < arrL; arr[j] *= bIn, j++ ) { } for ( arr[0] += DIGITS.indexOf( str.charAt(i) ), j = 0; j < arr.length; j++ ) { if ( arr[j] > baseOut - 1 ) { if ( arr[j + 1] == null ) { arr[j + 1] = 0; } arr[j + 1] += arr[j] / baseOut ^ 0; arr[j] %= baseOut; } } } return arr.reverse(); } // Convert array to string. // E.g. arrToStr( [9, 10, 11] ) becomes '9ab' (in bases above 11). function arrToStr( arr ) { var i = 0, arrL = arr.length, str = ''; for ( ; i < arrL; str += DIGITS.charAt( arr[i++] ) ) { } return str; } if ( baseIn < 37 ) { nStr = nStr.toLowerCase(); } /* * If non-integer convert integer part and fraction part separately. * Convert the fraction part as if it is an integer than use division to * reduce it down again to a value less than one. */ if ( ( e = nStr.indexOf( '.' ) ) > -1 ) { /* * Calculate the power to which to raise the base to get the number * to divide the fraction part by after it has been converted as an * integer to the required base. */ e = nStr.length - e - 1; // Use toFixed to avoid possible exponential notation. dvs = strToArr( new BigNumber(baseIn)['pow'](e)['toF'](), 10 ); nArr = nStr.split('.'); // Convert the base of the fraction part (as integer). dvd = strToArr( nArr[1] ); // Convert the base of the integer part. nArr = strToArr( nArr[0] ); // Result will be a BigNumber with a value less than 1. fracBN = divide( dvd, dvs, dvd.length - dvs.length, sign, baseOut, // Is least significant digit of integer part an odd number? nArr[nArr.length - 1] & 1 ); fracArr = fracBN['c']; // e can be <= 0 ( if e == 0, fracArr is [0] or [1] ). if ( e = fracBN['e'] ) { // Append zeros according to the exponent of the result. for ( ; ++e; fracArr.unshift(0) ) { } // Append the fraction part to the converted integer part. nStr = arrToStr(nArr) + '.' + arrToStr(fracArr); // fracArr is [1]. // Fraction digits rounded up, so increment last digit of integer part. } else if ( fracArr[0] ) { if ( nArr[ e = nArr.length - 1 ] < baseOut - 1 ) { ++nArr[e]; nStr = arrToStr(nArr); } else { nStr = new BigNumber( arrToStr(nArr), baseOut )['plus'](ONE)['toS'](baseOut); } // fracArr is [0]. No fraction digits. } else { nStr = arrToStr(nArr); } } else { // Simple integer. Convert base. nStr = arrToStr( strToArr(nStr) ); } return nStr; } // Perform division in the specified base. Called by div and convert. function divide( dvd, dvs, exp, s, base, isOdd ) { var dvsL, dvsT, next, cmp, remI, dvsZ = dvs.slice(), dvdI = dvsL = dvs.length, dvdL = dvd.length, rem = dvd.slice( 0, dvsL ), remL = rem.length, quo = new BigNumber(ONE), qc = quo['c'] = [], qi = 0, dig = DECIMAL_PLACES + ( quo['e'] = exp ) + 1; quo['s'] = s; s = dig < 0 ? 0 : dig; // Add zeros to make remainder as long as divisor. for ( ; remL++ < dvsL; rem.push(0) ) { } // Create version of divisor with leading zero. dvsZ.unshift(0); do { // 'next' is how many times the divisor goes into the current remainder. for ( next = 0; next < base; next++ ) { // Compare divisor and remainder. if ( dvsL != ( remL = rem.length ) ) { cmp = dvsL > remL ? 1 : -1; } else { for ( remI = -1, cmp = 0; ++remI < dvsL; ) { if ( dvs[remI] != rem[remI] ) { cmp = dvs[remI] > rem[remI] ? 1 : -1; break; } } } // Subtract divisor from remainder (if divisor < remainder). if ( cmp < 0 ) { // Remainder cannot be more than one digit longer than divisor. // Equalise lengths using divisor with extra leading zero? for ( dvsT = remL == dvsL ? dvs : dvsZ; remL; ) { if ( rem[--remL] < dvsT[remL] ) { for ( remI = remL; remI && !rem[--remI]; rem[remI] = base - 1 ) { } --rem[remI]; rem[remL] += base; } rem[remL] -= dvsT[remL]; } for ( ; !rem[0]; rem.shift() ) { } } else { break; } } // Add the 'next' digit to the result array. qc[qi++] = cmp ? next : ++next; // Update the remainder. rem[0] && cmp ? ( rem[remL] = dvd[dvdI] || 0 ) : ( rem = [ dvd[dvdI] ] ); } while ( ( dvdI++ < dvdL || rem[0] != null ) && s-- ); // Leading zero? Do not remove if result is simply zero (qi == 1). if ( !qc[0] && qi != 1 ) { // There can't be more than one zero. --quo['e']; qc.shift(); } // Round? if ( qi > dig ) { rnd( quo, DECIMAL_PLACES, base, isOdd, rem[0] != null ); } // Overflow? if ( quo['e'] > MAX_EXP ) { // Infinity. quo['c'] = quo['e'] = null; // Underflow? } else if ( quo['e'] < MIN_EXP ) { // Zero. quo['c'] = [quo['e'] = 0]; } return quo; } /* * Return a string representing the value of BigNumber n in normal or * exponential notation rounded to the specified decimal places or * significant digits. * Called by toString, toExponential (exp 1), toFixed, and toPrecision (exp 2). * d is the index (with the value in normal notation) of the digit that may be * rounded up. */ function format( n, d, exp ) { // Initially, i is the number of decimal places required. var i = d - (n = new BigNumber(n))['e'], c = n['c']; // +-Infinity or NaN? if ( !c ) { return n['toS'](); } // Round? if ( c.length > ++d ) { rnd( n, i, 10 ); } // Recalculate d if toFixed as n['e'] may have changed if value rounded up. i = c[0] == 0 ? i + 1 : exp ? d : n['e'] + i + 1; // Append zeros? for ( ; c.length < i; c.push(0) ) { } i = n['e']; /* * toPrecision returns exponential notation if the number of significant * digits specified is less than the number of digits necessary to * represent the integer part of the value in normal notation. */ return exp == 1 || exp == 2 && ( --d < i || i <= TO_EXP_NEG ) // Exponential notation. ? ( n['s'] < 0 && c[0] ? '-' : '' ) + ( c.length > 1 ? ( c.splice( 1, 0, '.' ), c.join('') ) : c[0] ) + ( i < 0 ? 'e' : 'e+' ) + i // Normal notation. : n['toS'](); } // Round if necessary. // Called by divide, format, setMode and sqrt. function rnd( x, dp, base, isOdd, r ) { var xc = x['c'], isNeg = x['s'] < 0, half = base / 2, i = x['e'] + dp + 1, // 'next' is the digit after the digit that may be rounded up. next = xc[i], /* * 'more' is whether there are digits after 'next'. * E.g. * 0.005 (e = -3) to be rounded to 0 decimal places (dp = 0) gives i = -2 * The 'next' digit is zero, and there ARE 'more' digits after it. * 0.5 (e = -1) dp = 0 gives i = 0 * The 'next' digit is 5 and there are no 'more' digits after it. */ more = r || i < 0 || xc[i + 1] != null; r = ROUNDING_MODE < 4 ? ( next != null || more ) && ( ROUNDING_MODE == 0 || ROUNDING_MODE == 2 && !isNeg || ROUNDING_MODE == 3 && isNeg ) : next > half || next == half && ( ROUNDING_MODE == 4 || more || /* * isOdd is used in base conversion and refers to the least significant * digit of the integer part of the value to be converted. The fraction * part is rounded by this method separately from the integer part. */ ROUNDING_MODE == 6 && ( xc[i - 1] & 1 || !dp && isOdd ) || ROUNDING_MODE == 7 && !isNeg || ROUNDING_MODE == 8 && isNeg ); if ( i < 1 || !xc[0] ) { xc.length = 0; xc.push(0); if ( r ) { // 1, 0.1, 0.01, 0.001, 0.0001 etc. xc[0] = 1; x['e'] = -dp; } else { // Zero. x['e'] = 0; } return x; } // Remove any digits after the required decimal places. xc.length = i--; // Round up? if ( r ) { // Rounding up may mean the previous digit has to be rounded up and so on. for ( --base; ++xc[i] > base; ) { xc[i] = 0; if ( !i-- ) { ++x['e']; xc.unshift(1); } } } // Remove trailing zeros. for ( i = xc.length; !xc[--i]; xc.pop() ) { } return x; } // Round after setting the appropriate rounding mode. // Handles ceil, floor and round. function setMode( x, dp, rm ) { var r = ROUNDING_MODE; ROUNDING_MODE = rm; x = new BigNumber(x); x['c'] && rnd( x, dp, 10 ); ROUNDING_MODE = r; return x; } // PROTOTYPE/INSTANCE METHODS /* * Return a new BigNumber whose value is the absolute value of this BigNumber. */ P['abs'] = P['absoluteValue'] = function () { var x = new BigNumber(this); if ( x['s'] < 0 ) { x['s'] = 1; } return x; }; /* * Return a new BigNumber whose value is the value of this BigNumber * rounded to a whole number in the direction of Infinity. */ P['ceil'] = function () { return setMode( this, 0, 2 ); }; /* * Return * 1 if the value of this BigNumber is greater than the value of BigNumber(y, b), * -1 if the value of this BigNumber is less than the value of BigNumber(y, b), * 0 if they have the same value, * or null if the value of either is NaN. */ P['comparedTo'] = P['cmp'] = function ( y, b ) { var a, x = this, xc = x['c'], yc = ( id = -id, y = new BigNumber( y, b ) )['c'], i = x['s'], j = y['s'], k = x['e'], l = y['e']; // Either NaN? if ( !i || !j ) { return null; } a = xc && !xc[0], b = yc && !yc[0]; // Either zero? if ( a || b ) { return a ? b ? 0 : -j : i; } // Signs differ? if ( i != j ) { return i; } // Either Infinity? if ( a = i < 0, b = k == l, !xc || !yc ) { return b ? 0 : !xc ^ a ? 1 : -1; } // Compare exponents. if ( !b ) { return k > l ^ a ? 1 : -1; } // Compare digit by digit. for ( i = -1, j = ( k = xc.length ) < ( l = yc.length ) ? k : l; ++i < j; ) { if ( xc[i] != yc[i] ) { return xc[i] > yc[i] ^ a ? 1 : -1; } } // Compare lengths. return k == l ? 0 : k > l ^ a ? 1 : -1; }; /* * Return the number of decimal places of the value of this BigNumber, * or null if the value of this BigNumber is +-Infinity or NaN. */ P['decimalPlaces'] = P['dp'] = function () { var dp; if ( this['c'] ) { dp = this['c'].length - this['e'] - 1; return dp < 0 ? 0 : dp; } return null; }; /* * n / 0 = I * n / N = N * n / I = 0 * 0 / n = 0 * 0 / 0 = N * 0 / N = N * 0 / I = 0 * N / n = N * N / 0 = N * N / N = N * N / I = N * I / n = I * I / 0 = I * I / N = N * I / I = N * * Return a new BigNumber whose value is the value of this BigNumber * divided by the value of BigNumber(y, b), rounded according to * DECIMAL_PLACES and ROUNDING_MODE. */ P['dividedBy'] = P['div'] = function ( y, b ) { var xc = this['c'], xe = this['e'], xs = this['s'], yc = ( id = 2, y = new BigNumber( y, b ) )['c'], ye = y['e'], ys = y['s'], s = xs == ys ? 1 : -1; // Either NaN/Infinity/0? return !xe && ( !xc || !xc[0] ) || !ye && ( !yc || !yc[0] ) // Either NaN? ? new BigNumber( !xs || !ys || // Both 0 or both Infinity? ( xc ? yc && xc[0] == yc[0] : !yc ) // Return NaN. ? NaN // x is 0 or y is Infinity? : xc && xc[0] == 0 || !yc // Return +-0. ? s * 0 // y is 0. Return +-Infinity. : s / 0 ) : divide( xc, yc, xe - ye, s, 10 ); }; /* * Return true if the value of this BigNumber is equal to the value of * BigNumber(n, b), otherwise returns