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.
 
 

100 lines
3.0 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.minus(1)
  33. const isOdd = false
  34. return getLatinPowerName(basicIndex, isOdd, config)
  35. }
  36. const getGroupDigitsName = (digitsRaw: string) => {
  37. const { grouping, onesNames, teensNames, tensNames, hundredName } = config
  38. const digits = digitsRaw.padStart(grouping, BLANK_DIGIT)
  39. const [hundreds, tens, ones] = digits.split('').map(s => Number(s))
  40. const names = []
  41. if (hundreds !== 0) {
  42. names.push(onesNames[hundreds])
  43. names.push(hundredName)
  44. }
  45. if (tens === 1) {
  46. names.push(teensNames[ones])
  47. } else if (tens > 1) {
  48. names.push(tensNames[tens])
  49. if (ones > 0) {
  50. names.push(onesNames[ones])
  51. }
  52. } else {
  53. if (hundreds !== 0 && ones > 0 || hundreds === 0) {
  54. names.push(onesNames[ones])
  55. }
  56. }
  57. return names.join(' ')
  58. }
  59. const getGroupName = (g: [string, BigNumber]) => {
  60. const [digits, index] = g
  61. if (index.lt(1)) {
  62. return getGroupDigitsName(digits)
  63. }
  64. return [getGroupDigitsName(digits), getGroupIndexName(index)].join(' ')
  65. }
  66. type Options = {
  67. groupSeparator: string
  68. }
  69. const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => {
  70. const {
  71. groupSeparator = ' '
  72. } = options
  73. const x = normalizeNumeric(xRaw)
  74. const { significandDigits, exponent } = deconstructNumeric(x)
  75. const blankDigits = createBlankDigits(config.grouping)
  76. const groups = groupDigits(significandDigits, exponent, config.grouping)
  77. if (groups.length === 1) {
  78. return getGroupName(groups[0])
  79. }
  80. const base = groups.filter(([digits]) => digits !== blankDigits).map(g => getGroupName(g)).join(groupSeparator)
  81. if (x.startsWith(NEGATIVE_SIGN)) {
  82. return [config.negative, base].join(' ')
  83. }
  84. return base
  85. }
  86. export default getLocalizedNumberName