]>
git.wh0rd.org - tt-rss.git/blob - lib/dojo/number.js.uncompressed.js
1 define("dojo/number", [/*===== "./_base/declare", =====*/ "./_base/lang", "./i18n", "./i18n!./cldr/nls/number", "./string", "./regexp"],
2 function(/*===== declare, =====*/ lang
, i18n
, nlsNumber
, dstring
, dregexp
){
9 // localized formatting and parsing routines for Number
11 lang
.setObject("dojo.number", number
);
14 number.__FormatOptions = declare(null, {
16 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
17 // with this string. Default value is based on locale. Overriding this property will defeat
18 // localization. Literal characters in patterns are not supported.
20 // choose a format type based on the locale from the following:
21 // decimal, scientific (not yet supported), percent, currency. decimal by default.
23 // fixed number of decimal places to show. This overrides any
24 // information in the provided pattern.
26 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
27 // means do not round.
29 // override the locale used to determine formatting rules
30 // fractional: Boolean?
31 // If false, show no decimal places, overriding places and pattern settings.
35 number
.format = function(/*Number*/ value
, /*number.__FormatOptions?*/ options
){
37 // Format a Number as a String, using locale-specific settings
39 // Create a string from a Number using a known localized pattern.
40 // Formatting patterns appropriate to the locale are chosen from the
41 // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and
43 // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
45 // the number to be formatted
47 options
= lang
.mixin({}, options
|| {});
48 var locale
= i18n
.normalizeLocale(options
.locale
),
49 bundle
= i18n
.getLocalization("dojo.cldr", "number", locale
);
50 options
.customs
= bundle
;
51 var pattern
= options
.pattern
|| bundle
[(options
.type
|| "decimal") + "Format"];
52 if(isNaN(value
) || Math
.abs(value
) == Infinity
){ return null; } // null
53 return number
._applyPattern(value
, pattern
, options
); // String
56 //number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
57 number
._numberPatternRE
= /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
59 number
._applyPattern = function(/*Number*/ value
, /*String*/ pattern
, /*number.__FormatOptions?*/ options
){
61 // Apply pattern to format value as a string using options. Gives no
62 // consideration to local customs.
64 // the number to be formatted.
66 // a pattern string as described by
67 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
68 // options: number.__FormatOptions?
69 // _applyPattern is usually called via `dojo/number.format()` which
70 // populates an extra property in the options parameter, "customs".
71 // The customs object specifies group and decimal parameters if set.
73 //TODO: support escapes
74 options
= options
|| {};
75 var group
= options
.customs
.group
,
76 decimal = options
.customs
.decimal,
77 patternList
= pattern
.split(';'),
78 positivePattern
= patternList
[0];
79 pattern
= patternList
[(value
< 0) ? 1 : 0] || ("-" + positivePattern
);
81 //TODO: only test against unescaped
82 if(pattern
.indexOf('%') != -1){
84 }else if(pattern
.indexOf('\u2030') != -1){
85 value
*= 1000; // per mille
86 }else if(pattern
.indexOf('\u00a4') != -1){
87 group
= options
.customs
.currencyGroup
|| group
;//mixins instead?
88 decimal = options
.customs
.currencyDecimal
|| decimal;// Should these be mixins instead?
89 pattern
= pattern
.replace(/\u00a4{1,3}/, function(match
){
90 var prop
= ["symbol", "currency", "displayName"][match
.length
-1];
91 return options
[prop
] || options
.currency
|| "";
93 }else if(pattern
.indexOf('E') != -1){
94 throw new Error("exponential notation not supported");
97 //TODO: support @ sig figs?
98 var numberPatternRE
= number
._numberPatternRE
;
99 var numberPattern
= positivePattern
.match(numberPatternRE
);
101 throw new Error("unable to find a number expression in pattern: "+pattern
);
103 if(options
.fractional
=== false){ options
.places
= 0; }
104 return pattern
.replace(numberPatternRE
,
105 number
._formatAbsolute(value
, numberPattern
[0], {decimal: decimal, group
: group
, places
: options
.places
, round
: options
.round
}));
108 number
.round = function(/*Number*/ value
, /*Number?*/ places
, /*Number?*/ increment
){
110 // Rounds to the nearest value with the given number of decimal places, away from zero
112 // Rounds to the nearest value with the given number of decimal places, away from zero if equal.
113 // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by
114 // fractional increments also, such as the nearest quarter.
115 // NOTE: Subject to floating point errors. See dojox/math/round for experimental workaround.
117 // The number to round
119 // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
120 // Must be non-negative.
122 // Rounds next place to nearest value of increment/10. 10 by default.
124 // | >>> number.round(-0.5)
126 // | >>> number.round(162.295, 2)
127 // | 162.29 // note floating point error. Should be 162.3
128 // | >>> number.round(10.71, 0, 2.5)
130 var factor
= 10 / (increment
|| 10);
131 return (factor
* +value
).toFixed(places
) / factor
; // Number
134 if((0.9).toFixed() == 0){
135 // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
136 // is just after the rounding place and is >=5
137 var round
= number
.round
;
138 number
.round = function(v
, p
, m
){
139 var d
= Math
.pow(10, -p
|| 0), a
= Math
.abs(v
);
144 if(a
< 0.5 || a
>= 0.95){
148 return round(v
, p
, m
) + (v
> 0 ? d
: -d
);
151 // Use "doc hint" so the doc parser ignores this new definition of round(), and uses the one above.
152 /*===== number.round = round; =====*/
156 number.__FormatAbsoluteOptions = declare(null, {
158 // the decimal separator
160 // the group separator
161 // places: Number|String?
162 // number of decimal places. the range "n,m" will format to m places.
164 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
165 // means don't round.
169 number
._formatAbsolute = function(/*Number*/ value
, /*String*/ pattern
, /*number.__FormatAbsoluteOptions?*/ options
){
171 // Apply numeric pattern to absolute value using options. Gives no
172 // consideration to local customs.
174 // the number to be formatted, ignores sign
176 // the number portion of a pattern (e.g. `#,##0.00`)
177 options
= options
|| {};
178 if(options
.places
=== true){options
.places
=0;}
179 if(options
.places
=== Infinity
){options
.places
=6;} // avoid a loop; pick a limit
181 var patternParts
= pattern
.split("."),
182 comma
= typeof options
.places
== "string" && options
.places
.indexOf(","),
183 maxPlaces
= options
.places
;
185 maxPlaces
= options
.places
.substring(comma
+ 1);
186 }else if(!(maxPlaces
>= 0)){
187 maxPlaces
= (patternParts
[1] || []).length
;
189 if(!(options
.round
< 0)){
190 value
= number
.round(value
, maxPlaces
, options
.round
);
193 var valueParts
= String(Math
.abs(value
)).split("."),
194 fractional
= valueParts
[1] || "";
195 if(patternParts
[1] || options
.places
){
197 options
.places
= options
.places
.substring(0, comma
);
199 // Pad fractional with trailing zeros
200 var pad
= options
.places
!== undefined ? options
.places
: (patternParts
[1] && patternParts
[1].lastIndexOf("0") + 1);
201 if(pad
> fractional
.length
){
202 valueParts
[1] = dstring
.pad(fractional
, pad
, '0', true);
205 // Truncate fractional
206 if(maxPlaces
< fractional
.length
){
207 valueParts
[1] = fractional
.substr(0, maxPlaces
);
210 if(valueParts
[1]){ valueParts
.pop(); }
213 // Pad whole with leading zeros
214 var patternDigits
= patternParts
[0].replace(',', '');
215 pad
= patternDigits
.indexOf("0");
217 pad
= patternDigits
.length
- pad
;
218 if(pad
> valueParts
[0].length
){
219 valueParts
[0] = dstring
.pad(valueParts
[0], pad
);
223 if(patternDigits
.indexOf("#") == -1){
224 valueParts
[0] = valueParts
[0].substr(valueParts
[0].length
- pad
);
228 // Add group separators
229 var index
= patternParts
[0].lastIndexOf(','),
230 groupSize
, groupSize2
;
232 groupSize
= patternParts
[0].length
- index
- 1;
233 var remainder
= patternParts
[0].substr(0, index
);
234 index
= remainder
.lastIndexOf(',');
236 groupSize2
= remainder
.length
- index
- 1;
240 for(var whole
= valueParts
[0]; whole
;){
241 var off
= whole
.length
- groupSize
;
242 pieces
.push((off
> 0) ? whole
.substr(off
) : whole
);
243 whole
= (off
> 0) ? whole
.slice(0, off
) : "";
245 groupSize
= groupSize2
;
249 valueParts
[0] = pieces
.reverse().join(options
.group
|| ",");
251 return valueParts
.join(options
.decimal || ".");
255 number.__RegexpOptions = declare(null, {
257 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
258 // with this string. Default value is based on locale. Overriding this property will defeat
261 // choose a format type based on the locale from the following:
262 // decimal, scientific (not yet supported), percent, currency. decimal by default.
264 // override the locale used to determine formatting rules
266 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
267 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
268 // places: Number|String?
269 // number of decimal places to accept: Infinity, a positive number, or
270 // a range "n,m". Defined by pattern or Infinity if pattern not provided.
273 number
.regexp = function(/*number.__RegexpOptions?*/ options
){
275 // Builds the regular needed to parse a number
277 // Returns regular expression with positive and negative match, group
278 // and decimal separators
279 return number
._parseInfo(options
).regexp
; // String
282 number
._parseInfo = function(/*Object?*/ options
){
283 options
= options
|| {};
284 var locale
= i18n
.normalizeLocale(options
.locale
),
285 bundle
= i18n
.getLocalization("dojo.cldr", "number", locale
),
286 pattern
= options
.pattern
|| bundle
[(options
.type
|| "decimal") + "Format"],
288 group
= bundle
.group
,
289 decimal = bundle
.decimal,
292 if(pattern
.indexOf('%') != -1){
294 }else if(pattern
.indexOf('\u2030') != -1){
295 factor
/= 1000; // per mille
297 var isCurrency
= pattern
.indexOf('\u00a4') != -1;
299 group
= bundle
.currencyGroup
|| group
;
300 decimal = bundle
.currencyDecimal
|| decimal;
304 //TODO: handle quoted escapes
305 var patternList
= pattern
.split(';');
306 if(patternList
.length
== 1){
307 patternList
.push("-" + patternList
[0]);
310 var re
= dregexp
.buildGroupRE(patternList
, function(pattern
){
311 pattern
= "(?:"+dregexp
.escapeString(pattern
, '.')+")";
312 return pattern
.replace(number
._numberPatternRE
, function(format
){
315 separator
: options
.strict
? group
: [group
,""],
316 fractional
: options
.fractional
,
321 parts
= format
.split('.'),
322 places
= options
.places
;
324 // special condition for percent (factor != 1)
325 // allow decimal places even if not specified in pattern
326 if(parts
.length
== 1 && factor
!= 1){
329 if(parts
.length
== 1 || places
=== 0){
330 flags
.fractional
= false;
332 if(places
=== undefined){ places
= options
.pattern
? parts
[1].lastIndexOf('0') + 1 : Infinity
; }
333 if(places
&& options
.fractional
== undefined){flags
.fractional
= true;} // required fractional, unless otherwise specified
334 if(!options
.places
&& (places
< parts
[1].length
)){ places
+= "," + parts
[1].length
; }
335 flags
.places
= places
;
337 var groups
= parts
[0].split(',');
338 if(groups
.length
> 1){
339 flags
.groupSize
= groups
.pop().length
;
340 if(groups
.length
> 1){
341 flags
.groupSize2
= groups
.pop().length
;
344 return "("+number
._realNumberRegexp(flags
)+")";
349 // substitute the currency symbol for the placeholder in the pattern
350 re
= re
.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match
, before
, target
, after
){
351 var prop
= ["symbol", "currency", "displayName"][target
.length
-1],
352 symbol
= dregexp
.escapeString(options
[prop
] || options
.currency
|| "");
353 before
= before
? "[\\s\\xa0]" : "";
354 after
= after
? "[\\s\\xa0]" : "";
356 if(before
){before
+= "*";}
357 if(after
){after
+= "*";}
358 return "(?:"+before
+symbol
+after
+")?";
360 return before
+symbol
+after
;
364 //TODO: substitute localized sign/percent/permille/etc.?
366 // normalize whitespace and return
367 return {regexp
: re
.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group
: group
, decimal: decimal, factor
: factor
}; // Object
371 number.__ParseOptions = declare(null, {
373 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
374 // with this string. Default value is based on locale. Overriding this property will defeat
375 // localization. Literal characters in patterns are not supported.
377 // choose a format type based on the locale from the following:
378 // decimal, scientific (not yet supported), percent, currency. decimal by default.
380 // override the locale used to determine formatting rules
382 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
383 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
384 // fractional: Boolean|Array?
385 // Whether to include the fractional portion, where the number of decimal places are implied by pattern
386 // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional.
389 number
.parse = function(/*String*/ expression
, /*number.__ParseOptions?*/ options
){
391 // Convert a properly formatted string to a primitive Number, using
392 // locale-specific settings.
394 // Create a Number from a string using a known localized pattern.
395 // Formatting patterns are chosen appropriate to the locale
396 // and follow the syntax described by
397 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
398 // Note that literal characters in patterns are not supported.
400 // A string representation of a Number
401 var info
= number
._parseInfo(options
),
402 results
= (new RegExp("^"+info
.regexp
+"$")).exec(expression
);
406 var absoluteMatch
= results
[1]; // match for the positive expression
411 // matched the negative pattern
412 absoluteMatch
=results
[2];
416 // Transform it to something Javascript can parse as a number. Normalize
417 // decimal point and strip out group separators or alternate forms of whitespace
418 absoluteMatch
= absoluteMatch
.
419 replace(new RegExp("["+info
.group
+ "\\s\\xa0"+"]", "g"), "").
420 replace(info
.decimal, ".");
421 // Adjust for negative sign, percent, etc. as necessary
422 return absoluteMatch
* info
.factor
; //Number
426 number.__RealNumberRegexpFlags = declare(null, {
428 // The integer number of decimal places or a range given as "n,m". If
429 // not given, the decimal part is optional and the number of places is
432 // A string for the character used as the decimal point. Default
434 // fractional: Boolean|Array?
435 // Whether decimal places are used. Can be true, false, or [true,
436 // false]. Default is [true, false] which means optional.
437 // exponent: Boolean|Array?
438 // Express in exponential notation. Can be true, false, or [true,
439 // false]. Default is [true, false], (i.e. will match if the
440 // exponential part is present are not).
441 // eSigned: Boolean|Array?
442 // The leading plus-or-minus sign on the exponent. Can be true,
443 // false, or [true, false]. Default is [true, false], (i.e. will
444 // match if it is signed or unsigned). flags in regexp.integer can be
449 number
._realNumberRegexp = function(/*__RealNumberRegexpFlags?*/ flags
){
451 // Builds a regular expression to match a real number in exponential
454 // assign default values to missing parameters
456 //TODO: use mixin instead?
457 if(!("places" in flags
)){ flags
.places
= Infinity
; }
458 if(typeof flags
.decimal != "string"){ flags
.decimal = "."; }
459 if(!("fractional" in flags
) || /^0/.test(flags
.places
)){ flags
.fractional
= [true, false]; }
460 if(!("exponent" in flags
)){ flags
.exponent
= [true, false]; }
461 if(!("eSigned" in flags
)){ flags
.eSigned
= [true, false]; }
463 var integerRE
= number
._integerRegexp(flags
),
464 decimalRE
= dregexp
.buildGroupRE(flags
.fractional
,
467 if(q
&& (flags
.places
!==0)){
468 re
= "\\" + flags
.decimal;
469 if(flags
.places
== Infinity
){
470 re
= "(?:" + re
+ "\\d+)?";
472 re
+= "\\d{" + flags
.places
+ "}";
480 var exponentRE
= dregexp
.buildGroupRE(flags
.exponent
,
482 if(q
){ return "([eE]" + number
._integerRegexp({ signed
: flags
.eSigned
}) + ")"; }
487 var realRE
= integerRE
+ decimalRE
;
488 // allow for decimals without integers, e.g. .25
489 if(decimalRE
){realRE
= "(?:(?:"+ realRE
+ ")|(?:" + decimalRE
+ "))";}
490 return realRE
+ exponentRE
; // String
494 number.__IntegerRegexpFlags = declare(null, {
496 // The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
497 // Default is `[true, false]`, (i.e. will match if it is signed
499 // separator: String?
500 // The character used as the thousands separator. Default is no
501 // separator. For more than one symbol use an array, e.g. `[",", ""]`,
502 // makes ',' optional.
503 // groupSize: Number?
504 // group size between separators
505 // groupSize2: Number?
506 // second grouping, where separators 2..n have a different interval than the first separator (for India)
510 number
._integerRegexp = function(/*number.__IntegerRegexpFlags?*/ flags
){
512 // Builds a regular expression that matches an integer
514 // assign default values to missing parameters
516 if(!("signed" in flags
)){ flags
.signed
= [true, false]; }
517 if(!("separator" in flags
)){
518 flags
.separator
= "";
519 }else if(!("groupSize" in flags
)){
523 var signRE
= dregexp
.buildGroupRE(flags
.signed
,
524 function(q
){ return q
? "[-+]" : ""; },
528 var numberRE
= dregexp
.buildGroupRE(flags
.separator
,
534 sep
= dregexp
.escapeString(sep
);
535 if(sep
== " "){ sep
= "\\s"; }
536 else if(sep
== "\xa0"){ sep
= "\\s\\xa0"; }
538 var grp
= flags
.groupSize
, grp2
= flags
.groupSize2
;
539 //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933
541 var grp2RE
= "(?:0|[1-9]\\d{0," + (grp2
-1) + "}(?:[" + sep
+ "]\\d{" + grp2
+ "})*[" + sep
+ "]\\d{" + grp
+ "})";
542 return ((grp
-grp2
) > 0) ? "(?:" + grp2RE
+ "|(?:0|[1-9]\\d{0," + (grp
-1) + "}))" : grp2RE
;
544 return "(?:0|[1-9]\\d{0," + (grp
-1) + "}(?:[" + sep
+ "]\\d{" + grp
+ "})*)";
549 return signRE
+ numberRE
; // String