//
// Mono.Data.SybaseTypes.SybaseDecimal
//
// Author:
//   Tim Coleman <tim@timcoleman.com>
//
// Based on System.Data.SqlTypes.SqlDecimal
//
// (C) Ximian, Inc. 2002-2003
// (C) Copyright Tim Coleman, 2002
//

using Mono.Data.Tds.Protocol;
using System;
using System.Data.SqlTypes;
using System.Globalization;
using System.Text;

namespace Mono.Data.SybaseTypes {
	public struct SybaseDecimal : INullable, IComparable
	{

		#region Fields

		int[] value;
		byte precision;
		byte scale;
		bool positive;

		bool notNull;

		// borrowed from System.Decimal
		const int SCALE_SHIFT = 16;
		const int SIGN_SHIFT = 31;
		const int RESERVED_SS32_BITS = 0x7F00FFFF;
		const ulong LIT_GUINT64_HIGHBIT = 0x8000000000000000;
		const ulong LIT_GUINT32_HIGHBIT = 0x80000000;
		const byte DECIMAL_MAX_INTFACTORS = 9;
		static uint [] constantsDecadeInt32Factors = new uint [10]
			{
				1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u,
				10000000u, 100000000u, 1000000000u
			};

		public static readonly byte MaxPrecision = 38;
		public static readonly byte MaxScale = 38;

		public static readonly SybaseDecimal MaxValue = new SybaseDecimal (MaxPrecision, (byte)0, true, (int)716002642, Int32.MaxValue, (int)1518778966, (int)1262177448);
		public static readonly SybaseDecimal MinValue = new SybaseDecimal (MaxPrecision, (byte)0, false, (int)716002642, Int32.MaxValue, (int)1518778966, (int)1262177448);
		public static readonly SybaseDecimal Null;

		#endregion

		#region Constructors

		public SybaseDecimal (decimal value) 
		{
			int[] binData = Decimal.GetBits (value);

			this.scale = (byte)(binData[3] >> SCALE_SHIFT);
			if (this.scale > MaxScale || (this.scale & RESERVED_SS32_BITS) != 0)
				throw new ArgumentException(Locale.GetText ("Invalid scale"));

			this.value = new int[4];
			this.value[0] = binData[0];
			this.value[1] = binData[1];
			this.value[2] = binData[2];
			this.value[3] = 0;
			notNull = true;

			positive = (value >= 0);
			precision = GetPrecision (value);
		}

		public SybaseDecimal (double value) : this ((decimal)value) { }
		public SybaseDecimal (int value) : this ((decimal)value) { }
		public SybaseDecimal (long value) : this ((decimal)value) { }

		public SybaseDecimal (byte bPrecision, byte bScale, bool fPositive, int[] bits) : this (bPrecision, bScale, fPositive, bits[0], bits[1], bits[2], bits[3]) { }

		public SybaseDecimal (byte bPrecision, byte bScale, bool fPositive, int data1, int data2, int data3, int data4) 
		{
			this.precision = bPrecision;
			this.scale = bScale;
			this.positive = fPositive;
			this.value = new int[4];
			this.value[0] = data1;
			this.value[1] = data2;
			this.value[2] = data3;
			this.value[3] = data4;
			notNull = true;

			if (precision < scale)
				throw new ArgumentException ("Invalid scale");
			if (this.ToDouble () > (System.Math.Pow (10, 38) -1) || this.ToDouble () < -(System.Math.Pow (10, 38)))
				throw new SybaseTypeException ("Can't convert to SybaseDecimal.");
		}

		#endregion

		#region Properties

		public byte[] BinData {
			get { 
				byte[] b = new byte [value.Length * 4];
				int j = 0;
				for (int i = 0; i < value.Length; i += 1) {
					b [j++] = (byte) (0xff & value [i]);
					b [j++] = (byte) (0xff & value [i] >> 8);
					b [j++] = (byte) (0xff & value [i] >> 16);
					b [j++] = (byte) (0xff & value [i] >> 24);
				}
				return b;
			}
		}

		public int[] Data { 
			get { 
				if (this.IsNull)
					throw new SybaseNullValueException ();
				else
					return (value);
			}
		}

		public bool IsNull { 
			get { return !notNull; }
		}

		public bool IsPositive { 
			get { return positive; }
		}

		public byte Precision { 
			get { return precision; }
		}

		public byte Scale { 
			get { return scale; }
		}

