number.js

import { formatString, padRight, padLeft, getAllChars } from './string.js'
import { isString, isNumeric, isNumber, isNaN, isUndefined } from './is.js'

/**
 * @param {number|string}
 * @returns {string}
 */
export function numerify(num) {
	if (isString(num)) {
		if (!isNumeric(num)) {
			return ''
		}
		let value = clearNum00(num)
		return value
	}
	else if (isNumber(num)) {
		let value = num.toString()
		if (value.indexOf('e') > -1) {
			return enumerify(value)
		}
		else {
			return value
		}
	}
	else {
		return ''
	}
}

/**
 * @param {number|string}
 * @returns {string}
 */
export function enumerify(input) {
  let num = parseFloat(input);
  if (isNaN(num)) {
    return ''
  }

  if (!input && input !== 0) {
    return ''
  }

  let str = input.toString()

  if (str.indexOf('e') === -1) {
    return str
  }

  let [base, exp] = str.split('e')
  let count = Number.parseInt(exp, 10)
  if (count >= 0) {
    let arr = base.split('')
    for (let i = 0; i < count; i ++) {
      let index = arr.indexOf('.')
      let next = index === arr.length - 1 ? '0' : arr[index + 1]
      arr[index] = next;
      arr[index + 1] = '.'
    }
    if (arr[arr.length - 1] === '.') {
      arr.pop()
    }
    let result = arr.join('')
    return result
  }
  else {
    let arr = base.split('')
    let rarr = arr.reverse()
    for (let i = count; i < 0; i ++) {
      let index = rarr.indexOf('.')
      let next = index === rarr.length - 1 ? '0' : rarr[index + 1]
      rarr[index] = next
      rarr[index + 1] = '.'
    }
    let rrarr = rarr.reverse()
    if (rrarr[0] === '.') {
      rrarr.unshift('0')
    }
    let result = rrarr.join('')
    return result
  }
}

/**
 * @param {number|string}
 * @returns {string}
 */
export function clearNum00(input) {
  input = input.toString()
  let [ integerPart, decimalPart = '' ] = input.split('.')
  let isNegative = false
  if (integerPart.indexOf('-') === 0) {
    isNegative = true
    integerPart = integerPart.substring(1)
  }
  integerPart = integerPart.replace(/^0+/, '')
  decimalPart = decimalPart.replace(/0+$/, '')
  let value = (isNegative && (integerPart || decimalPart) ? '-' : '') + (integerPart ? integerPart : '0') + (decimalPart ? '.' + decimalPart : '')
  return value
}

/**
 * @param {number|string} a
 * @param {number|string} b
 * @returns {string}
 */
export function plusby(a, b) {
  a = numerify(a)
  b = numerify(b)

  if (a === '0') {
    return b
  }
  else if (b === '0') {
    return a
  }

  let [ ia, da = '0' ] = a.split('.')
  let [ ib, db = '0' ] = b.split('.')

  let na = false
  let nb = false
  if (ia.indexOf('-') === 0) {
    ia = ia.substring(1)
    na = true
  }
  if (ib.indexOf('-') === 0) {
    ib = ib.substring(1)
    nb = true
  }

  if (na && !nb) {
    return minusby(b, a.substring(1))
  }
  if (nb && !na) {
    return minusby(a, b.substring(1))
  }

  const plus = (x, y) => {
    let xr = x.split('').reverse()
    let yr = y.split('').reverse()
    let len = Math.max(xr.length, yr.length)
    let items = []
    for (let i = 0; i < len; i ++) {
      let xv = xr[i] || '0'
      let yv = yr[i] || '0'
      items[i] = ((+xv) + (+yv)) + ''
    }

    let sum = items.reduce((sum, item, index) => {
      let sumlen = sum.length
      if (sumlen > index) {
        let borrow = sum.substring(0, 1)
        let placed = sum.substring(1)
        let next = (+borrow + +item) + ''
        return next + placed
      }
      else {
        return item + sum
      }
    }, '')
    return sum
  }

  const dalen = da.length
  const dblen = db.length
  const dlen = Math.max(dalen, dblen)
  if (dalen < dlen) {
    da = padRight(da, dlen, '0')
  }
  if (dblen < dlen) {
    db = padRight(db, dlen, '0')
  }

  const ta = ia + da
  const tb = ib + db

  let sum = plus(ta, tb)

  let sumr = sum.split('')
  let sumlen = sumr.length
  let index = sumlen - dlen
  sumr.splice(index, 0, '.')
  sum = sumr.join('')

  sum = clearNum00(sum)
  sum = sum === '' ? '0' : sum

  if (sum !== '0' && na && nb) {
    sum = '-' + sum
  }

  return sum
}

