K345 number methods source
source code
/** * converts a numeric string to number * can handle comma|dot|space formatted strings like * "1,000,000.00" , "1 000 000.00", "1.000.000,00" * * @param {String} inStr * (formatted) string represenation of a number * @param {Number} [radix=10] * number system of result. defaults is 10 (decimal numeral system) * @returns {Number|NaN) * numeric value or NaN for illegal input */ K345.numString2Number = function (inStr, radix) { var dot, com, len; if (typeof inStr === 'string' && (/^\-?[0-9\s,.-]+$/i).test(inStr)) { len = inStr.split(/[,.]/).length; dot = inStr.indexOf('.'); com = inStr.indexOf(','); /* remove all whitespace; replace all »,« and ».« with spaces */ inStr = inStr.replace(/\s/g, '') .replace(/[,.]/g, ' '); if ((dot > -1 && com > -1) || len === 2) { /* de facto: replace last space with "." */ inStr = inStr.replace(/^(.+) ([^\s]+)$/, '$1.$2'); } if (typeof radix !== 'number' || radix < 2 || radix > 36) { radix = 10; } return parseFloat( inStr.replace(/\s/g, ''), /* remove all remaining spaces */ Math.floor(radix) ); } return NaN; }; /*! docs: http://javascript.knrs.de/k345/numeric/#numinput */ /** * ### K345.numericInput ### */ (function () { var sysDefs = { /* (en|dis)abled numeral systems defaults */ dec: {enabled: true, radix: 10}, hex: {enabled: true, radix: 16}, oct: {enabled: true, radix: 8}, bin: {enabled: true, radix: 2}, sci: {enabled: true}, exp: {enabled: false}, rom: {enabled: false}, boo: {enabled: false}, num: {enabled: false} }, sysIDs = { '0x': 'hex', '0d': 'dec', '0b': 'bin', '0o': 'oct', '#': 'hex' }, rChars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; /* get string with valid chars for numeral system */ function getChars(radix) { return rChars.substring(0, radix); } /* validate radix for parseInt and parseFloat */ function isValidRadix(radix) { return (typeof radix === 'number' && radix >= 2 && radix <= 36); } /* set flags in config object for enabled/disabled numeral systems and add default * flags for omitted systems */ function setSystems(so, defs) { var enable_all = false; if (!K345.isObject(so)) { so = {}; } else if ('all' in so) { if (so.all === true) { enable_all = true; } delete so.all; } Object.forEach(defs, function (item) { if (enable_all) { so[item] = true; } else { if (item in so) { /* convert non boolean values */ so[item] = Boolean(so[item]); } else { /* add default value if property is missing */ so[item] = defs[item].enabled; } } }); return so; } /* convert strings matching decimal, hexadeimal, octal or binary notation to decimal */ function convertBinHexOctDec(val, sys) { var sInfo, sChar, sName, reg, radix, mch, rv = NaN; sInfo = val.match(/^(\-?)(0[bdox]|#)/i); /* strings prefixed by "0x", "0d", "0o", "0b" or "#" */ if (sInfo && sInfo.length > 2) { sChar = sInfo[2].toLowerCase(); sName = sysIDs[sChar]; if (sChar && sName && (sName in sys) && sys[sName] && (sName in sysDefs)) { radix = sysDefs[sName].radix; if (isValidRadix(radix)) { reg = new RegExp( '^' + sInfo[0] + '([' + getChars(radix) + ']+)$', 'i' ); mch = val.match(reg); if (mch && mch.length > 1) { rv = parseInt(sInfo[1] + mch[1], radix); } } } } /* decimal notation string containig only 0-9, comma and dot */ else if (sys.dec && (/^\-?[0-9,.]+$/).test(val)) { if (typeof K345.numString2Number === 'function') { rv = K345.numString2Number(val, 10); } else { rv = parseFloat(val, 10); } } return rv; } /* convert roman number to decimal using external function */ function convertRoman(val) { var mul = 1, rv = NaN; if (typeof K345.rom2dec === 'function') { if (val.charAt(0) === '-') { val = val.substring(1); mul = -1; } rv = K345.rom2dec(val); if (!isNaN(rv)) { rv = rv * mul; } } return rv; } /* convert "power of" or "root of" to decimal */ function convertPowRoot(val) { var mch, op, rv = NaN; mch = val.match( /^(\-?[0-9]+(?:\.[0-9]+)?)?(\^|pow|\*\*|√|r(?:oo)?t)(\-?[0-9]+(?:\.[0-9]+)?)$/i ); if (mch && mch.length > 2 && !isNaN(mch[3])) { op = mch[2]; if ('√|rt|root'.indexOf(op) > -1) { if (typeof mch[1] === 'undefined') { mch[1] = 2; } if (!isNaN(mch[1]) && mch[1] > 0) { rv = Math.pow(mch[3], 1 / mch[1]); } } else if (!isNaN(mch[1])) { rv = Math.pow(mch[1], mch[3]); } } return (rv === Infinity) ? NaN : rv; } /* convert custom numeral systems to decimal. base may be 2 to 36 */ function convertNumSystems(val) { var mch, radix, reg, rv = NaN; mch = val.match(/\[([0-9]+)\]/); if (mch && mch.length > 0) { radix = Number(mch[1]); if (isValidRadix(radix)) { reg = new RegExp( '^(\\-?[' + getChars(radix) + ']+)', 'i' ); mch = val.match(reg); if (mch && mch.length > 1) { rv = parseInt(mch[1], radix); } } } return rv; } /** * Convert several types of numeric strings to decimal * * doc: http://javascript.knrs.de/k345/numeric/#numinput * * supports by default: * - decimal ("13", "0d13"); * - hexadecimal ("0xA0", "#FF"); * - octal ("0o27"); * - binary ("0b101010"); * - e notation ("2E3", "2000E-5"); * * supports if enabled: * - boolean ("true", true); * - other numeral systems "[value]base" ("[30]7", "[-41]12") * * supports using external function: * - roman ("MMXIV" "V*MDCCLXVIII") with K345.rom2dec() * * @param {String} val * numeric string to convert * @param {Object} [sys={dec: true, hex: true, oct: true, bin: true, sci: true, * exp: false, rom: false, boo: false, num: false}] * enabled numberal systems * @returns {Number|NaN} * decimal value of numeric string or NaN */ K345.numericInput = function (val, sys) { var rv = NaN, ty = typeof val; /* enabled/disabled numeral systems*/ sys = setSystems(sys, sysDefs); /* handle non string values for val, */ if (ty !== 'string') { if (sys.dec && ty === 'number') { return val; } else if (sys.boo && ty === 'boolean') { return Number(val); } return rv; } val = val.replace(/\s/g, '').replace('+', ''); /* binary, hexadecimal, decimal, octal */ if ( (sys.bin || sys.oct || sys.dec || sys.hex) && (/^\-?(?:0[bdox]|#|[0-9,.]+$)/i).test(val) ) { rv = convertBinHexOctDec(val, sys); } /* scientific E notation (calculator notation) */ else if (sys.sci && (/^\-?[0-9]+(?:\.[0-9]+)?e\-?[0-9]+$/i).test(val)) { rv = Number(val); } /* exponential and root */ else if (sys.exp && (/^(?:[0-9.-]+)?(?:\^|pow|\*\*|√|r(?:oo)?t)[0-9.-]+$/i).test(val) ) { rv = convertPowRoot(val); } /* roman */ else if (sys.rom && (/^\-?[IJVXLCDM•\*\u2180-\u2183]+$/i).test(val)) { rv = convertRoman(val); } /* convert strings with words "true" or "false" to 0/1 */ else if (sys.boo && (val === 'true' || val === 'false')) { rv = Number(val === 'true'); } /* other numeral systems */ else if (sys.num && (/^\-?[0-9a-z]+\[[0-9]+\]$/i).test(val)) { rv = convertNumSystems(val); } return rv; }; })(); /** * convert number to/from roman * supports 3 and 4 char syntax (e.g IX or VIIII => 9, XC or LXXXX => 90 ) NEEDS REWRITE * * Römische Zahlen verarbeiten. * Unterstützt 3- und 4-Zeichen-Syntax (z.B. IX oder VIIII => 9; XC oder LXXXX => 90 usw.) */ K345.parseRoman = (function () { var rData, synCheck, validChars, decMax, rGroups, rTokens = [], rValues = []; /* Zeichen, die auftreten können und deren Werte. Zwischenwerte werden automatisch * generiert. Bei allen Werten > 1000 werden intern Ersatzbuchstaben verwendet, um die * Verarbeitung zu vereinfachen. Bei der Ein/Ausgabe werden Unicode-Spezial-Zeichen * entsprechend zu diesen Buchstaben gewandelt. * * Eigenschaften: * tk: Token; für Werte > 1000 (M) beliebiger unbelegter Buchstabe * val: Zahlenwert , der zu Token gehört * ci: Zeichen werden zusätzlich als Eingabe bei rom2dec erkannt * cio: Zeichen wird als Ausgabe bei dec2rom benutzt und bei rom2dec erkannt * * Sämtliche erforderliche regulären Ausdrücke werde aus diesen Daten automatisch * generiert. */ rData = [ {tk: 'S', val: 1000000000, cio: 'M•M•M'}, {tk: 'R', val: 500000000, cio: 'D•M•M'}, {tk: 'Q', val: 100000000, cio: 'C•M•M'}, {tk: 'P', val: 50000000, cio: 'L•M•M'}, {tk: 'N', val: 10000000, cio: 'X•M•M'}, {tk: 'K', val: 5000000, cio: 'V•M•M'}, {tk: 'H', val: 1000000, cio: 'M•M'}, {tk: 'G', val: 500000, cio: 'D•M'}, {tk: 'F', val: 100000, cio: 'C•M'}, {tk: 'E', val: 50000, cio: 'L•M'}, {tk: 'B', val: 10000, cio: 'X•M', ci: '\u2182'}, {tk: 'A', val: 5000, cio: 'V•M', ci: '\u2181'}, /* ab hier übliche römisch Zahlzeichen in "tk" */ {tk: 'M', val: 1000, ci: '\u2180'}, //{tk: 'D', val: 500, ci: '\u2183'}, {tk: 'D', val: 500}, {tk: 'C', val: 100}, {tk: 'L', val: 50}, {tk: 'X', val: 10}, {tk: 'V', val: 5}, {tk: 'I', val: 1, ci: 'J'} ]; /* RegEx für erlaubte Zeichen erzeugen */ validChars = (function () { var ch = ''; rData.forEach(function (o) { if (o.cio) { if (o.cio.indexOf('•') === -1 && ch.indexOf(o.cio) === -1) { ch += o.cio; } } if (o.ci && ch.indexOf(o.ci) === -1) { ch += o.ci; } if (!o.cio && ch.indexOf(o.tk) === -1) { ch += o.tk; } }); return new RegExp('^[' + ch + '•\\(\\)\\*\\s\\-]+$', 'i'); })(); /* Mögliche Zeicheneinheiten und deren Werte in den Arrays „rTokens“ und „rValues“ * abspeichern. Jeder Wert in „rValues“ entspricht der jeweiligen Zeicheneinheit mit * gleichem Index im Array „rTokens“ */ (function () { var l = rData.length, i; for (i = (l % 2); i < l; i += 2) { if (rData[i - 1]) { rTokens.push( rData[i - 1].tk, rData[i + 1].tk + rData[i - 1].tk ); rValues.push( rData[i - 1].val, rData[i - 1].val - rData[i + 1].val ); } rTokens.push( rData[i].tk, rData[i + 1].tk + rData[i].tk ); rValues.push( rData[i].val, rData[i].val - rData[i + 1].val ); } rTokens.push(rData[l - 1].tk); rValues.push(rData[l - 1].val); })(); /* RegExp für Zeichengruppen-Erkennung erzeugen */ rGroups = new RegExp('(' + rTokens.join('|') + ')', 'g'); /* maximal erlaubter Eingangswert für dec2rom */ decMax = (5 * rValues[0]) - 1; /* RegEx für Test auf gültige Syntax erzeugen */ synCheck = (function () { var i = Math.floor(rTokens.length / 4), /* Je 4 Einheiten bilden eine Gruppe */ r = '', pat = '(§1?§0{0,4}|§0[§2§1])', base; while (i--) { base = 4 * (i + 1); r = pat.replace(/§0/g, rTokens[base]) .replace(/§1/g, rTokens[base - 2]) .replace(/§2/g, rTokens[base - 4]) + r; } return new RegExp('^' + rTokens[0] + '{0,4}' + r + '$', 'i'); })(); /* Die Zeichenkette zu Großbuchstaben wandeln, Whitespace entfernen, Unicode-Zeichen * und Multi-Zeichen-Kombinationen für interne Weiterverarbeitung durch einen einzelnen * Buchstaben ersetzen. **/ function replaceInChars(s) { s = s.replace(/[\s\(\)]/g, '').replace(/\*/g, '•').toUpperCase(); rData.forEach(function (o) { if (typeof o.cio === 'string') { s = s.replace(new RegExp(o.cio, 'g'), o.tk); } if (typeof o.ci === 'string') { o.ci = o.ci.split(''); } if (Array.isArray(o.ci)) { o.ci.forEach(function (ch) { s = s.replace(new RegExp(ch, 'g'), o.tk); }); } }); return s; } /* Interne Zeichen zu gültiger Ausgabe umwandeln */ function replaceOutChars(s, fmt) { s = s.join((fmt) ? ' ' : ''); rData.forEach(function (o) { if (o.cio) { s = s.replace(new RegExp(o.tk, 'g'), o.cio); } }); if (fmt) { s = s.replace(/(•[a-z])([a-z]•)/gi, '$1 $2') .replace(/(•[a-z])([a-z])/gi, '$1 $2'); } return s; } return { /** * Parsen einer römischen zahl zu einer Dezimalzahl * * @param {String} romStr * Römische Zahl. Erlaubt sind Kombinationen aus M D C L X V I und * \u2180 (1000), \u2181 (5000) und \u2182 (10000) sowie Leerzeichen zur * lesbareren Gruppierung * @returns {Number|NaN} * dezimale Repräsentation der Zeichenkette oder NaN bei ungültigen Werten */ rom2dec: function (romStr) { var fnd; /* Testen, ob nur valid Zeichen verwendet werden */ if (typeof romStr === 'string' && validChars.test(romStr)) { /* Zur Vereinfachung der Verarbeitung diverse Zeichen ersetzen */ romStr = replaceInChars(romStr); /* Test auf korrekt Syntax bezüglich Zeichenposition */ if (synCheck.test(romStr)) { /* Array fnd enthält die gültigen Einheiten */ fnd = romStr.match(rGroups); /* Wertzuweisung dieser Einheiten ermitteln und zu Startwert 0 addieren*/ return fnd.reduce(function (p, c) { p += rValues[rTokens.indexOf(c)]; return p; }, 0); } } return NaN; }, /** * Umwandlung einer Dezimalzahl zu einer römischen Zahl * * @param {Number} num * Eine Zahl zwischen 1 und dem errechneten Wert 5 * höchste Zahl - 1 * @param {Boolean} [grp=false] * Die erzeugte Zeichenkette mit Leerzeichen gruppieren * @returns {String} * Repräsentation der Zahl in römischer Schreibweise */ dec2rom: function (num, grp) { var s = []; num = Math.floor(num); if (num > 0 && num <= decMax) { /* Durchläuft solange Array rValues, bis entweder Callback nicht mehr * „true“ zurückgibt (Restzahl ist <= 0) oder alle Array-Einträge * durchlaufen wurden */ rValues.every(function (val, i) { while (num >= val) { s.push(rTokens[i]); num -= val; } return (num > 0); }); return replaceOutChars(s, !!grp); } return ''; } }; })(); K345.rom2dec = K345.parseRoman.rom2dec; K345.dec2rom = K345.parseRoman.dec2rom;