		public decimal Value { 
			get { 
				if (this.IsNull) 
					throw new SybaseNullValueException ();
				if (this.value[3] > 0)
					throw new OverflowException ();
				return new decimal (value[0], value[1], value[2], !positive, scale);
			}
		}

		#endregion

		#region Methods

		public static SybaseDecimal Abs (SybaseDecimal n)
		{
			return new SybaseDecimal (n.Precision, n.Scale, true, n.BinData [0], n.BinData [1], n.BinData [2], n.BinData [3]);
		}

		public static SybaseDecimal Add (SybaseDecimal x, SybaseDecimal y)
		{
			return (x + y);
		}

		public static SybaseDecimal AdjustScale (SybaseDecimal n, int digits, bool fRound)
		{
			byte prec = n.Precision;
			if (n.IsNull)
				throw new SybaseNullValueException ();
			if (digits > 0)
				prec = (byte) (prec + digits);
			if (fRound)
				n = Round (n, digits + n.Scale);
			return new SybaseDecimal (prec, (byte) (n.Scale + digits), n.IsPositive, n.Data);
		}

		public static SybaseDecimal Ceiling (SybaseDecimal n)
		{
			return AdjustScale (n, -(n.Scale), true);
		}

		public int CompareTo (object value)
		{
			if (value == null)
				return 1;
			else if (!(value is SybaseDecimal))
				throw new ArgumentException (Locale.GetText ("Value is not a System.Data.SybaseTypes.SybaseDecimal"));
			else if (((SybaseDecimal)value).IsNull)
				return 1;
			else
				return this.Value.CompareTo (((SybaseDecimal)value).Value);
		}

		public static SybaseDecimal ConvertToPrecScale (SybaseDecimal n, int precision, int scale)
		{
			return new SybaseDecimal ((byte) precision, (byte) scale, n.IsPositive, n.Data);
		}

		public static SybaseDecimal Divide (SybaseDecimal x, SybaseDecimal y)
		{
			return (x / y);
		}

		public override bool Equals (object value)
		{
			if (!(value is SybaseDecimal))
				return false;
			else if (this.IsNull && ((SybaseDecimal) value).IsNull)
				return true;
			else if (((SybaseDecimal) value).IsNull)
				return false;
			else
				return (bool) (this == (SybaseDecimal)value);
		}

		public static SybaseBoolean Equals (SybaseDecimal x, SybaseDecimal y)
		{
			return (x == y);
		}

		public static SybaseDecimal Floor (SybaseDecimal n)
		{
			return AdjustScale (n, -(n.Scale), false);
		}

		internal static SybaseDecimal FromTdsBigDecimal (TdsBigDecimal x)
		{
			if (x == null)
				return Null;
			else
				return new SybaseDecimal (x.Precision, x.Scale, !x.IsNegative, x.Data);
                }

		public override int GetHashCode ()
		{
			int result = 10;
			result = 91 * result + this.Data [0];
			result = 91 * result + this.Data [1];
			result = 91 * result + this.Data [2];
			result = 91 * result + this.Data [3];
			result = 91 * result + (int) this.Scale;
			result = 91 * result + (int) this.Precision;

			return result;
		}

		public static SybaseBoolean GreaterThan (SybaseDecimal x, SybaseDecimal y)
		{
			return (x > y);
		}

		public static SybaseBoolean GreaterThanOrEqual (SybaseDecimal x, SybaseDecimal y)
		{
			return (x >= y);
		}

		public static SybaseBoolean LessThan (SybaseDecimal x, SybaseDecimal y)
		{
			return (x < y);
		}

		public static SybaseBoolean LessThanOrEqual (SybaseDecimal x, SybaseDecimal y)
		{
			return (x <= y);
		}

		public static SybaseDecimal Multiply (SybaseDecimal x, SybaseDecimal y)
		{
			return (x * y);
		}

		public static SybaseBoolean NotEquals (SybaseDecimal x, SybaseDecimal y)
		{
			return (x != y);
		}

		public static SybaseDecimal Parse (string s)
		{
			if (s == null)
				throw new ArgumentNullException ();
			else
				return SybaseDouble.Parse (s).ToSybaseDecimal ();
		}

		public static SybaseDecimal Power (SybaseDecimal n, double exp)
		{
			if (n.IsNull)
				return SybaseDecimal.Null;
			return new SybaseDecimal (System.Math.Pow (n.ToDouble (), exp));
		}

