Files
numnum/numerals.js
T

275 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const SYSTEMS = {
binary: {
name: 'Binary',
short: 'BIN',
toDisplay: (n) => n.toString(2),
},
octal: {
name: 'Octal',
short: 'OCT',
toDisplay: (n) => n.toString(8),
},
hex: {
name: 'Hexadecimal',
short: 'HEX',
toDisplay: (n) => n.toString(16).toUpperCase(),
},
base3: {
name: 'Ternary',
short: 'TER',
toDisplay: toBase3,
},
ternary: {
name: 'Balanced Ternary',
short: 'BT3',
toDisplay: toBalTernary,
},
braille: {
name: 'Braille Decimal',
short: 'BRL',
toDisplay: toBraille,
},
roman: {
name: 'Roman',
short: 'ROM',
toDisplay: toRoman,
},
slavonic: {
name: 'Church Slavonic',
short: 'SLV',
toDisplay: toSlavonic,
},
greek: {
name: 'Greek',
short: 'GRK',
toDisplay: toGreek,
},
hebrew: {
name: 'Hebrew',
short: 'HEB',
toDisplay: toHebrew,
},
};
function toRoman(num) {
if (num <= 0 || num > 3999) return String(num);
const vals = [1000,900,500,400,100,90,50,40,10,9,5,4,1];
const syms = ['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I'];
let result = '';
for (let i = 0; i < vals.length; i++) {
while (num >= vals[i]) {
result += syms[i];
num -= vals[i];
}
}
return result;
}
function toBase3(num) {
if (num === 0) return '0';
let n = Math.abs(num);
let result = '';
while (n > 0) {
result = (n % 3) + result;
n = Math.floor(n / 3);
}
return num < 0 ? '-' + result : result;
}
function toBalTernary(num) {
if (num === 0) return '0';
let sign = 1;
let n = num;
if (n < 0) {
sign = -1;
n = -n;
}
const digits = [];
while (n > 0) {
let rem = n % 3;
n = Math.floor(n / 3);
if (rem === 2) {
digits.unshift('\u2296');
n += 1;
} else if (rem === 1) {
digits.unshift('\u2295');
} else {
digits.unshift('0');
}
}
if (sign === -1) {
return digits.map(d => {
if (d === '\u2295') return '\u2296';
if (d === '\u2296') return '\u2295';
return d;
}).join('');
}
return digits.join('');
}
const BRAILLE_NUMS = ['\u281A','\u2801','\u2803','\u2809','\u2819','\u2811','\u280B','\u281B','\u2813','\u280A'];
const BRAILLE_HASH = '\u283C';
function toBraille(num) {
if (num < 0) return String(num);
if (num === 0) return BRAILLE_HASH + BRAILLE_NUMS[0];
return BRAILLE_HASH + String(num).split('').map(d => BRAILLE_NUMS[parseInt(d)]).join('');
}
const GRK_UNITS = ['\u03B1','\u03B2','\u03B3','\u03B4','\u03B5','\u03DB','\u03B6','\u03B7','\u03B8'];
const GRK_TENS = ['\u03B9','\u03BA','\u03BB','\u03BC','\u03BD','\u03BE','\u03BF','\u03C0','\u03DF'];
const GRK_HUNDREDS = ['\u03C1','\u03C3','\u03C4','\u03C5','\u03C6','\u03C7','\u03C8','\u03C9','\u03E1'];
const GRK_THOU = '\u0375';
const GRK_KER = '\u1FFD';
function toGreek(num) {
if (num <= 0 || num > 9999) return String(num);
let result = '';
const thou = Math.floor(num / 1000);
num %= 1000;
const h = Math.floor(num / 100);
num %= 100;
const t = Math.floor(num / 10);
const u = num % 10;
if (thou > 0) result += GRK_THOU + GRK_UNITS[thou - 1];
if (h > 0) result += GRK_HUNDREDS[h - 1];
if (t > 0) result += GRK_TENS[t - 1];
if (u > 0) result += GRK_UNITS[u - 1];
return result ? result + GRK_KER : '0';
}
const SLV_UNITS = ['а','в','г','д','є','ѕ','з','и','ѳ'];
const SLV_TENS = ['і','к','л','м','н','ѯ','ѻ','п','ч'];
const SLV_HUNDREDS = ['р','с','т','у','ф','х','ѱ','ѿ','ц'];
const SLV_THOU = '\u0482';
const SLV_TITLO = '\u0483';
function toSlavonic(num) {
if (num <= 0 || num > 9999) return String(num);
const groups = [];
const thou = Math.floor(num / 1000);
num %= 1000;
const h = Math.floor(num / 100);
num %= 100;
const t = Math.floor(num / 10);
const u = num % 10;
if (thou > 0) groups.push({ p: SLV_THOU, l: SLV_UNITS[thou - 1] });
if (h > 0) groups.push({ p: '', l: SLV_HUNDREDS[h - 1] });
const twoDigit = t * 10 + u;
if (twoDigit >= 11 && twoDigit <= 19) {
if (u > 0) groups.push({ p: '', l: SLV_UNITS[u - 1] });
if (t > 0) groups.push({ p: '', l: SLV_TENS[t - 1] });
} else {
if (t > 0) groups.push({ p: '', l: SLV_TENS[t - 1] });
if (u > 0) groups.push({ p: '', l: SLV_UNITS[u - 1] });
}
if (groups.length === 0) return '0';
const titloIdx = groups.length >= 2 ? groups.length - 2 : 0;
groups[titloIdx].l += SLV_TITLO;
return groups.map(g => g.p + g.l).join('') + '.';
}
const HEB_UNITS = ['\u05D0','\u05D1','\u05D2','\u05D3','\u05D4','\u05D5','\u05D6','\u05D7','\u05D8'];
const HEB_TENS = ['\u05D9','\u05DB','\u05DC','\u05DE','\u05E0','\u05E1','\u05E2','\u05E4','\u05E6'];
const HEB_HUNDREDS = ['\u05E7','\u05E8','\u05E9','\u05EA'];
const HEB_GERESH = '\u05F3';
const HEB_GERSHAYIM = '\u05F4';
function toHebrewLetters(num) {
const letters = [];
const h = Math.floor(num / 100);
num %= 100;
const t = Math.floor(num / 10);
const u = num % 10;
if (h === 4) {
letters.push(HEB_HUNDREDS[3]);
if (t > 0) letters.push(HEB_TENS[t - 1]);
if (u > 0) letters.push(HEB_UNITS[u - 1]);
} else if (h >= 5) {
letters.push(HEB_HUNDREDS[3]);
const extra = h - 4;
if (extra > 0) letters.push(HEB_HUNDREDS[extra - 1]);
if (t > 0) letters.push(HEB_TENS[t - 1]);
if (u > 0) letters.push(HEB_UNITS[u - 1]);
} else {
if (h > 0) letters.push(HEB_HUNDREDS[h - 1]);
if (t > 0) letters.push(HEB_TENS[t - 1]);
if (u > 0) letters.push(HEB_UNITS[u - 1]);
}
return letters;
}
function toHebrew(num) {
if (num <= 0 || num > 9999) return String(num);
if (num === 15) return '\u05D8' + HEB_GERSHAYIM + '\u05D5';
if (num === 16) return '\u05D8' + HEB_GERSHAYIM + '\u05D6';
const thousands = Math.floor(num / 1000);
const rest = num % 1000;
if (thousands > 0 && rest === 0) {
return HEB_UNITS[thousands - 1] + HEB_GERESH;
}
let result = '';
if (thousands > 0) {
result = HEB_UNITS[thousands - 1] + HEB_GERESH;
}
const letters = toHebrewLetters(rest);
if (letters.length === 0) return result;
if (letters.length === 1) return result + letters[0] + HEB_GERESH;
return result + letters.slice(0, -1).join('') + HEB_GERSHAYIM + letters[letters.length - 1];
}
export function generateChoices(correctAnswer, allRanges, count = 3, matchLastDigit = false) {
const choices = new Set([correctAnswer]);
const [min, max] = allRanges;
const useLastDigit = matchLastDigit && correctAnswer > 10;
let attempts = 0;
while (choices.size < count && attempts < 100) {
let wrong;
if (useLastDigit) {
const offset = (Math.floor(Math.random() * 20) + 1) * 10;
wrong = Math.random() < 0.5 ? correctAnswer + offset : correctAnswer - offset;
} else {
const offset = Math.floor(Math.random() * 20) + 1;
wrong = Math.random() < 0.5 ? correctAnswer + offset : correctAnswer - offset;
}
if (wrong >= min && wrong <= max && wrong !== correctAnswer) {
choices.add(wrong);
}
attempts++;
}
while (choices.size < count) {
let r;
if (useLastDigit) {
do {
r = min + Math.floor(Math.random() * (max - min + 1));
} while (r % 10 !== correctAnswer % 10 && choices.size < count + 50);
} else {
r = min + Math.floor(Math.random() * (max - min + 1));
}
choices.add(r);
}
const arr = Array.from(choices);
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}