number.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. define("dojo/number", ["./_base/kernel", "./_base/lang", "./i18n", "./i18n!./cldr/nls/number", "./string", "./regexp"],
  2. function(dojo, lang, i18n, nlsNumber, dstring, dregexp) {
  3. // module:
  4. // dojo/number
  5. // summary:
  6. // TODOC
  7. lang.getObject("number", true, dojo);
  8. /*=====
  9. dojo.number = {
  10. // summary: localized formatting and parsing routines for Number
  11. }
  12. dojo.number.__FormatOptions = function(){
  13. // pattern: String?
  14. // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
  15. // with this string. Default value is based on locale. Overriding this property will defeat
  16. // localization. Literal characters in patterns are not supported.
  17. // type: String?
  18. // choose a format type based on the locale from the following:
  19. // decimal, scientific (not yet supported), percent, currency. decimal by default.
  20. // places: Number?
  21. // fixed number of decimal places to show. This overrides any
  22. // information in the provided pattern.
  23. // round: Number?
  24. // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
  25. // means do not round.
  26. // locale: String?
  27. // override the locale used to determine formatting rules
  28. // fractional: Boolean?
  29. // If false, show no decimal places, overriding places and pattern settings.
  30. this.pattern = pattern;
  31. this.type = type;
  32. this.places = places;
  33. this.round = round;
  34. this.locale = locale;
  35. this.fractional = fractional;
  36. }
  37. =====*/
  38. dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){
  39. // summary:
  40. // Format a Number as a String, using locale-specific settings
  41. // description:
  42. // Create a string from a Number using a known localized pattern.
  43. // Formatting patterns appropriate to the locale are chosen from the
  44. // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and
  45. // delimiters.
  46. // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
  47. // value:
  48. // the number to be formatted
  49. options = lang.mixin({}, options || {});
  50. var locale = i18n.normalizeLocale(options.locale),
  51. bundle = i18n.getLocalization("dojo.cldr", "number", locale);
  52. options.customs = bundle;
  53. var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
  54. if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null
  55. return dojo.number._applyPattern(value, pattern, options); // String
  56. };
  57. //dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
  58. dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
  59. dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){
  60. // summary:
  61. // Apply pattern to format value as a string using options. Gives no
  62. // consideration to local customs.
  63. // value:
  64. // the number to be formatted.
  65. // pattern:
  66. // a pattern string as described by
  67. // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
  68. // options: dojo.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.
  72. //TODO: support escapes
  73. options = options || {};
  74. var group = options.customs.group,
  75. decimal = options.customs.decimal,
  76. patternList = pattern.split(';'),
  77. positivePattern = patternList[0];
  78. pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern);
  79. //TODO: only test against unescaped
  80. if(pattern.indexOf('%') != -1){
  81. value *= 100;
  82. }else if(pattern.indexOf('\u2030') != -1){
  83. value *= 1000; // per mille
  84. }else if(pattern.indexOf('\u00a4') != -1){
  85. group = options.customs.currencyGroup || group;//mixins instead?
  86. decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead?
  87. pattern = pattern.replace(/\u00a4{1,3}/, function(match){
  88. var prop = ["symbol", "currency", "displayName"][match.length-1];
  89. return options[prop] || options.currency || "";
  90. });
  91. }else if(pattern.indexOf('E') != -1){
  92. throw new Error("exponential notation not supported");
  93. }
  94. //TODO: support @ sig figs?
  95. var numberPatternRE = dojo.number._numberPatternRE;
  96. var numberPattern = positivePattern.match(numberPatternRE);
  97. if(!numberPattern){
  98. throw new Error("unable to find a number expression in pattern: "+pattern);
  99. }
  100. if(options.fractional === false){ options.places = 0; }
  101. return pattern.replace(numberPatternRE,
  102. dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round}));
  103. };
  104. dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){
  105. // summary:
  106. // Rounds to the nearest value with the given number of decimal places, away from zero
  107. // description:
  108. // Rounds to the nearest value with the given number of decimal places, away from zero if equal.
  109. // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by
  110. // fractional increments also, such as the nearest quarter.
  111. // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround.
  112. // value:
  113. // The number to round
  114. // places:
  115. // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
  116. // Must be non-negative.
  117. // increment:
  118. // Rounds next place to nearest value of increment/10. 10 by default.
  119. // example:
  120. // >>> dojo.number.round(-0.5)
  121. // -1
  122. // >>> dojo.number.round(162.295, 2)
  123. // 162.29 // note floating point error. Should be 162.3
  124. // >>> dojo.number.round(10.71, 0, 2.5)
  125. // 10.75
  126. var factor = 10 / (increment || 10);
  127. return (factor * +value).toFixed(places) / factor; // Number
  128. };
  129. if((0.9).toFixed() == 0){
  130. // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
  131. // is just after the rounding place and is >=5
  132. var round = dojo.number.round;
  133. dojo.number.round = function(v, p, m){
  134. var d = Math.pow(10, -p || 0), a = Math.abs(v);
  135. if(!v || a >= d){
  136. d = 0;
  137. }else{
  138. a /= d;
  139. if(a < 0.5 || a >= 0.95){
  140. d = 0;
  141. }
  142. }
  143. return round(v, p, m) + (v > 0 ? d : -d);
  144. };
  145. }
  146. /*=====
  147. dojo.number.__FormatAbsoluteOptions = function(){
  148. // decimal: String?
  149. // the decimal separator
  150. // group: String?
  151. // the group separator
  152. // places: Number?|String?
  153. // number of decimal places. the range "n,m" will format to m places.
  154. // round: Number?
  155. // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
  156. // means don't round.
  157. this.decimal = decimal;
  158. this.group = group;
  159. this.places = places;
  160. this.round = round;
  161. }
  162. =====*/
  163. dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
  164. // summary:
  165. // Apply numeric pattern to absolute value using options. Gives no
  166. // consideration to local customs.
  167. // value:
  168. // the number to be formatted, ignores sign
  169. // pattern:
  170. // the number portion of a pattern (e.g. `#,##0.00`)
  171. options = options || {};
  172. if(options.places === true){options.places=0;}
  173. if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit
  174. var patternParts = pattern.split("."),
  175. comma = typeof options.places == "string" && options.places.indexOf(","),
  176. maxPlaces = options.places;
  177. if(comma){
  178. maxPlaces = options.places.substring(comma + 1);
  179. }else if(!(maxPlaces >= 0)){
  180. maxPlaces = (patternParts[1] || []).length;
  181. }
  182. if(!(options.round < 0)){
  183. value = dojo.number.round(value, maxPlaces, options.round);
  184. }
  185. var valueParts = String(Math.abs(value)).split("."),
  186. fractional = valueParts[1] || "";
  187. if(patternParts[1] || options.places){
  188. if(comma){
  189. options.places = options.places.substring(0, comma);
  190. }
  191. // Pad fractional with trailing zeros
  192. var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1);
  193. if(pad > fractional.length){
  194. valueParts[1] = dstring.pad(fractional, pad, '0', true);
  195. }
  196. // Truncate fractional
  197. if(maxPlaces < fractional.length){
  198. valueParts[1] = fractional.substr(0, maxPlaces);
  199. }
  200. }else{
  201. if(valueParts[1]){ valueParts.pop(); }
  202. }
  203. // Pad whole with leading zeros
  204. var patternDigits = patternParts[0].replace(',', '');
  205. pad = patternDigits.indexOf("0");
  206. if(pad != -1){
  207. pad = patternDigits.length - pad;
  208. if(pad > valueParts[0].length){
  209. valueParts[0] = dstring.pad(valueParts[0], pad);
  210. }
  211. // Truncate whole
  212. if(patternDigits.indexOf("#") == -1){
  213. valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
  214. }
  215. }
  216. // Add group separators
  217. var index = patternParts[0].lastIndexOf(','),
  218. groupSize, groupSize2;
  219. if(index != -1){
  220. groupSize = patternParts[0].length - index - 1;
  221. var remainder = patternParts[0].substr(0, index);
  222. index = remainder.lastIndexOf(',');
  223. if(index != -1){
  224. groupSize2 = remainder.length - index - 1;
  225. }
  226. }
  227. var pieces = [];
  228. for(var whole = valueParts[0]; whole;){
  229. var off = whole.length - groupSize;
  230. pieces.push((off > 0) ? whole.substr(off) : whole);
  231. whole = (off > 0) ? whole.slice(0, off) : "";
  232. if(groupSize2){
  233. groupSize = groupSize2;
  234. delete groupSize2;
  235. }
  236. }
  237. valueParts[0] = pieces.reverse().join(options.group || ",");
  238. return valueParts.join(options.decimal || ".");
  239. };
  240. /*=====
  241. dojo.number.__RegexpOptions = function(){
  242. // pattern: String?
  243. // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
  244. // with this string. Default value is based on locale. Overriding this property will defeat
  245. // localization.
  246. // type: String?
  247. // choose a format type based on the locale from the following:
  248. // decimal, scientific (not yet supported), percent, currency. decimal by default.
  249. // locale: String?
  250. // override the locale used to determine formatting rules
  251. // strict: Boolean?
  252. // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
  253. // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
  254. // places: Number|String?
  255. // number of decimal places to accept: Infinity, a positive number, or
  256. // a range "n,m". Defined by pattern or Infinity if pattern not provided.
  257. this.pattern = pattern;
  258. this.type = type;
  259. this.locale = locale;
  260. this.strict = strict;
  261. this.places = places;
  262. }
  263. =====*/
  264. dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
  265. // summary:
  266. // Builds the regular needed to parse a number
  267. // description:
  268. // Returns regular expression with positive and negative match, group
  269. // and decimal separators
  270. return dojo.number._parseInfo(options).regexp; // String
  271. };
  272. dojo.number._parseInfo = function(/*Object?*/options){
  273. options = options || {};
  274. var locale = i18n.normalizeLocale(options.locale),
  275. bundle = i18n.getLocalization("dojo.cldr", "number", locale),
  276. pattern = options.pattern || bundle[(options.type || "decimal") + "Format"],
  277. //TODO: memoize?
  278. group = bundle.group,
  279. decimal = bundle.decimal,
  280. factor = 1;
  281. if(pattern.indexOf('%') != -1){
  282. factor /= 100;
  283. }else if(pattern.indexOf('\u2030') != -1){
  284. factor /= 1000; // per mille
  285. }else{
  286. var isCurrency = pattern.indexOf('\u00a4') != -1;
  287. if(isCurrency){
  288. group = bundle.currencyGroup || group;
  289. decimal = bundle.currencyDecimal || decimal;
  290. }
  291. }
  292. //TODO: handle quoted escapes
  293. var patternList = pattern.split(';');
  294. if(patternList.length == 1){
  295. patternList.push("-" + patternList[0]);
  296. }
  297. var re = dregexp.buildGroupRE(patternList, function(pattern){
  298. pattern = "(?:"+dregexp.escapeString(pattern, '.')+")";
  299. return pattern.replace(dojo.number._numberPatternRE, function(format){
  300. var flags = {
  301. signed: false,
  302. separator: options.strict ? group : [group,""],
  303. fractional: options.fractional,
  304. decimal: decimal,
  305. exponent: false
  306. },
  307. parts = format.split('.'),
  308. places = options.places;
  309. // special condition for percent (factor != 1)
  310. // allow decimal places even if not specified in pattern
  311. if(parts.length == 1 && factor != 1){
  312. parts[1] = "###";
  313. }
  314. if(parts.length == 1 || places === 0){
  315. flags.fractional = false;
  316. }else{
  317. if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; }
  318. if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified
  319. if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; }
  320. flags.places = places;
  321. }
  322. var groups = parts[0].split(',');
  323. if(groups.length > 1){
  324. flags.groupSize = groups.pop().length;
  325. if(groups.length > 1){
  326. flags.groupSize2 = groups.pop().length;
  327. }
  328. }
  329. return "("+dojo.number._realNumberRegexp(flags)+")";
  330. });
  331. }, true);
  332. if(isCurrency){
  333. // substitute the currency symbol for the placeholder in the pattern
  334. re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){
  335. var prop = ["symbol", "currency", "displayName"][target.length-1],
  336. symbol = dregexp.escapeString(options[prop] || options.currency || "");
  337. before = before ? "[\\s\\xa0]" : "";
  338. after = after ? "[\\s\\xa0]" : "";
  339. if(!options.strict){
  340. if(before){before += "*";}
  341. if(after){after += "*";}
  342. return "(?:"+before+symbol+after+")?";
  343. }
  344. return before+symbol+after;
  345. });
  346. }
  347. //TODO: substitute localized sign/percent/permille/etc.?
  348. // normalize whitespace and return
  349. return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
  350. };
  351. /*=====
  352. dojo.number.__ParseOptions = function(){
  353. // pattern: String?
  354. // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
  355. // with this string. Default value is based on locale. Overriding this property will defeat
  356. // localization. Literal characters in patterns are not supported.
  357. // type: String?
  358. // choose a format type based on the locale from the following:
  359. // decimal, scientific (not yet supported), percent, currency. decimal by default.
  360. // locale: String?
  361. // override the locale used to determine formatting rules
  362. // strict: Boolean?
  363. // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
  364. // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
  365. // fractional: Boolean?|Array?
  366. // Whether to include the fractional portion, where the number of decimal places are implied by pattern
  367. // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional.
  368. this.pattern = pattern;
  369. this.type = type;
  370. this.locale = locale;
  371. this.strict = strict;
  372. this.fractional = fractional;
  373. }
  374. =====*/
  375. dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){
  376. // summary:
  377. // Convert a properly formatted string to a primitive Number, using
  378. // locale-specific settings.
  379. // description:
  380. // Create a Number from a string using a known localized pattern.
  381. // Formatting patterns are chosen appropriate to the locale
  382. // and follow the syntax described by
  383. // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
  384. // Note that literal characters in patterns are not supported.
  385. // expression:
  386. // A string representation of a Number
  387. var info = dojo.number._parseInfo(options),
  388. results = (new RegExp("^"+info.regexp+"$")).exec(expression);
  389. if(!results){
  390. return NaN; //NaN
  391. }
  392. var absoluteMatch = results[1]; // match for the positive expression
  393. if(!results[1]){
  394. if(!results[2]){
  395. return NaN; //NaN
  396. }
  397. // matched the negative pattern
  398. absoluteMatch =results[2];
  399. info.factor *= -1;
  400. }
  401. // Transform it to something Javascript can parse as a number. Normalize
  402. // decimal point and strip out group separators or alternate forms of whitespace
  403. absoluteMatch = absoluteMatch.
  404. replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), "").
  405. replace(info.decimal, ".");
  406. // Adjust for negative sign, percent, etc. as necessary
  407. return absoluteMatch * info.factor; //Number
  408. };
  409. /*=====
  410. dojo.number.__RealNumberRegexpFlags = function(){
  411. // places: Number?
  412. // The integer number of decimal places or a range given as "n,m". If
  413. // not given, the decimal part is optional and the number of places is
  414. // unlimited.
  415. // decimal: String?
  416. // A string for the character used as the decimal point. Default
  417. // is ".".
  418. // fractional: Boolean?|Array?
  419. // Whether decimal places are used. Can be true, false, or [true,
  420. // false]. Default is [true, false] which means optional.
  421. // exponent: Boolean?|Array?
  422. // Express in exponential notation. Can be true, false, or [true,
  423. // false]. Default is [true, false], (i.e. will match if the
  424. // exponential part is present are not).
  425. // eSigned: Boolean?|Array?
  426. // The leading plus-or-minus sign on the exponent. Can be true,
  427. // false, or [true, false]. Default is [true, false], (i.e. will
  428. // match if it is signed or unsigned). flags in regexp.integer can be
  429. // applied.
  430. this.places = places;
  431. this.decimal = decimal;
  432. this.fractional = fractional;
  433. this.exponent = exponent;
  434. this.eSigned = eSigned;
  435. }
  436. =====*/
  437. dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){
  438. // summary:
  439. // Builds a regular expression to match a real number in exponential
  440. // notation
  441. // assign default values to missing parameters
  442. flags = flags || {};
  443. //TODO: use mixin instead?
  444. if(!("places" in flags)){ flags.places = Infinity; }
  445. if(typeof flags.decimal != "string"){ flags.decimal = "."; }
  446. if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; }
  447. if(!("exponent" in flags)){ flags.exponent = [true, false]; }
  448. if(!("eSigned" in flags)){ flags.eSigned = [true, false]; }
  449. var integerRE = dojo.number._integerRegexp(flags),
  450. decimalRE = dregexp.buildGroupRE(flags.fractional,
  451. function(q){
  452. var re = "";
  453. if(q && (flags.places!==0)){
  454. re = "\\" + flags.decimal;
  455. if(flags.places == Infinity){
  456. re = "(?:" + re + "\\d+)?";
  457. }else{
  458. re += "\\d{" + flags.places + "}";
  459. }
  460. }
  461. return re;
  462. },
  463. true
  464. );
  465. var exponentRE = dregexp.buildGroupRE(flags.exponent,
  466. function(q){
  467. if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
  468. return "";
  469. }
  470. );
  471. var realRE = integerRE + decimalRE;
  472. // allow for decimals without integers, e.g. .25
  473. if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";}
  474. return realRE + exponentRE; // String
  475. };
  476. /*=====
  477. dojo.number.__IntegerRegexpFlags = function(){
  478. // signed: Boolean?
  479. // The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
  480. // Default is `[true, false]`, (i.e. will match if it is signed
  481. // or unsigned).
  482. // separator: String?
  483. // The character used as the thousands separator. Default is no
  484. // separator. For more than one symbol use an array, e.g. `[",", ""]`,
  485. // makes ',' optional.
  486. // groupSize: Number?
  487. // group size between separators
  488. // groupSize2: Number?
  489. // second grouping, where separators 2..n have a different interval than the first separator (for India)
  490. this.signed = signed;
  491. this.separator = separator;
  492. this.groupSize = groupSize;
  493. this.groupSize2 = groupSize2;
  494. }
  495. =====*/
  496. dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
  497. // summary:
  498. // Builds a regular expression that matches an integer
  499. // assign default values to missing parameters
  500. flags = flags || {};
  501. if(!("signed" in flags)){ flags.signed = [true, false]; }
  502. if(!("separator" in flags)){
  503. flags.separator = "";
  504. }else if(!("groupSize" in flags)){
  505. flags.groupSize = 3;
  506. }
  507. var signRE = dregexp.buildGroupRE(flags.signed,
  508. function(q){ return q ? "[-+]" : ""; },
  509. true
  510. );
  511. var numberRE = dregexp.buildGroupRE(flags.separator,
  512. function(sep){
  513. if(!sep){
  514. return "(?:\\d+)";
  515. }
  516. sep = dregexp.escapeString(sep);
  517. if(sep == " "){ sep = "\\s"; }
  518. else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
  519. var grp = flags.groupSize, grp2 = flags.groupSize2;
  520. //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933
  521. if(grp2){
  522. var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
  523. return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
  524. }
  525. return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
  526. },
  527. true
  528. );
  529. return signRE + numberRE; // String
  530. };
  531. return dojo.number;
  532. });