#include "Core.h"

namespace Upp {

// iscale: computes x * y / z.

int iscale(int x, int y, int z)
{
#if CPU_32 && COMPILER_MSC
	__asm {
		mov		eax, [x]
		imul	[y]
		idiv	[z]
	}
#else
	int64 res = x;
	res *= y;
	res /= z;
	return (int)res;
#endif
}

// iscalefloor: computes x * y / z, rounded towards -infty.

int iscalefloor(int x, int y, int z)
{
#if CPU_32 && COMPILER_MSC
	__asm {
		mov		eax, [x]
		imul	[y]
		idiv	[z]
		and		edx, edx
		jge		__1
		dec		eax
	__1:
	}
#else
	int64 res = x;
	int64 mulres = res * y;
	res = mulres / z;
	if(res * z != mulres)
		res--;
	return (int)res;
#endif
}

// iscaleceil: computes x * y / z, rounded towards +infty.

int iscaleceil(int x, int y, int z)
{
#if CPU_32 && COMPILER_MSC
	__asm {
		mov		eax, [x]
		imul	[y]
		idiv	[z]
		and		edx, edx
		jle		__1
		inc		eax
	__1:
	}
#else
	int64 res = x;
	int64 mulres = res * y;
	res = mulres / z;
	if(res * z != mulres)
		res++;
	return (int)res;
#endif
}

#ifdef COMPILER_MSC
#pragma warning(default: 4035)
#endif

// idivfloor: returns x / y, truncated towards -infinity.

int idivfloor(int x, int y)
{
	return x / y - (x % y < 0);
}

// idivceil: returns x / y, truncated towards +infinity.

int idivceil(int x, int y)
{
	return x / y + (x % y > 0);
}

// itimesfloor: moves x to nearest lower multiple of y.

int itimesfloor(int x, int y)
{
	return y * idivfloor(x, y);
}

// itimesceil: moves x to nearest higher multiple of y.

int itimesceil(int x, int y)
{
	return y * idivceil(x, y);
}

// fround: converts double to nearest integer. Checks integer limits.

int fround(double value)
{
	return value >= INT_MAX ? INT_MAX : value <= INT_MIN ? INT_MIN
		: (int)floor(value + 0.5);
}

// ffloor: converts double to integer rounding towards -infinity. Checks integer limits.

int ffloor(double value)
{
	return value >= INT_MAX ? INT_MAX : value <= INT_MIN ? INT_MIN
		: (int)floor(value);
}

// fceil: converts double to integer rounding towards +infinity. Checks integer limits.

int fceil(double value)
{
	return value >= INT_MAX ? INT_MAX : value <= INT_MIN ? INT_MIN
		: (int)ceil(value);
}

// fround: converts double to nearest integer. Checks integer limits.

int64 fround64(double value)
{
	return value >= INT64_MAX ? INT64_MAX : value <= INT64_MIN ? INT64_MIN
		: (int64)floor(value + 0.5);
}

// ffloor: converts double to integer rounding towards -infinity. Checks integer limits.

int64 ffloor64(double value)
{
	return value >= INT64_MAX ? INT64_MAX : value <= INT64_MIN ? INT64_MIN
		: (int64)floor(value);
}

// fceil: converts double to integer rounding towards +infinity. Checks integer limits.

int64 fceil64(double value)
{
	return value >= INT64_MAX ? INT64_MAX : value <= INT64_MIN ? INT64_MIN
		: (int64)ceil(value);
}

// asString: converts double numbers to strings.

String AsString(double x, int digits)
{
	if(IsNull(x)) return Null;
	if(fabs(x) >= 1e15)
		return FormatDoubleExp(x, tabs(digits), digits < 0 ? FD_ZEROS : 0);
	return FormatDoubleFix(x, tabs(digits), digits < 0 ? FD_ZEROS : 0);
}

// modulo: working version of math function `fmod'.

double modulo(double x, double y)
{
	return y ? (x - y * floor(x / y)) : x;
}

int ilog10(double d)
{
	if(IsNull(d) || d == 0)
		return Null;
	return fceil(log10(fabs(d)));
}

double ipow10(int i)
{
	static const double pow_tbl[] =
	{
		1e00,
		1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10,
		1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20,
		1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30,
		1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39, 1e40,
		1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49, 1e50,
	};
	if(i >= 0 && i < __countof(pow_tbl))
		return pow_tbl[i];
	else if(i < 0 && i > -__countof(pow_tbl))
		return 1 / pow_tbl[-i];
	return pow(10.0, (double)i);
}

double normalize(double d, int& exp)
{
	if(IsNull(d) || d == 0)
	{
		exp = Null;
		return d;
	}
	bool sign = (d < 0);
	if(sign) d = -d;
	exp = minmax<int>(ilog10(d), -308, +308); // 8-byte double!
	d /= ipow10(exp);
	if(d >= 10) { d /= 10; exp++; }
	if(d < 1)   { d *= 10; exp--; }
	return sign ? -d : d;
}

double roundr(double d, int digits)
{
	int i = ilog10(d);
	if(IsNull(i))
		return d;
	double fac = ipow10(i - digits);
	return floor(d / fac + 0.5) * fac;
}

double floorr(double d, int digits)
{
	int i = ilog10(d);
	if(IsNull(i))
		return d;
	double fac = ipow10(i - digits);
	return floor(d / fac) * fac;
}

double ceilr(double d, int digits)
{
	int i = ilog10(d);
	if(IsNull(i))
		return d;
	double fac = ipow10(i - digits);
	return ceil(d / fac) * fac;
}

}
