jquery-ui/external/globalize/globalize-runtime/number.js

683 lines
16 KiB
JavaScript

/**
* Globalize Runtime v1.1.0-rc.6
*
* http://github.com/jquery/globalize
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-11-18T10:38Z
*/
/*!
* Globalize Runtime v1.1.0-rc.6 2015-11-18T10:38Z Released under the MIT license
* http://git.io/TrdQbw
*/
(function( root, factory ) {
// UMD returnExports
if ( typeof define === "function" && define.amd ) {
// AMD
define([
"../globalize-runtime"
], factory );
} else if ( typeof exports === "object" ) {
// Node, CommonJS
module.exports = factory( require( "../globalize-runtime" ) );
} else {
// Extend global
factory( root.Globalize );
}
}(this, function( Globalize ) {
var createError = Globalize._createError,
regexpEscape = Globalize._regexpEscape,
runtimeKey = Globalize._runtimeKey,
stringPad = Globalize._stringPad,
validateParameterType = Globalize._validateParameterType,
validateParameterPresence = Globalize._validateParameterPresence,
validateParameterTypeString = Globalize._validateParameterTypeString;
var createErrorUnsupportedFeature = function( feature ) {
return createError( "E_UNSUPPORTED", "Unsupported {feature}.", {
feature: feature
});
};
var validateParameterTypeNumber = function( value, name ) {
validateParameterType(
value,
name,
value === undefined || typeof value === "number",
"Number"
);
};
/**
* goupingSeparator( number, primaryGroupingSize, secondaryGroupingSize )
*
* @number [Number].
*
* @primaryGroupingSize [Number]
*
* @secondaryGroupingSize [Number]
*
* Return the formatted number with group separator.
*/
var numberFormatGroupingSeparator = function( number, primaryGroupingSize, secondaryGroupingSize ) {
var index,
currentGroupingSize = primaryGroupingSize,
ret = "",
sep = ",",
switchToSecondary = secondaryGroupingSize ? true : false;
number = String( number ).split( "." );
index = number[ 0 ].length;
while ( index > currentGroupingSize ) {
ret = number[ 0 ].slice( index - currentGroupingSize, index ) +
( ret.length ? sep : "" ) + ret;
index -= currentGroupingSize;
if ( switchToSecondary ) {
currentGroupingSize = secondaryGroupingSize;
switchToSecondary = false;
}
}
number[ 0 ] = number[ 0 ].slice( 0, index ) + ( ret.length ? sep : "" ) + ret;
return number.join( "." );
};
/**
* integerFractionDigits( number, minimumIntegerDigits, minimumFractionDigits,
* maximumFractionDigits, round, roundIncrement )
*
* @number [Number]
*
* @minimumIntegerDigits [Number]
*
* @minimumFractionDigits [Number]
*
* @maximumFractionDigits [Number]
*
* @round [Function]
*
* @roundIncrement [Function]
*
* Return the formatted integer and fraction digits.
*/
var numberFormatIntegerFractionDigits = function( number, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, round,
roundIncrement ) {
// Fraction
if ( maximumFractionDigits ) {
// Rounding
if ( roundIncrement ) {
number = round( number, roundIncrement );
// Maximum fraction digits
} else {
number = round( number, { exponent: -maximumFractionDigits } );
}
// Minimum fraction digits
if ( minimumFractionDigits ) {
number = String( number ).split( "." );
number[ 1 ] = stringPad( number[ 1 ] || "", minimumFractionDigits, true );
number = number.join( "." );
}
} else {
number = round( number );
}
number = String( number );
// Minimum integer digits
if ( minimumIntegerDigits ) {
number = number.split( "." );
number[ 0 ] = stringPad( number[ 0 ], minimumIntegerDigits );
number = number.join( "." );
}
return number;
};
/**
* toPrecision( number, precision, round )
*
* @number (Number)
*
* @precision (Number) significant figures precision (not decimal precision).
*
* @round (Function)
*
* Return number.toPrecision( precision ) using the given round function.
*/
var numberToPrecision = function( number, precision, round ) {
var roundOrder;
// Get number at two extra significant figure precision.
number = number.toPrecision( precision + 2 );
// Then, round it to the required significant figure precision.
roundOrder = Math.ceil( Math.log( Math.abs( number ) ) / Math.log( 10 ) );
roundOrder -= precision;
return round( number, { exponent: roundOrder } );
};
/**
* toPrecision( number, minimumSignificantDigits, maximumSignificantDigits, round )
*
* @number [Number]
*
* @minimumSignificantDigits [Number]
*
* @maximumSignificantDigits [Number]
*
* @round [Function]
*
* Return the formatted significant digits number.
*/
var numberFormatSignificantDigits = function( number, minimumSignificantDigits, maximumSignificantDigits, round ) {
var atMinimum, atMaximum;
// Sanity check.
if ( minimumSignificantDigits > maximumSignificantDigits ) {
maximumSignificantDigits = minimumSignificantDigits;
}
atMinimum = numberToPrecision( number, minimumSignificantDigits, round );
atMaximum = numberToPrecision( number, maximumSignificantDigits, round );
// Use atMaximum only if it has more significant digits than atMinimum.
number = +atMinimum === +atMaximum ? atMinimum : atMaximum;
// Expand integer numbers, eg. 123e5 to 12300.
number = ( +number ).toString( 10 );
if ( ( /e/ ).test( number ) ) {
throw createErrorUnsupportedFeature({
feature: "integers out of (1e21, 1e-7)"
});
}
// Add trailing zeros if necessary.
if ( minimumSignificantDigits - number.replace( /^0+|\./g, "" ).length > 0 ) {
number = number.split( "." );
number[ 1 ] = stringPad( number[ 1 ] || "", minimumSignificantDigits - number[ 0 ].replace( /^0+/, "" ).length, true );
number = number.join( "." );
}
return number;
};
/**
* format( number, properties )
*
* @number [Number].
*
* @properties [Object] Output of number/format-properties.
*
* Return the formatted number.
* ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
*/
var numberFormat = function( number, properties ) {
var infinitySymbol, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits,
minimumIntegerDigits, minimumSignificantDigits, nanSymbol, nuDigitsMap, padding, prefix,
primaryGroupingSize, pattern, ret, round, roundIncrement, secondaryGroupingSize, suffix,
symbolMap;
padding = properties[ 1 ];
minimumIntegerDigits = properties[ 2 ];
minimumFractionDigits = properties[ 3 ];
maximumFractionDigits = properties[ 4 ];
minimumSignificantDigits = properties[ 5 ];
maximumSignificantDigits = properties[ 6 ];
roundIncrement = properties[ 7 ];
primaryGroupingSize = properties[ 8 ];
secondaryGroupingSize = properties[ 9 ];
round = properties[ 15 ];
infinitySymbol = properties[ 16 ];
nanSymbol = properties[ 17 ];
symbolMap = properties[ 18 ];
nuDigitsMap = properties[ 19 ];
// NaN
if ( isNaN( number ) ) {
return nanSymbol;
}
if ( number < 0 ) {
pattern = properties[ 12 ];
prefix = properties[ 13 ];
suffix = properties[ 14 ];
} else {
pattern = properties[ 11 ];
prefix = properties[ 0 ];
suffix = properties[ 10 ];
}
// Infinity
if ( !isFinite( number ) ) {
return prefix + infinitySymbol + suffix;
}
ret = prefix;
// Percent
if ( pattern.indexOf( "%" ) !== -1 ) {
number *= 100;
// Per mille
} else if ( pattern.indexOf( "\u2030" ) !== -1 ) {
number *= 1000;
}
// Significant digit format
if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) {
number = numberFormatSignificantDigits( number, minimumSignificantDigits,
maximumSignificantDigits, round );
// Integer and fractional format
} else {
number = numberFormatIntegerFractionDigits( number, minimumIntegerDigits,
minimumFractionDigits, maximumFractionDigits, round, roundIncrement );
}
// Remove the possible number minus sign
number = number.replace( /^-/, "" );
// Grouping separators
if ( primaryGroupingSize ) {
number = numberFormatGroupingSeparator( number, primaryGroupingSize,
secondaryGroupingSize );
}
ret += number;
// Scientific notation
// TODO implement here
// Padding/'([^']|'')+'|''|[.,\-+E%\u2030]/g
// TODO implement here
ret += suffix;
return ret.replace( /('([^']|'')+'|'')|./g, function( character, literal ) {
// Literals
if ( literal ) {
literal = literal.replace( /''/, "'" );
if ( literal.length > 2 ) {
literal = literal.slice( 1, -1 );
}
return literal;
}
// Symbols
character = character.replace( /[.,\-+E%\u2030]/, function( symbol ) {
return symbolMap[ symbol ];
});
// Numbering system
if ( nuDigitsMap ) {
character = character.replace( /[0-9]/, function( digit ) {
return nuDigitsMap[ +digit ];
});
}
return character;
});
};
var numberFormatterFn = function( properties ) {
return function numberFormatter( value ) {
validateParameterPresence( value, "value" );
validateParameterTypeNumber( value, "value" );
return numberFormat( value, properties );
};
};
/**
* EBNF representation:
*
* number_pattern_re = prefix_including_padding?
* number
* scientific_notation?
* suffix?
*
* number = integer_including_group_separator fraction_including_decimal_separator
*
* integer_including_group_separator =
* regexp([0-9,]*[0-9]+)
*
* fraction_including_decimal_separator =
* regexp((\.[0-9]+)?)
* prefix_including_padding = non_number_stuff
*
* scientific_notation = regexp(E[+-]?[0-9]+)
*
* suffix = non_number_stuff
*
* non_number_stuff = regexp([^0-9]*)
*
*
* Regexp groups:
*
* 0: number_pattern_re
* 1: prefix
* 2: integer_including_group_separator fraction_including_decimal_separator
* 3: integer_including_group_separator
* 4: fraction_including_decimal_separator
* 5: scientific_notation
* 6: suffix
*/
var numberNumberRe = ( /^([^0-9]*)(([0-9,]*[0-9]+)(\.[0-9]+)?)(E[+-]?[0-9]+)?([^0-9]*)$/ );
/**
* parse( value, properties )
*
* @value [String].
*
* @properties [Object] Parser properties is a reduced pre-processed cldr
* data set returned by numberParserProperties().
*
* Return the parsed Number (including Infinity) or NaN when value is invalid.
* ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
*/
var numberParse = function( value, properties ) {
var aux, infinitySymbol, invertedNuDigitsMap, invertedSymbolMap, localizedDigitRe,
localizedSymbolsRe, negativePrefix, negativeSuffix, number, prefix, suffix;
infinitySymbol = properties[ 0 ];
invertedSymbolMap = properties[ 1 ];
negativePrefix = properties[ 2 ];
negativeSuffix = properties[ 3 ];
invertedNuDigitsMap = properties[ 4 ];
// Infinite number.
if ( aux = value.match( infinitySymbol ) ) {
number = Infinity;
prefix = value.slice( 0, aux.length );
suffix = value.slice( aux.length + 1 );
// Finite number.
} else {
// TODO: Create it during setup, i.e., make it a property.
localizedSymbolsRe = new RegExp(
Object.keys( invertedSymbolMap ).map(function( localizedSymbol ) {
return regexpEscape( localizedSymbol );
}).join( "|" ),
"g"
);
// Reverse localized symbols.
value = value.replace( localizedSymbolsRe, function( localizedSymbol ) {
return invertedSymbolMap[ localizedSymbol ];
});
// Reverse localized numbering system.
if ( invertedNuDigitsMap ) {
// TODO: Create it during setup, i.e., make it a property.
localizedDigitRe = new RegExp(
Object.keys( invertedNuDigitsMap ).map(function( localizedDigit ) {
return regexpEscape( localizedDigit );
}).join( "|" ),
"g"
);
value = value.replace( localizedDigitRe, function( localizedDigit ) {
return invertedNuDigitsMap[ localizedDigit ];
});
}
// Add padding zero to leading decimal.
if ( value.charAt( 0 ) === "." ) {
value = "0" + value;
}
// Is it a valid number?
value = value.match( numberNumberRe );
if ( !value ) {
// Invalid number.
return NaN;
}
prefix = value[ 1 ];
suffix = value[ 6 ];
// Remove grouping separators.
number = value[ 2 ].replace( /,/g, "" );
// Scientific notation
if ( value[ 5 ] ) {
number += value[ 5 ];
}
number = +number;
// Is it a valid number?
if ( isNaN( number ) ) {
// Invalid number.
return NaN;
}
// Percent
if ( value[ 0 ].indexOf( "%" ) !== -1 ) {
number /= 100;
suffix = suffix.replace( "%", "" );
// Per mille
} else if ( value[ 0 ].indexOf( "\u2030" ) !== -1 ) {
number /= 1000;
suffix = suffix.replace( "\u2030", "" );
}
}
// Negative number
// "If there is an explicit negative subpattern, it serves only to specify the negative prefix
// and suffix. If there is no explicit negative subpattern, the negative subpattern is the
// localized minus sign prefixed to the positive subpattern" UTS#35
if ( prefix === negativePrefix && suffix === negativeSuffix ) {
number *= -1;
}
return number;
};
var numberParserFn = function( properties ) {
return function numberParser( value ) {
validateParameterPresence( value, "value" );
validateParameterTypeString( value, "value" );
return numberParse( value, properties );
};
};
var numberTruncate = function( value ) {
if ( isNaN( value ) ) {
return NaN;
}
return Math[ value < 0 ? "ceil" : "floor" ]( value );
};
/**
* round( method )
*
* @method [String] with either "round", "ceil", "floor", or "truncate".
*
* Return function( value, incrementOrExp ):
*
* @value [Number] eg. 123.45.
*
* @incrementOrExp [Number] optional, eg. 0.1; or
* [Object] Either { increment: <value> } or { exponent: <value> }
*
* Return the rounded number, eg:
* - round( "round" )( 123.45 ): 123;
* - round( "ceil" )( 123.45 ): 124;
* - round( "floor" )( 123.45 ): 123;
* - round( "truncate" )( 123.45 ): 123;
* - round( "round" )( 123.45, 0.1 ): 123.5;
* - round( "round" )( 123.45, 10 ): 120;
*
* Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
* Ref: #376
*/
var numberRound = function( method ) {
method = method || "round";
method = method === "truncate" ? numberTruncate : Math[ method ];
return function( value, incrementOrExp ) {
var exp, increment;
value = +value;
// If the value is not a number, return NaN.
if ( isNaN( value ) ) {
return NaN;
}
// Exponent given.
if ( typeof incrementOrExp === "object" && incrementOrExp.exponent ) {
exp = +incrementOrExp.exponent;
increment = 1;
if ( exp === 0 ) {
return method( value );
}
// If the exp is not an integer, return NaN.
if ( !( typeof exp === "number" && exp % 1 === 0 ) ) {
return NaN;
}
// Increment given.
} else {
increment = +incrementOrExp || 1;
if ( increment === 1 ) {
return method( value );
}
// If the increment is not a number, return NaN.
if ( isNaN( increment ) ) {
return NaN;
}
increment = increment.toExponential().split( "e" );
exp = +increment[ 1 ];
increment = +increment[ 0 ];
}
// Shift & Round
value = value.toString().split( "e" );
value[ 0 ] = +value[ 0 ] / increment;
value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] - exp ) : -exp;
value = method( +( value[ 0 ] + "e" + value[ 1 ] ) );
// Shift back
value = value.toString().split( "e" );
value[ 0 ] = +value[ 0 ] * increment;
value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] + exp ) : exp;
return +( value[ 0 ] + "e" + value[ 1 ] );
};
};
Globalize._createErrorUnsupportedFeature = createErrorUnsupportedFeature;
Globalize._numberFormat = numberFormat;
Globalize._numberFormatterFn = numberFormatterFn;
Globalize._numberParse = numberParse;
Globalize._numberParserFn = numberParserFn;
Globalize._numberRound = numberRound;
Globalize._validateParameterPresence = validateParameterPresence;
Globalize._validateParameterTypeNumber = validateParameterTypeNumber;
Globalize._validateParameterTypeString = validateParameterTypeString;
Globalize.numberFormatter =
Globalize.prototype.numberFormatter = function( options ) {
options = options || {};
return Globalize[ runtimeKey( "numberFormatter", this._locale, [ options ] ) ];
};
Globalize.numberParser =
Globalize.prototype.numberParser = function( options ) {
options = options || {};
return Globalize[ runtimeKey( "numberParser", this._locale, [ options ] ) ];
};
Globalize.formatNumber =
Globalize.prototype.formatNumber = function( value, options ) {
validateParameterPresence( value, "value" );
validateParameterTypeNumber( value, "value" );
return this.numberFormatter( options )( value );
};
Globalize.parseNumber =
Globalize.prototype.parseNumber = function( value, options ) {
validateParameterPresence( value, "value" );
validateParameterTypeString( value, "value" );
return this.numberParser( options )( value );
};
return Globalize;
}));