############################################################################
#
#	File:     numbers.icn
#
#	Subject:  Procedures related to numbers
#
#	Author:   Ralph E. Griswold
#
#	Date:     July 14, 1998
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	Contributors:  Robert J. Alexander, Richard Goerwitz
#	   Tim Korb, and Gregg M. Townsend
#
############################################################################
#
#	These procedures format numbers in various ways:
#
#	amean(L)	returns arithmetic mean of numbers in L.
#
#	ceil(r)		returns nearest integer to r away from 0.
#
#	commas(s)	inserts commas in s to separate digits into groups of
#			three.
#
#	decipos(r, i, j)
#			positions decimal point at i in real number r in
#			field of width j.
#
#	digred(i)	reduction of number by adding digits until one digit is
#			reached.
#
#	div(i, j)	produces the result of real division of i by j.
#
#	fix(i, j, w, d)	formats i / j as a real (floating-point) number in
#			a field of width w with d digits to the right of
#			the decimal point, if possible. j defaults to 1,
#			w to 8, and d to 3. If w is less than 3 it is set
#			to 3. If d is less than 1, it is set to 1. The
#			function fails if j is 0 or if the number cannot
#			be formatted.
#
# 	floor(r)	nearest integer to r toward 0
#
#	frn(r, w, d)    format real number r into a string with d digits
#			after the decimal point; a result narrower than w
#			characters is padded on the left with spaces.
#			Fixed format is always used; there is no exponential
#			notation.  Defaults:  w 0, d  0
#
#	gcd(i, j)	returns greatest common divisor of i and j. It fails
#			if both are zero.
#
#	gcdl(L)		returns the greatest common division of the integers
#			list L.
#
#	gmean(L)	returns geometric mean of numbers in L.
#
#	hmean(L)	returns harmonic mean of numbers in L.
#
#	large(i)	succeeds if i is a large integer but fails otherwise.
#
#	lcm(i, j)	returns the least common multiple of i and j.
#
#	lcml(L)		returns the least common multiple of the integers
#			in the list L.
#
#	max(L)		produces maximum of numbers in L.
#
#	min(L)		produces minimum of numbers in L.	
#
#	npalins(n)	generates palindomic n-digit numbers.
#
#	roman(i)	converts i to Roman numerals.
#
#	round(r)	returns nearest integer to r.
#
#	sign(r)		returns sign of r.
#
#	spell(i)	spells out i in English.
#
#	trunc(r)	returns nearest integer less than r.
#
#	unroman(s)	converts Roman numerals to integers.
#
############################################################################
#
#	Links:  strings
#	
############################################################################

link strings

procedure amean(L[])		#: arithmetic mean
   local m

   if *L = 0 then fail

   m := 0.0
   every m +:= !L

   return m / *L

end

procedure ceil(r)		#: ceiling

   if integer(r) = r then return integer(r)

   if r > 0 then return integer(r) + 1 else return -(integer(-r) + 1)

end

procedure commas(s)		#: commas in number

    local s2, sign

    # Don't bother if s is already comma-ized.
    if type(s) == "string" & find(",",  s) then fail

    # Take sign.  Save chars after the decimal point (if present).
    if s := abs(0 > s)
    then sign := "-" else sign := ""
    s ? {
	s := tab(find(".")) & ="." &
	not pos(0) & s2 := "." || integer(tab(0))
    }

    /s2 := ""
    integer(s) ? {
	tab(0)
	while s2 := "," || move(-3) || s2
	if pos(1)
	then s2 ?:= (move(1), tab(0))
	else s2 := tab(1) || s2
    }

    return sign || s2

end

procedure decipos(r, i, j)	#: position decimal point
   local head, tail

   /i := 3
   /j := 5

   r := real(r) | stop("*** non-numeric in decipos()")

   if i < 1 then fail

   r ? {
      head := tab(upto('.eE')) | fail
      move(1)
      tail := tab(0)
      return left(right(head, i - 1) || "." || tail, j)
      }

end

procedure digred(i)		#: sum digits of integer
   local j

   until *i = 1 do {
      j := 0
      every j +:= !i
      i := j
      }

   return i

end

procedure div(i, j)		#: real division

   return i / real(j)

end

procedure fix(i,j,w,d)		#: format real number
   local r, int, dec

   /j := 1
   /w := 8
   /d := 3
   if j = 0 then fail
   w <:= 3
   d <:= 1
   r := real(i) / j

   int := dec := "0"				# prepare for small number

   if not(r < ("0." || repl("0", d - 1) || "1")) then {	# formats as zero
      string(r) ? {
         if upto('eE') then fail		# can't format
         if int := tab(find(".")) then {
            move(1)
            dec := tab(0)
            }
         }
      }

   return right(int || "." || left(dec, d, "0"), w)

end

procedure floor(r)		#: floor

   if r > 0 then return integer(r) else return -integer(-r)

end

$define MAXDECIMALS 25

