diff -r 000000000000 -r 40c8f766c9b8 src/cm/media/js/lib/flexible-js-formatting/numbers/number-functions.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/flexible-js-formatting/numbers/number-functions.js Mon Nov 23 15:14:29 2009 +0100 @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2006 Baron Schwartz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, version 2.1. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * $Revision: 1.3 $ + */ + +// Abbreviations: LODP = Left Of Decimal Point, RODP = Right Of Decimal Point +Number.formatFunctions = {count:0}; + +// Constants useful for controlling the format of numbers in special cases. +Number.prototype.NaN = 'NaN'; +Number.prototype.posInfinity = 'Infinity'; +Number.prototype.negInfinity = '-Infinity'; + +Number.prototype.numberFormat = function(format, context) { + if (isNaN(this) ) { + return Number.prototype.NaNstring; + } + else if (this == +Infinity ) { + return Number.prototype.posInfinity; + } + else if ( this == -Infinity) { + return Number.prototype.negInfinity; + } + else if (Number.formatFunctions[format] == null) { + Number.createNewFormat(format); + } + return this[Number.formatFunctions[format]](context); +} + +Number.createNewFormat = function(format) { + var funcName = "format" + Number.formatFunctions.count++; + Number.formatFunctions[format] = funcName; + var code = "Number.prototype." + funcName + " = function(context){\n"; + + // Decide whether the function is a terminal or a pos/neg/zero function + var formats = format.split(";"); + switch (formats.length) { + case 1: + code += Number.createTerminalFormat(format); + break; + case 2: + code += "return (this < 0) ? this.numberFormat(\"" + + String.escape(formats[1]) + + "\", 1) : this.numberFormat(\"" + + String.escape(formats[0]) + + "\", 2);"; + break; + case 3: + code += "return (this < 0) ? this.numberFormat(\"" + + String.escape(formats[1]) + + "\", 1) : ((this == 0) ? this.numberFormat(\"" + + String.escape(formats[2]) + + "\", 2) : this.numberFormat(\"" + + String.escape(formats[0]) + + "\", 3));"; + break; + default: + code += "throw 'Too many semicolons in format string';"; + break; + } + eval(code + "}"); +} + +Number.createTerminalFormat = function(format) { + // If there is no work to do, just return the literal value + if (format.length > 0 && format.search(/[0#?]/) == -1) { + return "return '" + String.escape(format) + "';\n"; + } + // Negative values are always displayed without a minus sign when section separators are used. + var code = "var val = (context == null) ? new Number(this) : Math.abs(this);\n"; + var thousands = false; + var lodp = format; + var rodp = ""; + var ldigits = 0; + var rdigits = 0; + var scidigits = 0; + var scishowsign = false; + var sciletter = ""; + // Look for (and remove) scientific notation instructions, which can be anywhere + m = format.match(/\..*(e)([+-]?)(0+)/i); + if (m) { + sciletter = m[1]; + scishowsign = (m[2] == "+"); + scidigits = m[3].length; + format = format.replace(/(e)([+-]?)(0+)/i, ""); + } + // Split around the decimal point + var m = format.match(/^([^.]*)\.(.*)$/); + if (m) { + lodp = m[1].replace(/\./g, ""); + rodp = m[2].replace(/\./g, ""); + } + // Look for % + if (format.indexOf('%') >= 0) { + code += "val *= 100;\n"; + } + // Look for comma-scaling to the left of the decimal point + m = lodp.match(/(,+)(?:$|[^0#?,])/); + if (m) { + code += "val /= " + Math.pow(1000, m[1].length) + "\n;"; + } + // Look for comma-separators + if (lodp.search(/[0#?],[0#?]/) >= 0) { + thousands = true; + } + // Nuke any extraneous commas + if ((m) || thousands) { + lodp = lodp.replace(/,/g, ""); + } + // Figure out how many digits to the l/r of the decimal place + m = lodp.match(/0[0#?]*/); + if (m) { + ldigits = m[0].length; + } + m = rodp.match(/[0#?]*/); + if (m) { + rdigits = m[0].length; + } + // Scientific notation takes precedence over rounding etc + if (scidigits > 0) { + code += "var sci = Number.toScientific(val," + + ldigits + ", " + rdigits + ", " + scidigits + ", " + scishowsign + ");\n" + + "var arr = [sci.l, sci.r];\n"; + } + else { + // If there is no decimal point, round to nearest integer, AWAY from zero + if (format.indexOf('.') < 0) { + code += "val = (val > 0) ? Math.ceil(val) : Math.floor(val);\n"; + } + // Numbers are rounded to the correct number of digits to the right of the decimal + code += "var arr = val.round(" + rdigits + ").toFixed(" + rdigits + ").split('.');\n"; + // There are at least "ldigits" digits to the left of the decimal, so add zeros if needed. + code += "arr[0] = (val < 0 ? '-' : '') + String.leftPad((val < 0 ? arr[0].substring(1) : arr[0]), " + + ldigits + ", '0');\n"; + } + // Add thousands separators + if (thousands) { + code += "arr[0] = Number.addSeparators(arr[0]);\n"; + } + // Insert the digits into the formatting string. On the LHS, extra digits are copied + // into the result. On the RHS, rounding has chopped them off. + code += "arr[0] = Number.injectIntoFormat(arr[0].reverse(), '" + + String.escape(lodp.reverse()) + "', true).reverse();\n"; + if (rdigits > 0) { + code += "arr[1] = Number.injectIntoFormat(arr[1], '" + String.escape(rodp) + "', false);\n"; + } + if (scidigits > 0) { + code += "arr[1] = arr[1].replace(/(\\d{" + rdigits + "})/, '$1" + sciletter + "' + sci.s);\n"; + } + return code + "return arr.join('.');\n"; +} + +Number.toScientific = function(val, ldigits, rdigits, scidigits, showsign) { + var result = {l:"", r:"", s:""}; + var ex = ""; + // Make ldigits + rdigits significant figures + var before = Math.abs(val).toFixed(ldigits + rdigits + 1).trim('0'); + // Move the decimal point to the right of all digits we want to keep, + // and round the resulting value off + var after = Math.round(new Number(before.replace(".", "").replace( + new RegExp("(\\d{" + (ldigits + rdigits) + "})(.*)"), "$1.$2"))).toFixed(0); + // Place the decimal point in the new string + if (after.length >= ldigits) { + after = after.substring(0, ldigits) + "." + after.substring(ldigits); + } + else { + after += '.'; + } + // Find how much the decimal point moved. This is #places to LODP in the original + // number, minus the #places in the new number. There are no left-padded zeroes in + // the new number, so the calculation for it is simpler than for the old number. + result.s = (before.indexOf(".") - before.search(/[1-9]/)) - after.indexOf("."); + // The exponent is off by 1 when it gets moved to the left. + if (result.s < 0) { + result.s++; + } + // Split the value around the decimal point and pad the parts appropriately. + result.l = (val < 0 ? '-' : '') + String.leftPad(after.substring(0, after.indexOf(".")), ldigits, "0"); + result.r = after.substring(after.indexOf(".") + 1); + if (result.s < 0) { + ex = "-"; + } + else if (showsign) { + ex = "+"; + } + result.s = ex + String.leftPad(Math.abs(result.s).toFixed(0), scidigits, "0"); + return result; +} + +Number.prototype.round = function(decimals) { + if (decimals > 0) { + var m = this.toFixed(decimals + 1).match( + new RegExp("(-?\\d*)\.(\\d{" + decimals + "})(\\d)\\d*$")); + if (m && m.length) { + return new Number(m[1] + "." + String.leftPad(Math.round(m[2] + "." + m[3]), decimals, "0")); + } + } + return this; +} + +Number.injectIntoFormat = function(val, format, stuffExtras) { + var i = 0; + var j = 0; + var result = ""; + var revneg = val.charAt(val.length - 1) == '-'; + if ( revneg ) { + val = val.substring(0, val.length - 1); + } + while (i < format.length && j < val.length && format.substring(i).search(/[0#?]/) >= 0) { + if (format.charAt(i).match(/[0#?]/)) { + // It's a formatting character; copy the corresponding character + // in the value to the result + if (val.charAt(j) != '-') { + result += val.charAt(j); + } + else { + result += "0"; + } + j++; + } + else { + result += format.charAt(i); + } + ++i; + } + if ( revneg && j == val.length ) { + result += '-'; + } + if (j < val.length) { + if (stuffExtras) { + result += val.substring(j); + } + if ( revneg ) { + result += '-'; + } + } + if (i < format.length) { + result += format.substring(i); + } + return result.replace(/#/g, "").replace(/\?/g, " "); +} + +Number.addSeparators = function(val) { + return val.reverse().replace(/(\d{3})/g, "$1,").reverse().replace(/^(-)?,/, "$1"); +} + +String.prototype.reverse = function() { + var res = ""; + for (var i = this.length; i > 0; --i) { + res += this.charAt(i - 1); + } + return res; +} + +String.prototype.trim = function(ch) { + if (!ch) ch = ' '; + return this.replace(new RegExp("^" + ch + "+|" + ch + "+$", "g"), ""); +} + +String.leftPad = function (val, size, ch) { + var result = new String(val); + if (ch == null) { + ch = " "; + } + while (result.length < size) { + result = ch + result; + } + return result; +} + +String.escape = function(string) { + return string.replace(/('|\\)/g, "\\$1"); +}