Gets the name of a number, even if it's stupidly big. Supersedes TheoryOfNekomata/number-name.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

105 lines
3.2 KiB

  1. import BigNumber from 'bignumber.js';
  2. import {
  3. BLANK_DIGIT,
  4. createBlankDigits,
  5. deconstructNumeric,
  6. groupDigits,
  7. NEGATIVE_SIGN,
  8. normalizeNumeric,
  9. Numeric,
  10. } from '../../utils/numeric';
  11. import getLatinPowerName from '../../utils/common/latinPowers';
  12. const config = {
  13. "onesNames": ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"],
  14. "teensNames": ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"],
  15. "tensNames": ["zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"],
  16. "hundredName": "hundred",
  17. "thousandName": "thousand",
  18. "millia": "millia",
  19. "illion": "illion",
  20. "hundredsLatinNames": ["", "cen", "duocen", "trecen", "quadringen", "quingen", "sescen", "septingen", "octingen", "nongen"],
  21. "onesLatinNames": ["", "un", "duo", "tre", "quattuor", "quin", "sex", "septen", "octo", "novem"],
  22. "tensLatinNames": ["", "dec", "vigin", "trigin", "quadragin", "quinquagin", "sexagin", "septuagin", "octogin", "nonagin"],
  23. "onesSpecialLatinNames": ["", "m", "b", "tr", "quadr", "quin", "sex", "sep", "oct", "non"],
  24. "negative": "negative",
  25. "grouping": 3,
  26. "latinGrouping": 3
  27. }
  28. const getGroupIndexName = (index: BigNumber) => {
  29. if (index.eq(1)) {
  30. return config.thousandName
  31. }
  32. const basicIndex = index.dividedToIntegerBy(2)
  33. const isOdd = false
  34. const latinPowerName = getLatinPowerName(basicIndex, isOdd, config)
  35. if (index.mod(2).eq(1)) {
  36. return [config.thousandName, latinPowerName].join(' ')
  37. }
  38. return latinPowerName
  39. }
  40. const getGroupDigitsName = (digitsRaw: string) => {
  41. const { grouping, onesNames, teensNames, tensNames, hundredName } = config
  42. const digits = digitsRaw.padStart(grouping, BLANK_DIGIT)
  43. const [hundreds, tens, ones] = digits.split('').map(s => Number(s))
  44. const names = []
  45. if (hundreds !== 0) {
  46. names.push(onesNames[hundreds])
  47. names.push(hundredName)
  48. }
  49. if (tens === 1) {
  50. names.push(teensNames[ones])
  51. } else if (tens > 1) {
  52. names.push(tensNames[tens])
  53. if (ones > 0) {
  54. names.push(onesNames[ones])
  55. }
  56. } else {
  57. if (hundreds !== 0 && ones > 0 || hundreds === 0) {
  58. names.push(onesNames[ones])
  59. }
  60. }
  61. return names.join(' ')
  62. }
  63. const getGroupName = (g: [string, BigNumber]) => {
  64. const [digits, index] = g
  65. if (index.lt(1)) {
  66. return getGroupDigitsName(digits)
  67. }
  68. return [getGroupDigitsName(digits), getGroupIndexName(index)].join(' ')
  69. }
  70. type Options = {
  71. groupSeparator: string
  72. }
  73. const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => {
  74. const {
  75. groupSeparator = ' '
  76. } = options
  77. const x = normalizeNumeric(xRaw)
  78. const { significandDigits, exponent } = deconstructNumeric(x)
  79. const blankDigits = createBlankDigits(config.grouping)
  80. const groups = groupDigits(significandDigits, exponent, config.grouping)
  81. if (groups.length === 1) {
  82. return getGroupName(groups[0])
  83. }
  84. const base = groups.filter(([digits]) => digits !== blankDigits).map(g => getGroupName(g)).join(groupSeparator)
  85. if (x.startsWith(NEGATIVE_SIGN)) {
  86. return [config.negative, base].join(' ')
  87. }
  88. return base
  89. }
  90. export default getLocalizedNumberName