procedure frn(r, w, d)		#: format real number

   local s
   static mlist
   initial every put(mlist := list(), 10.0 ^ (0 to MAXDECIMALS))

   r := real(r) | runerr(102, r)
   (/d := 0) | (d >:= MAXDECIMALS)
   if r >= 0.0 then {
      s := string(integer(r * mlist[d + 1] + 0.5))
      s := right(s, *s < d + 1, "0")
      }
   else {
      s := string(integer(-r * mlist[d + 1] + 0.5))
      s := right(s, *s < d + 1, "0")
      s := "-" || s
      }
   s := right(s, *s < (\w - 1))

   return s ? (tab(-d) || "." || tab(0))

end

procedure gcd(i,j)		#: greatest common divisor
   local r

   if i = j = 0 then fail
   if i = 0 then return j
   if j = 0 then return i
   i := abs(i)
   j := abs(j)
   repeat {
      r := i % j
      if r = 0 then return j
      i := j
      j := r
      }
end

procedure gcdl(L[])		#: greatest common divisor of list
   local i, j

   i := get(L) | fail

   while j := get(L) do
      i := gcd(i, j)

   return i

end
   
procedure gmean(L[])		#: geometric mean
   local m

   if *L = 0 then fail

   m := 1.0
   every m *:= !L
   m := abs(m)
   if m > 0.0 then
      return exp (log(m) / *L)
   else
      fail
end
   
procedure hmean(L[])		#: harmonic mean
   local m, r

   if *L = 0 then fail

   m := 0.0

   every r := !L do {
      if r = 0.0 then fail
      else m +:= 1.0 / r
      }

   return *L / m

end

#
#  At the source-language level, "native" integers and "large"
#  integers have the same type, "integer".  The creation of a large
#  integer causes storage allocation, which this procedure detects.
#

procedure large(i)		#: detect large integers
   local mem

   mem := &allocated
   i +:= 0
   if &allocated > mem then return i
   else fail

end

procedure lcm(i, j)		#: least common multiple

   if i = j = 0 then fail

   return abs(i * j) / gcd(i, j)

end

procedure lcml(L[])		#: least common multiple of list
   local i, j

   i := get(L) | fail

   while j := get(L) do
      i := lcm(i, j)

   return i

end

procedure npalins(n)		#: palindromic numbers
   local i

   every i := palins(&digits, n) do
      if i[1] ~== "0" then suspend i	# can't start with zero

end

#  This procedure is based on a SNOBOL4 function written by Jim Gimpel.
#
procedure roman(n)		#: convert integer to Roman numeral
   local arabic, result
   static equiv

   initial equiv := ["","I","II","III","IV","V","VI","VII","VIII","IX"]

   integer(n) > 0 | fail
   result := ""
   every arabic := !n do
      result := map(result,"IVXLCDM","XLCDM**") || equiv[arabic + 1]
   if find("*",result) then fail else return result

end

procedure round(r)		#: round real

   if r > 0 then return integer(r + 0.5) else return -integer(0.5 - r)

end

procedure sign(r)		#: sign

   if r = 0 then return 0
   else if r < 0 then return -1
   else return 1

end

procedure spell(n)		#: spell out integer
   local m

   n := integer(n) | stop(image(n)," is not an integer")
   if n <= 12 then return {
      "0zero,1one,2two,3three,4four,5five,6six,7seven,8eight,_
         9nine,10ten,11eleven,12twelve," ? {
            tab(find(n))
            move(*n)
            tab(find(","))
            }
      }
   else if n <= 19 then return {
      spell(n[2] || "0") ?
         (if ="for" then "four" else tab(find("ty"))) || "teen"
      }
   else if n <= 99 then return {
      "2twen,3thir,4for,5fif,6six,7seven,8eigh,9nine," ? {
         tab(find(n[1]))
         move(1)
         tab(find(",")) || "ty" ||
            (if n[2] ~= 0 then "-" || spell(n[2]) else "")
         }
      }
   else if n <= 999 then return {
      spell(n[1]) || " hundred" ||
         (if (m := n[2:0]) ~= 0 then " and " || spell(m) else "")
      }
   else if n <= 999999 then return {
      spell(n[1:-3]) || " thousand" ||
         (if (m := n[2:0]) ~= 0 then " and " || spell(m) else "")
      }
   else if n <= 999999999 then return {
      spell(n[1:-6]) || " million" ||
         (if (m := n[2:0]) ~= 0 then " and " || spell(m) else "")
      }
   else fail

end

procedure trunc(r)		#: truncate real

   return integer(r)

end

procedure unroman(s)		#: convert Roman numeral to integer
   local nbr,lastVal,val

   nbr := lastVal := 0

   s ? {
      while val := case map(move(1)) of {
	 "m": 1000
	 "d": 500
	 "c": 100
	 "l": 50
	 "x": 10
	 "v": 5
	 "i": 1
	 } do {
	 nbr +:= if val <= lastVal then val else val - 2 * lastVal
	 lastVal := val
	 }
      }
   return nbr

end

procedure min(values[])		#: minimum value
   local minimum

   minimum := get(values) | fail
   every minimum >:= !values

   return minimum

end

procedure max(values[])		#: maximum value
   local maximum

   maximum := get(values) | fail
   every maximum <:= !values

   return maximum

end