		public static SybaseDecimal Round (SybaseDecimal n, int position)
		{
			if (n.IsNull)
				throw new SybaseNullValueException ();
			SybaseDecimal result = new SybaseDecimal (System.Math.Round ((double) (n.ToDouble () * System.Math.Pow (10, position))));
			result = result / new SybaseDecimal (System.Math.Pow (10, position));
			return result;
		}

		public static SybaseInt32 Sign (SybaseDecimal n)
		{
			SybaseInt32 result = 0;
			if (n >= new SybaseDecimal (0))
				result = 1;
			else
				result = -1;
			return result;
		}

		public static SybaseDecimal Subtract (SybaseDecimal x, SybaseDecimal y)
		{
			return (x - y);
		}

		private static byte GetPrecision (decimal value)
		{
			string str = value.ToString ();
			byte result = 0;
			foreach (char c in str) 
				if (c >= '0' && c <= '9')
					result ++;
			return result;
		}

		public double ToDouble ()
		{
			// FIXME: This is the wrong way to do this
			double d = (uint) this.Data [0];
			d += ((uint) this.Data [1]) * System.Math.Pow (2, 32);
			d += ((uint) this.Data [2]) * System.Math.Pow (2, 64);
			d += ((uint) this.Data [3]) * System.Math.Pow (2, 96);
			d /= System.Math.Pow (10, scale);
			return d;
		}

		public SybaseBoolean ToSybaseBoolean ()
		{
			return ((SybaseBoolean)this);
		}
		
		public SybaseByte ToSybaseByte ()
		{
			return ((SybaseByte)this);
		}

		public SybaseDouble ToSybaseDouble ()
		{
			return ((SybaseDouble)this);
		}

		public SybaseInt16 ToSybaseInt16 ()
		{
			return ((SybaseInt16)this);
		}

		public SybaseInt32 ToSybaseInt32 ()
		{
			return ((SybaseInt32)this);
		}

		public SybaseInt64 ToSybaseInt64 ()
		{
			return ((SybaseInt64)this);
		}

		public SybaseMoney ToSybaseMoney ()
		{
			return ((SybaseMoney)this);
		}

		public SybaseSingle ToSybaseSingle ()
		{
			return ((SybaseSingle)this);
		}

		public SybaseString ToSybaseString ()
		{
			return ((SybaseString)this);
		}

		public override string ToString ()
		{
			if (this.IsNull)
				return String.Empty;

			// convert int [4] -> ulong [2]
			ulong lo = (uint) this.Data [0] + (ulong) ((ulong) this.Data [1] << 32);
			ulong hi = (uint) this.Data [2] + (ulong) ((ulong) this.Data [3] << 32);

			uint rest = 0;
			StringBuilder result = new StringBuilder ();
			for (int i = 0; lo != 0 || hi != 0; i += 1) {
				Div128By32 (ref hi, ref lo, 10, ref rest);
				result.Insert (0, rest.ToString ());
			}
			while (result.Length < Precision)
				result.Append ("0");
			while (result.Length > Precision)
				result.Remove (result.Length - 1, 1);
			if (Scale > 0)
				result.Insert (result.Length - Scale, ".");
			return result.ToString ();
		}

		// from decimal.c
		private static int Div128By32 (ref ulong hi, ref ulong lo, uint divider)
		{
			uint t = 0;
			return Div128By32 (ref hi, ref lo, divider, ref t);
		}

		// from decimal.c
		private static int Div128By32 (ref ulong hi, ref ulong lo, uint divider, ref uint rest)
		{
			ulong a = 0;
			ulong b = 0;
			ulong c = 0;

			a = (uint) (hi >> 32);
			b = a / divider;
			a -= b * divider;
			a <<= 32;
			a |= (uint) hi;
			c = a / divider;
			a -= c * divider;
			a <<= 32;
			hi = b << 32 | (uint) c;

			a = (uint) (lo >> 32);
			b = a / divider;
			a -= b * divider;
			a <<= 32;
			a |= (uint) lo;
			c = a / divider;
			a -= c * divider;
			a <<= 32;
			lo = b << 32 | (uint) c;

			rest = (uint) a;
			a <<= 1;

			return (a > divider || (a == divider && (c & 1) == 1)) ? 1 : 0;
		}

