Gets the name of a number, even if it's stupidly big. Supersedes TheoryOfNekomata/number-name.
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 

270 linhas
7.1 KiB

  1. import { Group, GROUP_DIGITS_INDEX, GROUP_PLACE_INDEX } from '../../common';
  2. import { numberToExponential } from '../../exponent';
  3. import {
  4. CENTILLIONS_PREFIXES,
  5. CentillionsPrefix,
  6. DECILLIONS_PREFIXES,
  7. DecillionsPrefix,
  8. DECIMAL_POINT,
  9. EMPTY_GROUP_DIGITS,
  10. EXPONENT_DELIMITER,
  11. GROUPING_SYMBOL,
  12. HUNDRED,
  13. ILLION_SUFFIX,
  14. MILLIA_PREFIX,
  15. MILLIONS_PREFIXES,
  16. MILLIONS_SPECIAL_PREFIXES,
  17. MillionsPrefix,
  18. MillionsSpecialPrefix, NEGATIVE,
  19. ONES,
  20. OnesName,
  21. SHORT_MILLIA_DELIMITER,
  22. TEN_PLUS_ONES,
  23. TenPlusOnesName,
  24. TENS,
  25. TENS_ONES_SEPARATOR,
  26. TensName,
  27. THOUSAND,
  28. } from './common';
  29. /**
  30. * Builds a name for numbers in tens and ones.
  31. * @param tens - Tens digit.
  32. * @param ones - Ones digit.
  33. * @returns string The name for the number.
  34. */
  35. const makeTensName = (tens: number, ones: number) => {
  36. if (tens === 0) {
  37. return ONES[ones];
  38. }
  39. if (tens === 1) {
  40. return TEN_PLUS_ONES[ones] as unknown as TenPlusOnesName;
  41. }
  42. if (ones === 0) {
  43. return TENS[tens];
  44. }
  45. return `${TENS[tens] as Exclude<TensName, 'zero' | 'ten'>}${TENS_ONES_SEPARATOR}${ONES[ones] as Exclude<OnesName, 'zero'>}` as const;
  46. };
  47. /**
  48. * Builds a name for numbers in hundreds, tens, and ones.
  49. * @param hundreds - Hundreds digit.
  50. * @param tens - Tens digit.
  51. * @param ones - Ones digit.
  52. * @returns string The name for the number.
  53. */
  54. const makeHundredsName = (hundreds: number, tens: number, ones: number) => {
  55. if (hundreds === 0) {
  56. return makeTensName(tens, ones);
  57. }
  58. if (tens === 0 && ones === 0) {
  59. return `${ONES[hundreds]} ${HUNDRED}` as const;
  60. }
  61. return `${ONES[hundreds]} ${HUNDRED} ${makeTensName(tens, ones)}` as const;
  62. };
  63. /**
  64. * Builds a name for numbers in the millions.
  65. * @param millions - Millions digit.
  66. * @param milliaCount - Number of millia- groups.
  67. * @returns string The millions prefix.
  68. */
  69. const makeMillionsPrefix = (millions: number, milliaCount: number) => {
  70. if (milliaCount > 0) {
  71. return MILLIONS_PREFIXES[millions] as MillionsPrefix;
  72. }
  73. return MILLIONS_SPECIAL_PREFIXES[millions] as MillionsSpecialPrefix;
  74. };
  75. /**
  76. * Builds a name for numbers in the decillions.
  77. * @param decillions - Decillions digit.
  78. * @param millions - Millions digit.
  79. * @param milliaCount - Number of millia- groups.
  80. * @returns string The decillions prefix.
  81. */
  82. const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: number) => {
  83. if (decillions === 0) {
  84. return makeMillionsPrefix(millions, milliaCount);
  85. }
  86. const onesPrefix = MILLIONS_PREFIXES[millions] as MillionsPrefix;
  87. const tensName = DECILLIONS_PREFIXES[decillions] as DecillionsPrefix;
  88. return `${onesPrefix}${tensName}` as const;
  89. };
  90. /**
  91. * Builds a name for numbers in the centillions.
  92. * @param centillions - Centillions digit.
  93. * @param decillions - Decillions digit.
  94. * @param millions - Millions digit.
  95. * @param milliaCount - Number of millia- groups.
  96. * @returns string The centillions prefix.
  97. */
  98. const makeCentillionsPrefix = (
  99. centillions: number,
  100. decillions: number,
  101. millions: number,
  102. milliaCount: number,
  103. ) => {
  104. if (centillions === 0) {
  105. return makeDecillionsPrefix(decillions, millions, milliaCount);
  106. }
  107. const onesPrefix = MILLIONS_PREFIXES[millions] as MillionsPrefix;
  108. const tensName = DECILLIONS_PREFIXES[decillions] as DecillionsPrefix;
  109. const hundredsName = CENTILLIONS_PREFIXES[centillions] as CentillionsPrefix;
  110. return `${hundredsName}${onesPrefix}${decillions > 0 ? tensName : ''}` as const;
  111. };
  112. const getGroupName = (place: number, shortenMillia: boolean) => {
  113. if (place === 0) {
  114. return '' as const;
  115. }
  116. if (place === 1) {
  117. return THOUSAND;
  118. }
  119. const bigGroupPlace = place - 1;
  120. const groupGroups = bigGroupPlace
  121. .toString()
  122. .split('')
  123. .reduceRight<Group[]>(
  124. (acc, c, i, cc) => {
  125. const firstGroup = acc.at(0);
  126. const currentPlace = Math.floor((cc.length - i - 1) / 3);
  127. const newGroup = [EMPTY_GROUP_DIGITS, currentPlace] as Group;
  128. if (typeof firstGroup === 'undefined') {
  129. newGroup[GROUP_DIGITS_INDEX] = c;
  130. return [newGroup];
  131. }
  132. if (firstGroup[0].length > 2) {
  133. newGroup[GROUP_DIGITS_INDEX] = c;
  134. return [newGroup, ...acc];
  135. }
  136. newGroup[GROUP_DIGITS_INDEX] = c + firstGroup[0];
  137. return [
  138. newGroup,
  139. ...acc.slice(1),
  140. ];
  141. },
  142. [],
  143. )
  144. .map(([groupDigits, groupPlace]) => [groupDigits.padStart(3, '0'), groupPlace] as const)
  145. .filter(([groupDigits]) => groupDigits !== EMPTY_GROUP_DIGITS)
  146. .map(([groupDigits, groupPlace]) => {
  147. const [hundreds, tens, ones] = groupDigits.split('').map(Number);
  148. if (groupPlace < 1) {
  149. return makeCentillionsPrefix(hundreds, tens, ones, groupPlace);
  150. }
  151. const milliaSuffix = (
  152. shortenMillia && groupPlace > 1
  153. ? `${MILLIA_PREFIX}${SHORT_MILLIA_DELIMITER}${groupPlace}`
  154. : MILLIA_PREFIX.repeat(groupPlace)
  155. );
  156. if (groupDigits === '001') {
  157. return milliaSuffix;
  158. }
  159. return makeCentillionsPrefix(hundreds, tens, ones, groupPlace) + milliaSuffix;
  160. })
  161. .join('');
  162. if (groupGroups.endsWith(DECILLIONS_PREFIXES[1])) {
  163. return `${groupGroups}${ILLION_SUFFIX}` as const;
  164. }
  165. if (bigGroupPlace > 10) {
  166. return `${groupGroups}t${ILLION_SUFFIX}` as const;
  167. }
  168. return `${groupGroups}${ILLION_SUFFIX}` as const;
  169. };
  170. export const makeGroups = (groups: Group[], options?: Record<string, unknown>): string[] => {
  171. const filteredGroups = groups.filter(([digits, place]) => (
  172. place === 0 || digits !== EMPTY_GROUP_DIGITS
  173. ));
  174. return filteredGroups.map(
  175. ([group, place]) => {
  176. const makeHundredsArgs = group
  177. .padStart(3, '0')
  178. .split('')
  179. .map((s) => Number(s)) as [number, number, number];
  180. const groupDigitsName = makeHundredsName(...makeHundredsArgs);
  181. const groupName = getGroupName(place, options?.shortenMillia as boolean ?? false);
  182. if (groupName.length > 0) {
  183. return `${groupDigitsName} ${groupName}`;
  184. }
  185. return groupDigitsName;
  186. },
  187. );
  188. };
  189. /**
  190. * Group a number string into groups of three digits, starting from the decimal point.
  191. * @param value - The number string to group.
  192. */
  193. export const group = (value: string): Group[] => {
  194. const [significand, exponentString] = numberToExponential(
  195. value,
  196. {
  197. decimalPoint: DECIMAL_POINT,
  198. groupingSymbol: GROUPING_SYMBOL,
  199. exponentDelimiter: EXPONENT_DELIMITER,
  200. },
  201. )
  202. .split(EXPONENT_DELIMITER);
  203. const exponent = Number(exponentString);
  204. const significantDigits = significand.replace(DECIMAL_POINT, '');
  205. return significantDigits.split('').reduce<Group[]>(
  206. (acc, c, i) => {
  207. const currentPlace = Math.floor((exponent - i) / 3);
  208. const lastGroup = acc.at(-1) ?? [EMPTY_GROUP_DIGITS, currentPlace];
  209. const currentPlaceInGroup = 2 - ((exponent - i) % 3);
  210. if (lastGroup[GROUP_PLACE_INDEX] === currentPlace) {
  211. const lastGroupDigits = lastGroup[0].split('');
  212. lastGroupDigits[currentPlaceInGroup] = c;
  213. return [...acc.slice(0, -1) ?? [], [
  214. lastGroupDigits.join(''),
  215. currentPlace,
  216. ]];
  217. }
  218. return [...acc, [c.padEnd(3, '0'), currentPlace]];
  219. },
  220. [],
  221. );
  222. };
  223. /**
  224. * Formats the final tokenized string.
  225. * @param tokens - The tokens to finalize.
  226. */
  227. export const finalize = (tokens: string[]) => (
  228. tokens
  229. .map((t) => t.trim())
  230. .join(' ')
  231. .trim()
  232. );
  233. /**
  234. * Makes a negative string.
  235. * @param s - The string to make negative.
  236. */
  237. export const makeNegative = (s: string) => (
  238. `${NEGATIVE} ${s}`
  239. );