/**
 * @param {number|string} a
 * @param {number|string} b
 * @returns {string}
 */
export function minusby(a, b) {
  a = numerify(a)
  b = numerify(b)

  if (b === '0') {
    return a
  }
  else if (a === '0') {
    if (b.indexOf('-') === 0) {
      return b.substring(1)
    }
    else {
      return '-' + b
    }
  }
  else if (a === b) {
    return '0'
  }

  let [ ia, da = '0' ] = a.split('.')
  let [ ib, db = '0' ] = b.split('.')

  let na = false
  let nb = false
  if (ia.indexOf('-') === 0) {
    ia = ia.substring(1)
    na = true
  }
  if (ib.indexOf('-') === 0) {
    ib = ib.substring(1)
    nb = true
  }

  if (na && !nb) {
    return plusby(a, '-' + b)
  }
  if (nb && !na) {
    return plusby(a, b.substring(1))
  }

  if (compareby(b, a) > 0) {
    let diff = minusby(b, a)
    return '-' + diff
  }

  const minus = (x, y) => {
    let xr = x.split('').reverse()
    let yr = y.split('').reverse()
    let len = Math.max(xr.length, yr.length)
    let items = []
    for (let i = 0; i < len; i ++) {
      let xv = xr[i] || '0'
      let yv = yr[i] || '0'
      items[i] = {
        xv,
        yv,
      }
    }

    let isBorrowed = false
    let diff = items.reduce((diff, item, index) => {
      let { xv, yv } = item

      xv = +xv
      yv = +yv

      if (isBorrowed) {
        xv --
      }

      if (xv < yv) {
        isBorrowed = true
        xv += 10
      }
      else {
        isBorrowed = false
      }

      let v = xv - yv
      diff = v + diff

      return diff
    }, '')

    return diff
  }

  const dalen = da.length
  const dblen = db.length
  const dlen = Math.max(dalen, dblen)
  if (dalen < dlen) {
    da = padRight(da, dlen, '0')
  }
  if (dblen < dlen) {
    db = padRight(db, dlen, '0')
  }

  const ta = ia + da
  const tb = ib + db

  let diff = minus(ta, tb)

  let diffr = diff.split('')
  let difflen = diffr.length
  let index = difflen - dlen
  diffr.splice(index, 0, '.')
  diff = diffr.join('')

  diff = clearNum00(diff)
  diff = diff === '' ? '0' : diff

  return diff
}

/**
 * @param {number|string} a
 * @param {number|string} b
 * @returns {string}
 */
export function multiplyby(a, b) {
  a = numerify(a)
  b = numerify(b)

  if (a === '0' || b === '0') {
    return '0'
  }
  else if (a === '1') {
    return b
  }
  else if (b === '1') {
    return a
  }
  else if (a === '-1') {
    if (b.indexOf('-') === 0) {
      return b.substring(1)
    }
    else {
      return '-' + b
    }
  }
  else if (b === '-1') {
    if (a.indexOf('-') === 0) {
      return a.substring(1)
    }
    else {
      return '-' + a
    }
  }
  else if (/^10+/.test(b)) {
    let wei = Math.log10(b)
    let value = numerify(a)
    let [ integerPart, decimalPart = '' ] = value.split('.')
    let decimalLen = decimalPart.length
    if (decimalLen <= wei) {
      value = integerPart + padRight(decimalPart, wei, '0')
    }
    else {
      value = integerPart + decimalPart.substring(0, wei) + '.' + decimalPart.substring(wei)
    }
    value = clearNum00(value)
    return value
  }

  const multiply = (a, b) => {
    const result = []
    const aArr = a.toString().split('').map(t => parseInt(t))
    const bArr = b.toString().split('').map(t => parseInt(t))
    const aLen = aArr.length
    const bLen = bArr.length

    for (let bIndex = bLen-1; bIndex >= 0; bIndex--) {
      for (let aIndex = aLen-1; aIndex >= 0; aIndex--) {
        let index = bIndex + aIndex
        if (!result[index]) {
          result[index] = 0
        }
        result[index] += bArr[bIndex] * aArr[aIndex]
      }
    }

    result.reverse()
    for (let i = 0; i < result.length; i ++) {
      if (!result[i]) {
        result[i] = 0
      }

      let more = parseInt(result[i] / 10)
      if (more > 0) {
        if (!result[i + 1]) {
          result[i + 1] = 0
        }
        result[i + 1] += more
      }
      result[i] = result[i] % 10
    }
    result.reverse()

    return result.join('')
  }

  let [ ia, da = '' ] = a.split('.')
  let [ ib, db = '' ] = b.split('.')

  let na = false
  let nb = false
  let isNegative = false
  if (ia.indexOf('-') === 0) {
    ia = ia.substring(1)
    na = true
  }
  if (ib.indexOf('-') === 0) {
    ib = ib.substring(1)
    nb = true
  }
  if ((na && !nb) || (!na && nb)) {
    isNegative = true
  }

  let totalResult = multiply(clearNum00(ia + da), clearNum00(ib + db))
  const decimalCount = da.length + db.length
  let finalResult = totalResult
  if (decimalCount && decimalCount > totalResult.length) {
    finalResult = '0.' + padLeft(totalResult, decimalCount, '0')
  }
  else if (decimalCount) {
    const index = totalResult.length - decimalCount
    finalResult = totalResult.substring(0, index) + '.' + totalResult.substring(index)
  }

  let value = clearNum00(finalResult)
  value = (isNegative ? '-' : '') + finalResult
  value = finalResult === '' ? '0' : finalResult

  return value
}