		[MonoTODO ("Find out what is the right way to set scale and precision")]
		private static SybaseDecimal DecimalDiv (SybaseDecimal x, SybaseDecimal y)
		{
			ulong lo = 0;
			ulong hi = 0;
			int sc = 0; // scale
			int texp = 0;
			byte prec = 0;

			prec = x.Precision >= y.Precision ? x.Precision : y.Precision;
			DecimalDivSub (ref x, ref y, ref lo, ref hi, ref texp);

			sc = x.Scale - y.Scale;

			Rescale128 (ref lo, ref hi, ref sc, texp, 0, 38, 1);

			uint r = 0;
			while (prec < sc) {
				Div128By32 (ref hi, ref lo, 10, ref r);
				sc -= 1;
			}

			if (r >= 5)
				lo += 1;
		
			while ((((double) hi) * System.Math.Pow (2, 64) + lo) - System.Math.Pow (10, prec) > 0)
				prec += 1;

			while ((prec + sc) > MaxScale) {
				Div128By32 (ref hi, ref lo, 10, ref r);
				sc -= 1;
				if (r >= 5)
					lo += 1;
			}

			int resultLo = (int) lo;
			int resultMi = (int) (lo >> 32);
			int resultMi2 = (int) hi;
			int resultHi = (int) (hi >> 32);

			return new SybaseDecimal (prec, (byte) sc, true, resultLo, resultMi, resultMi2, resultHi);
		}

		// From decimal.c
		private static void Rescale128 (ref ulong clo, ref ulong chi, ref int scale, int texp, int minScale, int maxScale, int roundFlag) 
		{
			uint factor = 0;
			uint overhang = 0;
			int sc = 0;
			int i = 0;
			int roundBit = 0;

			sc = scale;
			if (texp > 0) {
				// reduce exp
				while (texp > 0 && sc <= maxScale) {
					overhang = (uint) (chi >> 64);
					while (texp > 0 && (((clo & 1) == 0) || overhang > 0)) {
						if (--texp == 0)
							roundBit = (int) (clo & 1);
						RShift128 (ref clo, ref chi);
						overhang = (uint) (chi >> 32);
					}

					if (texp > DECIMAL_MAX_INTFACTORS)
						i = DECIMAL_MAX_INTFACTORS;
					else
						i = texp;
					
					if (sc + i > maxScale) 
						i = maxScale - sc;
					if (i == 0)
						break;
					
					texp -= i;
					sc += i;

					// 10^i/2^i=5^i
					factor = constantsDecadeInt32Factors [i] >> i;
					Mult128By32 (ref clo, ref chi, factor, 0);
				}

				while (texp > 0) {
					if (--texp == 0)
						roundBit = (int) (clo & 1);
					RShift128 (ref clo, ref chi);

				}
			}

			while (sc > maxScale) {
				i = scale - maxScale;
				if (i > DECIMAL_MAX_INTFACTORS)
					i = DECIMAL_MAX_INTFACTORS;
				sc -= i;
				roundBit = Div128By32 (ref clo, ref chi, constantsDecadeInt32Factors [i]);
			}

			while (sc < minScale) {
				if (roundFlag == 0)
					roundBit = 0;
				i = minScale - sc;
				if (i > DECIMAL_MAX_INTFACTORS)
					i = DECIMAL_MAX_INTFACTORS;
				sc += i;
				Mult128By32 (ref clo, ref chi, constantsDecadeInt32Factors [i], roundBit);
				roundBit = 0;
			}
			scale = sc;
			Normalize128 (ref clo, ref chi, ref sc, roundFlag, roundBit);
		}

		// from decimal.c
		private static void Normalize128 (ref ulong clo, ref ulong chi, ref int scale, int roundFlag, int roundBit)
		{
			if ((roundFlag != 0) && (roundBit != 0))
				RoundUp128 (ref clo, ref chi);
		}

		// from decimal.c
		private static void RoundUp128 (ref ulong lo, ref ulong hi)
		{
			if ((++lo) == 0)
				++hi;
		}

