package com.exanimo.utils { /** * * NumberUtil.as * * Convenience functions for working with Numbers. * * todo: * - add support for negative numbers, fractions in toWordsString * * @author matthew at exanimo dot com * @version 2007.10.22 * */ public class NumberUtil { /** * * */ public function NumberUtil() { throw new Error('NumberUtil contains static utility methods and cannot be instantialized.'); } ///////////////////////////////////////////////////////////////////////////// // // toWordsString, toOrdinalString // ///////////////////////////////////////////////////////////////////////////// private static var _ordinalStrings:Object = { 1: ['st', 'first', 'one'], 2: ['nd', 'second', 'two'], 3: ['rd', 'third', 'three'], 4: ['th', 'fourth', 'four'], 5: ['th', 'fifth', 'five'], 6: ['th', 'sixth', 'six'], 7: ['th', 'seventh', 'seven'], 8: ['th', 'eighth', 'eight'], 9: ['th', 'ninth', 'nine'], 10: ['th', 'tenth', 'ten'], 11: ['th', 'eleventh', 'eleven'], 12: ['th', 'twelfth', 'twelve'], 13: ['th', 'thirteenth', 'thirteen'], 14: ['th', 'fourteenth', 'fourteen'], 15: ['th', 'fifteenth', 'fifteen'], 16: ['th', 'sixteenth', 'sixteen'], 17: ['th', 'seventeenth', 'seventeen'], 18: ['th', 'eighteenth', 'eighteen'], 19: ['th', 'nineteenth', 'nineteen'], 20: ['th', 'twentieth', 'twenty'], 30: ['th', 'thirtieth', 'thirty'], 40: ['th', 'fourtieth', 'forty'], 50: ['th', 'fiftieth', 'fifty'], 60: ['th', 'sixtieth', 'sixty'], 70: ['th', 'seventieth', 'seventy'], 80: ['th', 'eightieth', 'eighty'], 90: ['th', 'ninetieth', 'ninety'], 100: ['th', 'hundredth', 'hundred'], 1000: ['th', 'thousandth', 'thousand'], 1000000: ['th', 'millionth', 'million'], 1000000000: ['th', 'billionth', 'billion'], 1000000000000: ['th', 'trillionth', 'trillion'], 1000000000000000: ['th', 'quadrillionth', 'quadrillion'], 1000000000000000000: ['th', 'quintillionth', 'quintillion'] } /** * * Gets a spelled-out version of an integer. * * @param number:Number * the integer to spell out. * */ public static function toWordsString(number:Number):String { if ((number <= 0) || (Math.floor(number) != number)) { throw new ArgumentError('NumberUtil.toWordsString currently only supports positive integers.'); } return NumberUtil._getSpelledOutNumber(number, false); } /** * * Gets a string describing the position of an item with a certain * index. (i.e. NumberUtil.toOrdinalString(56) == '56th') * * @param number:Number * the index you want to describe * */ public static function toOrdinalString(number:Number):String { return NumberUtil._toOrdinalString(number, false); } /** * * Gets a string describing the position of an item with a certain * index. (i.e. NumberUtil.toOrdinalString(56) == 'fifty-sixth') * * @param number:Number * the index you want to describe * */ public static function toOrdinalWordsString(number:Number):String { return NumberUtil._toOrdinalString(number, true); } /** * * Gets a string describing the position of an item with a certain * index. * * @param number:Number * the index you want to describe * @param spellOut:Boolean * whether the number should be spelled out ("fifth") or not ("5th") * */ private static function _toOrdinalString(number:Number, spellOut:Boolean):String { if ((number <= 0) || (Math.floor(number) != number)) { throw new ArgumentError('Only the positive integers have corresponding ordinal numbers.'); } var output:String; var tmp:String = number.toString(); if (!spellOut) { if (tmp.charAt(tmp.length - 2) == '1') { output = tmp + 'th'; } else { output = tmp + NumberUtil._ordinalStrings[parseInt(tmp.substr(-1))][0]; } } else { output = NumberUtil._getSpelledOutNumber(number, true); } return output; } /** * * Gets a spelled-out version of a number. * * @param number:Number * the number to spell out. * @param isOrdinal:Boolean * specifies whether the function should return an ordinal number or not * */ private static function _getSpelledOutNumber(number:Number, isOrdinal:Boolean):String { var output:String = ''; var tmp:String = number.toString(); // Break the number into sets of three digits. var i:uint; var triplet:uint; var triplets:Array = []; var lengthOfFirstTriplet:uint = tmp.length % 3 || 3; triplets.push(parseInt(tmp.substr(0, lengthOfFirstTriplet))); for (i = lengthOfFirstTriplet || 3; i < tmp.length; i += 3) { triplets.push(parseInt(tmp.substr(i, 3))); } for (i = 0; i < triplets.length; i++) { if (triplets[i]) { if ((triplets[i] < 100) && (i == triplets.length - 1) && (triplets.length > 1)) { output += 'and '; } output += NumberUtil._getTripletWordsString(triplets[i], isOrdinal, i == triplets.length - 1) + NumberUtil._getTripletSuffix(triplets.length - i - 1) + ', '; } } return output.substr(0, -2); } /** * * Helper function for NumberUtil.toOrdinalString. * * @param tripletIndex:uint * .. * */ private static function _getTripletSuffix(tripletIndex:uint):String { return !tripletIndex ? '' : ' ' + NumberUtil._ordinalStrings[Math.pow(1000, tripletIndex)][2]; } /** * * Helper function for NumberUtil.toOrdinalString. * * @param number:Number * .. * @param isLastDigit:Boolean * tells whether the number is the last digit of the sequence. if * it is, it will include the ordinal ending (st, nd, rd, th) * */ private static function _getTripletWordsString(number:Number, isOrdinal:Boolean, isLastTriplet:Boolean):String { var i:uint; var digit:int; var ordinalText:Array = []; var hasAnd:Boolean = false // Separate the digits. var digits:Array = []; var length:uint = number.toString().length; var index: uint; for (i = 0; i < length; i++) { index = length - i - 1; digits.push(parseInt(number.toString().charAt(index))); } for (i = 0; i < digits.length; i++) { digit = digits[i]; // If the digit is zero, the stringification relies on the greater digits. if (digit != 0) { // The last digit (we go in reverse order) is a special case. if (!ordinalText.length) { if ((i == 0) && (digits[i + 1] == 1)) { ordinalText.unshift(NumberUtil._getOrdinalString(10 + digit, isOrdinal, isLastTriplet)); i++; } else { ordinalText.unshift(NumberUtil._getOrdinalString(Math.pow(10, i) * digit, isOrdinal, isLastTriplet)); } } else { ordinalText.unshift(NumberUtil._getOrdinalString(Math.pow(10, i) * digit, isOrdinal, false)); } } if (isLastTriplet && ordinalText.length && (i == 1) && !hasAnd && (i + 1 < digits.length)) { ordinalText.unshift('and'); hasAnd = true; } } return NumberUtil._concat.apply(null, ordinalText); } /** * * Helper function for NumberUtil.toOrdinalString. Combines multiple * strings. If one argument ends in 'y', it will be joined to the next * argument with a hyphen. Otherwise, a space is used. * * @param ...rest:Array * a list of the Strings to join * */ private static function _concat(...rest:Array):String { var output:String = rest[0]; for (var i:uint = 1; i < rest.length; i++) { output += (rest[i - 1] && (rest[i - 1].substr(-1) == 'y') ? '-' : ' ') + rest[i]; } return output; } /** * * Helper function for NumberUtil.toOrdinalString. * * @param number:Number * a number of the form j * Math.pow(10, k), where 0 < j < 10 and k > 0 * @param isLastDigit:Boolean * tells whether the number is the last digit of the sequence. if * it is, it will include the ordinal ending (st, nd, rd, th) * */ private static function _getOrdinalString(number:Number, isOrdinal:Boolean, isLastDigit:Boolean = false):String { if (number < 100) { return NumberUtil._ordinalStrings[number][isOrdinal && isLastDigit ? 1 : 2]; } else { var firstDigit:uint = parseInt(number.toString().substring(0, 1)); return NumberUtil._concat(NumberUtil._ordinalStrings[firstDigit][2], NumberUtil._ordinalStrings[number / firstDigit][isOrdinal && isLastDigit ? 1 : 2]); } } } }