/**
 * @param {number|string} a
 * @param {number|string} b
 * @param {number} [decimal] decimal length
 * @returns {string}
 */
export function divideby(a, b, decimal) {
  if (isUndefined(decimal)) {
    decimal = divideby.InfiniteDecimalLength || 15
  }

  a = numerify(a)
  b = numerify(b)

  if (b === '0') {
    throw new Error('除数不能为0')
  }

  if (a === '0') {
    return '0'
  }
  else if (b === '1') {
    return a
  }
  else if (a === b) {
    return '1'
  }
  else if (/^10+/.test(b)) {
    let wei = Math.log10(b)
    let value = numerify(a)
    let [ integerPart, decimalPart = '' ] = value.split('.')
    let integerLen = integerPart.length
    if (integerLen <= wei) {
      value = '0.' + padLeft(integerPart, wei, '0') + decimalPart
    }
    else {
      let pos = integerLen - wei
      let left = integerPart.substring(0, pos)
      let right = integerPart.substring(pos)
      value = left + '.' + right + decimalPart
    }
    value = clearNum00(value)
    return value
  }

  let na = false
  let nb = false
  let isNegative = false
  if (a.indexOf('-') === 0) {
    a = a.substring(1)
    na = true
  }
  if (b.indexOf('-') === 0) {
    b = b.substring(1)
    nb = true
  }
  if ((na && !nb) || (!na && nb)) {
    isNegative = true
  }

  let [, db = ''] = b.split('.')
  if (db.length) {
    let len = db.length
    let pow = Math.pow(10, len)
    a = multiplyby(a, pow)
    b = multiplyby(b, pow)
  }

  let [, da = ''] = a.split('.')
  if (da.length) {
    let len = da.length
    let pow = Math.pow(10, len)
    a = multiplyby(a, pow)
    b = multiplyby(b, pow)
  }

  const divide = (x, y) => {
    const uselen = y.length
    const result = []

    let waitforcompare = x.substr(0, uselen)
    let waittouse = x.substring(uselen)

    let stillhave = waitforcompare
    let inrange = 0

    do {
      let c
      while (c = compareby(stillhave, y) >= 0) {
        if (c > 0) {
          inrange ++
          stillhave = minusby(stillhave, y)
        }
        else if (c === 0) {
          inrange ++
          stillhave = ''
          break
        }
      }
      result.push(inrange)
      if (stillhave === '0') {
        stillhave = ''
      }

      let stillhavelen = stillhave.length
      let nextlen = uselen - stillhavelen
      nextlen = nextlen > 0 ? nextlen : 1
      waitforcompare = stillhave + waittouse.substr(0, nextlen)
      waittouse = waittouse.substring(nextlen)

      const leftletters = waitforcompare + waittouse
      if (/^0+$/.test(leftletters)) {
        result.push(leftletters)
        stillhave = ''
        break
      }

      stillhave = waitforcompare
      inrange = 0
    } while (compareby(stillhave, y) >= 0)

    let remainder = stillhave || '0'
    let quotient = result.join('')

    remainder = clearNum00(remainder)
    quotient = clearNum00(quotient)

    return { remainder, quotient }
  }

  let result = divide(a, b)
  let { remainder, quotient } = result
  let value = quotient

  if (remainder && remainder !== '0') {
    let decimalPart = ''
    let nextto = remainder + '0'
    while (/[1-9]/.test(nextto)) {
      let dvd = divide(nextto, b)
      let { remainder, quotient } = dvd
      decimalPart += quotient

      if (remainder === '0') {
        break
      }

      nextto = remainder + '0'

      if (decimalPart.length >= decimal) {
        break
      }
    }
    value = quotient + '.' + decimalPart
  }

  value = clearNum00(value)

  if (isNegative) {
    value = '-' + value
  }

  return value
}

