# Numbers Source: https://pinyinjs.dev/numerals/ Index of every page: https://pinyinjs.dev/llms.txt PinyinJS converts numbers to Chinese numerals and pinyin without loading a dictionary. ```ts import { numeralHanzi, readNumeral } from "@kensio/pinyinjs"; numeralHanzi(12345); // "一万两千三百四十五" numeralHanzi(2026, { style: "digits" }); // "二〇二六" ``` ## Quantities and digit sequences A number can be read as a quantity or as individual digits: ```ts numeralHanzi(2026); // "两千零二十六", 2026个, a quantity numeralHanzi(2026, { style: "digits" }); // "二〇二六", 2026年, a year ``` Choose the style for standalone numbers. For example, a year is usually read digit by digit, while a count uses a quantity. The default style is `cardinal`. ## Counting The following rules determine where 零 and 十 appear: ```ts numeralHanzi(10); // "十", not 一十 numeralHanzi(115); // "一百一十五", but here the 一 stays numeralHanzi(1005); // "一千零五", a skipped place is spoken numeralHanzi(1500); // "一千五百", a trailing one is not numeralHanzi(25_000); // "两万五千" numeralHanzi(20_050); // "两万零五十", the lower group leaves a gap numeralHanzi(100_000_005); // "一亿零五" ``` Chinese groups large numbers by 万 (ten thousand). For example, 12,345 is one 万 plus 2,345. With the default setting, a lone 2 before 千, 万 or 亿 is written 两. Examples include 两千, 两万 and 一万两千. Other positions use 二, as in 十二, 二十, 二百 and 十二万. The `liang` option controls this choice. `leading` follows the dictionary prescription that excludes 两 in forms such as 三万二千. `always` also permits 两 there: ```ts numeralHanzi(12_000); // "一万两千", always, the default numeralHanzi(12_000, { liang: "leading" }); // "一万二千", the 词典's own rule numeralHanzi(12_000, { liang: "never" }); // "一万二千", and 二千 for 2,000 too ``` Set `counts` when a standalone 2 counts a following noun or measure word, as in 两个西瓜 or 两个人: ```ts numeralHanzi(2); // "二", a number counting nothing numeralHanzi(2, { counts: true }); // "两", 两个西瓜 numeralHanzi(12, { counts: true }); // "十二", the 二 of 十二个 is inside the number numeralHanzi(200, { counts: true }); // "二百", and so is the 二 of 二百个 ``` This changes only a standalone 2. `liang: "never"` keeps 二 even when `counts` is set. ## Spelling digits out ```ts numeralHanzi("007", { style: "digits" }); // "〇〇七", the zeros survive numeralHanzi("007"); // "七", counted, they do not numeralHanzi(2019, { style: "digits", zero: "零" }); // "二零一九" ``` Pass a string to preserve leading zeros, such as those in a room number. A JavaScript `number` cannot retain them. ## Reading ```ts import { writeSyllable } from "@kensio/pinyinjs"; const said = (value, options) => readNumeral(value, options) ?.map((s) => writeSyllable(s)) .join(" "); said(2026); // "liǎng qiān líng èr shí liù" said(2026, { style: "digits" }); // "èr líng èr liù" said(110, { style: "digits", yao: true }); // "yāo yāo líng" said("3.14"); // "sān diǎn yī sì" said(-40); // "fù sì shí" ``` The optional `yao` setting reads 一 as `yāo` in digit sequences, as in telephone numbers. It is off by default. Ordinary year readings use `yī`, as in 2019 (`èr líng yī jiǔ`). The returned syllables have underlying tones. For example, 一 is returned as `yī`. Apply [tone sandhi](https://pinyinjs.dev/sandhi/) when you need its contextual spoken tone: ```ts import { applySandhi } from "@kensio/pinyinjs"; applySandhi(readNumeral(100) ?? []); // yì bǎi ``` Apply sandhi to the integer part of a cardinal number. Individually spoken digits retain their underlying tones, including digits after a decimal point. For example, 一百 becomes `yìbǎi`, while 3.14 is `sān diǎn yī sì`. The `pinyinjs number` command applies this distinction. ## Percentages and fractions Chinese reads the denominator before the numerator. Use these functions for the correct order: ```ts percentHanzi(95); // "百分之九十五", "of a hundred parts, ninety-five" fractionHanzi(3, 4); // "四分之三", the denominator is named first ``` ## On the command line ```console $ pinyinjs number 2026 --digits 2026 二〇二六 èr líng èr liù $ pinyinjs number 95 --percent 95 百分之九十五 bǎi fēn zhī jiǔ shí wǔ ``` The command runs without a dictionary. See [the command line](https://pinyinjs.dev/cli/) for its options. ## Numbers inside text `convert` reads numbers within Chinese text and infers their style from the surrounding characters: ```ts convert(dictionary, "我有3个苹果。"); // "Wǒ yǒu sān gè píngguǒ." convert(dictionary, "1988年之后"); // "yī jiǔ bā bā nián zhīhòu" convert(dictionary, "95%的人"); // "bǎifēnzhījiǔshíwǔ de rén" convert(dictionary, "3D打印"); // "sān D dǎyìn" convert(dictionary, "我有3个", { numbers: "keep" }); // "wǒ yǒu3gè" ``` Style selection follows these rules: | The text | What happens | Why | | --------------------- | ---------------- | ------------------------------------------ | | four digits before 年 | spelled out | 1998年 is a year; 30年 is thirty years | | digits before % or % | 百分之, reversed | the sign is read, and read first | | anything else | counted | what almost every digit in running text is | A standalone 2 immediately before a Han character normally reads 两. This sets the `counts` behaviour described above: ```ts convert(dictionary, "我们买了2个西瓜"); // "wǒmen mǎile liǎng gè xīguā" convert(dictionary, "他2岁"); // "tā liǎng suì" convert(dictionary, "2点"); // "liǎng diǎn", the same 两 as 2:00 convert(dictionary, "2万人"); // "liǎng wàn rén", as 20,000 is 两万 ``` Exceptions use 二 when the digit labels a month, day, number, floor, route, class or period (月, 日, 号, 楼, 路, 班 and 期). Ordinals preceded by 第 also use 二. 十 and 百 keep the forms 二十 and 二百. ```ts convert(dictionary, "2月"); // "èr yuè", February convert(dictionary, "2号"); // "èr hào", the second of the month convert(dictionary, "第2次"); // "dì èr cì", an ordinal names a position convert(dictionary, "12个"); // "shí'èr gè", a 2 inside a larger number ``` A cardinal number is written as one pinyin word. For example, 25个 is `èrshíwǔ gè`. Digit sequences remain separated, as in 1998年 (`yī jiǔ jiǔ bā nián`). Sandhi crosses the word boundary, so 1个 becomes `yí gè`. A decimal joins the cardinal integer part into one word, then writes 点 and the fractional digits separately: ```ts convert(dictionary, "一共75.5元"); // "yígòng qīshíwǔ diǎn wǔ yuán" convert(dictionary, "3.14"); // "sān diǎn yī sì" ``` Digits touching `-`, `/` or their full-width equivalents are preserved. This includes labels such as 3202-5625 and COVID-19. Colon-separated times are read aloud: ```ts convert(dictionary, "6:30起床"); // "liù diǎn sānshí fēn qǐchuáng" convert(dictionary, "2:30"); // "liǎng diǎn sānshí fēn", two o'clock is 两 convert(dictionary, "12:00"); // "shí'èr diǎn", no 零零分 on the hour convert(dictionary, "16:9的"); // "16:9de", a ratio, and left alone ``` Time recognition requires exactly two digits after the colon. In the measured corpus, all 104 matching examples were times. Ratios and scores such as 16:9 and 2:1 remained unchanged. Time output includes 分 after the minutes to distinguish a time from a decimal. The hour, 点, minutes and 分 are separate words. ### Limitations of automatic number reading A four-digit number without 年 is read as a quantity. For example, `他生于1990。` does not automatically select year-style digits. Use the standalone number functions with an explicit style when the application knows that the number is a year or label. Currency expressions such as `$5` and `¥100` are preserved. The converter does not infer a currency name from the symbol. Dashed and slashed dates are preserved, and phone numbers do not automatically select `yāo`. Dates written with 年月日 use the normal number rules. For example, 三月 is `sān yuè` and 三十一日 is `sānshíyī rì`. ## Validation `pnpm numerals` compares numeral readings with CC-CEDICT. All 17 digit sequences with recorded readings match when the appropriate settings are supplied. Three emergency numbers require `yāo`. The source examples all spell out digits, so cardinal readings are tested separately with worked examples. End-to-end `convert` matches 7 of those 17 examples. The remaining ten require label-style digits, while automatic conversion selects a quantity. These include 110, 88, 996 and 95后. Context does not always distinguish a quantity from a label.