commit 45faac5b142a2378df8c3ad0e067a33f59110bf8 Author: Christian Lawson-Perfect Date: Mon Apr 14 11:58:47 2025 +0000 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bc0f76 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.make.* \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..657de5a --- /dev/null +++ b/index.html @@ -0,0 +1,26 @@ + + + + + + Numeral conversion + + + + +
+
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..a0555d7 --- /dev/null +++ b/script.js @@ -0,0 +1,536 @@ +function span(first, n=10) { + return [...new Array(n)].map((_,i) => first.slice(0,first.length-1)+String.fromCharCode(first.charCodeAt(first.length-1) + i)); +} + +function positional_unicode_span(name, zero, base=10n, extra) { + // Make a function to render a positional number system whose digits are in a contiguous span of unicode code points. + return Object.assign({ + name, + fn: (n) => { + let o = ''; + if(n==0n) { + return zero; + } + while(n) { + const d = n % base; + n = (n - d)/base; + const c = zero.slice(0,zero.length-1) + String.fromCharCode(zero.charCodeAt(zero.length-1) + Number(d)); + o = c + o; + } + return o; + } + }, extra || {}) +} + +function positional_symbols(name, symbols) { + // Make a function to render a positional number system whose digits are drawn from the given string. + return { + name, + fn: (n) => { + let o = ''; + const base = BigInt(symbols.length); + if(n == 0n) { + return symbols[0]; + } + + while(n) { + const d = n % base; + n = (n - d)/base; + o = symbols[d] + o; + } + return o; + } + }; +} + +function concatenative(power_lists, base=10n) { + return function(n) { + if(n==0n) { + return ''; + } + + let o = ''; + for(let symbols of power_lists) { + + const r = n % 10n; + if(r > symbols.length) { + return '???'; + + } + if(r > 0n) { + o = symbols[Number(r)-1] + o; + } + n = (n-r) / 10n; + } + if(n) { + return '???'; + } + return o; + } +} + +function chinese_system(zero,units, powers_of_ten, powers_of_myriad) { + return (n => { + if(n==0n) { + return zero; + } + + let o = ''; + for(let m of powers_of_myriad) { + if(n % 10000n == 0n) { + n = n / 10000n; + continue; + } + o = m + o; + const u = n % 10n; + n = (n-u) / 10n; + o = (u > 1 || m=='' ? units[u] : '') + o; + for(let pow of powers_of_ten) { + if(n==0n) { + break; + } + const d = n % 10n; + n = (n-d) / 10n; + if(d > 0n) { + o = (d>1 ? units[d] : '') + pow + o; + } + } + } + if(n) { + return '???'; + } + return o; + }) +} + +const systems = { + cuneiform: { + name: 'Cuneiform', + fn: (n) => { + const tens = [ "", "๐’Œ‹", "๐’Ž™", "๐’Œ", "๐’", "๐’" ]; + const ones = [ "", "๐’•", "๐’–", "๐’—", "๐’˜", "๐’™", "๐’š", "๐’›", "๐’œ", "๐’" ]; + + let bits = []; + while(n) { + const u = n%10n; + n = (n-u)/10n; + const t = n % 6n; + n = (n-t)/6n; + bits.splice(0,0,tens[t] + ones[u]); + } + return bits.join('\u{3000}'); + } + }, + + arabic: positional_unicode_span('Eastern Arabic', 'ู '), + + roman: { + name: 'Roman', + fn: concatenative([ + ['โ… ', 'โ…ก', 'โ…ข', 'โ…ฃ', 'โ…ค', 'โ…ฅ', 'โ…ฆ', 'โ…ง', 'โ…จ'], + ['โ…ฉ', 'โ…ฉโ…ฉ', 'โ…ฉโ…ฉโ…ฉ', 'โ…ฉโ…ฌ', 'โ…ฌ', 'โ…ฌโ…ฉ', 'โ…ฌโ…ฉโ…ฉ', 'โ…ฌโ…ฉโ…ฉโ…ฉ', 'โ…ฉโ…ญ'], + ['โ…ญ', 'โ…ญโ…ญ', 'โ…ญโ…ญโ…ญ', 'โ…ญโ…ฎ', 'โ…ฎ', 'โ…ฎโ…ญ', 'โ…ฎโ…ญโ…ญ', 'โ…ฎโ…ญโ…ญโ…ญ', 'โ…ญโ…ฏ' ], + ['โ…ฏ', 'โ…ฏโ…ฏ', 'โ…ฏโ…ฏโ…ฏ'] + ]) + }, + + hieratic: { + name: 'Egyptian hieroglyphs', + fn: concatenative([ + ['', '๐“บ', '๐“ป', '๐“ผ', '๐“ฝ', '๐“พ', '๐“ฟ', '๐“€', '๐“', '๐“‚'], + span('๐“ކ'), + span('๐“ข'), + span('๐“†ผ'), + span('๐“‚ญ'), + ['๐“†', '๐“†๐“†', '๐“†๐“†๐“†', '๐“†๐“†๐“†๐“†', '๐“†๐“†๐“†๐“†๐“†', '๐“†๐“†๐“†๐“†๐“†๐“†', '๐“†๐“†๐“†๐“†๐“†๐“†๐“†', '๐“†๐“†๐“†๐“†๐“†๐“†๐“†๐“†', '๐“†๐“†๐“†๐“†๐“†๐“†๐“†๐“†๐“†'], + ['๐“จ', '๐“จ๐“จ', '๐“จ๐“จ๐“จ', '๐“จ๐“จ๐“จ๐“จ', '๐“จ๐“จ๐“จ๐“จ๐“จ', '๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ', '๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ', '๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ', '๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ๐“จ'] + ]) + }, + + aegean: { + name: 'Aegean', + fn: concatenative([ + span('๐„‡'), + span('๐„'), + span('๐„™'), + span('๐„ข'), + span('๐„ซ'), + + ]) + }, + + hebrew: { + name: 'Hebrew', + fn: (n) => { + if(n >= 10000n) { + return '???'; + } + const ones = [ "ื", "ื‘", "ื’", "ื“", "ื”", "ื•", "ื–", "ื—", "ื˜" ]; + const tens = [ "ื™", "ืš", "ืœ", "ื", "ืŸ", "ืก", "ืข", "ืฃ", "ืฅ" ]; + const hundreds = [ "ืง", "ืจ", "ืฉ", "ืช", "ืชืง", "ืชืจ", "ืชืฉ", "ืชืชืง", "ืชืชืจ" ]; + const thousands = [`ืืณ`,`ื‘ืณ`,`ืื‘ืณ`,`ื‘ื‘ืณ`,`ื”ืณ`,`ืื”ืณ`,`ื‘ื”ืณ`,`ืื”ืณ`,`ื‘ื‘ื”ืณ`]; + if(n==0n) { + return ''; + } + + let o = ''; + for(let symbols of [ones,tens,hundreds,thousands]) { + console.log(symbols, n); + if(n==0n) { + break; + } + const d = n % 10n; + n = (n-d) / 10n; + if(d > 0n) { + o = symbols[Number(d)-1] + o; + } + } + return o; + } + }, + + greek: { + name: 'Greek', + fn: concatenative([ + ["ฮ‘สน", "ฮ’สน", "ฮ“สน", "ฮ”สน", "ฮ•สน", "ฯšสน", "ฮ–สน", "ฮ—สน", "ฮ˜สน" ], + ["ฮ™สน", "ฮšสน", "ฮ›สน", "ฮœสน", "ฮสน", "ฮžสน", "ฮŸสน", "ฮ สน", "ฯžสน"], + [ "ฮกสน", "ฮฃสน", "ฮคสน", "ฮฅสน", "ฮฆสน", "ฮงสน", "ฮจสน", "ฮฉสน", "ฯ สน" ], + [ "อตฮ‘", "อตฮ’", ",ฮ“", "อตฮ”", "อตฮ•", "อตฯšอตฮฃฮค", "อตฮ–", "อตฮ—", "อตฮ˜" ], + [ "อตฮ™", "อตฮš", "อตฮ›", "อตฮœ", "อตฮ", "อตฮž", "อตฮŸ", "อตฮ ", "อตฯž" ], + [ "อตฮก", "อตฮฃ", "อตฮค", "อตฮฅ", "อตฮฆ", "อตฮง", "อตฮจ", "อตฮฉ", "อตฯ " ] + ]) + }, + + chinese: { + name: 'Chinese', + fn: chinese_system( + 'ใ€‡', + [ '', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ', 'ไธƒ', 'ๅ…ซ', 'ไน' ], + [ 'ๅ', '็™พ', 'ๅƒ' ], + [ '', 'ไธ‡', 'ไบฟ', 'ๅ…†', 'ไบฌ', 'ๅž“', '็งญ', '็ฉฐ', 'ๆฒŸ', 'ๆถง', 'ๆญฃ', '่ฝฝ' ] + ) + }, + + counting_rod_horizontal: positional_symbols('Counting rod (horizontal)', ['ใ€‡'].concat(span('๐ ',9))), + + counting_rod_vertical: positional_symbols('Counting rod (vertical)', ['ใ€‡'].concat(span('๐ฉ',9)), 10n, {orientation: 'vertical-lr'}), + + suzhou: { + name: 'Suzhou', + fn: (n) => { + const zero = 'ใ€‡'; + + if(n==0n) { + return zero; + } + + let o = ''; + while(n) { + const d = n % 10n; + n = (n - d) / 10n; + o = (d==0n ? zero : String.fromCharCode(0x3020 + Number(d))) + o; + } + + return o; + } + }, + + ethiopic: { + name: 'Ethiopic', + fn: concatenative([ + span('แฉ',9), + span('แฒ',9), + span('แฉ',9).map(x => x +'แป'), + span('แฒ',9).map(x => x +'แป'), + span('แฉ',9).map(x => x +'แผ'), + span('แฒ',9).map(x => x +'แผ'), + ]) + }, + + kharosthi: { + name: 'Kharosthi', + fn: (n) => { + const ones = [ "๐ฉ€", "๐ฉ", "๐ฉ‚", "๐ฉƒ", "๐ฉƒ๐ฉ€", "๐ฉƒ๐ฉ", "๐ฉƒ๐ฉ‚", "๐ฉƒ๐ฉƒ", "๐ฉƒ๐ฉƒ๐ฉ€", "๐ฉƒ๐ฉƒ๐ฉ" ]; + const tens = ["๐ฉ„","๐ฉ…","๐ฉ…๐ฉ„","๐ฉ…๐ฉ…","๐ฉ…๐ฉ…๐ฉ„","๐ฉ…๐ฉ…๐ฉ…","๐ฉ…๐ฉ…๐ฉ…๐ฉ„","๐ฉ…๐ฉ…๐ฉ…๐ฉ…","๐ฉ…๐ฉ…๐ฉ…๐ฉ…๐ฉ„"]; + if(n == 0) { + return ''; + } + let o = ''; + const u = n % 10n; + n = (n - u) / 10n; + o = (u > 0 ? ones[u - 1n] : '') + o; + const t = n % 10n; + n = (n - t) / 10n; + o = (t > 0 ? tens[t - 1n] : '') + o; + const h = n % 10n; + n = (n - h) / 10n; + o = (h > 0 ? (h > 1 ? ones[h - 1n] : '') + "๐ฉ†" : '') + o; + const th = n % 10n; + n = (n - th) / 10n; + o = (th > 0 ? (th > 1 ? ones[th - 1n] : '') + "๐ฉ‡" : '') + o; + + return o; + } + }, + + phoenician: { + name: 'Phoenician', + fn: concatenative([ + ["๐ค–","๐คš","๐ค›","๐ค›๐ค–","๐ค›๐คš","๐ค›๐ค›","๐ค›๐ค›๐ค–","๐ค›๐ค›๐คš","๐ค›๐ค›๐ค›",], + ["๐ค—","๐ค˜","๐ค˜๐ค—","๐ค˜๐ค˜","๐ค˜๐ค˜๐ค—","๐ค˜๐ค˜๐ค˜","๐ค˜๐ค˜๐ค˜๐ค—","๐ค˜๐ค˜๐ค˜๐ค˜","๐ค˜๐ค˜๐ค˜๐ค˜๐ค—"], + ["๐ค™","๐คš๐ค™","๐ค›๐ค™","๐ค›๐ค–๐ค™","๐ค›๐คš๐ค™","๐ค›๐ค›๐ค™","๐ค›๐ค›๐ค–๐ค™","๐ค›๐ค›๐คš๐ค™","๐ค›๐ค›๐ค›๐ค™"], + ["๐ค™","๐ค˜๐ค™","๐ค˜๐ค—๐ค™","๐ค˜๐ค˜๐ค™","๐ค˜๐ค˜๐ค—๐ค™","๐ค˜๐ค˜๐ค˜๐ค™","๐ค˜๐ค˜๐ค˜๐ค—๐ค™","๐ค˜๐ค˜๐ค˜๐ค˜๐ค™","๐ค˜๐ค˜๐ค˜๐ค˜๐ค—๐ค™"] + ]) + }, + + armenian: { + name: 'Armenian', + fn: concatenative([ + ['ิฑ','ิฒ','ิณ','ิด','ิต','ิถ','ิท','ิธ','ิน'], + ['ิบ','ิป','ิผ','ิฝ','ิพ','ิฟ','ี€','ี','ี‚'], + ['ีƒ', 'ี„', 'ี…', 'ี†', 'ี‡', 'ีˆ', 'ี‰', 'ีŠ', 'ี‹'], + ['ีŒ', 'ี', 'ีŽ', 'ี', 'ี', 'ี‘', 'ี’', 'ี“', 'ี”'], + ['ี•', 'ี–'] + ]) + }, + + abjad: { + name: 'Arabic abjad', + fn: concatenative([ + ["ุง","ุจ","ุฌู€","ุฏ","ู‡ู€","ูˆ","ุฒ","ุญู€","ุท"], + ["ู‰","ูƒ","ู„","ู…ู€","ู†","ุณ","ุน","ู","ุต"], + ["ู‚","ุฑ","ุด","ุช","ุซ","ุฎู€","ุฐ","ุถ","ุธ"] + ]) + }, + + glagolitic: { + name: 'Glagolitic', + fn: concatenative([ + span('โฐ€',9), + span('โฐ‰',9), + span('โฐ“',9), + span('โฐ',9), + ]) + }, + + cyrillic: { + name: 'Cyrillic', + fn: (() => { + const units = ["ะ","ะ’","ะ“","ะ”","ะ„","ะ…","ะ—","ะ˜","ัฒ"]; + const tens = ["ะ†","ะš","ะ›","ะœ","ะ","ัฎ","ับ","ะŸ","ะง"]; + const hundreds = ["ะ ","ะก","ะข","ะฃ","ะค","ะฅ","ัฐ","ั ","ะฆ"]; + return concatenative([ + units, + tens, + hundreds, + units.map(x => x+'า‚'), + units.map(x => x+"\u200d\u20dd "), + units.map(x => x+'\u200d\u0488 '), + units.map(x => x+'\u200d\u0489 '), + units.map(x => x+'\u200d\ua670 '), + units.map(x => x+'\u200d\ua671 '), + units.map(x => x+'\u200d\ua672 '), + ]) + })() + }, + + tangut: { + name: 'Tangut', + fn: chinese_system( + '', + ['',"๐˜ˆฉ","๐—ซ","๐˜••","๐—ฅƒ","๐—","๐—ค","๐—’น","๐˜‰‹","๐—ขญ"], + ['๐—ฐ—', '๐˜Š', '๐—กž'], + ['', '๐—•‘', '๐—ฆฒ'] + ) + }, + + hangul: { + name: 'Hangul', + fn: chinese_system( + '์˜', + ['',"์ผ","์ด","์‚ผ","์‚ฌ","์˜ค","์œก, ๋ฅ™","์น ","ํŒ”","๊ตฌ"], + ['์‹ญ','๋ฐฑ','์ฒœ'], + ['','๋งŒ','์–ต','์กฐ','๊ฒฝ','ํ•ด','์ž','์–‘','๊ตฌ','๊ฐ„','์ •','์žฌ','๊ทน','ํ•ญํ•˜์‚ฌ','์•„์Šน๊ธฐ','๋‚˜์œ ํƒ€','๋ถˆ๊ฐ€์‚ฌ์˜','๋ฌด๋Ÿ‰๋Œ€์ˆ˜'] + ) + }, + + // TODO: Cistercian (needs SVG?) + + // TODO: Pentadic numerals (needs SVG?) + + rumi: positional_unicode_span('Rumi', '๐น '), + + mayan: positional_unicode_span('Mayan', '๐‹ ', 20n, {orientation: 'vertical-lr'}), + + nko: positional_unicode_span('N\'Ko', '฿€'), + + devanagari: positional_unicode_span('Devanagari', 'เฅฆ'), + + bengali: positional_unicode_span('Bengali', 'เงฆ'), + + gurmukhi: positional_unicode_span('Gurmukhi','เฉฆ'), + + gujarati: positional_unicode_span('Gujarati', 'เซฆ'), + + odia: positional_unicode_span('Odia', 'เญฆ'), + + tamil: positional_unicode_span('Tamil', 'เฏฆ'), + + telugu: positional_unicode_span('Telugu', 'เฑฆ'), + + kannada: positional_unicode_span('Kannada', 'เณฆ'), + + malayalam: positional_unicode_span('Malayalam', 'เตฆ'), + + thai: positional_unicode_span('Thai', 'เน'), + + lao: positional_unicode_span('Lao', 'เป'), + + tibetan: positional_unicode_span('Tibetan', 'เผ '), + + burmese: positional_unicode_span('Burmese', 'แ€'), + + khmer: positional_unicode_span('Khmer', 'แŸ '), + + mongolian: positional_unicode_span('Mongolian', 'แ '), + + limbu: positional_unicode_span('Limbu', 'แฅ†'), + + new_tai_lue: positional_unicode_span('New Tai Lue', 'แง'), + + lek_hora: positional_unicode_span('Lek Hora','แช€'), + + lek_nai_tham: positional_unicode_span('Lek Nai Tham', 'แช'), + + balinese: positional_unicode_span('Balinese', 'แญ'), + + sundanese: positional_unicode_span('Sundanese', 'แฎฐ'), + + lepcha: positional_unicode_span('Lepcha', 'แฑ€'), + + ol_chiki: positional_unicode_span('Ol Chiki', 'แฑ'), + + vai: positional_unicode_span('Vai', '๊˜ '), + + saurashtra: positional_unicode_span('Saurashtra', '๊ฃ'), + + kayah_li: positional_unicode_span('Kayah Li', '๊ค€'), + + javanese: positional_unicode_span('Javanese', '๊ง'), + + cham: positional_unicode_span('Cham', '๊ฉ'), + + meitei: positional_unicode_span('meitei', '๊ฏฐ'), + + osmanya: positional_unicode_span('Osmanya', '๐’ '), + + brahmi: positional_unicode_span('Brahmi', '๐‘ฆ'), + + sorang_sompeng: positional_unicode_span('Sorang Sompeng', '๐‘ƒฐ'), + + chakma: positional_unicode_span('Chakma', '๐‘„ถ'), + + sharada: positional_unicode_span('Sharada', '๐‘‡'), + + takri: positional_unicode_span('Takri', '๐‘›€'), + + sinhala: positional_unicode_span('Sinhala', 'เทฆ'), + + tai_laing: positional_unicode_span('Tai Laing', '๊งฐ'), + + khudabadi: positional_unicode_span('Khudabadi', '๐‘‹ฐ'), + + tirhuta: positional_unicode_span('Tirhuta', '๐‘“'), + + modi: positional_unicode_span('Modi', '๐‘™'), + + warang_citi: positional_unicode_span('Warang Citi', '๐‘ฃ '), + + mro: positional_unicode_span('Mro', '๐–ฉ '), + + pahawh_hmong: positional_unicode_span('Pahawh Hmong', '๐–ญ'), + + ahom: positional_unicode_span('Tai Ahom', '๐‘œฐ'), + + prachalit: positional_unicode_span('Prachalit', '๐‘‘'), + + hanifi_rohingya: positional_unicode_span('Hanifi Rohingya', '๐ดฐ'), + + bhaiksuki: positional_unicode_span('Bhaiksuki', '๐‘ฑ'), + + masaram_gondi: positional_unicode_span('Masaram Gondi', '๐‘ต'), + + gunjala_gondi: positional_unicode_span('Gunjala Gondi', '๐‘ถ '), + + medefaidrin: positional_unicode_span('Medefaidrin', '๐–บ€', 20n), + + nyiakeng_puachue_hmong: positional_unicode_span('Nyiakeng Puachue Hmong', '๐ž…€'), + + wancho: positional_unicode_span('Wancho', '๐ž‹ฐ'), + + adlam: positional_unicode_span('Adlam', '๐žฅ'), + + kaktovik: positional_unicode_span('Kaktovik', '๐‹€', 20n), + + // garay: positional_unicode_span('Garay', '๐ต€'), //(not in noto??) + + binary: { + name: 'Binary', + fn: (n) => n.toString(2), + }, + + octal: { + name: 'Octal', + fn: (n) => n.toString(8), + }, + + hexadecimal: { + name: 'Hexadecimal', + fn: (n) => n.toString(16), + }, + + balanced_ternary: { + name: 'Balanced ternary', + fn: (n) => { + let o = ''; + while(n) { + const r = n % 3n; + n = (n - r) / 3n; + if(r == 2) { + n += 1n; + } + o = ['0','+','-'][r] + o; + } + return o; + } + } +} + +const dl = document.getElementById('systems'); + +Object.entries(systems).forEach(([id, entry]) => { + const {name, orientation} = entry; + const dt = document.createElement('dt'); + dt.textContent = name; + dl.append(dt); + const dd = document.createElement('dd'); + if(orientation) { + dd.style['writing-mode'] = orientation; + } + dl.append(dd); + entry.dd = dd; +}) + +function update() { + const n = BigInt(decimal_input.value || '0'); + Object.values(systems).forEach(({name,fn,dd}) => { + console.log(name); + dd.textContent = fn(n); + }); +} + +const decimal_input = document.getElementById('decimal'); + +update(); + +decimal_input.addEventListener('input', update); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..cb3f558 --- /dev/null +++ b/style.css @@ -0,0 +1,40 @@ +@import 'https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols+2&family=Noto+Sans+Symbols:wght@100..900&display=swap'; + +:root { + --spacing: 1em; + + color-scheme: light dark; +} + +body { + font-family: sans-serif, 'Noto Sans Symbols', 'Noto Sans Symbols 2'; + background: Canvas; +} + +dl { + display: grid; + grid-template-columns: repeat(auto-fit, 7em 10em); + align-items: center; + justify-content: center; + gap: 1em 0.5em; +} +dt, label { + text-align: end; + font-weight: bold; +} +dd { + margin: 0; + word-break: break-all; +} + +footer { + margin-top: 20svh; +} + +#decimal-container { + position: sticky; + top: 0; + background: Canvas; + text-align: center; + padding: var(--spacing); +} \ No newline at end of file