		// from decimal.c
		private static void DecimalDivSub (ref SybaseDecimal x, ref SybaseDecimal y, ref ulong clo, ref ulong chi, ref int exp)
		{
			ulong xlo, xmi, xhi;
			ulong tlo = 0;
			ulong tmi = 0;
			ulong thi = 0;
			uint ylo = 0;
			uint ymi = 0;
			uint ymi2 = 0;
			uint yhi = 0;
			int ashift = 0;
			int bshift = 0;
			int extraBit = 0;

			xhi = (ulong) ((ulong) x.Data [3] << 32) | (ulong) x.Data [2];
			xmi = (ulong) ((ulong) x.Data [1] << 32) | (ulong) x.Data [0];
			xlo = (uint) 0;
			ylo = (uint) y.Data [0];
			ymi = (uint) y.Data [1];
			ymi2 = (uint) y.Data [2];
			yhi = (uint) y.Data [3];

			if (ylo == 0 && ymi == 0 && ymi2 == 0 && yhi == 0)
				throw new DivideByZeroException ();
			if (xmi == 0 && xhi == 0) {
				clo = chi = 0;
				return;
			}

			// enlarge dividend to get maximal precision
			for (ashift = 0; (xhi & LIT_GUINT64_HIGHBIT) == 0; ++ashift) 
				LShift128 (ref xmi, ref xhi);

			// ensure that divisor is at least 2^95
			for (bshift = 0; (yhi & LIT_GUINT32_HIGHBIT) == 0; ++bshift) 
				LShift128 (ref ylo, ref ymi, ref ymi2, ref yhi);

			thi = ((ulong) yhi) << 32 | (ulong) ymi2;
			tmi = ((ulong) ymi) << 32 | (ulong) ylo;
			tlo = 0;

			if (xhi > thi || (xhi == thi && xmi >= tmi)) {
				Sub192 (xlo, xmi, xhi, tlo, tmi, thi, ref xlo, ref xmi, ref xhi);
				extraBit = 1;
			} else {
				extraBit = 0;
			}

			Div192By128To128 (xlo, xmi, xhi, ylo, ymi, ymi2, yhi, ref clo, ref chi);

			exp = 128 + ashift - bshift;

			if (extraBit != 0) {
				RShift128 (ref clo, ref chi);
				chi += LIT_GUINT64_HIGHBIT;
				exp -= 1;
			}

			// try loss free right shift
			while (exp > 0 && (clo & 1) == 0) {
				RShift128 (ref clo, ref chi);
				exp -= 1;
			}
		}

		// From decimal.c
		private static void RShift192 (ref ulong lo, ref ulong mi, ref ulong hi)
		{
			lo >>= 1;
			if ((mi & 1) != 0)
				lo |= LIT_GUINT64_HIGHBIT;
			
			mi >>= 1;
			if ((hi & 1) != 0)
				mi |= LIT_GUINT64_HIGHBIT;
	
			hi >>= 1;
		}

		// From decimal.c
		private static void RShift128 (ref ulong lo, ref ulong hi)
		{
			lo >>= 1;
			if ((hi & 1) != 0)
				lo |= LIT_GUINT64_HIGHBIT;
			hi >>= 1;
		}

		// From decimal.c
		private static void LShift128 (ref ulong lo, ref ulong hi)
		{
			hi <<= 1;

			if ((lo & LIT_GUINT64_HIGHBIT) != 0)
				hi += 1;
			
			lo <<= 1;
		}

		// From decimal.c
		private static void LShift128 (ref uint lo, ref uint mi, ref uint mi2, ref uint hi)
		{
			hi <<= 1;
			if ((mi2 & LIT_GUINT32_HIGHBIT) != 0)
				hi += 1;

			mi2 <<= 1;
			if ((mi & LIT_GUINT32_HIGHBIT) != 0)
				mi2 += 1;

			mi <<= 1;
			if ((lo & LIT_GUINT32_HIGHBIT) != 0)
				mi += 1;

			lo <<= 1;
		}

		// From decimal.c
		private static void Div192By128To128 (ulong xlo, ulong xmi, ulong xhi, uint ylo, uint ymi, uint ymi2, uint yhi, ref ulong clo, ref ulong chi)
		{
			ulong rlo, rmi, rhi; // remainders
			uint h, c;

			rlo = xlo;
			rmi = xmi;
			rhi = xhi;

			h = Div192By128To32WithRest (ref rlo, ref rmi, ref rhi, ylo, ymi, ymi2, yhi);

			// mid 32 bit
			rhi = (rhi << 32) | (rmi >> 32);
			rmi = (rmi << 32) | (rlo >> 32);
			rlo <<= 32;

			chi = (((ulong)h) << 32) | Div192By128To32WithRest (ref rlo, ref rmi, ref rhi, ylo, ymi, ymi2, yhi);

			// low 32 bit
			rhi = (rhi << 32) | (rmi >> 32);
			rmi = (rmi << 32) | (rlo >> 32);
			rlo <<= 32;

			h = Div192By128To32WithRest (ref rlo, ref rmi, ref rhi, ylo, ymi, ymi2, yhi);

			// estimate lowest 32 bit (two last bits may be wrong)
			if (rhi >= yhi)
				c =  0xFFFFFFFF;
			else {
				rhi <<= 32;
				c = (uint)(rhi / yhi);
			}

			clo = (((ulong)h) << 32) | c;
		}

		// From decimal.c
		private static uint Div192By128To32WithRest (ref ulong xlo, ref ulong xmi, ref ulong xhi, uint ylo, uint ymi, uint ymi2, uint yhi)
		{
			ulong rlo, rmi, rhi; // remainder
			ulong tlo = 0;
			ulong thi = 0;
			uint c;

			rlo = xlo;
			rmi = xmi;
			rhi = xhi;

			if (rhi >= (((ulong)yhi << 32)))
				c = 0xFFFFFFFF;
			else
				c = (uint) (rhi / yhi);

			Mult128By32To128 (ylo, ymi, ymi2, yhi, c, ref tlo, ref thi);
			Sub192 (rlo, rmi, rhi, 0, tlo, thi, ref rlo, ref rmi, ref rhi);

			while (((long)rhi) < 0) {
				c--;
				Add192 (rlo, rmi, rhi, 0, (((ulong)ymi) << 32) | ylo, yhi | ymi2, ref rlo, ref rmi, ref rhi);
			}
			xlo = rlo;
			xmi = rmi;
			xhi = rhi;

			return c;
		}

		// From decimal.c
		private static void Mult192By32 (ref ulong clo, ref ulong cmi, ref ulong chi, ulong factor, int roundBit)
		{
			ulong a = 0;
			uint h0 = 0;
			uint h1 = 0;

			a = ((ulong)(uint)clo) * factor;

			if (roundBit != 0)
				a += factor / 2;

			h0 = (uint)a;
			a >>= 32;
			a += (clo >> 32) * factor;
			h1 = (uint)a;

			clo = ((ulong)h1) << 32 | h0;

			a >>= 32;
			a += ((ulong)(uint)cmi) * factor;
			h0 = (uint)a;

			a >>= 32;
			a += (cmi >> 32) * factor;
			h1 = (uint)a;

			cmi = ((ulong)h1) << 32 | h0;
			a >>= 32;
			a += ((ulong)(uint)chi) * factor;
			h0 = (uint)a;

			a >>= 32;
			a += (chi >> 32) * factor;
			h1 = (uint)a;
			chi = ((ulong)h1) << 32 | h0;
		}

		// From decimal.c
		private static void Mult128By32 (ref ulong clo, ref ulong chi, uint factor, int roundBit)
		{
			ulong a = 0;
			uint h0 = 0;
			uint h1 = 0;

			a = ((ulong)(uint)clo) * factor;

			if (roundBit != 0)
				a += factor / 2;

			h0 = (uint)a;

			a >>= 32;
			a += (clo >> 32) * factor;
			h1 = (uint)a;

			clo = ((ulong)h1) << 32 | h0;

			a >>= 32;
			a += ((ulong)(uint)chi) * factor;
			h0 = (uint)a;

			a >>= 32;
			a += (chi >> 32) * factor;
			h1 = (uint)a;

			chi = ((ulong)h1) << 32 | h0;
		}

		// From decimal.c
		private static void Mult128By32To128 (uint xlo, uint xmi, uint xmi2, uint xhi, uint factor, ref ulong clo, ref ulong chi)
		{
			ulong a;
			uint h0, h1, h2;

			a = ((ulong)xlo) * factor;
			h0 = (uint)a;

			a >>= 32;
			a += ((ulong)xmi) * factor;
			h1 = (uint)a;

			a >>= 32;
			a += ((ulong)xmi2) * factor;
			h2 = (uint)a;

			a >>= 32;
			a += ((ulong)xhi) * factor;

			clo = ((ulong)h1) << 32 | h0;
			chi = a | h2;
		}

		// From decimal.c
		private static void Add192 (ulong xlo, ulong xmi, ulong xhi, ulong ylo, ulong ymi, ulong yhi, ref ulong clo, ref ulong cmi, ref ulong chi)
		{
			xlo += ylo;
			if (xlo < ylo) {
				xmi++;
				if (xmi == 0)
					xhi++;
			}

			xmi += ymi;

			if (xmi < ymi)
				xmi++;

			xhi += yhi;
			clo = xlo;
			cmi = xmi;
			chi = xhi;
		}

		// From decimal.c
		private static void Sub192 (ulong xlo, ulong xmi, ulong xhi, ulong ylo, ulong ymi, ulong yhi, ref ulong lo, ref ulong mi, ref ulong hi)
		{
			ulong clo = 0;
			ulong cmi = 0;
			ulong chi = 0;

			clo = xlo - ylo;
			cmi = xmi - ymi;
			chi = xhi - yhi;

			if (xlo < ylo) {
				if (cmi == 0)
					chi--;
				cmi--;
			}

			if (xmi < ymi)
				chi--;

			lo = clo;
			mi = cmi;
			hi = chi;
		}

		public static SybaseDecimal Truncate (SybaseDecimal n, int position)
		{
			return new SybaseDecimal ((byte) n.Precision, (byte) position, n.IsPositive, n.Data);
		}

		public static SybaseDecimal operator + (SybaseDecimal x, SybaseDecimal y)
		{
			// if one of them is negative, perform subtraction
			if (x.IsPositive && !y.IsPositive) return x - y;
			if (y.IsPositive && !x.IsPositive) return y - x;
		
			// adjust the scale to the smaller of the two beforehand
			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			// set the precision to the greater of the two
			byte resultPrecision;
			if (x.Precision > y.Precision)
				resultPrecision = x.Precision;
			else
				resultPrecision = y.Precision;
				
			int[] xData = x.Data;
			int[] yData = y.Data;
			int[] resultBits = new int[4];

			ulong res; 
			ulong carry = 0;

			// add one at a time, and carry the results over to the next
			for (int i = 0; i < 4; i +=1)
			{
				carry = 0;
				res = (ulong)(xData[i]) + (ulong)(yData[i]) + carry;
				if (res > Int32.MaxValue)
				{
					carry = res - Int32.MaxValue;
					res = Int32.MaxValue;
				}
				resultBits [i] = (int)res;
			}

			// if we have carry left, then throw an exception
			if (carry > 0)
				throw new OverflowException ();
			else
				return new SybaseDecimal (resultPrecision, x.Scale, x.IsPositive, resultBits);
		}

		public static SybaseDecimal operator / (SybaseDecimal x, SybaseDecimal y)
		{
			return DecimalDiv (x, y);
		}

		public static SybaseBoolean operator == (SybaseDecimal x, SybaseDecimal y)
		{
			if (x.IsNull || y.IsNull) 
				return SybaseBoolean.Null;

			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			for (int i = 0; i < 4; i += 1)
			{
				if (x.Data[i] != y.Data[i])
					return new SybaseBoolean (false);
			}
			return new SybaseBoolean (true);
		}

		public static SybaseBoolean operator > (SybaseDecimal x, SybaseDecimal y)
		{
			if (x.IsNull || y.IsNull) 
				return SybaseBoolean.Null;

			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			for (int i = 3; i >= 0; i -= 1)
			{
				if (x.Data[i] == 0 && y.Data[i] == 0) 
					continue;
				else
					return new SybaseBoolean (x.Data[i] > y.Data[i]);
			}
			return new SybaseBoolean (false);
		}

		public static SybaseBoolean operator >= (SybaseDecimal x, SybaseDecimal y)
		{
			if (x.IsNull || y.IsNull) 
				return SybaseBoolean.Null;

			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			for (int i = 3; i >= 0; i -= 1)
			{
				if (x.Data[i] == 0 && y.Data[i] == 0) 
					continue;
				else
					return new SybaseBoolean (x.Data[i] >= y.Data[i]);
			}
			return new SybaseBoolean (true);
		}

		public static SybaseBoolean operator != (SybaseDecimal x, SybaseDecimal y)
		{
			if (x.IsNull || y.IsNull) 
				return SybaseBoolean.Null;

			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			for (int i = 0; i < 4; i += 1)
			{
				if (x.Data[i] != y.Data[i])
					return new SybaseBoolean (true);
			}
			return new SybaseBoolean (false);
		}

		public static SybaseBoolean operator < (SybaseDecimal x, SybaseDecimal y)
		{
			if (x.IsNull || y.IsNull) 
				return SybaseBoolean.Null;

			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			for (int i = 3; i >= 0; i -= 1)
			{
				if (x.Data[i] == 0 && y.Data[i] == 0) 
					continue;

				return new SybaseBoolean (x.Data[i] < y.Data[i]);
			}
			return new SybaseBoolean (false);
		}

		public static SybaseBoolean operator <= (SybaseDecimal x, SybaseDecimal y)
		{
			if (x.IsNull || y.IsNull) 
				return SybaseBoolean.Null;

			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			for (int i = 3; i >= 0; i -= 1)
			{
				if (x.Data[i] == 0 && y.Data[i] == 0) 
					continue;
				else
					return new SybaseBoolean (x.Data[i] <= y.Data[i]);
			}
			return new SybaseBoolean (true);
		}

		public static SybaseDecimal operator * (SybaseDecimal x, SybaseDecimal y)
		{
			// adjust the scale to the smaller of the two beforehand
			if (x.Scale > y.Scale)
				x = SybaseDecimal.AdjustScale(x, y.Scale - x.Scale, true);
			else if (y.Scale > x.Scale)
				y = SybaseDecimal.AdjustScale(y, x.Scale - y.Scale, true);

			// set the precision to the greater of the two
			byte resultPrecision;
			if (x.Precision > y.Precision)
				resultPrecision = x.Precision;
			else
				resultPrecision = y.Precision;
				
			int[] xData = x.Data;
			int[] yData = y.Data;
			int[] resultBits = new int[4];

			ulong res; 
			ulong carry = 0;

			// multiply one at a time, and carry the results over to the next
			for (int i = 0; i < 4; i +=1)
			{
				carry = 0;
				res = (ulong)(xData[i]) * (ulong)(yData[i]) + carry;
				if (res > Int32.MaxValue)
				{
					carry = res - Int32.MaxValue;
					res = Int32.MaxValue;
				}
				resultBits [i] = (int)res;
			}

			// if we have carry left, then throw an exception
			if (carry > 0)
				throw new OverflowException ();
			else
				return new SybaseDecimal (resultPrecision, x.Scale, (x.IsPositive == y.IsPositive), resultBits);
				
		}

		public static SybaseDecimal operator - (SybaseDecimal x, SybaseDecimal y)
		{
			if (x.IsPositive && !y.IsPositive) return x + y;
			if (!x.IsPositive && y.IsPositive) return -(x + y);
			if (!x.IsPositive && !y.IsPositive) return y - x;

			// otherwise, x is positive and y is positive
			bool resultPositive = (bool)(x > y);
			int[] yData = y.Data;

			for (int i = 0; i < 4; i += 1) yData[i] = -yData[i];

			SybaseDecimal yInverse = new SybaseDecimal (y.Precision, y.Scale, y.IsPositive, yData);

			if (resultPositive)
				return x + yInverse;
			else
				return -(x + yInverse);
		}

		public static SybaseDecimal operator - (SybaseDecimal n)
		{
			return new SybaseDecimal (n.Precision, n.Scale, !n.IsPositive, n.Data);
		}

		public static explicit operator SybaseDecimal (SybaseBoolean x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.ByteValue);
		}

		public static explicit operator Decimal (SybaseDecimal n)
		{
			return n.Value;
		}

		public static explicit operator SybaseDecimal (SybaseDouble x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.Value);
		}

		public static explicit operator SybaseDecimal (SybaseSingle x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.Value);
		}

		[MonoTODO]
		public static explicit operator SybaseDecimal (SybaseString x)
		{
			throw new NotImplementedException ();
		}

		public static implicit operator SybaseDecimal (decimal x)
		{
			return new SybaseDecimal (x);
		}

		public static implicit operator SybaseDecimal (SybaseByte x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.Value);
		}

		public static implicit operator SybaseDecimal (SybaseInt16 x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.Value);
		}

		public static implicit operator SybaseDecimal (SybaseInt32 x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.Value);
		}

		public static implicit operator SybaseDecimal (SybaseInt64 x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.Value);
		}

		public static implicit operator SybaseDecimal (SybaseMoney x)
		{
			if (x.IsNull) 
				return Null;
			else
				return new SybaseDecimal ((decimal)x.Value);
		}

		#endregion
	}
}
			
