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