// MultNm()
//
// Multiply two numerics.
//
// Parameters:
// x - IN Multiplier
// y - IN Multiplicand
//
// Result scale and precision(same as in SQL Server Manual and Hydra):
// scale = s1 + s2
// precision = s1 + s2 + (p1 - s1) + (p2 - s2) + 1
//
// Overflow Rules:
// If scale is greater than NUMERIC_MAX_PRECISION it is set to
// NUMERIC_MAX_PRECISION. If precision is greater than NUMERIC_MAX_PRECISION
// it is set to NUMERIC_MAX_PRECISION, then scale is reduced to keep the
// integer part untruncated but keeping a minimum value of x_cNumeDivScaleMin.
// For example, if using the above formula, the resulting precision is 46 and
// scale is 10, the precision will be reduced to 38. To keep the integral part
// untruncated the scale needs be reduced to 2, but since x_cNumeDivScaleMin
// is set to 6 currently, resulting scale will be 6.
// O_OVERFLOW is returned only if the actual precision is greater than
// NUMERIC_MAX_PRECISION or the actual length is greater than x_cbNumeBuf.
//
// Algorithm:
// Starting from the lowest significant UI4, for each UI4 of the multiplier
// iterate through the UI4s of the multiplicand starting from
// the least significant UI4s, multiply the multiplier UI4 with
// multiplicand UI4, update the result buffer with the product modulo
// x_dwlBaseUI4 at the same index as the multiplicand, and carry the quotient to
// add to the next multiplicand UI4. Until the end of the multiplier data
// array is reached.
//
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public static SqlDecimal operator *(SqlDecimal x, SqlDecimal y)
{
x.AssertValid();
y.AssertValid();
if (x.IsNull || y.IsNull)
return Null;
//Implementation:
// I) Figure result scale,prec
// II) Perform mult.
// III) Adjust product to result scale,prec
// Local variables for actual multiplication
int iulPlier; //index of UI4 in the Multiplier
uint ulPlier; //current multiplier UI4
ulong dwlAccum; //accumulated sum
ulong dwlNextAccum; //overflow of accumulated sum
int culCand = y.m_bLen; //length of multiplicand in UI4s
//Local variables to track scale,precision
int ActualScale; // Scale after mult done
int ResScale; // Final scale we will force result to
int ResPrec; // Final precision we will force result to
int ResInteger; // # of digits in integer part of result (prec-scale)
int lScaleAdjust; //How much result scale will be adjusted
bool fResPositive; // Result sign
SqlDecimal ret;
//I) Figure result prec,scale
ActualScale = x.m_bScale + y.m_bScale;
ResScale = ActualScale;
ResInteger = (x.m_bPrec - x.m_bScale) + (y.m_bPrec - y.m_bScale) + 1;
//result precision = s1 + s2 + (p1 - s1) + (p2 - s2) + 1
ResPrec = ResScale + ResInteger;
// Downward adjust res prec,scale if either larger than NUMERIC_MAX_PRECISION
if (ResPrec > s_NUMERIC_MAX_PRECISION)
ResPrec = s_NUMERIC_MAX_PRECISION;
if (ResScale > s_NUMERIC_MAX_PRECISION)
ResScale = s_NUMERIC_MAX_PRECISION;
//
// It is possible when two large numbers are being multiplied the scale
// can be reduced to 0 to keep data untruncated; the fix here is to
// preserve a minimum scale of 6.
//
// If overflow, reduce the scale to avoid truncation of data
ResScale = Math.Min((ResPrec - ResInteger), ResScale);
// But keep a minimum scale of NUMERIC_MIN_DVSCALE
ResScale = Math.Max(ResScale, Math.Min(ActualScale, s_cNumeDivScaleMin));
lScaleAdjust = ResScale - ActualScale;
fResPositive = (x.IsPositive == y.IsPositive);//positive if both signs same.
// II) Perform multiplication
uint[] rglData1 = new uint[4] { x.m_data1, x.m_data2, x.m_data3, x.m_data4 };
uint[] rglData2 = new uint[4] { y.m_data1, y.m_data2, y.m_data3, y.m_data4 };
//Local buffer to hold the result of multiplication.
//Longer than CReNumeBuf because full precision of multiplication is carried out
const int x_culNumeMultRes = 9; // Maximum # UI4s in result buffer in multiplication
uint[] rgulRes = new uint[x_culNumeMultRes]; //new [] are already initialized to zero
int culRes; // # of UI4s in result
int idRes = 0;
//Iterate over the bytes of multiplier
for (iulPlier = 0; iulPlier < x.m_bLen; iulPlier++)
{
ulPlier = rglData1[iulPlier];
dwlAccum = 0;
//Multiply each UI4 of multiCand by ulPliear and accumulate into result buffer
// Start on correct place in result
idRes = iulPlier;
for (int iulCand = 0; iulCand < culCand; iulCand++)
{
// dwlAccum = dwlAccum + rgulRes[idRes] + ulPlier*rglData2[iulCand]
// use dwlNextAccum to detect overflow of DWORDLONG
dwlNextAccum = dwlAccum + rgulRes[idRes];
ulong ulTemp = (ulong)rglData2[iulCand];
dwlAccum = (ulong)ulPlier * ulTemp;
dwlAccum += dwlNextAccum;
if (dwlAccum < dwlNextAccum) // indicates dwl addition overflowed
dwlNextAccum = s_ulInt32Base; // = maxUI64/x_dwlBaseUI4
else
dwlNextAccum = 0;
// Update result and accum
rgulRes[idRes++] = (uint)(dwlAccum);// & x_ulInt32BaseForMod); // equiv to mod x_lInt32Base
dwlAccum = (dwlAccum >> 32) + dwlNextAccum; // equiv to div BaseUI4 + dwlNAccum
// dwlNextAccum can't overflow next iteration
Debug.Assert(dwlAccum < s_ulInt32Base * 2);
}
Debug.Assert(dwlAccum < s_ulInt32Base); // can never final accum > 1 more UI4
if (dwlAccum != 0)
rgulRes[idRes++] = (uint)dwlAccum;
}
// Skip leading 0s (may exist if we are multiplying by 0)
for (; (rgulRes[idRes] == 0) && (idRes > 0); idRes--)
;
// Calculate actual result length
culRes = idRes + 1;
// III) Adjust precision,scale to result prec,scale
if (lScaleAdjust != 0)
{
// If need to decrease scale
if (lScaleAdjust < 0)
{
Debug.Assert(s_NUMERIC_MAX_PRECISION == ResPrec);
// have to adjust - might yet end up fitting.
// Cannot call AdjustScale - number cannot fit in a numeric, so
// have to duplicate code here
uint ulRem; //Remainder when downshifting
uint ulShiftBase; //What to multiply by to effect scale adjust
do
{
if (lScaleAdjust <= -9)
{
ulShiftBase = s_rgulShiftBase[8];
lScaleAdjust += 9;
}
else
{
ulShiftBase = s_rgulShiftBase[-lScaleAdjust - 1];
lScaleAdjust = 0;
}
MpDiv1(rgulRes, ref culRes, ulShiftBase, out ulRem);
}
while (lScaleAdjust != 0);
// Still do not fit?
if (culRes > s_cNumeMax)
throw new OverflowException(SQLResource.ArithOverflowMessage);
for (idRes = culRes; idRes < s_cNumeMax; idRes++)
rgulRes[idRes] = 0;
ret = new SqlDecimal(rgulRes, (byte)culRes, (byte)ResPrec, (byte)ResScale, fResPositive);
// Is it greater than 10**38?
if (ret.FGt10_38())
throw new OverflowException(SQLResource.ArithOverflowMessage);
ret.AssertValid();
// If remainder is 5 or above, increment/decrement by 1.
if (ulRem >= ulShiftBase / 2)
ret.AddULong(1);
// After adjusting, if the result is 0 and remainder is less than 5,
// set the sign to be positive
if (ret.FZero())
ret.SetPositive();
return ret;
}
// Otherwise call AdjustScale
if (culRes > s_cNumeMax) // Do not fit now, so will not fit after adjustment
throw new OverflowException(SQLResource.ArithOverflowMessage);
// NOTE: Have not check for value in the range (10**38..2**128),
// as we'll call AdjustScale with positive argument, and it'll
// return "normal" overflow
for (idRes = culRes; idRes < s_cNumeMax; idRes++)
rgulRes[idRes] = 0;
ret = new SqlDecimal(rgulRes, (byte)culRes, (byte)ResPrec, (byte)ActualScale, fResPositive);
if (ret.FZero())
ret.SetPositive();
ret.AssertValid();
ret.AdjustScale(lScaleAdjust, true);
return ret;
}
else
{
if (culRes > s_cNumeMax)
throw new OverflowException(SQLResource.ArithOverflowMessage);
for (idRes = culRes; idRes < s_cNumeMax; idRes++)
rgulRes[idRes] = 0;
ret = new SqlDecimal(rgulRes, (byte)culRes, (byte)ResPrec, (byte)ResScale, fResPositive);
// Is it greater than 10**38?
if (ret.FGt10_38())
throw new OverflowException(SQLResource.ArithOverflowMessage);
if (ret.FZero())
ret.SetPositive();
ret.AssertValid();
return ret;
}
}