1
1
'use strict' ;
2
2
3
+ var MAX_DIGITS = 22 ;
4
+ var DECIMAL_SEP = '.' ;
5
+ var ZERO_CHAR = '0' ;
6
+
3
7
/**
4
8
* @ngdoc filter
5
9
* @name currency
@@ -124,8 +128,6 @@ function currencyFilter($locale) {
124
128
</file>
125
129
</example>
126
130
*/
127
-
128
-
129
131
numberFilter . $inject = [ '$locale' ] ;
130
132
function numberFilter ( $locale ) {
131
133
var formats = $locale . NUMBER_FORMATS ;
@@ -139,93 +141,194 @@ function numberFilter($locale) {
139
141
} ;
140
142
}
141
143
142
- var DECIMAL_SEP = '.' ;
143
- function formatNumber ( number , pattern , groupSep , decimalSep , fractionSize ) {
144
- if ( isObject ( number ) ) return '' ;
144
+ /**
145
+ * Parse a number (as a string) into three components that can be used
146
+ * for formatting the number.
147
+ *
148
+ * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/)
149
+ *
150
+ * @param {string } numStr The number to parse
151
+ * @return {object } An object describing this number, containing the following keys:
152
+ * - d : an array of digits containing leading zeros as necessary
153
+ * - i : the number of the digits in `d` that are to the left of the decimal point
154
+ * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
155
+ *
156
+ */
157
+ function parse ( numStr ) {
158
+ var exponent = 0 , digits , numberOfIntegerDigits ;
159
+ var i , j , zeros ;
160
+
161
+ // Decimal point?
162
+ if ( ( numberOfIntegerDigits = numStr . indexOf ( DECIMAL_SEP ) ) > - 1 ) {
163
+ numStr = numStr . replace ( DECIMAL_SEP , '' ) ;
164
+ }
145
165
146
- var isNegative = number < 0 ;
147
- number = Math . abs ( number ) ;
166
+ // Exponential form?
167
+ if ( ( i = numStr . search ( / e / i) ) > 0 ) {
168
+ // Work out the exponent.
169
+ if ( numberOfIntegerDigits < 0 ) numberOfIntegerDigits = i ;
170
+ numberOfIntegerDigits += + numStr . slice ( i + 1 ) ;
171
+ numStr = numStr . substring ( 0 , i ) ;
172
+ } else if ( numberOfIntegerDigits < 0 ) {
173
+ // There was no decimal point or exponent so it is an integer.
174
+ numberOfIntegerDigits = numStr . length ;
175
+ }
148
176
149
- var isInfinity = number === Infinity ;
150
- if ( ! isInfinity && ! isFinite ( number ) ) return '' ;
177
+ // Count the number of leading zeros.
178
+ for ( i = 0 ; numStr . charAt ( i ) == ZERO_CHAR ; i ++ ) ;
151
179
152
- var numStr = number + '' ,
153
- formatedText = '' ,
154
- hasExponent = false ,
155
- parts = [ ] ;
180
+ if ( i == ( zeros = numStr . length ) ) {
181
+ // The digits are all zero.
182
+ digits = [ 0 ] ;
183
+ numberOfIntegerDigits = 1 ;
184
+ } else {
185
+ // Count the number of trailing zeros
186
+ zeros -- ;
187
+ while ( numStr . charAt ( zeros ) == ZERO_CHAR ) zeros -- ;
188
+
189
+ // Trailing zeros are insignificant so ignore them
190
+ numberOfIntegerDigits -= i ;
191
+ digits = [ ] ;
192
+ // Convert string to array of digits without leading/trailing zeros.
193
+ for ( j = 0 ; i <= zeros ; i ++ , j ++ ) {
194
+ digits [ j ] = + numStr . charAt ( i ) ;
195
+ }
196
+ }
156
197
157
- if ( isInfinity ) formatedText = '\u221e' ;
198
+ // If the number overflows the maximum allowed digits then use an exponent.
199
+ if ( numberOfIntegerDigits > MAX_DIGITS ) {
200
+ digits = digits . splice ( 0 , MAX_DIGITS - 1 ) ;
201
+ exponent = numberOfIntegerDigits - 1 ;
202
+ numberOfIntegerDigits = 1 ;
203
+ }
204
+
205
+ return { d : digits , e : exponent , i : numberOfIntegerDigits } ;
206
+ }
158
207
159
- if ( ! isInfinity && numStr . indexOf ( 'e' ) !== - 1 ) {
160
- var match = numStr . match ( / ( [ \d \. ] + ) e ( - ? ) ( \d + ) / ) ;
161
- if ( match && match [ 2 ] == '-' && match [ 3 ] > fractionSize + 1 ) {
162
- number = 0 ;
208
+ /**
209
+ * Round the parsed number to the specified number of decimal places
210
+ * This function changed the parsedNumber in-place
211
+ */
212
+ function roundNumber ( parsedNumber , fractionSize , minFrac , maxFrac ) {
213
+ var digits = parsedNumber . d ;
214
+ var fractionLen = digits . length - parsedNumber . i ;
215
+
216
+ // determine fractionSize if it is not specified; `+fractionSize` converts it to a number
217
+ fractionSize = ( isUndefined ( fractionSize ) ) ? Math . min ( Math . max ( minFrac , fractionLen ) , maxFrac ) : + fractionSize ;
218
+
219
+ // The index of the digit to where rounding is to occur
220
+ var roundAt = fractionSize + parsedNumber . i ;
221
+ var digit = digits [ roundAt ] ;
222
+
223
+ if ( roundAt > 0 ) {
224
+ digits . splice ( roundAt ) ;
163
225
} else {
164
- formatedText = numStr ;
165
- hasExponent = true ;
226
+ // We rounded to zero so reset the parsedNumber
227
+ parsedNumber . i = 1 ;
228
+ digits . length = roundAt = fractionSize + 1 ;
229
+ for ( var i = 0 ; i < roundAt ; i ++ ) digits [ i ] = 0 ;
166
230
}
167
- }
168
231
169
- if ( ! isInfinity && ! hasExponent ) {
170
- var fractionLen = ( numStr . split ( DECIMAL_SEP ) [ 1 ] || '' ) . length ;
232
+ if ( digit >= 5 ) digits [ roundAt - 1 ] ++ ;
171
233
172
- // determine fractionSize if it is not specified
173
- if ( isUndefined ( fractionSize ) ) {
174
- fractionSize = Math . min ( Math . max ( pattern . minFrac , fractionLen ) , pattern . maxFrac ) ;
234
+ // Pad out with zeros to get the required fraction length
235
+ for ( ; fractionLen < fractionSize ; fractionLen ++ ) digits . push ( 0 ) ;
236
+
237
+
238
+ // Do any carrying, e.g. a digit was rounded up to 10
239
+ var carry = digits . reduceRight ( function ( carry , d , i , digits ) {
240
+ d = d + carry ;
241
+ digits [ i ] = d % 10 ;
242
+ return Math . floor ( d / 10 ) ;
243
+ } , 0 ) ;
244
+ if ( carry ) {
245
+ digits . unshift ( carry ) ;
246
+ parsedNumber . i ++ ;
175
247
}
248
+ }
176
249
177
- // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
178
- // inspired by:
179
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
180
- number = + ( Math . round ( + ( number . toString ( ) + 'e' + fractionSize ) ) . toString ( ) + 'e' + - fractionSize ) ;
181
-
182
- var fraction = ( '' + number ) . split ( DECIMAL_SEP ) ;
183
- var whole = fraction [ 0 ] ;
184
- fraction = fraction [ 1 ] || '' ;
185
-
186
- var i , pos = 0 ,
187
- lgroup = pattern . lgSize ,
188
- group = pattern . gSize ;
189
-
190
- if ( whole . length >= ( lgroup + group ) ) {
191
- pos = whole . length - lgroup ;
192
- for ( i = 0 ; i < pos ; i ++ ) {
193
- if ( ( pos - i ) % group === 0 && i !== 0 ) {
194
- formatedText += groupSep ;
195
- }
196
- formatedText += whole . charAt ( i ) ;
197
- }
250
+ /**
251
+ * Format a number into a string
252
+ * @param {number } number The number to format
253
+ * @param {{
254
+ * minFrac, // the minimum number of digits required in the fraction part of the number
255
+ * maxFrac, // the maximum number of digits required in the fraction part of the number
256
+ * gSize, // number of digits in each group of separated digits
257
+ * lgSize, // number of digits in the last group of digits before the decimal separator
258
+ * negPre, // the string to go in front of a negative number (e.g. `-` or `(`))
259
+ * posPre, // the string to go in front of a positive number
260
+ * negSuf, // the string to go after a negative number (e.g. `)`)
261
+ * posSuf // the string to go after a positive number
262
+ * }} pattern
263
+ * @param {string } groupSep The string to separate groups of number (e.g. `,`)
264
+ * @param {string } decimalSep The string to act as the decimal separator (e.g. `.`)
265
+ * @param {[type] } fractionSize The size of the fractional part of the number
266
+ * @return {string } The number formatted as a string
267
+ */
268
+ function formatNumber ( number , pattern , groupSep , decimalSep , fractionSize ) {
269
+
270
+ if ( ! ( isString ( number ) || isNumber ( number ) ) || isNaN ( number ) ) return '' ;
271
+
272
+ var isInfinity = ! isFinite ( number ) ;
273
+ var isZero = false ;
274
+ var numStr = Math . abs ( number ) + '' ,
275
+ formattedText = '' ,
276
+ parsedNumber ;
277
+
278
+ if ( isInfinity ) {
279
+ formattedText = '\u221e' ;
280
+ } else {
281
+ parsedNumber = parse ( numStr ) ;
282
+
283
+ roundNumber ( parsedNumber , fractionSize , pattern . minFrac , pattern . maxFrac ) ;
284
+
285
+ var digits = parsedNumber . d ;
286
+ var integerLen = parsedNumber . i ;
287
+ var exponent = parsedNumber . e ;
288
+ var decimals = [ ] ;
289
+ isZero = digits . reduce ( function ( isZero , d ) { return isZero && ! d ; } , true ) ;
290
+
291
+ // pad zeros for small numbers
292
+ while ( integerLen < 0 ) {
293
+ digits . unshift ( 0 ) ;
294
+ integerLen ++ ;
198
295
}
199
296
200
- for ( i = pos ; i < whole . length ; i ++ ) {
201
- if ( ( whole . length - i ) % lgroup === 0 && i !== 0 ) {
202
- formatedText += groupSep ;
203
- }
204
- formatedText += whole . charAt ( i ) ;
297
+ // extract decimals digits
298
+ if ( integerLen > 0 ) {
299
+ decimals = digits . splice ( integerLen ) ;
300
+ } else {
301
+ decimals = digits ;
302
+ digits = [ 0 ] ;
303
+ }
304
+
305
+ // format the integer digits with grouping separators
306
+ var groups = [ ] ;
307
+ if ( digits . length > pattern . lgSize ) {
308
+ groups . unshift ( digits . splice ( - pattern . lgSize ) . join ( '' ) ) ;
205
309
}
310
+ while ( digits . length > pattern . gSize ) {
311
+ groups . unshift ( digits . splice ( - pattern . gSize ) . join ( '' ) ) ;
312
+ }
313
+ if ( digits . length ) {
314
+ groups . unshift ( digits . join ( '' ) ) ;
315
+ }
316
+ formattedText = groups . join ( groupSep ) ;
206
317
207
- // format fraction part.
208
- while ( fraction . length < fractionSize ) {
209
- fraction += '0' ;
318
+ // append the decimal digits
319
+ if ( decimals . length ) {
320
+ formattedText += decimalSep + decimals . join ( '' ) ;
210
321
}
211
322
212
- if ( fractionSize && fractionSize !== "0" ) formatedText += decimalSep + fraction . substr ( 0 , fractionSize ) ;
213
- } else {
214
- if ( fractionSize > 0 && number < 1 ) {
215
- formatedText = number . toFixed ( fractionSize ) ;
216
- number = parseFloat ( formatedText ) ;
217
- formatedText = formatedText . replace ( DECIMAL_SEP , decimalSep ) ;
323
+ if ( exponent ) {
324
+ formattedText += 'e+' + exponent ;
218
325
}
219
326
}
220
-
221
- if ( number === 0 ) {
222
- isNegative = false ;
327
+ if ( number < 0 && ! isZero ) {
328
+ return pattern . negPre + formattedText + pattern . negSuf ;
329
+ } else {
330
+ return pattern . posPre + formattedText + pattern . posSuf ;
223
331
}
224
-
225
- parts . push ( isNegative ? pattern . negPre : pattern . posPre ,
226
- formatedText ,
227
- isNegative ? pattern . negSuf : pattern . posSuf ) ;
228
- return parts . join ( '' ) ;
229
332
}
230
333
231
334
function padNumber ( num , digits , trim ) {
@@ -235,7 +338,7 @@ function padNumber(num, digits, trim) {
235
338
num = - num ;
236
339
}
237
340
num = '' + num ;
238
- while ( num . length < digits ) num = '0' + num ;
341
+ while ( num . length < digits ) num = ZERO_CHAR + num ;
239
342
if ( trim ) {
240
343
num = num . substr ( num . length - digits ) ;
241
344
}
0 commit comments