# AVSLib :: 'numeric' package :: 'round' module
# Copyright (c) 2005 George Zarkadas (gzarkadas@users.sourceforge.net)

# This program is free software; you can redistribute it and/or modify it under the terms of 
# the GNU General Public License as published by the Free Software Foundation; either 
# version 2 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this program; 
# if not, write to the "Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
# MA  02111-1307  USA"

DefineModule("avslib", "numeric", "rounding")

global __load__ = Eval("""
	LoadModule("avslib", "numeric", "core")
	""")

# Rounds x to the closest even integer.
# The rounding is made such that all values inside [k-1..k+1) round to k (k even).
#
Function RoundEven(float x)
{ 
	n = Int(x)
	return IsEven(n) ? n : n + Sign(x)
}

# Rounds x to the closest odd integer.
# The rounding is made such that all values inside [k-1..k+1) round to k (k odd).
#
Function RoundOdd(float x)
{ 
	n = Int(x)
	return IsOdd(n) ? n : n + Sign(x)
} 

# Int, Floor, Ceil, Round to an arbitrary positive integer (base) multiple functions.

# Default Avisynth functions behavior:
# Int   -> point summetry across (0,0), not periodic  int(x)   =  ceil(x), x < 0 | floor(x), x > 0
# Floor -> not symmetrical across (0,0), periodic     floor(x) = k, x e [k..k+1) for all k e Z
# Ceil  -> not symmetrical across (0,0), periodic     ceil(x)  = k, x e (k-1..k] for all k e Z
# Round -> point summetry across (0,0), not periodic  round(x) = k, x e (k-0.5..k+0.5) for all k e Z
# Our functions behavior reproduces the above (substitute k-1 -> k-base, etc.)

Function IntBs(float x, int "base")
{
	base = Default(base, 1)
	Assert(base > 0, "IntBs: 'base' must be greater than zero")
	return Sign(x) * (base * (Int(Abs(x)) / base))
}

Function FloorBs(float x, int "base")
{
	base = Default(base, 1)
	Assert(base > 0, "FloorBs: 'base' must be greater than zero")
	ret = Sign(x) * (base * (Int(Abs(x)) / base))
	return  ret + (x < 0 && x != ret ? -base : 0)
}

Function CeilBs(float x, int "base")
{
	base = Default(base, 1)
	Assert(base > 0, "CeilBs: 'base' must be greater than zero")
	ret = Sign(x) * (base * (Int(Abs(x)) / base))
	return  ret + (x > 0 && x != ret ? base : 0)
}

Function RoundBs(float x, int "base")
{
	base = Default(base, 1)
	Assert(base > 0, "RoundBs: 'base' must be greater than zero")
	# this overflows near MAX_INT/MIN_INT; may fix it later
	return x < 0 ? CeilBs(x - 0.5 * base, base) : FloorBs(x + 0.5 * base, base)
}

# Rounds 'x' to 'decimals' accuracy, taking care not to overflow during 
# the intermediate calculations. Note that when using negative decimals 
# it is possible to zero the result if x < 0.5*Pow(10,-decimals)

	# rounds 'x' when 'decimals' are positive; 
	# the breakup to int and frac parts is done to avoid 
	# overflowing processed x 
	#
	Function __fround_pos_dec(float x, int decimals)
	{
		p1 = Int(x)
		p2 = Round(Frac(x) * Pow(10, decimals)) * Pow(10, -decimals)
		return p1 + p2
	}
	
	# rounds 'x' when 'decimals' are negative; 
	# note that if Abs(decimals) >= 10-base order of x, x may become zero 
	# (examples: 657.5 | -3 -> 1000 but 345.5 | -3 -> 0 )
	#
	Function __fround_neg_dec(float x, int decimals)
	{
		p1 = Round(x * Pow(10, decimals))
		return p1 * Pow(10, -decimals)
	}

Function FRound(float x, int decimals)
{
	return decimals >= 0 \
		? __fround_pos_dec(x, decimals) \
		: __fround_neg_dec(x, decimals)
}
