commit 5932e3061f68b1ec22385097531f909e3d894996
parent 6e0eecba61a371a44891972b2c4750ab79ecd792
Author: brookjeynes <me@brookjeynes.dev>
Date: Sun, 31 May 2026 12:26:02 +1000
feat: add native korean number lookups
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
| A | src/native.js | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| D | src/number.js | | | 105 | ------------------------------------------------------------------------------- |
| A | src/sino.js | | | 105 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/store.js | | | 7 | ++++--- |
4 files changed, 172 insertions(+), 108 deletions(-)
diff --git a/src/native.js b/src/native.js
@@ -0,0 +1,63 @@
+/**
+ * @param {number} num
+ * @returns {string}
+ */
+export function nativeStringFromNumber(num) {
+ if (num <= 0) throw new Error("not a valid native korean number");
+ if (num > 99) throw new Error("number cannot be larger than 99")
+
+ const ONES = ["", "하나", "둘", "셋", "넷", "다섯", "여섯", "일곱", "여덟", "아홉"];
+ const TENS = ["", "열", "스물", "서른", "마흔", "쉰", "예순", "일흔", "여든", "아흔"];
+
+ const groups = [];
+ let temp = num;
+ while (temp > 0) {
+ groups.push(temp % 10);
+ temp = Math.floor(temp / 10);
+ }
+
+ const ones = ONES[groups[0]];
+
+ if (groups.length === 1) {
+ return ones;
+ }
+
+ const tens = TENS[groups[1]];
+ return tens + ones;
+}
+
+/**
+ * @param {string} str
+ * @returns {number}
+ */
+export function numberFromNativeString(str) {
+ if (str.length === 0) throw new Error("string cannot be of zero length");
+ if (str.length > 4) throw new Error("string cannot be greater than 4 characters");
+
+ const ONES = { "하나": 1, "둘": 2, "셋": 3, "넷": 4, "다섯": 5, "여섯": 6, "일곱": 7, "여덟": 8, "아홉": 9 };
+ const TENS = { "열": 10, "스물": 20, "서른": 30, "마흔": 40, "쉰": 50, "예순": 60, "일흔": 70, "여든": 80, "아흔": 90 };
+
+ /** @type{number} */
+ let result = 0;
+ /** @type{string} */
+ let temp = str;
+
+ for (const ten of Object.keys(TENS)) {
+ if (temp.startsWith(ten)) {
+ result += TENS[ten];
+ temp = temp.slice(ten.length);
+ break;
+ }
+ }
+ for (const one of Object.keys(ONES)) {
+ if (temp.startsWith(one)) {
+ result += ONES[one];
+ temp = temp.slice(one.length);
+ break;
+ }
+ }
+
+ if (temp.length !== 0) throw new Error("invalid input");
+
+ return result;
+}
diff --git a/src/number.js b/src/number.js
@@ -1,105 +0,0 @@
-/**
- * @param {number} num
- * @returns {string}
- */
-export function hangeulStringFromNumber(num) {
- const UNITS = ["", "일", "이", "삼", "사", "오", "육", "칠", "팔", "구"];
- const SMALL = ["", "십", "백", "천"];
- const LARGE = ["", "만", "억", "조"];
-
- if (num === 0) return "영";
-
- /**
- * Holds the number split into groups of four ordered by least significant
- * group.
- * @type {number[]}
- */
- const groups = [];
- /** @type {number} */
- let temp = num;
- while (temp > 0) {
- groups.push(temp % 1_0000);
- temp = Math.floor(temp / 1_0000)
- }
-
- /** @type {string} */
- let result = [];
- for (let g = groups.length - 1; g >= 0; g--) {
- /** @type {number} */
- const group = groups[g];
- if (group === 0) continue;
-
- /** @type {number[]} */
- const digits = [
- Math.floor(group / 1000), // 천
- Math.floor((group % 1000) / 100), // 백
- Math.floor((group % 100) / 10), // 십
- group % 10, // 일
- ]
-
- /** @type {string} */
- let groupStr = "";
- for (let d = 0; d < 4; d++) {
- /** @type {number} */
- const digit = digits[d];
- if (digit === 0) continue;
-
- /**
- * Drop 일 before 십/백/천, but keep it before 만/억/조
- * @type {string}
- */
- const prefix = (digit === 1 && d > 0 && d < 3) ? "" : UNITS[digit];
- groupStr += prefix + SMALL[3 - d];
- }
-
- result.push(groupStr + LARGE[g]);
- }
-
- return result.join(" ");
-}
-
-/**
- * @param {string} str
- * @returns {number}
- */
-export function numberFromHangeulString(str) {
- const UNITS = { "일": 1, "이": 2, "삼": 3, "사": 4, "오": 5, "육": 6, "칠": 7, "팔": 8, "구": 9 };
- const SMALL = { "십": 10, "백": 100, "천": 1000 };
- const LARGE = { "만": 10000, "억": 100000000, "조": 1000000000000 };
-
- if (str.length === 0) throw new Error("string cannot be of zero length");
- if (str === "영") return 0;
-
- /** @type {number} */
- let result = 0;
- /**
- * Accumulates value within current large-unit group.
- * @type {number}
- */
- let currentGroup = 0;
- /**
- * Defaults to 1 to handle omitted 일 (e.g. "십" = 10).
- * @type {number}
- */
- let currentDigit = 1;
-
- for (const char of str) {
- if (UNITS[char] !== undefined) {
- currentDigit = UNITS[char];
- } else if (SMALL[char] !== undefined) {
- currentGroup += currentDigit * SMALL[char];
- // reset for next digit (restores 일-omission default)
- currentDigit = 1;
- } else if (LARGE[char] !== undefined) {
- // flush any trailing 일-place digit into group, then apply large unit
- result += (currentGroup + currentDigit) * LARGE[char];
- currentGroup = 0;
- currentDigit = 1;
- }
- }
-
- // flush the final group (no large unit suffix)
- result += currentGroup + currentDigit;
-
- return result;
-}
diff --git a/src/sino.js b/src/sino.js
@@ -0,0 +1,105 @@
+/**
+ * @param {number} num
+ * @returns {string}
+ */
+export function sinoStringFromNumber(num) {
+ const UNITS = ["", "일", "이", "삼", "사", "오", "육", "칠", "팔", "구"];
+ const SMALL = ["", "십", "백", "천"];
+ const LARGE = ["", "만", "억", "조"];
+
+ if (num === 0) return "영";
+
+ /**
+ * Holds the number split into groups of four ordered by least significant
+ * group.
+ * @type {number[]}
+ */
+ const groups = [];
+ /** @type {number} */
+ let temp = num;
+ while (temp > 0) {
+ groups.push(temp % 1_0000);
+ temp = Math.floor(temp / 1_0000)
+ }
+
+ /** @type {string} */
+ let result = [];
+ for (let g = groups.length - 1; g >= 0; g--) {
+ /** @type {number} */
+ const group = groups[g];
+ if (group === 0) continue;
+
+ /** @type {number[]} */
+ const digits = [
+ Math.floor(group / 1000), // 천
+ Math.floor((group % 1000) / 100), // 백
+ Math.floor((group % 100) / 10), // 십
+ group % 10, // 일
+ ]
+
+ /** @type {string} */
+ let groupStr = "";
+ for (let d = 0; d < 4; d++) {
+ /** @type {number} */
+ const digit = digits[d];
+ if (digit === 0) continue;
+
+ /**
+ * Drop 일 before 십/백/천, but keep it before 만/억/조
+ * @type {string}
+ */
+ const prefix = (digit === 1 && d > 0 && d < 3) ? "" : UNITS[digit];
+ groupStr += prefix + SMALL[3 - d];
+ }
+
+ result.push(groupStr + LARGE[g]);
+ }
+
+ return result.join(" ");
+}
+
+/**
+ * @param {string} str
+ * @returns {number}
+ */
+export function numberFromSinoString(str) {
+ const UNITS = { "일": 1, "이": 2, "삼": 3, "사": 4, "오": 5, "육": 6, "칠": 7, "팔": 8, "구": 9 };
+ const SMALL = { "십": 10, "백": 100, "천": 1000 };
+ const LARGE = { "만": 10000, "억": 100000000, "조": 1000000000000 };
+
+ if (str.length === 0) throw new Error("string cannot be of zero length");
+ if (str === "영") return 0;
+
+ /** @type {number} */
+ let result = 0;
+ /**
+ * Accumulates value within current large-unit group.
+ * @type {number}
+ */
+ let currentGroup = 0;
+ /**
+ * Defaults to 1 to handle omitted 일 (e.g. "십" = 10).
+ * @type {number}
+ */
+ let currentDigit = 1;
+
+ for (const char of str) {
+ if (UNITS[char] !== undefined) {
+ currentDigit = UNITS[char];
+ } else if (SMALL[char] !== undefined) {
+ currentGroup += currentDigit * SMALL[char];
+ // reset for next digit (restores 일-omission default)
+ currentDigit = 1;
+ } else if (LARGE[char] !== undefined) {
+ // flush any trailing 일-place digit into group, then apply large unit
+ result += (currentGroup + currentDigit) * LARGE[char];
+ currentGroup = 0;
+ currentDigit = 1;
+ }
+ }
+
+ // flush the final group (no large unit suffix)
+ result += currentGroup + currentDigit;
+
+ return result;
+}
diff --git a/src/store.js b/src/store.js
@@ -1,4 +1,5 @@
-import { hangeulStringFromNumber, numberFromHangeulString } from "./number.js";
+import { nativeStringFromNumber, numberFromNativeString } from "./native.js";
+import { sinoStringFromNumber, numberFromSinoString } from "./sino.js";
/**
* @typedef {
@@ -73,14 +74,14 @@ window.Alpine.store("app", {
/** @returns {string} */
get string() {
- return hangeulStringFromNumber(this.number);
+ return sinoStringFromNumber(this.number);
},
/** @returns {string} */
get answer() {
switch (this.mode) {
case "number":
- return hangeulStringFromNumber(this.number);
+ return sinoStringFromNumber(this.number);
case "hangeul":
return this.number.toString();
}