All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zillion.Util.scala Maven / Gradle / Ivy

The newest version!
package zillion

import spire.math.{Rational, SafeLong}

private[zillion] object Util {

  // Below this value we use "short scale" thousands
  val SmallCutoff = SafeLong(10).pow(33)

  // This is the smallest value that we can't name.
  val LargeCutoff = SafeLong(10).pow(3003)

  /**
   * Render the given number as a cardinal/ordinal string.
   *
   * The mode parameter determines which type of name we are
   * creating. This method is recursive. Note that even ordinal mode
   * still requires cardinal renderings in some cases (i.e. the "four"
   * in "four hundred third").
   *
   * The naming scheme for large values was introduced by John Horton
   * Conway and Richard K. Guy [1].
   *
   * [1] http://en.wikipedia.org/wiki/Names_of_large_numbers#Proposals_for_new_naming_system
   */
  def render(n: SafeLong, mode: NameMode): String =
    if (n < 0) {
      "negative " + render(-n, mode)
    } else if (n < 10) {
      mode.ones(n.toInt)
    } else if (n < 20) {
      mode.teens(n.toInt - 10)
    } else if (n < 100) {
      val i = (n / 10).toInt
      val r = (n % 10).toInt
      if (r == 0) mode.tens(i) else Cardinal.tens(i) + "-" + mode.ones(r)
    } else if (n < 1000) {
      val h = render((n / 100).toInt, Cardinal)
      val r = (n % 100).toInt
      if (r == 0) mode.suffix(s"$h hundred") else s"$h hundred ${render(r, mode)}"
    } else if (n < 1000000) {
      val m = render((n / 1000).toInt, Cardinal)
      val r = (n % 1000).toInt
      if (r == 0) mode.suffix(s"$m thousand") else s"$m thousand ${render(r, mode)}"
    } else if (n < SmallCutoff) {
      val x = log10Mult3(n)
      val p = SafeLong(10).pow(x)
      val b = render(n / p, Cardinal)
      val r = n % p
      val t = thousands(x / 3)
      if (r == 0) mode.suffix(s"$b $t") else s"$b $t ${render(r, mode)}"
    } else if (n < LargeCutoff) {
      val x = log10Mult3(n)
      val xm3 = x - 3
      val p = SafeLong(10).pow(x)
      val b = render(n / p, Cardinal)
      val r = n % p
      val lh = largeHundreds(xm3 / 300)
      val lt = largeTens((xm3 % 300) / 30, false)
      val lu = largeUnits((xm3 % 30) / 3, if (lt == "") lh.last else lt.last)
      if (r == 0) mode.suffix(s"$b ${lu}${lt}${lh}llion")
      else s"$b ${lu}${lt}${lh}llion ${render(r, mode)}"
    } else {
      throw new IllegalArgumentException(s"number is >= 10^3003")
    }

  val thousands = Vector("", "thousand", "million", "billion",
    "trillion", "quadrillion", "quintillion", "sextillion",
    "septillion", "octillion", "nonillion")

  /**
   * This method is used to create the large units between 10^33
   * (below which we use the short-scale names such as thousand,
   * million, billion) and 10^3003 (the limit of names we can
   * produce).
   */
  def largeUnits(num: Int, nextc: Char): String = {

    def m(c: Char): Boolean = c match {
      case 'c' | 'o' | 'q' | 't' | 'v' => true
      case _ => false
    }

    def s(c: Char): Boolean = c match {
      case 'c' | 'd' | 'o' | 'q' | 's' | 't' | 'v' => true
      case _ => false
    }

    num match {
      case 0 => ""
      case 1 => "un"
      case 2 => "duo"
      case 3 => if (s(nextc)) "tres" else "tre"
      case 4 => "quattor"
      case 5 => "quinqua"
      case 6 => if (s(nextc)) "ses" else "se"
      case 7 => if (m(nextc)) "septem" else "septe"
      case 8 => "octo"
      case 9 => if (m(nextc)) "novem" else "nove"
    }
  }

  def largeTens(num: Int, last: Boolean): String =
    num match {
      case 0 => ""
      case 1 => "deci"
      case 2 => "viginti"
      case 3 => if (last) "triginti" else "triginta"
      case 4 => if (last) "quadraginti" else "quadraginta"
      case 5 => if (last) "quinquaginti" else "quinquaginta"
      case 6 => if (last) "sexaginti" else "sexaginta"
      case 7 => if (last) "septuaginti" else "septuaginta"
      case 8 => if (last) "octoginti" else "octoginta"
      case 9 => if (last) "nonaginti" else "nonaginta"
    }

  def largeHundreds(num: Int): String =
    num match {
      case 0 => ""
      case 1 => "centi"
      case 2 => "ducenti"
      case 3 => "trecenti"
      case 4 => "quadringenti"
      case 5 => "quingenti"
      case 6 => "sescenti"
      case 7 => "septingenti"
      case 8 => "octingenti"
      case 9 => "nongenti"
    }

  import spire.math.{ceil, floor, log}

  /**
   * Return the floor of log10(n).
   * 
   * We assume n is non-negative.
   */
  def log10(n: SafeLong): Int = {
    // get a lower bound approximation for log10(n)
    var x = floor((n.bitLength - 1) * log(2) / log(10)).toInt
    var q = n / SafeLong(10).pow(x)
    while (q >= 10) { x += 1; q /= 10 }
    x
  }

  /**
   * Return the largest multiple of 3 <= log10(n).
   *
   * We assume n is non-negative.
   */
  def log10Mult3(n: SafeLong): Int = {
    val m = log10(n)
    m - (m % 3)
  }

  /**
   * Naming mode, used to abstract across cardinal/ordinal.
   *
   * This mode has some arrays for dealing with the smallest numbers
   * (whose cardinal => ordinal mappings are non-standard). It also
   * has a suffix() method which deals with all "larger" mappings.
   */
  sealed trait NameMode {
    def ones: Vector[String]
    def teens: Vector[String]
    def tens: Vector[String]
    def suffix(s: String): String
  }

  case object Cardinal extends NameMode {
    val ones = Vector("zero", "one", "two", "three", "four",
      "five", "six", "seven", "eight", "nine")

    val teens = Vector("ten", "eleven", "twelve", "thirteen", "fourteen",
      "fifteen", "sixteen", "seventeen", "eighteen", "nineteen")

    val tens = Vector("", "", "twenty", "thirty", "forty",
      "fifty", "sixty", "seventy", "eighty", "ninety")

    def suffix(s: String): String = s
  }

  case object Ordinal extends NameMode {
    val ones = Vector("zeroth", "first", "second", "third", "fourth",
      "fifth", "sixth", "seventh", "eighth", "ninth")

    val teens = Vector("tenth", "eleventh", "twelfth", "thirteenth",
      "fourteenth", "fifteenth", "sixteenth", "seventeenth",
      "eighteenth", "nineteenth")

    val tens = Vector("", "", "twentieth", "thirtieth", "fortieth",
      "fiftieth", "sixtieth", "seventieth", "eightieth", "ninetieth")

    def suffix(s: String): String = s + "th"
  }

  import fraction._

  def isPowerOfTen(x: SafeLong): Boolean = {
    val (q, r) = x /% 10
    if (r != 0) false
    else if (q == 1) true
    else isPowerOfTen(q)
  }

  def denominator(n: SafeLong): String =
    if (n < 0) denominator(-n)
    else if (n == 0) throw new IllegalArgumentException("/0")
    else if (n == 1) ""
    else if (n == 2) "half"
    else ordinal(n)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy