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.getNumericInput ###
*/
(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', '#': 'hex', '0d': 'dec', '0b': 'bin', '0o': 'oct', '0': 'oct'
},
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]|0|#)/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:
* - dec decimal ("13", "0d13");
* - hex hexadecimal ("0xA0", "#FF");
* - oct octal ("0o27");
* - bin binary ("0b101010");
* - sci e notation ("2E3", "2000E-5");
*
* supports if enabled:
* - boo boolean ("true", true);
* - num other numeral systems "[value]base" ("[30]7", "[-41]12")
*
* supports using external function:
* - rom 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.getNumericInput = 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;