/**
 * @param {number|string} a
 * @param {number|string} b
 * @returns {number}
 */
export function compareby(a, b) {
  a = numerify(a)
  b = numerify(b)

  let [ ia, da = '' ] = a.split('.')
  let [ ib, db = '' ] = b.split('.')

  const compare2 = (n, m) => {
    if (n.length > m.length) {
      return 1
    }
    else if (n.length < m.length) {
      return -1
    }
    else {
      for (let i = 0, len = n.length; i < len; i ++) {
        let nv = n.charAt(i)
        let mv = m.charAt(i)
        if (+nv > +mv) {
          return 1
        }
        else if (+nv < +mv) {
          return -1
        }
      }
      return 0
    }
  }

  const compare = (x, y) => {
    let nx = x.indexOf('-') === 0
    let ny = y.indexOf('-') === 0

    if (!nx && ny) {
      return 1
    }
    else if (nx && !ny) {
      return -1
    }
    else if (nx && ny) {
      x = x.substring(1)
      y = y.substring(1)
      let result = compare2(x, y)
      return -result
    }
    else if (!nx && !ny) {
      return compare2(x, y)
    }
  }

  const ci = compare(ia, ib)
  if (ci) {
    return ci
  }

  const dalen = da.length
  const dblen = db.length
  const dlen = Math.max(dalen, dblen)
  if (dalen < dlen) {
    da = padRight(da, dlen, '0')
  }
  if (dblen < dlen) {
    db = padRight(db, dlen, '0')
  }
  const cd = compare(da, db)
  if (cd) {
    return cd
  }

  return 0
}

/**
 * @param {string} exp
 * @param {number} [decimal]
 * @returns {string}
 */
