/** * @class Ext.Number * * A collection of useful static methods to deal with numbers * @singleton */Ext.Number = (new function() { // jshint ignore:line// @define Ext.lang.Number// @define Ext.Number// @require Ext var ExtNumber = this, isToFixedBroken = (0.9).toFixed() !== '1', math = Math, ClipDefault = { count: false, inclusive: false, wrap: true }; // polyfill Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -(math.pow(2, 53) - 1); Number.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || math.pow(2, 53) - 1; Ext.apply(ExtNumber, { MIN_SAFE_INTEGER: Number.MIN_SAFE_INTEGER, MAX_SAFE_INTEGER: Number.MAX_SAFE_INTEGER, MAX_32BIT_INTEGER: math.pow(2, 31) - 1, // No good way to allow "9." w/o allowing "." alone but we use isNaN to reject that floatRe: /^[-+]?(?:\d+|\d*\.\d*)(?:[Ee][+-]?\d+)?$/, intRe: /^[-+]?\d+(?:[Ee]\+?\d+)?$/, Clip: { DEFAULT: ClipDefault, COUNT: Ext.applyIf({ count: true }, ClipDefault), INCLUSIVE: Ext.applyIf({ inclusive: true }, ClipDefault), NOWRAP: Ext.applyIf({ wrap: false }, ClipDefault) }, /** * Strictly parses the given value and returns the value as a number or `null` if * the value is not a number or contains non-numeric pieces. * @param {String} value * @return {Number} * @since 6.5.1 */ parseFloat: function(value) { if (value === undefined) { value = null; } if (value !== null && typeof value !== 'number') { value = String(value); value = ExtNumber.floatRe.test(value) ? +value : null; if (isNaN(value)) { value = null; } } return value; }, /** * Strictly parses the given value and returns the value as a number or `null` if * the value is not an integer number or contains non-integer pieces. * @param {String} value * @return {Number} * @since 6.5.1 */ parseInt: function(value) { if (value === undefined) { value = null; } if (typeof value === 'number') { value = Math.floor(value); } else if (value !== null) { value = String(value); value = ExtNumber.intRe.test(value) ? +value : null; } return value; }, binarySearch: function(array, value, begin, end) { var middle, midVal; if (begin === undefined) { begin = 0; } if (end === undefined) { end = array.length; } --end; while (begin <= end) { middle = (begin + end) >>> 1; // unsigned right shift = Math.floor(x/2) midVal = array[middle]; if (value === midVal) { return middle; } if (midVal < value) { begin = middle + 1; } else { end = middle - 1; } } return begin; }, bisectTuples: function(array, value, index, begin, end) { var middle, midVal; if (begin === undefined) { begin = 0; } if (end === undefined) { end = array.length; } --end; while (begin <= end) { middle = (begin + end) >>> 1; // unsigned right shift = Math.floor(x/2) midVal = array[middle][index]; if (value === midVal) { return middle; } if (midVal < value) { begin = middle + 1; } else { end = middle - 1; } } return begin; }, /** * Coerces a given index into a valid index given a `length`. * * Negative indexes are interpreted starting at the end of the collection. That is, * a value of -1 indicates the last item, or equivalent to `length - 1`. * * When handling methods that take "begin" and "end" arguments like most array or * string methods, this method can be used like so: * * function foo (array, begin, end) { * var range = Ext.Number.clipIndices(array.length, [begin, end]); * * begin = range[0]; * end = range[1]; * * // 0 <= begin <= end <= array.length * * var length = end - begin; * } * * For example: * * +---+---+---+---+---+---+---+---+ * | | | | | | | | | length = 8 * +---+---+---+---+---+---+---+---+ * 0 1 2 3 4 5 6 7 * -8 -7 -6 -5 -4 -3 -2 -1 * * console.log(Ext.Number.clipIndices(8, [3, 10])); // logs "[3, 8]" * console.log(Ext.Number.clipIndices(8, [-5])); // logs "[3, 8]" * console.log(Ext.Number.clipIndices(8, [])); * console.log(Ext.Number.clipIndices(8, [])); * * @param {Number} length * @param {Number[]} indices * @param {Object} [options] An object with different option flags. * @param {Boolean} [options.count=false] The second number in `indices` is the * count not and an index. * @param {Boolean} [options.inclusive=false] The second number in `indices` is * "inclusive" meaning that the item should be considered in the range. Normally, * the second number is considered the first item outside the range or as an * "exclusive" bound. * @param {Boolean} [options.wrap=true] Wraps negative numbers backwards from the * end of the array. Passing `false` simply clips negative index values at 0. * @return {Number[]} The normalized `[begin, end]` array where `end` is now * exclusive such that `length = end - begin`. Both values are between 0 and the * given `length` and `end` will not be less-than `begin`. */ clipIndices: function(length, indices, options) { var defaultValue = 0, // default value for "begin" wrap, begin, end, i; options = options || ClipDefault; wrap = options.wrap; indices = indices || []; for (i = 0; i < 2; ++i) { // names are off on first pass but used this way so things make sense // following the loop.. begin = end; // pick up and keep the result from the first loop end = indices[i]; if (end == null) { end = defaultValue; } else if (i && options.count) { end += begin; // this is the length not "end" so convert to "end" end = (end > length) ? length : end; } else { if (wrap) { end = (end < 0) ? (length + end) : end; } if (i && options.inclusive) { ++end; } end = (end < 0) ? 0 : ((end > length) ? length : end); } defaultValue = length; // default value for "end" } // after loop: // 0 <= begin <= length (calculated from indices[0]) // 0 <= end <= length (calculated from indices[1]) indices[0] = begin; indices[1] = (end < begin) ? begin : end; return indices; }, /** * Checks whether or not the passed number is within a desired range. If the number is * already within the range it is returned, otherwise the min or max value is returned * depending on which side of the range is exceeded. Note that this method returns the * constrained value but does not change the current number. * @param {Number} number The number to check * @param {Number} min The minimum number in the range * @param {Number} max The maximum number in the range * @return {Number} The constrained value if outside the range, otherwise the current value */ constrain: function(number, min, max) { var x = parseFloat(number); // (x < Nan) || (x < undefined) == false // same for (x > NaN) || (x > undefined) // sadly this is not true of null - (1 > null) if (min === null) { min = number; } if (max === null) { max = number; } // Watch out for NaN in Chrome 18 // V8bug: http://code.google.com/p/v8/issues/detail?id=2056 // Operators are faster than Math.min/max. See http://jsperf.com/number-constrain return (x < min) ? min : ((x > max) ? max : x); }, /** * Snaps the passed number between stopping points based upon a passed increment value. * * The difference between this and {@link #snapInRange} is that {@link #snapInRange} uses * the minValue when calculating snap points: * * // Returns 56 - snap points are zero based * r = Ext.Number.snap(56, 2, 55, 65); * * // Returns 57 - snap points are based from minValue * r = Ext.Number.snapInRange(56, 2, 55, 65); * * @param {Number} value The unsnapped value. * @param {Number} increment The increment by which the value must move. * @param {Number} minValue The minimum value to which the returned value must be * constrained. Overrides the increment. * @param {Number} maxValue The maximum value to which the returned value must be * constrained. Overrides the increment. * @return {Number} The value of the nearest snap target. */ snap: function(value, increment, minValue, maxValue) { var m; // If no value passed, or minValue was passed and value is less than minValue // (anything < undefined is false) // Then use the minValue (or zero if the value was undefined) if (value === undefined || value < minValue) { return minValue || 0; } if (increment) { m = value % increment; if (m !== 0) { value -= m; if (m * 2 >= increment) { value += increment; } else if (m * 2 < -increment) { value -= increment; } } } return ExtNumber.constrain(value, minValue, maxValue); }, /** * Snaps the passed number between stopping points based upon a passed increment value. * * The difference between this and {@link #snap} is that {@link #snap} does not use * the minValue when calculating snap points: * * // Returns 56 - snap points are zero based * r = Ext.Number.snap(56, 2, 55, 65); * * // Returns 57 - snap points are based from minValue * r = Ext.Number.snapInRange(56, 2, 55, 65); * * @param {Number} value The unsnapped value. * @param {Number} increment The increment by which the value must move. * @param {Number} [minValue=0] The minimum value to which the returned value must be * constrained. * @param {Number} [maxValue=Infinity] The maximum value to which the returned value * must be constrained. * @return {Number} The value of the nearest snap target. */ snapInRange: function(value, increment, minValue, maxValue) { var tween; // default minValue to zero minValue = (minValue || 0); // If value is undefined, or less than minValue, use minValue if (value === undefined || value < minValue) { return minValue; } // Calculate how many snap points from the minValue the passed value is. if (increment && (tween = ((value - minValue) % increment))) { value -= tween; tween *= 2; if (tween >= increment) { value += increment; } } // If constraining within a maximum, ensure the maximum is on a snap point if (maxValue !== undefined) { if (value > (maxValue = ExtNumber.snapInRange(maxValue, increment, minValue))) { value = maxValue; } } return value; }, /** * Round a number to the nearest interval. * @param {Number} value The value to round. * @param {Number} interval The interval to round to. * @return {Number} The rounded value. * * @since 6.2.0 */ roundToNearest: function(value, interval) { interval = interval || 1; return interval * math.round(value / interval); }, /** * Rounds a number to the specified precision. * @param value * @param precision * @return {number} */ roundToPrecision: function(value, precision) { var factor = math.pow(10, precision || 1); return math.round(value * factor) / factor; }, /** * Truncates a number to the specified precision, * without rounding. * @param value * @param precision * @return {number} * @since 6.5.1 */ truncateToPrecision: function(value, precision) { var factor = math.pow(10, precision || 1); return parseInt(value * factor, 10) / factor; }, /** * Returns the sign of the given number. See also MDN for Math.sign documentation * for the standard method this method emulates. * @param {Number} x The number. * @return {Number} The sign of the number `x`, indicating whether the number is * positive (1), negative (-1) or zero (0). * @method sign */ sign: math.sign || function(x) { x = +x; // force to a Number if (x === 0 || isNaN(x)) { return x; } return (x > 0) ? 1 : -1; }, /** * Returns the base 10 logarithm of a number. * This will use Math.log10, if available (ES6). * @param {Number} x The number. * @return {Number} Base 10 logarithm of the number 'x'. * @method log10 */ log10: math.log10 || function(x) { return math.log(x) * math.LOG10E; }, /** * Determines if two numbers `n1` and `n2` are equal within a given * margin of precision `epsilon`. * @param {Number} n1 First number. * @param {Number} n2 Second number. * @param {Number} epsilon Margin of precision. * @return {Boolean} `true`, if numbers are equal. `false` otherwise. */ isEqual: function(n1, n2, epsilon) { //<debug> /* eslint-disable-next-line max-len */ if (!(typeof n1 === 'number' && typeof n2 === 'number' && typeof epsilon === 'number')) { Ext.raise("All parameters should be valid numbers."); } //</debug> return math.abs(n1 - n2) < epsilon; }, /** * Determines if the value passed is a number and also finite. * This a Polyfill version of Number.isFinite(),differently than * the isFinite() function, this method doesn't convert the parameter to a number. * @param {Number} value Number to be tested. * @return {Boolean} `true`, if the parameter is a number and finite, `false` otherwise. * @since 6.2 */ isFinite: Number.isFinite || function(value) { return typeof value === 'number' && isFinite(value); }, isInteger: Number.isInteger || function(value) { // Add zero get a valid result in a special case where the value is a number string. // E.g. '10' + 0 is '100'. return ~~(value + 0) === value; }, /** * @method * Formats a number using fixed-point notation * @param {Number} value The number to format * @param {Number} precision The number of digits to show after the decimal point */ toFixed: isToFixedBroken ? function(value, precision) { var pow; precision = precision || 0; pow = math.pow(10, precision); return (math.round(value * pow) / pow).toFixed(precision); } : function(value, precision) { return value.toFixed(precision); }, /** * Validate that a value is numeric and convert it to a number if necessary. * Returns the specified default value if it is not. * * Ext.Number.from('1.23', 1); // returns 1.23 * Ext.Number.from('abc', 1); // returns 1 * * @param {Object} value * @param {Number} defaultValue The value to return if the original value is non-numeric * @return {Number} value, if numeric, defaultValue otherwise */ from: function(value, defaultValue) { if (isFinite(value)) { value = parseFloat(value); } return !isNaN(value) ? value : defaultValue; }, /** * Returns a random integer between the specified range (inclusive) * @param {Number} from Lowest value to return. * @param {Number} to Highest value to return. * @return {Number} A random integer within the specified range. */ randomInt: function(from, to) { return math.floor(math.random() * (to - from + 1) + from); }, /** * Corrects floating point numbers that overflow to a non-precise * value because of their floating nature, for example `0.1 + 0.2` * @param {Number} n The number * @return {Number} The correctly rounded number */ correctFloat: function(n) { // This is to correct the type of errors where 2 floats end with // a long string of decimals, eg 0.1 + 0.2. When they overflow in this // manner, they usually go to 15-16 decimals, so we cut it off at 14. return parseFloat(n.toPrecision(14)); } }); /** * @deprecated 4.0.0 Please use {@link Ext.Number#from} instead. * @member Ext * @method num * @inheritdoc Ext.Number#from */ Ext.num = function() { return ExtNumber.from.apply(this, arguments); };}());