Home | History | Annotate | Download | only in app
      1 /**
      2  * Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3  * Use of this source code is governed by a BSD-style license that can be
      4  * found in the LICENSE file.
      5  **/
      6 
      7 function Model(precision) {
      8   this.reset_({precision: precision});
      9 }
     10 
     11 /**
     12  * Handles a calculator key input, updating the calculator state accordingly and
     13  * returning an object with 'accumulator', 'operator', and 'operand' properties
     14  * representing that state.
     15  *
     16  * @private
     17  */
     18 Model.prototype.handle = function(input) {
     19   switch (input) {
     20     case '+':
     21     case '-':
     22     case '/':
     23     case '*':
     24       // For operations, ignore the last operator if no operand was entered,
     25       // otherwise perform the current calculation before setting the new
     26       // operator. In either case, clear the operand and the defaults.
     27       var operator = this.operand && this.operator;
     28       var result = this.calculate_(operator, this.operand);
     29       return this.reset_({accumulator: result, operator: input});
     30     case '=':
     31       // For the equal sign, perform the current calculation and save the
     32       // operator and operands used as defaults, or if there is no current
     33       // operator, use the default operators and operands instead. In any case,
     34       // clear the operator and operand and return a transient state with a '='
     35       // operator.
     36       var operator = this.operator || this.defaults.operator;
     37       var operand = this.operator ? this.operand : this.defaults.operand;
     38       var result = this.calculate_(operator, operand);
     39       var defaults = {operator: operator, operand: this.operand};
     40       return this.reset_({accumulator: result, defaults: defaults});
     41     case 'AC':
     42       return this.reset_({});
     43     case 'C':
     44       return this.operand  ? this.set_({operand: null}) :
     45              this.operator ? this.set_({operator: null}) :
     46                              this.handle('AC');
     47     case 'back':
     48       var length = (this.operand || '').length;
     49       return (length > 1)  ? this.set_({operand: this.operand.slice(0, -1)}) :
     50              this.operand  ? this.set_({operand: null}) :
     51                              this.set_({operator: null});
     52     case '+ / -':
     53       var initial = (this.operand || '0')[0];
     54       return (initial === '-') ? this.set_({operand: this.operand.slice(1)}) :
     55              (initial !== '0') ? this.set_({operand: '-' + this.operand}) :
     56                                  this.set_({});
     57     default:
     58       var operand = (this.operand || '0') + input;
     59       var duplicate = (operand.replace(/[^.]/g, '').length > 1);
     60       var overflow = (operand.replace(/[^0-9]/g, '').length > this.precision);
     61       return operand.match(/^0[0-9]/)  ? this.set_({operand: operand[1]}) :
     62              (!duplicate && !overflow) ? this.set_({operand: operand}) :
     63                                          this.set_({});
     64   }
     65 }
     66 
     67 /**
     68  * Reset the model's state to the passed in state.
     69  *
     70  * @private
     71  */
     72 Model.prototype.reset_ = function(state) {
     73   this.accumulator = this.operand = this.operator = null;
     74   this.defaults = {operator: null, operand: null};
     75   return this.set_(state);
     76 }
     77 
     78 /**
     79  * Selectively replace the model's state with the passed in state.
     80  *
     81  * @private
     82  */
     83 Model.prototype.set_ = function(state) {
     84   var ifDefined = function(x, y) { return (x !== undefined) ? x : y; };
     85   var precision = (state && state.precision) || this.precision || 9;
     86   this.precision = Math.min(Math.max(precision, 1), 9);
     87   this.accumulator = ifDefined(state && state.accumulator, this.accumulator);
     88   this.operator = ifDefined(state && state.operator, this.operator);
     89   this.operand = ifDefined(state && state.operand, this.operand);
     90   this.defaults = ifDefined(state && state.defaults, this.defaults);
     91   return this;
     92 }
     93 
     94 /**
     95  * Performs a calculation based on the passed in operator and operand, updating
     96  * the model's state with the operator and operand used but returning the result
     97  * of the calculation instead of updating the model's state with it.
     98  *
     99  * @private
    100  */
    101 Model.prototype.calculate_ = function(operator, operand) {
    102   var x = Number(this.accumulator) || 0;
    103   var y = operand ? Number(operand) : x;
    104   this.set_({accumulator: String(x), operator: operator, operand: String(y)});
    105   return (this.operator == '+') ? this.round_(x + y) :
    106          (this.operator == '-') ? this.round_(x - y) :
    107          (this.operator == '*') ? this.round_(x * y) :
    108          (this.operator == '/') ? this.round_(x / y) :
    109                                   this.round_(y);
    110 }
    111 
    112 /**
    113  * Returns the string representation of the passed in value rounded to the
    114  * model's precision, or "E" on overflow.
    115  *
    116  * @private
    117  */
    118 Model.prototype.round_ = function(x) {
    119   var exponent = Number(x.toExponential(this.precision - 1).split('e')[1]);
    120   var digits = this.digits_(exponent);
    121   var exponential = x.toExponential(digits).replace(/\.?0+e/, 'e');
    122   var fixed = (Math.abs(exponent) < this.precision && exponent > -7);
    123   return !digits ? 'E' : fixed ? String(Number(exponential)) : exponential;
    124 }
    125 
    126 /**
    127  * Returns the appropriate number of digits to include of a number based on
    128  * its size.
    129  *
    130  * @private
    131  */
    132 Model.prototype.digits_ = function(exponent) {
    133   return (isNaN(exponent) || exponent < -199 || exponent > 199) ? 0 :
    134          (exponent < -99) ? (this.precision - 1 - 5) :
    135          (exponent < -9) ? (this.precision - 1 - 4) :
    136          (exponent < -6) ? (this.precision - 1 - 3) :
    137          (exponent < 0) ? (this.precision - 1 + exponent) :
    138          (exponent < this.precision) ? (this.precision - 1) :
    139          (exponent < 10) ? (this.precision - 1 - 3) :
    140          (exponent < 100) ? (this.precision - 1 - 4) :
    141                             (this.precision - 1 - 5);
    142 }
    143