export function calculate(exp, decimal) {
  const contains = (str, items) => {
    for (let i = 0, len = items.length; i < len; i ++) {
      let item = items[i]
      if (str.indexOf(item) > -1) {
        return true
      }
    }
    return false
  }

  if (!/^[\(\-]?[0-9]+[0-9\+\-\*\/\(\)]*[0-9\)]$/.test(exp)) {
    throw new Error(`exp contains unexpected content.`)
  }
  if (contains(exp, ['---', '++', '**', '//'])) {
    throw new Error(`exp contains one of ['---', '++', '**', '//'].`)
  }
  if (contains(exp, ['-*', '-/', '+*', '+/'])) {
    throw new Error(`exp contains one of ['-*', '-/', '+*', '+/'].`)
  }
  if (exp.indexOf(')(') > -1) {
    throw new Error(`exp contains ')('.`)
  }
  if (exp.indexOf('()') > -1) {
    throw new Error(`exp contains empty sub-exp '()'.`)
  }
  if (/\)[0-9]/.test(exp)) {
    throw new Error(`exp contains number which follows ')'.`)
  }
  if (/[0-9]\(/.test(exp)) {
    throw new Error(`exp contians '(' which follows number.`)
  }

  const parse = (exp) => {
    let inGroup = 0
    let exparr = []
    let expstr = ''
    let groups = []
    let groupstr = ''
    for (let i = 0, len = exp.length; i < len; i ++) {
      let char = exp.charAt(i)
      if (char === '(') {
        if (inGroup) {
          groupstr += char
        }
        else {
          if (expstr) {
            exparr.push(expstr)
            expstr = ''
          }
        }
        inGroup ++
      }
      else if (char === ')') {
        if (!inGroup) {
          throw new Error(`exp has unexpected ')': ... ${groupstr})`)
        }

        if (inGroup === 1) {
          if (groupstr) {
            let index = groups.length
            exparr.push(index)
            groups.push(groupstr)
            groupstr = ''
          }
        }
        else {
          groupstr += char
        }
        inGroup --
      }
      else if (inGroup) {
        groupstr += char
      }
      else {
        if (/[\+\-\*\/]/.test(char)) {
          if (expstr) {
            exparr.push(expstr)
          }
          expstr = ''
          exparr.push(char)
        }
        else {
          expstr += char
        }
      }
    }

    if (inGroup) {
      throw new Error(`exp '(' is not closed.`)
    }

    if (expstr) {
      exparr.push(expstr)
    }

    const exparr2 = []
    for (let i = 0, len = exparr.length; i < len; i ++) {
      const current = exparr[i]
      const prev = exparr[i - 1]
      const next = exparr[i + 1]
      if (current === '-') {
        if (i === 0 || inArray(prev, ['*', '/', '+', '-'])) {
          if (next === '-') {
            i ++
            continue
          }
          else if (next === '+') {
            let nextnext = exparr[i + 2]
            let text = '-' + nextnext
            exparr2.push(text)
            i += 2
          }
          else {
            let text = '-' + next
            exparr2.push(text)
            i ++
          }
        }
        else {
          exparr2.push(current)
        }
      }
      else {
        exparr2.push(current)
      }
    }

    const expsrc = []
    exparr2.forEach((item, i) => {
      if (isNumber(item)) {
        item = groups[item]
        item = parse(item)
      }
      expsrc.push(item)
    })

    let expast = []

    if (contains(exp, ['+', '-']) && contains(exp, ['*', '/'])) {
      let combine = []
      let started = false
      for (let i = 0; i < expsrc.length; i ++) {
        let current = expsrc[i]
        if (!started && (current === '*' || current === '/')) {
          let prev = expast.pop()
          combine.push(prev)
          combine.push(current)
          started = true
        }
        else if (started) {
          if (current === '+' || (!inArray(combine[combine.length - 1], ['*', '/']) && current === '-')) {
            expast.push(combine)
            expast.push(current)
            started = false
            combine = []
          }
          else if (i === (expsrc.length - 1)) {
            combine.push(current)
            expast.push(combine)
            started = false
            combine = []
          }
          else {
            combine.push(current)
          }
        }
        else {
          expast.push(current)
        }
      }
    }
    else {
      expast = expsrc
    }

    return expast
  }

  const execute = (expast) => {
    const exparr = []
    expast.forEach((item) => {
      if (isArray(item)) {
        item = execute(item)
      }
      exparr.push(item)
    })

    let expres = []
    const leftres = []
    const rightres = []
    for (let i = 0, len = exparr.length; i < len; i ++) {
      const current = exparr[i]
      const next = exparr[i + 1]
      if (current === '*') {
        leftres.push(current)
        leftres.push(next)
        i ++
      }
      else if (current === '/') {
        rightres.push(current)
        rightres.push(next)
        i ++
      }
      else {
        expres.push(current)
      }
    }
    expres = expres.concat(leftres).concat(rightres)

    let result = ''
    for (let i = 0, len = expres.length; i < len; i ++) {
      let current = expres[i]
      if (i === 0) {
        result = current === '-' ? '0' : current
      }
      if (/[\+\-\*\/]/.test(current)) {
        let next = expres[i + 1]
        if (current === '+') {
          result = plusby(result, next)
        }
        else if (current === '-') {
          result = minusby(result, next)
        }
        else if (current === '*') {
          result = multiplyby(result, next)
        }
        else if (current === '/') {
          result = divideby(result, next, decimal)
        }
      }
    }

    return result
  }

  const expast = parse(exp)
  const result = execute(expast)
  return result
}

/**
 * @param {number|string} input
 * @param {number} [decimal]
 * @param {boolean} [pad]
 * @param {boolean} [floor]
 * @returns {string}
 */
