// DecAddSub adds or subtracts two decimal values. On return, d1 contains the result // of the operation. Passing in true for bSign means subtract and false means add. // // Returns true if we overflow otherwise false. private static bool DecAddSub(ref Decimal d1, ref Decimal d2, bool bSign) { uint[] rgulNum = new uint[6]; uint ulPwr; int iScale; int iHiProd; int iCur; Split64 sdlTmp = new Split64(); Decimal result = new Decimal(); Decimal tmp = new Decimal(); bSign ^= d2.Sign ^ d1.Sign; if (d2.Scale == d1.Scale) { // Scale factors are equal, no alignment necessary. // result.Sign = d1.Sign; result.Scale = d1.Scale; if (AlignedAdd(ref result, ref d1, ref d2, bSign)) return true; } else { // Scale factors are not equal. Assume that a larger scale // factor (more decimal places) is likely to mean that number // is smaller. Start by guessing that the right operand has // the larger scale factor. The result will have the larger // scale factor. // result.Scale = d2.Scale; // scale factor of "smaller" result.Sign = d1.Sign; // but sign of "larger" iScale = result.Scale - d1.Scale; if (iScale < 0) { // Guessed scale factor wrong. Swap operands. // iScale = -iScale; result.Scale = d1.Scale; result.Sign ^= bSign; tmp = d2; d2 = d1; d1 = tmp; } // d1 will need to be multiplied by 10^iScale so // it will have the same scale as d2. We could be // extending it to up to 192 bits of precision. // if (iScale <= MaxInt32Scale) { // Scaling won't make it larger than 4 ULONGs // ulPwr = s_powers10[iScale]; tmp = UInt32x32To64(d1.Low, ulPwr); sdlTmp.int64 = UInt32x32To64(d1.Mid, ulPwr); sdlTmp.int64 += tmp.Mid; tmp.Mid = sdlTmp.Low32; tmp.High = sdlTmp.High32; sdlTmp.int64 = UInt32x32To64(d1.High, ulPwr); sdlTmp.int64 += tmp.High; if (sdlTmp.High32 == 0) { // Result fits in 96 bits. Use standard aligned add. // tmp.High = sdlTmp.Low32; d1 = tmp; if (AlignedAdd(ref result, ref d1, ref d2, bSign)) return true; d1 = result; return false; } rgulNum[0] = tmp.Low; rgulNum[1] = tmp.Mid; rgulNum[2] = sdlTmp.Low32; rgulNum[3] = sdlTmp.High32; iHiProd = 3; } else { // Have to scale by a bunch. Move the number to a buffer // where it has room to grow as it's scaled. // rgulNum[0] = d1.Low; rgulNum[1] = d1.Mid; rgulNum[2] = d1.High; iHiProd = 2; // Scan for zeros in the upper words. // if (rgulNum[2] == 0) { iHiProd = 1; if (rgulNum[1] == 0) { iHiProd = 0; if (rgulNum[0] == 0) { // Left arg is zero, return right. // result.Low64 = d2.Low64; result.High = d2.High; result.Sign ^= bSign; d1 = result; return false; } } } // Scaling loop, up to 10^9 at a time. iHiProd stays updated // with index of highest non-zero ULONG. // for (; iScale > 0; iScale -= MaxInt32Scale) { if (iScale > MaxInt32Scale) ulPwr = TenToPowerNine; else ulPwr = s_powers10[iScale]; sdlTmp.High32 = 0; for (iCur = 0; iCur <= iHiProd; iCur++) { sdlTmp.int64 = UInt32x32To64(rgulNum[iCur], ulPwr) + sdlTmp.High32; rgulNum[iCur] = sdlTmp.Low32; } if (sdlTmp.High32 != 0) // We're extending the result by another ULONG. rgulNum[++iHiProd] = sdlTmp.High32; } } // Scaling complete, do the add. Could be subtract if signs differ. // sdlTmp.Low32 = rgulNum[0]; sdlTmp.High32 = rgulNum[1]; if (bSign) { // Signs differ, subtract. // result.Low64 = sdlTmp.int64 - d2.Low64; result.High = rgulNum[2] - d2.High; // Propagate carry // if (result.Low64 > sdlTmp.int64) { result.High--; if (result.High >= rgulNum[2]) if (LongSub(ref result, ref iHiProd, rgulNum)) { d1 = result; return false; } } else if (result.High > rgulNum[2]) { if (LongSub(ref result, ref iHiProd, rgulNum)) { d1 = result; return false; } } } else { // Signs the same, add. // result.Low64 = sdlTmp.int64 + d2.Low64; result.High = rgulNum[2] + d2.High; // Propagate carry // if (result.Low64 < sdlTmp.int64) { result.High++; if (result.High <= rgulNum[2]) LongAdd(ref iHiProd, rgulNum); } else if (result.High < rgulNum[2]) { LongAdd(ref iHiProd, rgulNum); } } if (iHiProd > 2) { rgulNum[0] = result.Low; rgulNum[1] = result.Mid; rgulNum[2] = result.High; int scale = ScaleResult(rgulNum, iHiProd, result.Scale); if (scale == -1) return true; result.Scale = scale; result.Low = rgulNum[0]; result.Mid = rgulNum[1]; result.High = rgulNum[2]; } } d1 = result; return false; }
/*** * ScaleResult * * Entry: * rgulRes - Array of ULONGs with value, least-significant first. * iHiRes - Index of last non-zero value in rgulRes. * iScale - Scale factor for this value, range 0 - 2 * DEC_SCALE_MAX * * Purpose: * See if we need to scale the result to fit it in 96 bits. * Perform needed scaling. Adjust scale factor accordingly. * * Exit: * rgulRes updated in place, always 3 ULONGs. * New scale factor returned, -1 if overflow error. * ***********************************************************************/ private static int ScaleResult(uint[] rgulRes, int iHiRes, int iScale) { int iNewScale; int iCur; uint ulPwr; uint ulTmp; uint ulSticky; Split64 sdlTmp = new Split64(); // See if we need to scale the result. The combined scale must // be <= DEC_SCALE_MAX and the upper 96 bits must be zero. // // Start by figuring a lower bound on the scaling needed to make // the upper 96 bits zero. iHiRes is the index into rgulRes[] // of the highest non-zero ULONG. // iNewScale = iHiRes * 32 - 64 - 1; if (iNewScale > 0) { // Find the MSB. // ulTmp = rgulRes[iHiRes]; if ((ulTmp & 0xFFFF0000) == 0) { iNewScale -= 16; ulTmp <<= 16; } if ((ulTmp & 0xFF000000) == 0) { iNewScale -= 8; ulTmp <<= 8; } if ((ulTmp & 0xF0000000) == 0) { iNewScale -= 4; ulTmp <<= 4; } if ((ulTmp & 0xC0000000) == 0) { iNewScale -= 2; ulTmp <<= 2; } if ((ulTmp & 0x80000000) == 0) { iNewScale--; ulTmp <<= 1; } // Multiply bit position by log10(2) to figure it's power of 10. // We scale the log by 256. log(2) = .30103, * 256 = 77. Doing this // with a multiply saves a 96-byte lookup table. The power returned // is <= the power of the number, so we must add one power of 10 // to make it's integer part zero after dividing by 256. // // Note: the result of this multiplication by an approximation of // log10(2) have been exhaustively checked to verify it gives the // correct result. (There were only 95 to check...) // iNewScale = ((iNewScale * 77) >> 8) + 1; // iNewScale = min scale factor to make high 96 bits zero, 0 - 29. // This reduces the scale factor of the result. If it exceeds the // current scale of the result, we'll overflow. // if (iNewScale > iScale) return -1; } else iNewScale = 0; // Make sure we scale by enough to bring the current scale factor // into valid range. // if (iNewScale < iScale - DEC_SCALE_MAX) iNewScale = iScale - DEC_SCALE_MAX; if (iNewScale != 0) { // Scale by the power of 10 given by iNewScale. Note that this is // NOT guaranteed to bring the number within 96 bits -- it could // be 1 power of 10 short. // iScale -= iNewScale; ulSticky = 0; sdlTmp.High32 = 0; // initialize remainder for (;;) { ulSticky |= sdlTmp.High32; // record remainder as sticky bit if (iNewScale > MaxInt32Scale) ulPwr = TenToPowerNine; else ulPwr = s_powers10[iNewScale]; // Compute first quotient. // DivMod64by32 returns quotient in Lo, remainder in Hi. // sdlTmp.int64 = DivMod64by32(rgulRes[iHiRes], ulPwr); rgulRes[iHiRes] = sdlTmp.Low32; iCur = iHiRes - 1; if (iCur >= 0) { // If first quotient was 0, update iHiRes. // if (sdlTmp.Low32 == 0) iHiRes--; // Compute subsequent quotients. // do { sdlTmp.Low32 = rgulRes[iCur]; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, ulPwr); rgulRes[iCur] = sdlTmp.Low32; iCur--; } while (iCur >= 0); } iNewScale -= MaxInt32Scale; if (iNewScale > 0) continue; // scale some more // If we scaled enough, iHiRes would be 2 or less. If not, // divide by 10 more. // if (iHiRes > 2) { iNewScale = 1; iScale--; continue; // scale by 10 } // Round final result. See if remainder >= 1/2 of divisor. // If remainder == 1/2 divisor, round up if odd or sticky bit set. // ulPwr >>= 1; // power of 10 always even if (ulPwr <= sdlTmp.High32 && (ulPwr < sdlTmp.High32 || ((rgulRes[0] & 1) | ulSticky) != 0)) { iCur = -1; while (++rgulRes[++iCur] == 0) ; if (iCur > 2) { // The rounding caused us to carry beyond 96 bits. // Scale by 10 more. // iHiRes = iCur; ulSticky = 0; // no sticky bit sdlTmp.High32 = 0; // or remainder iNewScale = 1; iScale--; continue; // scale by 10 } } // We may have scaled it more than we planned. Make sure the scale // factor hasn't gone negative, indicating overflow. // if (iScale < 0) return -1; return iScale; } // for(;;) } return iScale; }
// Adjust the quotient to deal with an overflow. We need to divide by 10, // feed in the high bit to undo the overflow and then round as required, private static void OverflowUnscale(uint[] rgulQuo, bool fRemainder) { Split64 sdlTmp = new Split64(); // We have overflown, so load the high bit with a one. sdlTmp.High32 = 1; sdlTmp.Low32 = rgulQuo[2]; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, 10); rgulQuo[2] = sdlTmp.Low32; sdlTmp.Low32 = rgulQuo[1]; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, 10); rgulQuo[1] = sdlTmp.Low32; sdlTmp.Low32 = rgulQuo[0]; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, 10); rgulQuo[0] = sdlTmp.Low32; // The remainder is the last digit that does not fit, so we can use it to work out if we need to round up if ((sdlTmp.High32 > 5) || ((sdlTmp.High32 == 5) && (fRemainder || (rgulQuo[0] & 1) != 0))) Add32To96(rgulQuo, 1); }
/*** * Div128By96 * * Entry: * rgulNum - Pointer to 128-bit dividend as array of ULONGs, least-sig first * rgulDen - Pointer to 96-bit divisor. * * Purpose: * Do partial divide, yielding 32-bit result and 96-bit remainder. * Top divisor ULONG must be larger than top dividend ULONG. This is * assured in the initial call because the divisor is normalized * and the dividend can't be. In subsequent calls, the remainder * is multiplied by 10^9 (max), so it can be no more than 1/4 of * the divisor which is effectively multiplied by 2^32 (4 * 10^9). * * Exit: * Remainder overwrites lower 96-bits of dividend. * Returns quotient. * * Exceptions: * None. * ***********************************************************************/ private static uint Div128By96(uint[] rgulNum, uint[] rgulDen) { Split64 sdlQuo = new Split64(); Split64 sdlNum = new Split64(); Split64 sdlProd1 = new Split64(); Split64 sdlProd2 = new Split64(); sdlNum.Low32 = rgulNum[0]; sdlNum.High32 = rgulNum[1]; if (rgulNum[3] == 0 && rgulNum[2] < rgulDen[2]) // Result is zero. Entire dividend is remainder. // return 0; // DivMod64by32 returns quotient in Lo, remainder in Hi. // sdlQuo.Low32 = rgulNum[2]; sdlQuo.High32 = rgulNum[3]; sdlQuo.int64 = DivMod64by32(sdlQuo.int64, rgulDen[2]); // Compute full remainder, rem = dividend - (quo * divisor). // sdlProd1.int64 = UInt32x32To64(sdlQuo.Low32, rgulDen[0]); // quo * lo divisor sdlProd2.int64 = UInt32x32To64(sdlQuo.Low32, rgulDen[1]); // quo * mid divisor sdlProd2.int64 += sdlProd1.High32; sdlProd1.High32 = sdlProd2.Low32; sdlNum.int64 -= sdlProd1.int64; rgulNum[2] = sdlQuo.High32 - sdlProd2.High32; // sdlQuo.Hi is remainder // Propagate carries // bool fallthru = false; if (sdlNum.int64 > ~sdlProd1.int64) { rgulNum[2]--; if (rgulNum[2] >= ~sdlProd2.High32) fallthru = true; } if (fallthru || rgulNum[2] > ~sdlProd2.High32) { // Remainder went negative. Add divisor back in until it's positive, // a max of 2 times. // sdlProd1.Low32 = rgulDen[0]; sdlProd1.High32 = rgulDen[1]; for (;;) { sdlQuo.Low32--; sdlNum.int64 += sdlProd1.int64; rgulNum[2] += rgulDen[2]; if (sdlNum.int64 < sdlProd1.int64) { // Detected carry. Check for carry out of top // before adding it in. // if (rgulNum[2]++ < rgulDen[2]) break; } if (rgulNum[2] < rgulDen[2]) break; // detected carry } } rgulNum[0] = sdlNum.Low32; rgulNum[1] = sdlNum.High32; return sdlQuo.Low32; }
/*** * IncreaseScale * * Entry: * rgulNum - Pointer to 96-bit number as array of ULONGs, least-sig first * ulPwr - Scale factor to multiply by * * Purpose: * Multiply the two numbers. The low 96 bits of the result overwrite * the input. The last 32 bits of the product are the return value. * * Exit: * Returns highest 32 bits of product. * * Exceptions: * None. * ***********************************************************************/ private static uint IncreaseScale(uint[] rgulNum, uint ulPwr) { Split64 sdlTmp = new Split64(); sdlTmp.int64 = UInt32x32To64(rgulNum[0], ulPwr); rgulNum[0] = sdlTmp.Low32; sdlTmp.int64 = UInt32x32To64(rgulNum[1], ulPwr) + sdlTmp.High32; rgulNum[1] = sdlTmp.Low32; sdlTmp.int64 = UInt32x32To64(rgulNum[2], ulPwr) + sdlTmp.High32; rgulNum[2] = sdlTmp.Low32; return sdlTmp.High32; }
/*** * Div96By32 * * Entry: * rgulNum - Pointer to 96-bit dividend as array of ULONGs, least-sig first * ulDen - 32-bit divisor. * * Purpose: * Do full divide, yielding 96-bit result and 32-bit remainder. * * Exit: * Quotient overwrites dividend. * Returns remainder. * * Exceptions: * None. * ***********************************************************************/ private static uint Div96By32(uint[] rgulNum, uint ulDen) { Split64 sdlTmp = new Split64(); sdlTmp.High32 = 0; if (rgulNum[2] != 0) goto Div3Word; if (rgulNum[1] >= ulDen) goto Div2Word; sdlTmp.High32 = rgulNum[1]; rgulNum[1] = 0; goto Div1Word; Div3Word: sdlTmp.Low32 = rgulNum[2]; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, ulDen); rgulNum[2] = sdlTmp.Low32; Div2Word: sdlTmp.Low32 = rgulNum[1]; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, ulDen); rgulNum[1] = sdlTmp.Low32; Div1Word: sdlTmp.Low32 = rgulNum[0]; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, ulDen); rgulNum[0] = sdlTmp.Low32; return sdlTmp.High32; }
/*** * Div96By64 * * Entry: * rgulNum - Pointer to 96-bit dividend as array of ULONGs, least-sig first * sdlDen - 64-bit divisor. * * Purpose: * Do partial divide, yielding 32-bit result and 64-bit remainder. * Divisor must be larger than upper 64 bits of dividend. * * Exit: * Remainder overwrites lower 64-bits of dividend. * Returns quotient. * * Exceptions: * None. * ***********************************************************************/ private static uint Div96By64(System.Collections.Generic.IList<uint> rgulNum, Split64 sdlDen) { Split64 sdlQuo = new Split64(); Split64 sdlNum = new Split64(); Split64 sdlProd = new Split64(); sdlNum.Low32 = rgulNum[0]; if (rgulNum[2] >= sdlDen.High32) { // Divide would overflow. Assume a quotient of 2^32, and set // up remainder accordingly. // sdlNum.High32 = rgulNum[1] - sdlDen.Low32; sdlQuo.Low32 = 0; // Remainder went negative. Add divisor back in until it's positive, // a max of 2 times. // do { sdlQuo.Low32--; sdlNum.int64 += sdlDen.int64; } while (sdlNum.int64 >= sdlDen.int64); goto Done; } // Hardware divide won't overflow // if (rgulNum[2] == 0 && rgulNum[1] < sdlDen.High32) // Result is zero. Entire dividend is remainder. // return 0; // DivMod64by32 returns quotient in Lo, remainder in Hi. // sdlQuo.Low32 = rgulNum[1]; sdlQuo.High32 = rgulNum[2]; sdlQuo.int64 = DivMod64by32(sdlQuo.int64, sdlDen.High32); sdlNum.High32 = sdlQuo.High32; // remainder // Compute full remainder, rem = dividend - (quo * divisor). // sdlProd.int64 = UInt32x32To64(sdlQuo.Low32, sdlDen.Low32); // quo * lo divisor sdlNum.int64 -= sdlProd.int64; if (sdlNum.int64 > ~sdlProd.int64) { // Remainder went negative. Add divisor back in until it's positive, // a max of 2 times. // do { sdlQuo.Low32--; sdlNum.int64 += sdlDen.int64; } while (sdlNum.int64 >= sdlDen.int64); } Done: rgulNum[0] = sdlNum.Low32; rgulNum[1] = sdlNum.High32; return sdlQuo.Low32; }
private static uint FullDiv64By32(ref ulong pdlNum, uint ulDen) { Split64 sdlTmp = new Split64(); Split64 sdlRes = new Split64(); sdlTmp.int64 = pdlNum; sdlRes.High32 = 0; if (sdlTmp.High32 >= ulDen) { // DivMod64by32 returns quotient in Lo, remainder in Hi. // sdlRes.Low32 = sdlTmp.High32; sdlRes.int64 = DivMod64by32(sdlRes.int64, ulDen); sdlTmp.High32 = sdlRes.High32; sdlRes.High32 = sdlRes.Low32; } sdlTmp.int64 = DivMod64by32(sdlTmp.int64, ulDen); sdlRes.Low32 = sdlTmp.Low32; pdlNum = sdlRes.int64; return sdlTmp.High32; }
private static ulong UInt64x64To128(Split64 sdlOp1, Split64 sdlOp2, out ulong dlHi) { Split64 sdlTmp1 = new Split64(); Split64 sdlTmp2 = new Split64(); Split64 sdlTmp3 = new Split64(); sdlTmp1.int64 = UInt32x32To64(sdlOp1.Low32, sdlOp2.Low32); // lo partial prod sdlTmp2.int64 = UInt32x32To64(sdlOp1.Low32, sdlOp2.High32); // mid 1 partial prod sdlTmp1.High32 += sdlTmp2.Low32; if (sdlTmp1.High32 < sdlTmp2.Low32) // test for carry sdlTmp2.High32++; sdlTmp3.int64 = UInt32x32To64(sdlOp1.High32, sdlOp2.High32) + sdlTmp2.High32; sdlTmp2.int64 = UInt32x32To64(sdlOp1.High32, sdlOp2.Low32); sdlTmp1.High32 += sdlTmp2.Low32; if (sdlTmp1.High32 < sdlTmp2.Low32) // test for carry sdlTmp2.High32++; sdlTmp3.int64 += sdlTmp2.High32; dlHi = sdlTmp3.int64; return sdlTmp1.int64; }
private static ulong DivMod32by32(uint num, uint den) { Split64 sdl = new Split64(); sdl.Low32 = num / den; sdl.High32 = num % den; return sdl.int64; }
// VarDecDiv divides two decimal values. On return, d1 contains the result // of the operation. internal static void VarDecDiv(ref Decimal d1, ref Decimal d2) { uint[] rgulQuo = new uint[3]; uint[] rgulQuoSave = new uint[3]; uint[] rgulRem = new uint[4]; uint[] rgulDivisor = new uint[3]; uint ulPwr; uint ulTmp; uint ulTmp1; Split64 sdlTmp = new Split64(); Split64 sdlDivisor = new Split64(); int iScale; int iCurScale; bool fUnscale; iScale = d1.Scale - d2.Scale; fUnscale = false; rgulDivisor[0] = d2.Low; rgulDivisor[1] = d2.Mid; rgulDivisor[2] = d2.High; if (rgulDivisor[1] == 0 && rgulDivisor[2] == 0) { // Divisor is only 32 bits. Easy divide. // if (rgulDivisor[0] == 0) throw new DivideByZeroException(SR.Overflow_Decimal); rgulQuo[0] = d1.Low; rgulQuo[1] = d1.Mid; rgulQuo[2] = d1.High; rgulRem[0] = Div96By32(rgulQuo, rgulDivisor[0]); for (;;) { if (rgulRem[0] == 0) { if (iScale < 0) { iCurScale = Math.Min(9, -iScale); goto HaveScale; } break; } // We need to unscale if and only if we have a non-zero remainder fUnscale = true; // We have computed a quotient based on the natural scale // ( <dividend scale> - <divisor scale> ). We have a non-zero // remainder, so now we should increase the scale if possible to // include more quotient bits. // // If it doesn't cause overflow, we'll loop scaling by 10^9 and // computing more quotient bits as long as the remainder stays // non-zero. If scaling by that much would cause overflow, we'll // drop out of the loop and scale by as much as we can. // // Scaling by 10^9 will overflow if rgulQuo[2].rgulQuo[1] >= 2^32 / 10^9 // = 4.294 967 296. So the upper limit is rgulQuo[2] == 4 and // rgulQuo[1] == 0.294 967 296 * 2^32 = 1,266,874,889.7+. Since // quotient bits in rgulQuo[0] could be all 1's, then 1,266,874,888 // is the largest value in rgulQuo[1] (when rgulQuo[2] == 4) that is // assured not to overflow. // iCurScale = SearchScale(rgulQuo[2], rgulQuo[1], rgulQuo[0], iScale); if (iCurScale == 0) { // No more scaling to be done, but remainder is non-zero. // Round quotient. // ulTmp = rgulRem[0] << 1; if (ulTmp < rgulRem[0] || (ulTmp >= rgulDivisor[0] && (ulTmp > rgulDivisor[0] || (rgulQuo[0] & 1) != 0))) RoundUp(rgulQuo, ref iScale); break; } if (iCurScale < 0) throw new OverflowException(SR.Overflow_Decimal); HaveScale: ulPwr = s_powers10[iCurScale]; iScale += iCurScale; if (IncreaseScale(rgulQuo, ulPwr) != 0) throw new OverflowException(SR.Overflow_Decimal); sdlTmp.int64 = DivMod64by32(UInt32x32To64(rgulRem[0], ulPwr), rgulDivisor[0]); rgulRem[0] = sdlTmp.High32; if (!Add32To96(rgulQuo, sdlTmp.Low32)) { if (iScale == 0) throw new OverflowException(SR.Overflow_Decimal); iScale--; OverflowUnscale(rgulQuo, (rgulRem[0] != 0)); break; } } // for (;;) } else { // Divisor has bits set in the upper 64 bits. // // Divisor must be fully normalized (shifted so bit 31 of the most // significant ULONG is 1). Locate the MSB so we know how much to // normalize by. The dividend will be shifted by the same amount so // the quotient is not changed. // if (rgulDivisor[2] == 0) ulTmp = rgulDivisor[1]; else ulTmp = rgulDivisor[2]; iCurScale = 0; if ((ulTmp & 0xFFFF0000) == 0) { iCurScale += 16; ulTmp <<= 16; } if ((ulTmp & 0xFF000000) == 0) { iCurScale += 8; ulTmp <<= 8; } if ((ulTmp & 0xF0000000) == 0) { iCurScale += 4; ulTmp <<= 4; } if ((ulTmp & 0xC0000000) == 0) { iCurScale += 2; ulTmp <<= 2; } if ((ulTmp & 0x80000000) == 0) { iCurScale++; ulTmp <<= 1; } // Shift both dividend and divisor left by iCurScale. // sdlTmp.int64 = d1.Low64 << iCurScale; rgulRem[0] = sdlTmp.Low32; rgulRem[1] = sdlTmp.High32; sdlTmp.Low32 = d1.Mid; sdlTmp.High32 = d1.High; sdlTmp.int64 <<= iCurScale; rgulRem[2] = sdlTmp.High32; rgulRem[3] = (d1.High >> (31 - iCurScale)) >> 1; sdlDivisor.Low32 = rgulDivisor[0]; sdlDivisor.High32 = rgulDivisor[1]; sdlDivisor.int64 <<= iCurScale; if (rgulDivisor[2] == 0) { // Have a 64-bit divisor in sdlDivisor. The remainder // (currently 96 bits spread over 4 ULONGs) will be < divisor. // sdlTmp.Low32 = rgulRem[2]; sdlTmp.High32 = rgulRem[3]; rgulQuo[2] = 0; rgulQuo[1] = Div96By64(new ArraySegment<uint>(rgulRem, 1, 3), sdlDivisor); rgulQuo[0] = Div96By64(rgulRem, sdlDivisor); for (;;) { if ((rgulRem[0] | rgulRem[1]) == 0) { if (iScale < 0) { iCurScale = Math.Min(9, -iScale); goto HaveScale64; } break; } // We need to unscale if and only if we have a non-zero remainder fUnscale = true; // Remainder is non-zero. Scale up quotient and remainder by // powers of 10 so we can compute more significant bits. // iCurScale = SearchScale(rgulQuo[2], rgulQuo[1], rgulQuo[0], iScale); if (iCurScale == 0) { // No more scaling to be done, but remainder is non-zero. // Round quotient. // sdlTmp.Low32 = rgulRem[0]; sdlTmp.High32 = rgulRem[1]; if (sdlTmp.High32 >= 0x80000000 || (sdlTmp.int64 <<= 1) > sdlDivisor.int64 || (sdlTmp.int64 == sdlDivisor.int64 && (rgulQuo[0] & 1) != 0)) RoundUp(rgulQuo, ref iScale); break; } if (iCurScale < 0) throw new OverflowException(SR.Overflow_Decimal); HaveScale64: ulPwr = s_powers10[iCurScale]; iScale += iCurScale; if (IncreaseScale(rgulQuo, ulPwr) != 0) throw new OverflowException(SR.Overflow_Decimal); rgulRem[2] = 0; // rem is 64 bits, IncreaseScale uses 96 IncreaseScale(rgulRem, ulPwr); ulTmp = Div96By64(rgulRem, sdlDivisor); if (!Add32To96(rgulQuo, ulTmp)) { if (iScale == 0) throw new OverflowException(SR.Overflow_Decimal); iScale--; OverflowUnscale(rgulQuo, (rgulRem[0] != 0 || rgulRem[1] != 0)); break; } } // for (;;) } else { // Have a 96-bit divisor in rgulDivisor[]. // // Start by finishing the shift left by iCurScale. // sdlTmp.Low32 = rgulDivisor[1]; sdlTmp.High32 = rgulDivisor[2]; sdlTmp.int64 <<= iCurScale; rgulDivisor[0] = sdlDivisor.Low32; rgulDivisor[1] = sdlDivisor.High32; rgulDivisor[2] = sdlTmp.High32; // The remainder (currently 96 bits spread over 4 ULONGs) // will be < divisor. // rgulQuo[2] = 0; rgulQuo[1] = 0; rgulQuo[0] = Div128By96(rgulRem, rgulDivisor); for (;;) { if ((rgulRem[0] | rgulRem[1] | rgulRem[2]) == 0) { if (iScale < 0) { iCurScale = Math.Min(9, -iScale); goto HaveScale96; } break; } // We need to unscale if and only if we have a non-zero remainder fUnscale = true; // Remainder is non-zero. Scale up quotient and remainder by // powers of 10 so we can compute more significant bits. // iCurScale = SearchScale(rgulQuo[2], rgulQuo[1], rgulQuo[0], iScale); if (iCurScale == 0) { // No more scaling to be done, but remainder is non-zero. // Round quotient. // if (rgulRem[2] >= 0x80000000) { RoundUp(rgulQuo, ref iScale); break; } ulTmp = (rgulRem[0] > 0x80000000) ? 1u : 0u; ulTmp1 = (rgulRem[1] > 0x80000000) ? 1u : 0u; rgulRem[0] <<= 1; rgulRem[1] = (rgulRem[1] << 1) + ulTmp; rgulRem[2] = (rgulRem[2] << 1) + ulTmp1; if (rgulRem[2] > rgulDivisor[2] || rgulRem[2] == rgulDivisor[2] && (rgulRem[1] > rgulDivisor[1] || rgulRem[1] == rgulDivisor[1] && (rgulRem[0] > rgulDivisor[0] || rgulRem[0] == rgulDivisor[0] && (rgulQuo[0] & 1) != 0))) RoundUp(rgulQuo, ref iScale); break; } if (iCurScale < 0) throw new OverflowException(SR.Overflow_Decimal); HaveScale96: ulPwr = s_powers10[iCurScale]; iScale += iCurScale; if (IncreaseScale(rgulQuo, ulPwr) != 0) throw new OverflowException(SR.Overflow_Decimal); rgulRem[3] = IncreaseScale(rgulRem, ulPwr); ulTmp = Div128By96(rgulRem, rgulDivisor); if (!Add32To96(rgulQuo, ulTmp)) { if (iScale == 0) throw new OverflowException(SR.Overflow_Decimal); iScale--; OverflowUnscale(rgulQuo, (rgulRem[0] != 0 || rgulRem[1] != 0 || rgulRem[2] != 0 || rgulRem[3] != 0)); break; } } // for (;;) } } // We need to unscale if and only if we have a non-zero remainder if (fUnscale) { // Try extracting any extra powers of 10 we may have // added. We do this by trying to divide out 10^8, 10^4, 10^2, and 10^1. // If a division by one of these powers returns a zero remainder, then // we keep the quotient. If the remainder is not zero, then we restore // the previous value. // // Since 10 = 2 * 5, there must be a factor of 2 for every power of 10 // we can extract. We use this as a quick test on whether to try a // given power. // while ((rgulQuo[0] & 0xFF) == 0 && iScale >= 8) { rgulQuoSave[0] = rgulQuo[0]; rgulQuoSave[1] = rgulQuo[1]; rgulQuoSave[2] = rgulQuo[2]; if (Div96By32(rgulQuoSave, 100000000) == 0) { rgulQuo[0] = rgulQuoSave[0]; rgulQuo[1] = rgulQuoSave[1]; rgulQuo[2] = rgulQuoSave[2]; iScale -= 8; } else break; } if ((rgulQuo[0] & 0xF) == 0 && iScale >= 4) { rgulQuoSave[0] = rgulQuo[0]; rgulQuoSave[1] = rgulQuo[1]; rgulQuoSave[2] = rgulQuo[2]; if (Div96By32(rgulQuoSave, 10000) == 0) { rgulQuo[0] = rgulQuoSave[0]; rgulQuo[1] = rgulQuoSave[1]; rgulQuo[2] = rgulQuoSave[2]; iScale -= 4; } } if ((rgulQuo[0] & 3) == 0 && iScale >= 2) { rgulQuoSave[0] = rgulQuo[0]; rgulQuoSave[1] = rgulQuo[1]; rgulQuoSave[2] = rgulQuo[2]; if (Div96By32(rgulQuoSave, 100) == 0) { rgulQuo[0] = rgulQuoSave[0]; rgulQuo[1] = rgulQuoSave[1]; rgulQuo[2] = rgulQuoSave[2]; iScale -= 2; } } if ((rgulQuo[0] & 1) == 0 && iScale >= 1) { rgulQuoSave[0] = rgulQuo[0]; rgulQuoSave[1] = rgulQuo[1]; rgulQuoSave[2] = rgulQuo[2]; if (Div96By32(rgulQuoSave, 10) == 0) { rgulQuo[0] = rgulQuoSave[0]; rgulQuo[1] = rgulQuoSave[1]; rgulQuo[2] = rgulQuoSave[2]; iScale -= 1; } } } d1.Sign = d1.Sign ^ d2.Sign; d1.High = rgulQuo[2]; d1.Mid = rgulQuo[1]; d1.Low = rgulQuo[0]; d1.Scale = iScale; }
private static ulong DivMod64by32(ulong num, uint den) { Split64 sdl = new Split64(); sdl.Low32 = (uint)(num / den); sdl.High32 = (uint)(num % den); return sdl.int64; }
//********************************************************************** // VarDecFromR8 - Convert double to Decimal //********************************************************************** internal static void VarDecFromR8(double input, out Decimal pdecOut) { int iExp; // number of bits to left of binary point int iPower; // power-of-10 scale factor Split64 sdlMant = new Split64(); Split64 sdlLo = new Split64(); double dbl; int lmax, cur; // temps used during scale reduction uint ulPwrCur; uint ulQuo; pdecOut = new Decimal(); // The most we can scale by is 10^28, which is just slightly more // than 2^93. So a float with an exponent of -94 could just // barely reach 0.5, but smaller exponents will always round to zero. // iExp = (int)(GetExponent(input) - DBLBIAS); if (iExp < -94) return; // result should be zeroed out if (iExp > 96) throw new OverflowException(SR.Overflow_Decimal); dbl = input; if (dbl < 0) dbl *= -1; // Round the input to a 15-digit integer. The R8 format has // only 15 digits of precision, and we want to keep garbage digits // out of the Decimal were making. // // Calculate max power of 10 input value could have by multiplying // the exponent by log10(2). Using scaled integer multiplcation, // log10(2) * 2 ^ 16 = .30103 * 65536 = 19728.3. // iPower = 14 - ((iExp * 19728) >> 16); if (iPower >= 0) { // We have less than 15 digits, scale input up. // if (iPower > DEC_SCALE_MAX) iPower = DEC_SCALE_MAX; dbl = dbl * s_doublePowers10[iPower]; } else { if (iPower != -1 || dbl >= 1E15) dbl = dbl / GetDoublePower10(-iPower); else iPower = 0; // didn't scale it } System.Diagnostics.Debug.Assert(dbl < 1E15); if (dbl < 1E14 && iPower < DEC_SCALE_MAX) { dbl *= 10; iPower++; System.Diagnostics.Debug.Assert(dbl >= 1E14); } // Round to int64 // sdlMant.int64 = (ulong)dbl; dbl -= (double)sdlMant.int64; // dif between input & integer if (dbl > 0.5 || dbl == 0.5 && (sdlMant.Low32 & 1) != 0) sdlMant.int64++; if (sdlMant.int64 == 0) return; // result should be zeroed out if (iPower < 0) { // Add -iPower factors of 10, -iPower <= (29 - 15) = 14. // iPower = -iPower; if (iPower < 10) { sdlLo.int64 = UInt32x32To64(sdlMant.Low32, s_powers10[iPower]); sdlMant.int64 = UInt32x32To64(sdlMant.High32, s_powers10[iPower]); sdlMant.int64 += sdlLo.High32; sdlLo.High32 = sdlMant.Low32; sdlMant.Low32 = sdlMant.High32; } else { // Have a big power of 10. // System.Diagnostics.Debug.Assert(iPower <= 14); ulong tmpValue; sdlLo.int64 = UInt64x64To128(sdlMant, new Split64((ulong)s_doublePowers10[iPower]), out tmpValue); sdlMant.int64 = tmpValue; if (sdlMant.High32 != 0) throw new OverflowException(SR.Overflow_Decimal); } pdecOut.Low64 = sdlLo.int64; pdecOut.High = sdlMant.Low32; pdecOut.Scale = 0; } else { // Factor out powers of 10 to reduce the scale, if possible. // The maximum number we could factor out would be 14. This // comes from the fact we have a 15-digit number, and the // MSD must be non-zero -- but the lower 14 digits could be // zero. Note also the scale factor is never negative, so // we can't scale by any more than the power we used to // get the integer. // // DivMod64by32 returns the quotient in Lo, the remainder in Hi. // lmax = iPower < 14 ? iPower : 14; // lmax is the largest power of 10 to try, lmax <= 14. // We'll try powers 8, 4, 2, and 1 unless they're too big. // for (cur = 8; cur > 0; cur >>= 1) { if (cur > lmax) continue; ulPwrCur = s_powers10[cur]; if (sdlMant.High32 >= ulPwrCur) { // Overflow if we try to divide in one step. // sdlLo.int64 = DivMod64by32(sdlMant.High32, ulPwrCur); ulQuo = sdlLo.Low32; sdlLo.Low32 = sdlMant.Low32; sdlLo.int64 = DivMod64by32(sdlLo.int64, ulPwrCur); } else { ulQuo = 0; sdlLo.int64 = DivMod64by32(sdlMant.int64, ulPwrCur); } if (sdlLo.High32 == 0) { sdlMant.High32 = ulQuo; sdlMant.Low32 = sdlLo.Low32; iPower -= cur; lmax -= cur; } } pdecOut.High = 0; pdecOut.Scale = iPower; pdecOut.Low64 = sdlMant.int64; } pdecOut.Sign = input < 0; }
//********************************************************************** // VarDecFromR4 - Convert float to Decimal //********************************************************************** internal static void VarDecFromR4(float input, out Decimal pdecOut) { int iExp; // number of bits to left of binary point int iPower; uint ulMant; double dbl; Split64 sdlLo = new Split64(); Split64 sdlHi = new Split64(); int lmax, cur; // temps used during scale reduction pdecOut = new Decimal(); // The most we can scale by is 10^28, which is just slightly more // than 2^93. So a float with an exponent of -94 could just // barely reach 0.5, but smaller exponents will always round to zero. // iExp = (int)(GetExponent(input) - SNGBIAS); if (iExp < -94) return; // result should be zeroed out if (iExp > 96) throw new OverflowException(SR.Overflow_Decimal); // Round the input to a 7-digit integer. The R4 format has // only 7 digits of precision, and we want to keep garbage digits // out of the Decimal were making. // // Calculate max power of 10 input value could have by multiplying // the exponent by log10(2). Using scaled integer multiplcation, // log10(2) * 2 ^ 16 = .30103 * 65536 = 19728.3. // dbl = input; if (dbl < 0) dbl *= -1; iPower = 6 - ((iExp * 19728) >> 16); if (iPower >= 0) { // We have less than 7 digits, scale input up. // if (iPower > DEC_SCALE_MAX) iPower = DEC_SCALE_MAX; dbl = dbl * s_doublePowers10[iPower]; } else { if (iPower != -1 || dbl >= 1E7) dbl = dbl / GetDoublePower10(-iPower); else iPower = 0; // didn't scale it } System.Diagnostics.Debug.Assert(dbl < 1E7); if (dbl < 1E6 && iPower < DEC_SCALE_MAX) { dbl *= 10; iPower++; System.Diagnostics.Debug.Assert(dbl >= 1E6); } // Round to integer // ulMant = (uint)dbl; dbl -= (double)ulMant; // difference between input & integer if (dbl > 0.5 || (dbl == 0.5) && (ulMant & 1) != 0) ulMant++; if (ulMant == 0) return; // result should be zeroed out if (iPower < 0) { // Add -iPower factors of 10, -iPower <= (29 - 7) = 22. // iPower = -iPower; if (iPower < 10) { pdecOut.Low64 = UInt32x32To64(ulMant, s_powers10[iPower]); pdecOut.High = 0; } else { // Have a big power of 10. // if (iPower > 18) { sdlLo.int64 = UInt32x32To64(ulMant, s_powers10[iPower - 18]); ulong tmplong; sdlLo.int64 = UInt64x64To128(sdlLo, s_tenToPowerEighteen, out tmplong); sdlHi.int64 = tmplong; if (sdlHi.High32 != 0) throw new OverflowException(SR.Overflow_Decimal); } else { sdlLo.int64 = UInt32x32To64(ulMant, s_powers10[iPower - 9]); sdlHi.int64 = UInt32x32To64(TenToPowerNine, sdlLo.High32); sdlLo.int64 = UInt32x32To64(TenToPowerNine, sdlLo.Low32); sdlHi.int64 += sdlLo.High32; sdlLo.High32 = sdlHi.Low32; sdlHi.Low32 = sdlHi.High32; } pdecOut.Low64 = sdlLo.int64; pdecOut.High = sdlHi.Low32; } pdecOut.Scale = 0; } else { // Factor out powers of 10 to reduce the scale, if possible. // The maximum number we could factor out would be 6. This // comes from the fact we have a 7-digit number, and the // MSD must be non-zero -- but the lower 6 digits could be // zero. Note also the scale factor is never negative, so // we can't scale by any more than the power we used to // get the integer. // // DivMod32by32 returns the quotient in Lo, the remainder in Hi. // lmax = iPower < 6 ? iPower : 6; // lmax is the largest power of 10 to try, lmax <= 6. // We'll try powers 4, 2, and 1 unless they're too big. // for (cur = 4; cur > 0; cur >>= 1) { if (cur > lmax) continue; sdlLo.int64 = DivMod32by32(ulMant, s_powers10[cur]); if (sdlLo.High32 == 0) { ulMant = sdlLo.Low32; iPower -= cur; lmax -= cur; } } pdecOut.Low = ulMant; pdecOut.Mid = 0; pdecOut.High = 0; pdecOut.Scale = iPower; } pdecOut.Sign = input < 0; }
//********************************************************************** // VarDecMul - Decimal Multiply //********************************************************************** internal static void VarDecMul(ref Decimal pdecL, ref Decimal pdecR, out Decimal pdecRes) { Split64 sdlTmp = new Split64(); Split64 sdlTmp2 = new Split64(); Split64 sdlTmp3 = new Split64(); int iScale; int iHiProd; uint ulPwr; uint ulRemLo; uint ulRemHi; uint[] rgulProd = new uint[6]; pdecRes = new Decimal(); iScale = pdecL.Scale + pdecR.Scale; if ((pdecL.High | pdecL.Mid | pdecR.High | pdecR.Mid) == 0) { // Upper 64 bits are zero. // sdlTmp.int64 = UInt32x32To64(pdecL.Low, pdecR.Low); if (iScale > DEC_SCALE_MAX) { // Result iScale is too big. Divide result by power of 10 to reduce it. // If the amount to divide by is > 19 the result is guaranteed // less than 1/2. [max value in 64 bits = 1.84E19] // iScale -= DEC_SCALE_MAX; if (iScale > 19) { //DECIMAL_SETZERO(*pdecRes); return; } if (iScale > MaxInt32Scale) { // Divide by 1E10 first, to get the power down to a 32-bit quantity. // 1E10 itself doesn't fit in 32 bits, so we'll divide by 2.5E9 now // then multiply the next divisor by 4 (which will be a max of 4E9). // ulRemLo = FullDiv64By32(ref sdlTmp.int64, TenToPowerTenDiv4); ulPwr = s_powers10[iScale - 10] << 2; } else { ulPwr = s_powers10[iScale]; ulRemLo = 0; } // Power to divide by fits in 32 bits. // ulRemHi = FullDiv64By32(ref sdlTmp.int64, ulPwr); // Round result. See if remainder >= 1/2 of divisor. // Divisor is a power of 10, so it is always even. // ulPwr >>= 1; if (ulRemHi >= ulPwr && (ulRemHi > ulPwr || (ulRemLo | (sdlTmp.Low32 & 1)) > 0)) sdlTmp.int64++; iScale = DEC_SCALE_MAX; } pdecRes.Low64 = sdlTmp.int64; pdecRes.High = 0; } else { // At least one operand has bits set in the upper 64 bits. // // Compute and accumulate the 9 partial products into a // 192-bit (24-byte) result. // // [l-h][l-m][l-l] left high, middle, low // x [r-h][r-m][r-l] right high, middle, low // ------------------------------ // // [0-h][0-l] l-l * r-l // [1ah][1al] l-l * r-m // [1bh][1bl] l-m * r-l // [2ah][2al] l-m * r-m // [2bh][2bl] l-l * r-h // [2ch][2cl] l-h * r-l // [3ah][3al] l-m * r-h // [3bh][3bl] l-h * r-m // [4-h][4-l] l-h * r-h // ------------------------------ // [p-5][p-4][p-3][p-2][p-1][p-0] prod[] array // sdlTmp.int64 = UInt32x32To64(pdecL.Low, pdecR.Low); rgulProd[0] = sdlTmp.Low32; sdlTmp2.int64 = UInt32x32To64(pdecL.Low, pdecR.Mid) + sdlTmp.High32; sdlTmp.int64 = UInt32x32To64(pdecL.Mid, pdecR.Low); sdlTmp.int64 += sdlTmp2.int64; // this could generate carry rgulProd[1] = sdlTmp.Low32; if (sdlTmp.int64 < sdlTmp2.int64) // detect carry sdlTmp2.High32 = 1; else sdlTmp2.High32 = 0; sdlTmp2.Low32 = sdlTmp.High32; sdlTmp.int64 = UInt32x32To64(pdecL.Mid, pdecR.Mid) + sdlTmp2.int64; if ((pdecL.High | pdecR.High) > 0) { // Highest 32 bits is non-zero. Calculate 5 more partial products. // sdlTmp2.int64 = UInt32x32To64(pdecL.Low, pdecR.High); sdlTmp.int64 += sdlTmp2.int64; // this could generate carry if (sdlTmp.int64 < sdlTmp2.int64) // detect carry sdlTmp3.High32 = 1; else sdlTmp3.High32 = 0; sdlTmp2.int64 = UInt32x32To64(pdecL.High, pdecR.Low); sdlTmp.int64 += sdlTmp2.int64; // this could generate carry rgulProd[2] = sdlTmp.Low32; if (sdlTmp.int64 < sdlTmp2.int64) // detect carry sdlTmp3.High32++; sdlTmp3.Low32 = sdlTmp.High32; sdlTmp.int64 = UInt32x32To64(pdecL.Mid, pdecR.High); sdlTmp.int64 += sdlTmp3.int64; // this could generate carry if (sdlTmp.int64 < sdlTmp3.int64) // detect carry sdlTmp3.High32 = 1; else sdlTmp3.High32 = 0; sdlTmp2.int64 = UInt32x32To64(pdecL.High, pdecR.Mid); sdlTmp.int64 += sdlTmp2.int64; // this could generate carry rgulProd[3] = sdlTmp.Low32; if (sdlTmp.int64 < sdlTmp2.int64) // detect carry sdlTmp3.High32++; sdlTmp3.Low32 = sdlTmp.High32; sdlTmp.int64 = UInt32x32To64(pdecL.High, pdecR.High) + sdlTmp3.int64; rgulProd[4] = sdlTmp.Low32; rgulProd[5] = sdlTmp.High32; iHiProd = 5; } else { rgulProd[2] = sdlTmp.Low32; rgulProd[3] = sdlTmp.High32; iHiProd = 3; } // Check for leading zero ULONGs on the product // while (rgulProd[iHiProd] == 0) { iHiProd--; if (iHiProd < 0) return; } iScale = ScaleResult(rgulProd, iHiProd, iScale); if (iScale == -1) throw new OverflowException(SR.Overflow_Decimal); pdecRes.Low = rgulProd[0]; pdecRes.Mid = rgulProd[1]; pdecRes.High = rgulProd[2]; } pdecRes.Sign = pdecR.Sign ^ pdecL.Sign; pdecRes.Scale = (char)iScale; }
// Returns true if we overflowed private static bool AlignedScale(ref Decimal value) { Split64 sdlTmp = new Split64(); // Divide the value by 10, dropping the scale factor. // if (value.Scale == 0) return true; value.Scale--; sdlTmp.Low32 = value.High; sdlTmp.High32 = 1; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, 10); value.High = sdlTmp.Low32; sdlTmp.Low32 = value.Mid; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, 10); value.Mid = sdlTmp.Low32; sdlTmp.Low32 = value.Low; sdlTmp.int64 = DivMod64by32(sdlTmp.int64, 10); value.Low = sdlTmp.Low32; // See if we need to round up. // if (sdlTmp.High32 >= 5 && (sdlTmp.High32 > 5 || (value.Low & 1) != 0)) { value.Low64 = value.Low64 + 1; if (value.Low64 == 0) value.High++; } return false; }