@@ -139,93 +139,188 @@ function numberFilter($locale) {
139
139
} ;
140
140
}
141
141
142
+ var isValid = / ^ - ? ( \d + ( \. \d * ) ? | \. \d + ) ( e [ + - ] ? \d + ) ? $ / i;
143
+ function parse ( numStr ) {
144
+ var parsedNumber = { } ;
145
+ var exponent , i , j , zeros ;
146
+
147
+ // Decimal point?
148
+ if ( ( exponent = numStr . indexOf ( '.' ) ) > - 1 ) {
149
+ numStr = numStr . replace ( '.' , '' ) ;
150
+ }
151
+ // Exponential form?
152
+ if ( ( i = numStr . search ( / e / i) ) > 0 ) {
153
+ // Determine exponent.
154
+ if ( exponent < 0 ) {
155
+ exponent = i ;
156
+ }
157
+ exponent += + numStr . slice ( i + 1 ) ;
158
+ numStr = numStr . substring ( 0 , i ) ;
159
+ } else if ( exponent < 0 ) {
160
+ // Integer.
161
+ exponent = numStr . length ;
162
+ }
163
+ // Determine leading zeros.
164
+ i = 0 ;
165
+ while ( numStr . charAt ( i ) == '0' ) i ++ ;
166
+
167
+ if ( i == ( zeros = numStr . length ) ) {
168
+ // Zero.
169
+ parsedNumber . digits = [ parsedNumber . exponent = 0 ] ;
170
+ } else {
171
+ // Determine trailing zeros.
172
+ do {
173
+ zeros -- ;
174
+ } while ( numStr . charAt ( zeros ) == '0' ) ;
175
+ parsedNumber . exponent = exponent - i - 1 ;
176
+ parsedNumber . digits = [ ] ;
177
+ // Convert string to array of digits without leading/trailing zeros.
178
+ j = 0 ;
179
+ while ( i <= zeros ) {
180
+ parsedNumber . digits [ j ++ ] = + numStr . charAt ( i ++ ) ;
181
+ }
182
+ }
183
+ return parsedNumber ;
184
+ }
185
+
186
+ function roundNumber ( parsedNumber , fractionSize , minFrac , maxFrac ) {
187
+ var digit = 0 ;
188
+ var digits = parsedNumber . digits ;
189
+ var exponent = parsedNumber . exponent ;
190
+ var fractionLen = digits . length - 1 - exponent ;
191
+
192
+ // determine fractionSize if it is not specified
193
+ if ( isUndefined ( fractionSize ) ) {
194
+ fractionSize = Math . min ( Math . max ( minFrac , fractionLen ) , maxFrac ) ;
195
+ }
196
+
197
+ // Cut off unwanted digits with rounding
198
+ while ( fractionLen > fractionSize && digits . length ) {
199
+ digit = digits . pop ( ) ;
200
+ // Round up if necessary
201
+ if ( digit >= 5 ) digits [ digits . length - 1 ] ++ ;
202
+ fractionLen -- ;
203
+ }
204
+
205
+ if ( digits . length === 0 ) {
206
+ // We rounded to zero so reset the parsedNumber
207
+ parsedNumber . exponent = 0 ;
208
+ // If the last removed digit was >= 5 then we need to round up
209
+ if ( digit >= 5 ) digits . push ( 1 ) ;
210
+ // Pad out with the necessary zeros
211
+ while ( digits . length <= fractionSize ) digits . unshift ( 0 ) ;
212
+ }
213
+
214
+ while ( fractionLen < fractionSize ) {
215
+ digits . push ( 0 ) ;
216
+ fractionLen ++ ;
217
+ }
218
+
219
+ // Do a final clear of any carrying, e.g. the last digit was rounded up to 10
220
+ var carry = digits . reduceRight ( function ( carry , d , i , digits ) {
221
+ d = d + carry ;
222
+ digits [ i ] = d % 10 ;
223
+ return Math . floor ( d / 10 ) ;
224
+ } , 0 ) ;
225
+ if ( carry ) {
226
+ digits . unshift ( carry ) ;
227
+ parsedNumber . exponent ++ ;
228
+ }
229
+ }
230
+
142
231
var DECIMAL_SEP = '.' ;
232
+ var MAX_DIGITS = 21 ;
233
+
234
+ /**
235
+ * Format a number into a string
236
+ * @param {number } number The number to format
237
+ * @param {{
238
+ * minFrac, // the minimum number of digits required in the fraction part of the number
239
+ * maxFrac, // the maximum number of digits required in the fraction part of the number
240
+ * gSize, // number of digits in each group of separated digits
241
+ * lgSize, // number of digits in the last group of digits before the decimal separator
242
+ * negPre, // the string to go in front of a negative number (e.g. `-` or `(`))
243
+ * posPre, // the string to go in front of a positive number
244
+ * negSuf, // the string to go after a negative number (e.g. `)`)
245
+ * posSuf // the string to go after a positive number
246
+ * }} pattern
247
+ * @param {string } groupSep The string to separate groups of number (e.g. `,`)
248
+ * @param {string } decimalSep The string to act as the decimal separator (e.g. `.`)
249
+ * @param {[type] } fractionSize The size of the fractional part of the number
250
+ * @return {string } The number formatted as a string
251
+ */
143
252
function formatNumber ( number , pattern , groupSep , decimalSep , fractionSize ) {
144
- if ( isObject ( number ) ) return '' ;
145
253
254
+ if ( isObject ( number ) ) return '' ;
255
+ var isInfinity = number === Infinity || number === - Infinity ;
256
+ if ( ! isInfinity && ! isFinite ( number ) ) return '' ;
146
257
var isNegative = number < 0 ;
258
+ var isZero = false ;
147
259
number = Math . abs ( number ) ;
148
-
149
- var isInfinity = number === Infinity ;
150
- if ( ! isInfinity && ! isFinite ( number ) ) return '' ;
151
-
152
260
var numStr = number + '' ,
153
- formatedText = '' ,
154
- hasExponent = false ,
155
- parts = [ ] ;
261
+ formattedText = '' ,
262
+ realExponent = 0 ,
263
+ parsedNumber ;
156
264
157
- if ( isInfinity ) formatedText = '\u221e' ;
265
+ if ( isInfinity ) {
266
+ formattedText = '\u221e' ;
267
+ } else {
268
+ parsedNumber = parse ( numStr ) ;
158
269
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 ;
163
- } else {
164
- formatedText = numStr ;
165
- hasExponent = true ;
270
+ if ( parsedNumber . exponent > MAX_DIGITS ) {
271
+ parsedNumber . digits = parsedNumber . digits . splice ( 0 , MAX_DIGITS - 1 ) ;
272
+ realExponent = parsedNumber . exponent ;
273
+ parsedNumber . exponent = 0 ;
166
274
}
167
- }
168
275
169
- if ( ! isInfinity && ! hasExponent ) {
170
- var fractionLen = ( numStr . split ( DECIMAL_SEP ) [ 1 ] || '' ) . length ;
276
+ roundNumber ( parsedNumber , fractionSize , pattern . minFrac , pattern . maxFrac ) ;
171
277
172
- // determine fractionSize if it is not specified
173
- if ( isUndefined ( fractionSize ) ) {
174
- fractionSize = Math . min ( Math . max ( pattern . minFrac , fractionLen ) , pattern . maxFrac ) ;
278
+ var digits = parsedNumber . digits ;
279
+ var exponent = parsedNumber . exponent ;
280
+ var decimals = [ ] ;
281
+ isZero = digits . reduce ( function ( isZero , d ) { return isZero && ! d ; } , true ) ;
282
+
283
+ // pad zeros for small numbers
284
+ while ( exponent < - 1 ) {
285
+ digits . unshift ( 0 ) ;
286
+ exponent ++ ;
175
287
}
176
288
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
- }
289
+ // extract decimals digits
290
+ if ( exponent >= 0 ) {
291
+ decimals = digits . splice ( exponent + 1 ) ;
292
+ } else {
293
+ decimals = digits ;
294
+ digits = [ 0 ] ;
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
+ // format the integer digits with grouping separators
298
+ var groups = [ ] ;
299
+ if ( digits . length > pattern . lgSize ) {
300
+ groups . unshift ( digits . splice ( - pattern . lgSize ) . join ( '' ) ) ;
301
+ }
302
+ while ( digits . length > pattern . gSize ) {
303
+ groups . unshift ( digits . splice ( - pattern . gSize ) . join ( '' ) ) ;
205
304
}
305
+ if ( digits . length ) {
306
+ groups . unshift ( digits . join ( '' ) ) ;
307
+ }
308
+ formattedText = groups . join ( groupSep ) ;
206
309
207
- // format fraction part.
208
- while ( fraction . length < fractionSize ) {
209
- fraction += '0' ;
310
+ // append the decimal digits
311
+ if ( decimals . length ) {
312
+ formattedText += decimalSep + decimals . join ( '' ) ;
210
313
}
211
314
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 ) ;
315
+ if ( realExponent ) {
316
+ formattedText += 'e+' + realExponent ;
218
317
}
219
318
}
220
-
221
- if ( number === 0 ) {
222
- isNegative = false ;
319
+ if ( isNegative && ! isZero ) {
320
+ return pattern . negPre + formattedText + pattern . negSuf ;
321
+ } else {
322
+ return pattern . posPre + formattedText + pattern . posSuf ;
223
323
}
224
-
225
- parts . push ( isNegative ? pattern . negPre : pattern . posPre ,
226
- formatedText ,
227
- isNegative ? pattern . negSuf : pattern . posSuf ) ;
228
- return parts . join ( '' ) ;
229
324
}
230
325
231
326
function padNumber ( num , digits , trim ) {
0 commit comments