export function fixNum(input, decimal = 2, pad = false, floor = false) {
  let num = parseFloat(input)
  if (isNaN(num)) {
    return ''
  }

  let value = numerify(input)
  let [ integerPart, decimalPart = '' ] = value.split('.')

  const plusOneToTail = (dnum) => {
    let [ integerPart, decimalPart ] = dnum.split('.')
    let dlen = decimalPart.length
    let one = '0.' + padLeft('1', dlen, '0')
    let value = plusby(dnum, one)
    return value
  }

  if (decimal === 0 && floor) {
    if (decimalPart && num < 0) {
      integerPart = minusby(integerPart, '1')
    }
    value = integerPart
  }
  else if (decimal === 0) {
    if (decimalPart && +('0.' + decimalPart) >= 0.5) {
      if (num >= 0) {
        integerPart = plusby(integerPart, '1')
      }
      else {
        integerPart = minusby(integerPart, '1')
      }
    }
    value = integerPart
  }
  else if (decimal > 0 && floor) {
    let isNegative = num < 0
    if (decimalPart) {
      if (isNegative) {
        let usePart = decimalPart.substring(0, decimal)
        let dropPart = decimalPart.substring(decimal)

        usePart = '0.' + usePart

        if (dropPart) {
          usePart = plusOneToTail(usePart)
        }

        value = minusby(integerPart, usePart)
      }
      else {
        value = integerPart + '.' + decimalPart.substring(0, decimal)
      }
    }
    else {
      value = integerPart
    }
  }
  else if (decimal > 0) {
    value = integerPart
    if (decimalPart) {
      let usePart = decimalPart.substring(0, decimal)
      let dropPart = decimalPart.substring(decimal, decimal + 1)

      usePart = '0.' + usePart

      if (+dropPart >= 5) {
        usePart = plusOneToTail(usePart)
      }

      let isNegative = num < 0
      if (isNegative) {
        value = minusby(integerPart, usePart)
      }
      else {
        value = plusby(integerPart, usePart)
      }
    }
  }

  value = clearNum00(value)

  if (pad) {
    let [ integerPart, decimalPart = '' ] = value.split('.')
    if ((decimalPart && decimalPart.length < decimal) || !decimalPart) {
      decimalPart = padRight(decimalPart || '', decimal, '0')
      value = integerPart + '.' + decimalPart
    }
  }

  return value
}

/**
 * @param {number|string} input
 * @param {string} separator
 * @param {number} count
 * @param {boolean} [formatdecimal]
 * @returns {string}
 */
export function formatNum(input, separator, count, formatdecimal = false) {
  let num = numerify(input);

  if (!num) {
    return '';
  }

  if (!/^\-{0,1}[0-9]+(\.{0,1}[0-9]+){0,1}$/.test(num)) {
    return '';
  }

  let blocks = num.split(/\-|\./);
  let isNegative = num.charAt(0) === '-';
  let integer;
  let decimal;

  if (isNegative) {
    integer = blocks[1];
    decimal = blocks[2] || '';
  }
  else {
    integer = blocks[0];
    decimal = blocks[1] || '';
  }

  integer = formatString(integer, separator, count, true);
  if (formatdecimal && decimal) {
    decimal = formatString(decimal, separator, count);
  }

  let result = '';
  if (isNegative) {
    result += '-';
  }

  result += integer;

  if (decimal) {
    result += '.' + decimal;
  }

  return result;
}

/**
 * @param {number|string} input
 * @param {boolean} [formatdecimal]
 * @returns {string}
 */
export function formatNum1000(input, formatdecimal = false) {
  return formatNum(input, ',', 3, formatdecimal);
}

// http://www.softwhy.com/article-4813-1.html

/**
 * convert base10 number to string
 * @param {number} num
 * @param {number} base
 * @returns {string}
 */
export function num10To(num, base) {
  const CHARS = getAllChars(base)
  const chars = CHARS.split('')
  const radix = chars.length
  const arr = []
  let qutient = +num
  do {
    const mod = qutient % radix
    qutient = (qutient - mod) / radix
    arr.unshift(chars[mod])
  }
  while (qutient)
  const code = arr.join('')
  return code
}

/**
 * convert string to base10 number
 * @param {string} code
 * @param {number} base
 * @returns {number}
 */
export function numTo10(code, base) {
  const CHARS = getAllChars(base)
  const radix = CHARS.length
  const len = code.length
  let i = 0
  let num = 0
  while (i < len) {
    num += Math.pow(radix, i++) * CHARS.indexOf(code.charAt(len - i) || 0)
  }
  return num
}

/**
 * @param {number|string} num
 * @returns {string}
 */
export function num10to62(num) {
  return num10To(num, 62);
}

/**
 * @param {string} code
 * @returns {number}
 */
export function num62to10(code) {
  return numTo10(code, 62);
}

/**
 * @param {number|string} num
 * @returns {string}
 */
export function num10to36(num) {
  return num10To(num, 36);
}

/**
 * @param {string} code
 * @returns {number}
 */
export function num36to10(code) {
  return numTo10(code, 36);
}