diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/Decimal.DecCalc.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/Decimal.DecCalc.cs | 2648 |
1 files changed, 2648 insertions, 0 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Decimal.DecCalc.cs b/src/System.Private.CoreLib/shared/System/Decimal.DecCalc.cs new file mode 100644 index 000000000..22574c106 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Decimal.DecCalc.cs @@ -0,0 +1,2648 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; +using X86 = System.Runtime.Intrinsics.X86; + +namespace System +{ + public partial struct Decimal + { + // Low level accessors used by a DecCalc and formatting + internal uint High => (uint)hi; + internal uint Low => (uint)lo; + internal uint Mid => (uint)mid; + + internal bool IsNegative => flags < 0; + + internal int Scale => (byte)(flags >> ScaleShift); + +#if BIGENDIAN + private ulong Low64 => ((ulong)Mid << 32) | Low; +#else + private ulong Low64 => Unsafe.As<int, ulong>(ref Unsafe.AsRef(in lo)); +#endif + + private static ref DecCalc AsMutable(ref decimal d) => ref Unsafe.As<decimal, DecCalc>(ref d); + + #region APIs need by number formatting. + + internal static uint DecDivMod1E9(ref decimal value) + { + return DecCalc.DecDivMod1E9(ref AsMutable(ref value)); + } + + #endregion + + /// <summary> + /// Class that contains all the mathematical calculations for decimal. Most of which have been ported from oleaut32. + /// </summary> + [StructLayout(LayoutKind.Explicit)] + private struct DecCalc + { + // NOTE: Do not change the offsets of these fields. This structure must have the same layout as Decimal. + [FieldOffset(0)] + private uint uflags; + [FieldOffset(4)] + private uint uhi; + [FieldOffset(8)] + private uint ulo; + [FieldOffset(12)] + private uint umid; + + /// <summary> + /// The low and mid fields combined in little-endian order + /// </summary> + [FieldOffset(8)] + private ulong ulomidLE; + + private uint High + { + get => uhi; + set => uhi = value; + } + + private uint Low + { + get => ulo; + set => ulo = value; + } + + private uint Mid + { + get => umid; + set => umid = value; + } + + private bool IsNegative => (int)uflags < 0; + + private int Scale => (byte)(uflags >> ScaleShift); + + private ulong Low64 + { +#if BIGENDIAN + get { return ((ulong)umid << 32) | ulo; } + set { umid = (uint)(value >> 32); ulo = (uint)value; } +#else + get => ulomidLE; + set => ulomidLE = value; +#endif + } + + private const uint SignMask = 0x80000000; + private const uint ScaleMask = 0x00FF0000; + + private const int DEC_SCALE_MAX = 28; + + private const uint TenToPowerNine = 1000000000; + private const ulong TenToPowerEighteen = 1000000000000000000; + + // The maximum power of 10 that a 32 bit integer can store + private const int MaxInt32Scale = 9; + // The maximum power of 10 that a 64 bit integer can store + private const int MaxInt64Scale = 19; + + // Fast access for 10^n where n is 0-9 + private static readonly uint[] s_powers10 = new uint[] { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 + }; + + // Fast access for 10^n where n is 1-19 + private static readonly ulong[] s_ulongPowers10 = new ulong[] { + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000, + }; + + private static readonly double[] s_doublePowers10 = new double[] { + 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 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, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59, + 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69, + 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79, + 1e80 + }; + + #region Decimal Math Helpers + + private static unsafe uint GetExponent(float f) + { + // Based on pulling out the exp from this single struct layout + //typedef struct { + // ULONG mant:23; + // ULONG exp:8; + // ULONG sign:1; + //} SNGSTRUCT; + + return (byte)(*(uint*)&f >> 23); + } + + private static unsafe uint GetExponent(double d) + { + // Based on pulling out the exp from this double struct layout + //typedef struct { + // DWORDLONG mant:52; + // DWORDLONG signexp:12; + // } DBLSTRUCT; + + return (uint)(*(ulong*)&d >> 52) & 0x7FFu; + } + + private static ulong UInt32x32To64(uint a, uint b) + { + return (ulong)a * (ulong)b; + } + + private static void UInt64x64To128(ulong a, ulong b, ref DecCalc pdecOut) + { + ulong low = UInt32x32To64((uint)a, (uint)b); // lo partial prod + ulong mid = UInt32x32To64((uint)a, (uint)(b >> 32)); // mid 1 partial prod + ulong high = UInt32x32To64((uint)(a >> 32), (uint)(b >> 32)); + high += mid >> 32; + low += mid <<= 32; + if (low < mid) // test for carry + high++; + + mid = UInt32x32To64((uint)(a >> 32), (uint)b); + high += mid >> 32; + low += mid <<= 32; + if (low < mid) // test for carry + high++; + + if (high > uint.MaxValue) + throw new OverflowException(SR.Overflow_Decimal); + pdecOut.Low64 = low; + pdecOut.High = (uint)high; + } + + /*** + * Div96By32 + * + * Entry: + * bufNum - 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(ref Buf12 bufNum, uint ulDen) + { + // TODO: https://github.com/dotnet/coreclr/issues/3439 + ulong tmp, div; + if (bufNum.U2 != 0) + { + tmp = bufNum.High64; + div = tmp / ulDen; + bufNum.High64 = div; + tmp = ((tmp - (uint)div * ulDen) << 32) | bufNum.U0; + if (tmp == 0) + return 0; + uint div32 = (uint)(tmp / ulDen); + bufNum.U0 = div32; + return (uint)tmp - div32 * ulDen; + } + + tmp = bufNum.Low64; + if (tmp == 0) + return 0; + div = tmp / ulDen; + bufNum.Low64 = div; + return (uint)(tmp - div * ulDen); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool Div96ByConst(ref ulong high64, ref uint low, uint pow) + { +#if BIT64 + ulong div64 = high64 / pow; + uint div = (uint)((((high64 - div64 * pow) << 32) + low) / pow); + if (low == div * pow) + { + high64 = div64; + low = div; + return true; + } +#else + // 32-bit RyuJIT doesn't convert 64-bit division by constant into multiplication by reciprocal. Do half-width divisions instead. + Debug.Assert(pow <= ushort.MaxValue); + uint num, mid32, low16, div; + if (high64 <= uint.MaxValue) + { + num = (uint)high64; + mid32 = num / pow; + num = (num - mid32 * pow) << 16; + + num += low >> 16; + low16 = num / pow; + num = (num - low16 * pow) << 16; + + num += (ushort)low; + div = num / pow; + if (num == div * pow) + { + high64 = mid32; + low = (low16 << 16) + div; + return true; + } + } + else + { + num = (uint)(high64 >> 32); + uint high32 = num / pow; + num = (num - high32 * pow) << 16; + + num += (uint)high64 >> 16; + mid32 = num / pow; + num = (num - mid32 * pow) << 16; + + num += (ushort)high64; + div = num / pow; + num = (num - div * pow) << 16; + mid32 = div + (mid32 << 16); + + num += low >> 16; + low16 = num / pow; + num = (num - low16 * pow) << 16; + + num += (ushort)low; + div = num / pow; + if (num == div * pow) + { + high64 = ((ulong)high32 << 32) | mid32; + low = (low16 << 16) + div; + return true; + } + } +#endif + return false; + } + + // Normalize (unscale) the number 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. + // + // 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. + // + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Unscale(ref uint low, ref ulong high64, ref int scale) + { +#if BIT64 + while ((byte)low == 0 && scale >= 8 && Div96ByConst(ref high64, ref low, 100000000)) + scale -= 8; + + if ((low & 0xF) == 0 && scale >= 4 && Div96ByConst(ref high64, ref low, 10000)) + scale -= 4; +#else + while ((low & 0xF) == 0 && scale >= 4 && Div96ByConst(ref high64, ref low, 10000)) + scale -= 4; +#endif + + if ((low & 3) == 0 && scale >= 2 && Div96ByConst(ref high64, ref low, 100)) + scale -= 2; + + if ((low & 1) == 0 && scale >= 1 && Div96ByConst(ref high64, ref low, 10)) + scale--; + } + + /*** + * Div96By64 + * + * Entry: + * bufNum - 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(ref Buf12 bufNum, ulong den) + { + uint quo; + ulong num; + uint num2 = bufNum.U2; + if (num2 == 0) + { + num = bufNum.Low64; + if (num < den) + // Result is zero. Entire dividend is remainder. + return 0; + + // TODO: https://github.com/dotnet/coreclr/issues/3439 + quo = (uint)(num / den); + num -= quo * den; // remainder + bufNum.Low64 = num; + return quo; + } + + uint denHigh32 = (uint)(den >> 32); + if (num2 >= denHigh32) + { + // Divide would overflow. Assume a quotient of 2^32, and set + // up remainder accordingly. + // + num = bufNum.Low64; + num -= den << 32; + quo = 0; + + // Remainder went negative. Add divisor back in until it's positive, + // a max of 2 times. + // + do + { + quo--; + num += den; + } while (num >= den); + + bufNum.Low64 = num; + return quo; + } + + // Hardware divide won't overflow + // + ulong num64 = bufNum.High64; + if (num64 < denHigh32) + // Result is zero. Entire dividend is remainder. + // + return 0; + + // TODO: https://github.com/dotnet/coreclr/issues/3439 + quo = (uint)(num64 / denHigh32); + num = bufNum.U0 | ((num64 - quo * denHigh32) << 32); // remainder + + // Compute full remainder, rem = dividend - (quo * divisor). + // + ulong prod = UInt32x32To64(quo, (uint)den); // quo * lo divisor + num -= prod; + + if (num > ~prod) + { + // Remainder went negative. Add divisor back in until it's positive, + // a max of 2 times. + // + do + { + quo--; + num += den; + } while (num >= den); + } + + bufNum.Low64 = num; + return quo; + } + + /*** + * Div128By96 + * + * Entry: + * bufNum - 128-bit dividend as array of ULONGs, least-sig first + * bufDen - 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(ref Buf16 bufNum, ref Buf12 bufDen) + { + ulong dividend = bufNum.High64; + uint den = bufDen.U2; + if (dividend < den) + // Result is zero. Entire dividend is remainder. + // + return 0; + + // TODO: https://github.com/dotnet/coreclr/issues/3439 + uint quo = (uint)(dividend / den); + uint remainder = (uint)dividend - quo * den; + + // Compute full remainder, rem = dividend - (quo * divisor). + // + ulong prod1 = UInt32x32To64(quo, bufDen.U0); // quo * lo divisor + ulong prod2 = UInt32x32To64(quo, bufDen.U1); // quo * mid divisor + prod2 += prod1 >> 32; + prod1 = (uint)prod1 | (prod2 << 32); + prod2 >>= 32; + + ulong num = bufNum.Low64; + num -= prod1; + remainder -= (uint)prod2; + + // Propagate carries + // + if (num > ~prod1) + { + remainder--; + if (remainder < ~(uint)prod2) + goto PosRem; + } + else if (remainder <= ~(uint)prod2) + goto PosRem; + { + // Remainder went negative. Add divisor back in until it's positive, + // a max of 2 times. + // + prod1 = bufDen.Low64; + + for (;;) + { + quo--; + num += prod1; + remainder += den; + + if (num < prod1) + { + // Detected carry. Check for carry out of top + // before adding it in. + // + if (remainder++ < den) + break; + } + if (remainder < den) + break; // detected carry + } + } +PosRem: + + bufNum.Low64 = num; + bufNum.U2 = remainder; + return quo; + } + + /*** + * IncreaseScale + * + * Entry: + * bufNum - 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(ref Buf12 bufNum, uint ulPwr) + { + ulong tmp = UInt32x32To64(bufNum.U0, ulPwr); + bufNum.U0 = (uint)tmp; + tmp >>= 32; + tmp += UInt32x32To64(bufNum.U1, ulPwr); + bufNum.U1 = (uint)tmp; + tmp >>= 32; + tmp += UInt32x32To64(bufNum.U2, ulPwr); + bufNum.U2 = (uint)tmp; + return (uint)(tmp >> 32); + } + + private static void IncreaseScale64(ref Buf12 bufNum, uint ulPwr) + { + ulong tmp = UInt32x32To64(bufNum.U0, ulPwr); + bufNum.U0 = (uint)tmp; + tmp >>= 32; + tmp += UInt32x32To64(bufNum.U1, ulPwr); + bufNum.High64 = tmp; + } + + /*** + * ScaleResult + * + * Entry: + * bufRes - Array of ULONGs with value, least-significant first. + * iHiRes - Index of last non-zero value in bufRes. + * 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: + * bufRes updated in place, always 3 ULONGs. + * New scale factor returned. + * + ***********************************************************************/ + private static unsafe int ScaleResult(Buf24* bufRes, uint iHiRes, int iScale) + { + Debug.Assert(iHiRes < bufRes->Length); + uint* rgulRes = (uint*)bufRes; + + // 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. + // + int iNewScale = 0; + if (iHiRes > 2) + { + iNewScale = (int)iHiRes * 32 - 64 - 1; + iNewScale -= X86.Lzcnt.IsSupported ? (int)X86.Lzcnt.LeadingZeroCount(rgulRes[iHiRes]) : LeadingZeroCount(rgulRes[iHiRes]); + + // 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) + goto ThrowOverflow; + } + + // 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; + uint ulSticky = 0; + uint quotient, remainder = 0; + + for (;;) + { + ulSticky |= remainder; // record remainder as sticky bit + + uint ulPwr; + // Scaling loop specialized for each power of 10 because division by constant is an order of magnitude faster (especially for 64-bit division that's actually done by 128bit DIV on x64) + switch (iNewScale) + { + case 1: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 10); + break; + case 2: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 100); + break; + case 3: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 1000); + break; + case 4: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 10000); + break; +#if BIT64 + case 5: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 100000); + break; + case 6: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 1000000); + break; + case 7: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 10000000); + break; + case 8: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, 100000000); + break; + default: + ulPwr = DivByConst(rgulRes, iHiRes, out quotient, out remainder, TenToPowerNine); + break; +#else + default: + goto case 4; +#endif + } + rgulRes[iHiRes] = quotient; + // If first quotient was 0, update iHiRes. + // + if (quotient == 0 && iHiRes != 0) + iHiRes--; + +#if BIT64 + iNewScale -= MaxInt32Scale; +#else + iNewScale -= 4; +#endif + 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) + { + if (iScale == 0) + goto ThrowOverflow; + 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 <= remainder && (ulPwr < remainder || ((rgulRes[0] & 1) | ulSticky) != 0) && ++rgulRes[0] == 0) + { + uint iCur = 0; + do + { + Debug.Assert(iCur + 1 < bufRes->Length); + } + while (++rgulRes[++iCur] == 0); + + if (iCur > 2) + { + // The rounding caused us to carry beyond 96 bits. + // Scale by 10 more. + // + if (iScale == 0) + goto ThrowOverflow; + iHiRes = iCur; + ulSticky = 0; // no sticky bit + remainder = 0; // or remainder + iNewScale = 1; + iScale--; + continue; // scale by 10 + } + } + + break; + } // for(;;) + } + return iScale; + +ThrowOverflow: + throw new OverflowException(SR.Overflow_Decimal); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe uint DivByConst(uint* rgulRes, uint iHiRes, out uint quotient, out uint remainder, uint power) + { + uint high = rgulRes[iHiRes]; + remainder = high - (quotient = high / power) * power; + for (uint i = iHiRes - 1; (int)i >= 0; i--) + { +#if BIT64 + ulong num = rgulRes[i] + ((ulong)remainder << 32); + remainder = (uint)num - (rgulRes[i] = (uint)(num / power)) * power; +#else + // 32-bit RyuJIT doesn't convert 64-bit division by constant into multiplication by reciprocal. Do half-width divisions instead. + Debug.Assert(power <= ushort.MaxValue); +#if BIGENDIAN + const int low16 = 2, high16 = 0; +#else + const int low16 = 0, high16 = 2; +#endif + // byte* is used here because Roslyn doesn't do constant propagation for pointer arithmetic + uint num = *(ushort*)((byte*)rgulRes + i * 4 + high16) + (remainder << 16); + uint div = num / power; + remainder = num - div * power; + *(ushort*)((byte*)rgulRes + i * 4 + high16) = (ushort)div; + + num = *(ushort*)((byte*)rgulRes + i * 4 + low16) + (remainder << 16); + div = num / power; + remainder = num - div * power; + *(ushort*)((byte*)rgulRes + i * 4 + low16) = (ushort)div; +#endif + } + return power; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LeadingZeroCount(uint value) + { + Debug.Assert(value > 0); + int c = 1; + if ((value & 0xFFFF0000) == 0) + { + value <<= 16; + c += 16; + } + if ((value & 0xFF000000) == 0) + { + value <<= 8; + c += 8; + } + if ((value & 0xF0000000) == 0) + { + value <<= 4; + c += 4; + } + if ((value & 0xC0000000) == 0) + { + value <<= 2; + c += 2; + } + return c + ((int)value >> 31); + } + + // 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 int OverflowUnscale(ref Buf12 bufQuo, int iScale, bool fRemainder) + { + if (--iScale < 0) + throw new OverflowException(SR.Overflow_Decimal); + + Debug.Assert(bufQuo.U2 == 0); + + // We have overflown, so load the high bit with a one. + const ulong highbit = 1UL << 32; + bufQuo.U2 = (uint)(highbit / 10); + ulong tmp = ((highbit % 10) << 32) + bufQuo.U1; + uint div = (uint)(tmp / 10); + bufQuo.U1 = div; + tmp = ((tmp - div * 10) << 32) + bufQuo.U0; + div = (uint)(tmp / 10); + bufQuo.U0 = div; + uint remainder = (uint)(tmp - div * 10); + // 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 (remainder > 5 || remainder == 5 && (fRemainder || (bufQuo.U0 & 1) != 0)) + Add32To96(ref bufQuo, 1); + return iScale; + } + + /*** + * SearchScale + * + * Entry: + * bufQuo - 96-bit quotient + * iScale - Scale factor of quotient, range -DEC_SCALE_MAX to DEC_SCALE_MAX-1 + * + * Purpose: + * Determine the max power of 10, <= 9, that the quotient can be scaled + * up by and still fit in 96 bits. + * + * Exit: + * Returns power of 10 to scale by. + * + ***********************************************************************/ + private static int SearchScale(ref Buf12 bufQuo, int iScale) + { + const uint OVFL_MAX_9_HI = 4; + const uint OVFL_MAX_8_HI = 42; + const uint OVFL_MAX_7_HI = 429; + const uint OVFL_MAX_6_HI = 4294; + const uint OVFL_MAX_5_HI = 42949; + const uint OVFL_MAX_4_HI = 429496; + const uint OVFL_MAX_3_HI = 4294967; + const uint OVFL_MAX_2_HI = 42949672; + const uint OVFL_MAX_1_HI = 429496729; + const ulong OVFL_MAX_9_MIDLO = 5441186219426131129; + + uint ulResHi = bufQuo.U2; + ulong ulResMidLo = bufQuo.Low64; + int iCurScale = 0; + + // Quick check to stop us from trying to scale any more. + // + if (ulResHi > OVFL_MAX_1_HI) + { + goto HaveScale; + } + + var powerOvfl = PowerOvflValues; + if (iScale > DEC_SCALE_MAX - 9) + { + // We can't scale by 10^9 without exceeding the max scale factor. + // See if we can scale to the max. If not, we'll fall into + // standard search for scale factor. + // + iCurScale = DEC_SCALE_MAX - iScale; + if (ulResHi < powerOvfl[iCurScale - 1].Hi) + goto HaveScale; + } + else if (ulResHi < OVFL_MAX_9_HI || ulResHi == OVFL_MAX_9_HI && ulResMidLo <= OVFL_MAX_9_MIDLO) + return 9; + + // Search for a power to scale by < 9. Do a binary search. + // + if (ulResHi > OVFL_MAX_5_HI) + { + if (ulResHi > OVFL_MAX_3_HI) + { + iCurScale = 2; + if (ulResHi > OVFL_MAX_2_HI) + iCurScale--; + } + else + { + iCurScale = 4; + if (ulResHi > OVFL_MAX_4_HI) + iCurScale--; + } + } + else + { + if (ulResHi > OVFL_MAX_7_HI) + { + iCurScale = 6; + if (ulResHi > OVFL_MAX_6_HI) + iCurScale--; + } + else + { + iCurScale = 8; + if (ulResHi > OVFL_MAX_8_HI) + iCurScale--; + } + } + + // In all cases, we already found we could not use the power one larger. + // So if we can use this power, it is the biggest, and we're done. If + // we can't use this power, the one below it is correct for all cases + // unless it's 10^1 -- we might have to go to 10^0 (no scaling). + // + if (ulResHi == powerOvfl[iCurScale - 1].Hi && ulResMidLo > powerOvfl[iCurScale - 1].MidLo) + iCurScale--; + + HaveScale: + // iCurScale = largest power of 10 we can scale by without overflow, + // iCurScale < 9. See if this is enough to make scale factor + // positive if it isn't already. + // + if (iCurScale + iScale < 0) + throw new OverflowException(SR.Overflow_Decimal); + + return iCurScale; + } + + // Add a 32 bit unsigned long to an array of 3 unsigned longs representing a 96 integer + // Returns false if there is an overflow + private static bool Add32To96(ref Buf12 bufNum, uint ulValue) + { + if ((bufNum.Low64 += ulValue) < ulValue) + { + if (++bufNum.U2 == 0) + return false; + } + return true; + } + + // DecAddSub adds or subtracts two decimal values. + // On return, d1 contains the result of the operation and d2 is trashed. + // Passing in true for bSign means subtract and false means add. + internal static unsafe void DecAddSub(ref DecCalc d1, ref DecCalc d2, bool bSign) + { + ulong low64 = d1.Low64; + uint high = d1.High, flags = d1.uflags, d2flags = d2.uflags; + + uint xorflags = d2flags ^ flags; + bSign ^= (xorflags & SignMask) != 0; + + if ((xorflags & ScaleMask) == 0) + { + // Scale factors are equal, no alignment necessary. + // + goto AlignedAdd; + } + 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. + // + uint d1flags = flags; + flags = d2flags & ScaleMask | flags & SignMask; // scale factor of "smaller", but sign of "larger" + int iScale = (int)(flags - d1flags) >> ScaleShift; + + if (iScale < 0) + { + // Guessed scale factor wrong. Swap operands. + // + iScale = -iScale; + flags = d1flags; + if (bSign) + flags ^= SignMask; + low64 = d2.Low64; + high = d2.High; + d2 = d1; + } + + uint ulPwr; + ulong tmp64, tmpLow; + + // 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. + + // Scan for zeros in the upper words. + // + if (high == 0) + { + if (low64 <= uint.MaxValue) + { + if ((uint)low64 == 0) + { + // Left arg is zero, return right. + // + uint signFlags = flags & SignMask; + if (bSign) + signFlags ^= SignMask; + d1 = d2; + d1.uflags = d2.uflags & ScaleMask | signFlags; + return; + } + + do + { + if (iScale <= MaxInt32Scale) + { + low64 = UInt32x32To64((uint)low64, s_powers10[iScale]); + goto AlignedAdd; + } + iScale -= MaxInt32Scale; + low64 = UInt32x32To64((uint)low64, TenToPowerNine); + } while (low64 <= uint.MaxValue); + } + + do + { + ulPwr = TenToPowerNine; + if (iScale < MaxInt32Scale) + ulPwr = s_powers10[iScale]; + tmpLow = UInt32x32To64((uint)low64, ulPwr); + tmp64 = UInt32x32To64((uint)(low64 >> 32), ulPwr) + (tmpLow >> 32); + low64 = (uint)tmpLow + (tmp64 << 32); + high = (uint)(tmp64 >> 32); + if ((iScale -= MaxInt32Scale) <= 0) + goto AlignedAdd; + } while (high == 0); + } + + while (true) + { + // Scaling won't make it larger than 4 ULONGs + // + ulPwr = TenToPowerNine; + if (iScale < MaxInt32Scale) + ulPwr = s_powers10[iScale]; + tmpLow = UInt32x32To64((uint)low64, ulPwr); + tmp64 = UInt32x32To64((uint)(low64 >> 32), ulPwr) + (tmpLow >> 32); + low64 = (uint)tmpLow + (tmp64 << 32); + tmp64 >>= 32; + tmp64 += UInt32x32To64(high, ulPwr); + + iScale -= MaxInt32Scale; + if (tmp64 > uint.MaxValue) + break; + + high = (uint)tmp64; + // Result fits in 96 bits. Use standard aligned add. + if (iScale <= 0) + goto AlignedAdd; + } + + // Have to scale by a bunch. Move the number to a buffer where it has room to grow as it's scaled. + // + Buf24 bufNum; + _ = &bufNum; // workaround for CS0165 + bufNum.Low64 = low64; + bufNum.Mid64 = tmp64; + uint iHiProd = 3; + + // Scaling loop, up to 10^9 at a time. iHiProd stays updated with index of highest non-zero ULONG. + // + for (; iScale > 0; iScale -= MaxInt32Scale) + { + ulPwr = TenToPowerNine; + if (iScale < MaxInt32Scale) + ulPwr = s_powers10[iScale]; + tmp64 = 0; + uint* rgulNum = (uint*)&bufNum; + for (uint iCur = 0; ;) + { + Debug.Assert(iCur < bufNum.Length); + tmp64 += UInt32x32To64(rgulNum[iCur], ulPwr); + rgulNum[iCur] = (uint)tmp64; + iCur++; + tmp64 >>= 32; + if (iCur > iHiProd) + break; + } + + if ((uint)tmp64 != 0) + { + // We're extending the result by another ULONG. + Debug.Assert(iHiProd + 1 < bufNum.Length); + rgulNum[++iHiProd] = (uint)tmp64; + } + } + + // Scaling complete, do the add. Could be subtract if signs differ. + // + tmp64 = bufNum.Low64; + low64 = d2.Low64; + uint tmpHigh = bufNum.U2; + high = d2.High; + + if (bSign) + { + // Signs differ, subtract. + // + low64 = tmp64 - low64; + high = tmpHigh - high; + + // Propagate carry + // + if (low64 > tmp64) + { + high--; + if (high < tmpHigh) + goto NoCarry; + } + else if (high <= tmpHigh) + goto NoCarry; + + // Carry the subtraction into the higher bits. + // + uint* rgulNum = (uint*)&bufNum; + uint iCur = 3; + do + { + Debug.Assert(iCur < bufNum.Length); + } while (rgulNum[iCur++]-- == 0); + Debug.Assert(iHiProd < bufNum.Length); + if (rgulNum[iHiProd] == 0 && --iHiProd <= 2) + goto ReturnResult; + } + else + { + // Signs the same, add. + // + low64 += tmp64; + high += tmpHigh; + + // Propagate carry + // + if (low64 < tmp64) + { + high++; + if (high > tmpHigh) + goto NoCarry; + } + else if (high >= tmpHigh) + goto NoCarry; + + uint* rgulNum = (uint*)&bufNum; + for (uint iCur = 3; ++rgulNum[iCur++] == 0;) + { + Debug.Assert(iCur < bufNum.Length); + if (iHiProd < iCur) + { + rgulNum[iCur] = 1; + iHiProd = iCur; + break; + } + } + } +NoCarry: + + bufNum.Low64 = low64; + bufNum.U2 = high; + int scale = ScaleResult(&bufNum, iHiProd, (byte)(flags >> ScaleShift)); + flags = (flags & ~ScaleMask) | ((uint)scale << ScaleShift); + low64 = bufNum.Low64; + high = bufNum.U2; + goto ReturnResult; + } + +SignFlip: + { + // Got negative result. Flip its sign. + flags ^= SignMask; + high = ~high; + low64 = (ulong)-(long)low64; + if (low64 == 0) + high++; + goto ReturnResult; + } + +AlignedScale: + { + // The addition carried above 96 bits. + // Divide the value by 10, dropping the scale factor. + // + if ((flags & ScaleMask) == 0) + throw new OverflowException(SR.Overflow_Decimal); + flags -= 1 << ScaleShift; + + const uint den = 10; + ulong num = high + (1UL << 32); + high = (uint)(num / den); + num = ((num - high * den) << 32) + (low64 >> 32); + uint div = (uint)(num / den); + num = ((num - div * den) << 32) + (uint)low64; + low64 = div; + low64 <<= 32; + div = (uint)(num / den); + low64 += div; + div = (uint)num - div * den; + + // See if we need to round up. + // + if (div >= 5 && (div > 5 || (low64 & 1) != 0)) + { + if (++low64 == 0) + high++; + } + goto ReturnResult; + } + +AlignedAdd: + { + ulong d1Low64 = low64; + uint d1High = high; + if (bSign) + { + // Signs differ - subtract + // + low64 = d1Low64 - d2.Low64; + high = d1High - d2.High; + + // Propagate carry + // + if (low64 > d1Low64) + { + high--; + if (high >= d1High) + goto SignFlip; + } + else if (high > d1High) + goto SignFlip; + } + else + { + // Signs are the same - add + // + low64 = d1Low64 + d2.Low64; + high = d1High + d2.High; + + // Propagate carry + // + if (low64 < d1Low64) + { + high++; + if (high <= d1High) + goto AlignedScale; + } + else if (high < d1High) + goto AlignedScale; + } + goto ReturnResult; + } + +ReturnResult: + d1.uflags = flags; + d1.High = high; + d1.Low64 = low64; + return; + } + +#endregion + + //********************************************************************** + // VarCyFromDec - Convert Currency to Decimal (similar to OleAut32 api.) + //********************************************************************** + internal static long VarCyFromDec(ref DecCalc pdecIn) + { + long value; + + int scale = pdecIn.Scale - 4; + // Need to scale to get 4 decimal places. -4 <= scale <= 24. + // + if (scale < 0) + { + if (pdecIn.High != 0) + goto ThrowOverflow; + uint pwr = s_powers10[-scale]; + ulong high = UInt32x32To64(pwr, pdecIn.Mid); + if (high > uint.MaxValue) + goto ThrowOverflow; + ulong low = UInt32x32To64(pwr, pdecIn.Low); + low += high <<= 32; + if (low < high) + goto ThrowOverflow; + value = (long)low; + } + else + { + if (scale != 0) + InternalRound(ref pdecIn, (uint)scale, RoundingMode.ToEven); + if (pdecIn.High != 0) + goto ThrowOverflow; + value = (long)pdecIn.Low64; + } + + if (value < 0 && (value != long.MinValue || !pdecIn.IsNegative)) + goto ThrowOverflow; + + if (pdecIn.IsNegative) + value = -value; + + return value; + +ThrowOverflow: + throw new OverflowException(SR.Overflow_Currency); + } + + //********************************************************************** + // VarDecCmp - Decimal Compare updated to return values similar to ICompareTo + //********************************************************************** + internal static int VarDecCmp(in decimal pdecL, in decimal pdecR) + { + if ((pdecR.Low | pdecR.Mid | pdecR.High) == 0) + { + if ((pdecL.Low | pdecL.Mid | pdecL.High) == 0) + return 0; + return (pdecL.flags >> 31) | 1; + } + if ((pdecL.Low | pdecL.Mid | pdecL.High) == 0) + return -((pdecR.flags >> 31) | 1); + + int sign = (pdecL.flags >> 31) - (pdecR.flags >> 31); + if (sign != 0) + return sign; + return VarDecCmpSub(in pdecL, in pdecR); + } + + private static int VarDecCmpSub(in decimal d1, in decimal d2) + { + int flags = d2.flags; + int sign = (flags >> 31) | 1; + int iScale = flags - d1.flags; + + ulong low64 = d1.Low64; + uint high = d1.High; + + ulong d2Low64 = d2.Low64; + uint d2High = d2.High; + + if (iScale != 0) + { + iScale >>= ScaleShift; + + // 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. + if (iScale < 0) + { + // Guessed scale factor wrong. Swap operands. + iScale = -iScale; + sign = -sign; + + ulong tmp64 = low64; + low64 = d2Low64; + d2Low64 = tmp64; + + uint tmp = high; + high = d2High; + d2High = tmp; + } + + // d1 will need to be multiplied by 10^iScale so it will have the same scale as d2. + // Scaling loop, up to 10^9 at a time. + do + { + uint ulPwr = iScale >= MaxInt32Scale ? TenToPowerNine : s_powers10[iScale]; + ulong tmpLow = UInt32x32To64((uint)low64, ulPwr); + ulong tmp = UInt32x32To64((uint)(low64 >> 32), ulPwr) + (tmpLow >> 32); + low64 = (uint)tmpLow + (tmp << 32); + tmp >>= 32; + tmp += UInt32x32To64(high, ulPwr); + // If the scaled value has more than 96 significant bits then it's greater than d2 + if (tmp > uint.MaxValue) + return sign; + high = (uint)tmp; + } while ((iScale -= MaxInt32Scale) > 0); + } + + uint cmpHigh = high - d2High; + if (cmpHigh != 0) + { + // check for overflow + if (cmpHigh > high) + sign = -sign; + return sign; + } + + ulong cmpLow64 = low64 - d2Low64; + if (cmpLow64 == 0) + sign = 0; + // check for overflow + else if (cmpLow64 > low64) + sign = -sign; + return sign; + } + + //********************************************************************** + // VarDecMul - Decimal Multiply + //********************************************************************** + internal static unsafe void VarDecMul(ref DecCalc pdecL, ref DecCalc pdecR) + { + int iScale = (byte)(pdecL.uflags + pdecR.uflags >> ScaleShift); + + ulong tmp; + uint iHiProd; + Buf24 bufProd; + _ = &bufProd; // workaround for CS0165 + + if ((pdecL.High | pdecL.Mid) == 0) + { + if ((pdecR.High | pdecR.Mid) == 0) + { + // Upper 64 bits are zero. + // + ulong low64 = 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] + // + if (iScale > DEC_SCALE_MAX + MaxInt64Scale) + goto ReturnZero; + + iScale -= DEC_SCALE_MAX + 1; + ulong ulPwr = s_ulongPowers10[iScale]; + + // TODO: https://github.com/dotnet/coreclr/issues/3439 + tmp = low64 / ulPwr; + ulong remainder = low64 - tmp * ulPwr; + low64 = tmp; + + // Round result. See if remainder >= 1/2 of divisor. + // Divisor is a power of 10, so it is always even. + // + ulPwr >>= 1; + if (remainder >= ulPwr && (remainder > ulPwr || ((uint)low64 & 1) > 0)) + low64++; + + iScale = DEC_SCALE_MAX; + } + pdecL.Low64 = low64; + pdecL.uflags = ((pdecR.uflags ^ pdecL.uflags) & SignMask) | ((uint)iScale << ScaleShift); + return; + } + else + { + // Left value is 32-bit, result fits in 4 uints + tmp = UInt32x32To64(pdecL.Low, pdecR.Low); + bufProd.U0 = (uint)tmp; + + tmp = UInt32x32To64(pdecL.Low, pdecR.Mid) + (tmp >> 32); + bufProd.U1 = (uint)tmp; + tmp >>= 32; + + if (pdecR.High != 0) + { + tmp += UInt32x32To64(pdecL.Low, pdecR.High); + if (tmp > uint.MaxValue) + { + bufProd.Mid64 = tmp; + iHiProd = 3; + goto SkipScan; + } + } + if ((uint)tmp != 0) + { + bufProd.U2 = (uint)tmp; + iHiProd = 2; + goto SkipScan; + } + iHiProd = 1; + } + } + else if ((pdecR.High | pdecR.Mid) == 0) + { + // Right value is 32-bit, result fits in 4 uints + tmp = UInt32x32To64(pdecR.Low, pdecL.Low); + bufProd.U0 = (uint)tmp; + + tmp = UInt32x32To64(pdecR.Low, pdecL.Mid) + (tmp >> 32); + bufProd.U1 = (uint)tmp; + tmp >>= 32; + + if (pdecL.High != 0) + { + tmp += UInt32x32To64(pdecR.Low, pdecL.High); + if (tmp > uint.MaxValue) + { + bufProd.Mid64 = tmp; + iHiProd = 3; + goto SkipScan; + } + } + if ((uint)tmp != 0) + { + bufProd.U2 = (uint)tmp; + iHiProd = 2; + goto SkipScan; + } + iHiProd = 1; + } + else + { + // Both operands have 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 + // + + tmp = UInt32x32To64(pdecL.Low, pdecR.Low); + bufProd.U0 = (uint)tmp; + + ulong tmp2 = UInt32x32To64(pdecL.Low, pdecR.Mid) + (tmp >> 32); + + tmp = UInt32x32To64(pdecL.Mid, pdecR.Low); + tmp += tmp2; // this could generate carry + bufProd.U1 = (uint)tmp; + if (tmp < tmp2) // detect carry + tmp2 = (tmp >> 32) | (1UL << 32); + else + tmp2 = tmp >> 32; + + tmp = UInt32x32To64(pdecL.Mid, pdecR.Mid) + tmp2; + + if ((pdecL.High | pdecR.High) > 0) + { + // Highest 32 bits is non-zero. Calculate 5 more partial products. + // + tmp2 = UInt32x32To64(pdecL.Low, pdecR.High); + tmp += tmp2; // this could generate carry + uint tmp3 = 0; + if (tmp < tmp2) // detect carry + tmp3 = 1; + + tmp2 = UInt32x32To64(pdecL.High, pdecR.Low); + tmp += tmp2; // this could generate carry + bufProd.U2 = (uint)tmp; + if (tmp < tmp2) // detect carry + tmp3++; + tmp2 = ((ulong)tmp3 << 32) | (tmp >> 32); + + tmp = UInt32x32To64(pdecL.Mid, pdecR.High); + tmp += tmp2; // this could generate carry + tmp3 = 0; + if (tmp < tmp2) // detect carry + tmp3 = 1; + + tmp2 = UInt32x32To64(pdecL.High, pdecR.Mid); + tmp += tmp2; // this could generate carry + bufProd.U3 = (uint)tmp; + if (tmp < tmp2) // detect carry + tmp3++; + tmp = ((ulong)tmp3 << 32) | (tmp >> 32); + + bufProd.High64 = UInt32x32To64(pdecL.High, pdecR.High) + tmp; + + iHiProd = 5; + } + else if (tmp != 0) + { + bufProd.Mid64 = tmp; + iHiProd = 3; + } + else + iHiProd = 1; + } + + // Check for leading zero ULONGs on the product + // + uint* rgulProd = (uint*)&bufProd; + while (rgulProd[(int)iHiProd] == 0) + { + if (iHiProd == 0) + goto ReturnZero; + iHiProd--; + } + +SkipScan: + if (iHiProd > 2 || iScale > DEC_SCALE_MAX) + { + iScale = ScaleResult(&bufProd, iHiProd, iScale); + } + + pdecL.Low64 = bufProd.Low64; + pdecL.High = bufProd.U2; + pdecL.uflags = ((pdecR.uflags ^ pdecL.uflags) & SignMask) | ((uint)iScale << ScaleShift); + return; + +ReturnZero: + pdecL = default; + } + + //********************************************************************** + // VarDecFromR4 - Convert float to Decimal + //********************************************************************** + internal static void VarDecFromR4(float input, out DecCalc pdecOut) + { + pdecOut = default; + + // 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. + // + const uint SNGBIAS = 126; + int iExp = (int)(GetExponent(input) - SNGBIAS); + if (iExp < -94) + return; // result should be zeroed out + + if (iExp > 96) + throw new OverflowException(SR.Overflow_Decimal); + + uint flags = 0; + if (input < 0) + { + input = -input; + flags = SignMask; + } + + // 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. + // + double dbl = input; + int iPower = 6 - ((iExp * 19728) >> 16); + // iPower is between -22 and 35 + + if (iPower >= 0) + { + // We have less than 7 digits, scale input up. + // + if (iPower > DEC_SCALE_MAX) + iPower = DEC_SCALE_MAX; + + dbl *= s_doublePowers10[iPower]; + } + else + { + if (iPower != -1 || dbl >= 1E7) + dbl /= s_doublePowers10[-iPower]; + else + iPower = 0; // didn't scale it + } + + Debug.Assert(dbl < 1E7); + if (dbl < 1E6 && iPower < DEC_SCALE_MAX) + { + dbl *= 10; + iPower++; + Debug.Assert(dbl >= 1E6); + } + + // Round to integer + // + uint ulMant; + // with SSE4.1 support ROUNDSD can be used + if (X86.Sse41.IsSupported) + ulMant = (uint)(int)Math.Round(dbl); + else + { + ulMant = (uint)(int)dbl; + dbl -= (int)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]); + } + else + { + // Have a big power of 10. + // + if (iPower > 18) + { + ulong low64 = UInt32x32To64(ulMant, s_powers10[iPower - 18]); + UInt64x64To128(low64, TenToPowerEighteen, ref pdecOut); + } + else + { + ulong low64 = UInt32x32To64(ulMant, s_powers10[iPower - 9]); + ulong hi64 = UInt32x32To64(TenToPowerNine, (uint)(low64 >> 32)); + low64 = UInt32x32To64(TenToPowerNine, (uint)low64); + pdecOut.Low = (uint)low64; + hi64 += low64 >> 32; + pdecOut.Mid = (uint)hi64; + hi64 >>= 32; + pdecOut.High = (uint)hi64; + } + } + } + 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. + // + int lmax = iPower; + if (lmax > 6) + lmax = 6; + + if ((ulMant & 0xF) == 0 && lmax >= 4) + { + const uint den = 10000; + uint div = ulMant / den; + if (ulMant == div * den) + { + ulMant = div; + iPower -= 4; + lmax -= 4; + } + } + + if ((ulMant & 3) == 0 && lmax >= 2) + { + const uint den = 100; + uint div = ulMant / den; + if (ulMant == div * den) + { + ulMant = div; + iPower -= 2; + lmax -= 2; + } + } + + if ((ulMant & 1) == 0 && lmax >= 1) + { + const uint den = 10; + uint div = ulMant / den; + if (ulMant == div * den) + { + ulMant = div; + iPower--; + } + } + + flags |= (uint)iPower << ScaleShift; + pdecOut.Low = ulMant; + } + + pdecOut.uflags = flags; + } + + //********************************************************************** + // VarDecFromR8 - Convert double to Decimal + //********************************************************************** + internal static void VarDecFromR8(double input, out DecCalc pdecOut) + { + pdecOut = default; + + // 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. + // + const uint DBLBIAS = 1022; + int iExp = (int)(GetExponent(input) - DBLBIAS); + if (iExp < -94) + return; // result should be zeroed out + + if (iExp > 96) + throw new OverflowException(SR.Overflow_Decimal); + + uint flags = 0; + if (input < 0) + { + input = -input; + flags = SignMask; + } + + // 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. + // + double dbl = input; + int iPower = 14 - ((iExp * 19728) >> 16); + // iPower is between -14 and 43 + + if (iPower >= 0) + { + // We have less than 15 digits, scale input up. + // + if (iPower > DEC_SCALE_MAX) + iPower = DEC_SCALE_MAX; + + dbl *= s_doublePowers10[iPower]; + } + else + { + if (iPower != -1 || dbl >= 1E15) + dbl /= s_doublePowers10[-iPower]; + else + iPower = 0; // didn't scale it + } + + Debug.Assert(dbl < 1E15); + if (dbl < 1E14 && iPower < DEC_SCALE_MAX) + { + dbl *= 10; + iPower++; + Debug.Assert(dbl >= 1E14); + } + + // Round to int64 + // + ulong ulMant; + // with SSE4.1 support ROUNDSD can be used + if (X86.Sse41.IsSupported) + ulMant = (ulong)(long)Math.Round(dbl); + else + { + ulMant = (ulong)(long)dbl; + dbl -= (long)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 - 15) = 14. + // + iPower = -iPower; + if (iPower < 10) + { + var pow10 = s_powers10[iPower]; + ulong low64 = UInt32x32To64((uint)ulMant, pow10); + ulong hi64 = UInt32x32To64((uint)(ulMant >> 32), pow10); + pdecOut.Low = (uint)low64; + hi64 += low64 >> 32; + pdecOut.Mid = (uint)hi64; + hi64 >>= 32; + pdecOut.High = (uint)hi64; + } + else + { + // Have a big power of 10. + // + Debug.Assert(iPower <= 14); + UInt64x64To128(ulMant, s_ulongPowers10[iPower - 1], ref pdecOut); + } + } + 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. + // + int lmax = iPower; + if (lmax > 14) + lmax = 14; + + if ((byte)ulMant == 0 && lmax >= 8) + { + const uint den = 100000000; + ulong div = ulMant / den; + if ((uint)ulMant == (uint)(div * den)) + { + ulMant = div; + iPower -= 8; + lmax -= 8; + } + } + + if (((uint)ulMant & 0xF) == 0 && lmax >= 4) + { + const uint den = 10000; + ulong div = ulMant / den; + if ((uint)ulMant == (uint)(div * den)) + { + ulMant = div; + iPower -= 4; + lmax -= 4; + } + } + + if (((uint)ulMant & 3) == 0 && lmax >= 2) + { + const uint den = 100; + ulong div = ulMant / den; + if ((uint)ulMant == (uint)(div * den)) + { + ulMant = div; + iPower -= 2; + lmax -= 2; + } + } + + if (((uint)ulMant & 1) == 0 && lmax >= 1) + { + const uint den = 10; + ulong div = ulMant / den; + if ((uint)ulMant == (uint)(div * den)) + { + ulMant = div; + iPower--; + } + } + + flags |= (uint)iPower << ScaleShift; + pdecOut.Low64 = ulMant; + } + + pdecOut.uflags = flags; + } + + //********************************************************************** + // VarR4ToDec - Convert Decimal to float + //********************************************************************** + internal static float VarR4FromDec(ref decimal pdecIn) + { + return (float)VarR8FromDec(ref pdecIn); + } + + //********************************************************************** + // VarR8ToDec - Convert Decimal to double + //********************************************************************** + internal static double VarR8FromDec(ref decimal pdecIn) + { + // Value taken via reverse engineering the double that corresponds to 2^64. (oleaut32 has ds2to64 = DEFDS(0, 0, DBLBIAS + 65, 0)) + const double ds2to64 = 1.8446744073709552e+019; + + double dbl = ((double)pdecIn.Low64 + + (double)pdecIn.High * ds2to64) / s_doublePowers10[pdecIn.Scale]; + + if (pdecIn.IsNegative) + dbl = -dbl; + + return dbl; + } + + internal static int GetHashCode(in decimal d) + { + if ((d.Low | d.Mid | d.High) == 0) + return 0; + + uint flags = (uint)d.flags; + if ((flags & ScaleMask) == 0 || (d.Low & 1) != 0) + return (int)(flags ^ d.High ^ d.Mid ^ d.Low); + + int scale = (byte)(flags >> ScaleShift); + uint low = d.Low; + ulong high64 = ((ulong)d.High << 32) | d.Mid; + + Unscale(ref low, ref high64, ref scale); + + flags = ((flags) & ~ScaleMask) | (uint)scale << ScaleShift; + return (int)(flags ^ (uint)(high64 >> 32) ^ (uint)high64 ^ low); + } + + // VarDecDiv divides two decimal values. On return, d1 contains the result + // of the operation. + internal static unsafe void VarDecDiv(ref DecCalc d1, ref DecCalc d2) + { + Buf12 bufQuo, bufDivisor; + _ = &bufQuo; // workaround for CS0165 + _ = &bufDivisor; // workaround for CS0165 + uint ulPwr; + int iCurScale; + + int iScale = (sbyte)(d1.uflags - d2.uflags >> ScaleShift); + bool fUnscale = false; + uint ulTmp; + + if (d2.High == 0 && d2.Mid == 0) + { + // Divisor is only 32 bits. Easy divide. + // + uint den = d2.Low; + if (den == 0) + throw new DivideByZeroException(); + + bufQuo.Low64 = d1.Low64; + bufQuo.U2 = d1.High; + uint remainder = Div96By32(ref bufQuo, den); + + for (;;) + { + if (remainder == 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. + // + if (iScale == DEC_SCALE_MAX || (iCurScale = SearchScale(ref bufQuo, iScale)) == 0) + { + // No more scaling to be done, but remainder is non-zero. + // Round quotient. + // + ulTmp = remainder << 1; + if (ulTmp < remainder || ulTmp >= den && (ulTmp > den || (bufQuo.U0 & 1) != 0)) + goto RoundUp; + break; + } + + HaveScale: + ulPwr = s_powers10[iCurScale]; + iScale += iCurScale; + + if (IncreaseScale(ref bufQuo, ulPwr) != 0) + goto ThrowOverflow; + + ulong num = UInt32x32To64(remainder, ulPwr); + // TODO: https://github.com/dotnet/coreclr/issues/3439 + uint div = (uint)(num / den); + remainder = (uint)num - div * den; + + if (!Add32To96(ref bufQuo, div)) + { + iScale = OverflowUnscale(ref bufQuo, iScale, remainder != 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. + // + bufDivisor.Low64 = d2.Low64; + ulTmp = d2.High; + bufDivisor.U2 = ulTmp; + if (ulTmp == 0) + ulTmp = d2.Mid; + + iCurScale = X86.Lzcnt.IsSupported ? (int)X86.Lzcnt.LeadingZeroCount(ulTmp) : LeadingZeroCount(ulTmp); + + // Shift both dividend and divisor left by iCurScale. + // + Buf16 bufRem; + _ = &bufRem; // workaround for CS0165 + bufRem.Low64 = d1.Low64 << iCurScale; + bufRem.High64 = (d1.Mid + ((ulong)d1.High << 32)) >> (31 - iCurScale) >> 1; + + ulong divisor = bufDivisor.Low64 << iCurScale; + + if (bufDivisor.U2 == 0) + { + // Have a 64-bit divisor in sdlDivisor. The remainder + // (currently 96 bits spread over 4 ULONGs) will be < divisor. + // + + bufQuo.U1 = Div96By64(ref *(Buf12*)&bufRem.U1, divisor); + bufQuo.U0 = Div96By64(ref *(Buf12*)&bufRem, divisor); + + for (;;) + { + if (bufRem.Low64 == 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. + // + if (iScale == DEC_SCALE_MAX || (iCurScale = SearchScale(ref bufQuo, iScale)) == 0) + { + // No more scaling to be done, but remainder is non-zero. + // Round quotient. + // + ulong tmp = bufRem.Low64; + if ((long)tmp < 0 || (tmp <<= 1) > divisor || + (tmp == divisor && (bufQuo.U0 & 1) != 0)) + goto RoundUp; + break; + } + + HaveScale64: + ulPwr = s_powers10[iCurScale]; + iScale += iCurScale; + + if (IncreaseScale(ref bufQuo, ulPwr) != 0) + goto ThrowOverflow; + + IncreaseScale64(ref *(Buf12*)&bufRem, ulPwr); + ulTmp = Div96By64(ref *(Buf12*)&bufRem, divisor); + if (!Add32To96(ref bufQuo, ulTmp)) + { + iScale = OverflowUnscale(ref bufQuo, iScale, bufRem.Low64 != 0); + break; + } + } // for (;;) + } + else + { + // Have a 96-bit divisor in rgulDivisor[]. + // + // Start by finishing the shift left by iCurScale. + // + uint tmp = (uint)(bufDivisor.High64 >> (31 - iCurScale) >> 1); + bufDivisor.Low64 = divisor; + bufDivisor.U2 = tmp; + + // The remainder (currently 96 bits spread over 4 ULONGs) + // will be < divisor. + // + bufQuo.Low64 = Div128By96(ref bufRem, ref bufDivisor); + + for (;;) + { + if ((bufRem.Low64 | bufRem.U2) == 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. + // + if (iScale == DEC_SCALE_MAX || (iCurScale = SearchScale(ref bufQuo, iScale)) == 0) + { + // No more scaling to be done, but remainder is non-zero. + // Round quotient. + // + if ((int)bufRem.U2 < 0) + { + goto RoundUp; + } + + ulTmp = bufRem.U1 >> 31; + bufRem.Low64 <<= 1; + bufRem.U2 = (bufRem.U2 << 1) + ulTmp; + + if (bufRem.U2 > bufDivisor.U2 || bufRem.U2 == bufDivisor.U2 && + (bufRem.Low64 > bufDivisor.Low64 || bufRem.Low64 == bufDivisor.Low64 && + (bufQuo.U0 & 1) != 0)) + goto RoundUp; + break; + } + + HaveScale96: + ulPwr = s_powers10[iCurScale]; + iScale += iCurScale; + + if (IncreaseScale(ref bufQuo, ulPwr) != 0) + goto ThrowOverflow; + + bufRem.U3 = IncreaseScale(ref *(Buf12*)&bufRem, ulPwr); + ulTmp = Div128By96(ref bufRem, ref bufDivisor); + if (!Add32To96(ref bufQuo, ulTmp)) + { + iScale = OverflowUnscale(ref bufQuo, iScale, (bufRem.Low64 | bufRem.High64) != 0); + break; + } + } // for (;;) + } + } + +Unscale: + if (fUnscale) + { + uint low = bufQuo.U0; + ulong high64 = bufQuo.High64; + Unscale(ref low, ref high64, ref iScale); + d1.Low = low; + d1.Mid = (uint)high64; + d1.High = (uint)(high64 >> 32); + } + else + { + d1.Low64 = bufQuo.Low64; + d1.High = bufQuo.U2; + } + + d1.uflags = ((d1.uflags ^ d2.uflags) & SignMask) | ((uint)iScale << ScaleShift); + return; + +RoundUp: + { + if (++bufQuo.Low64 == 0 && ++bufQuo.U2 == 0) + { + iScale = OverflowUnscale(ref bufQuo, iScale, true); + } + goto Unscale; + } + +ThrowOverflow: + throw new OverflowException(SR.Overflow_Decimal); + } + + //********************************************************************** + // VarDecMod - Computes the remainder between two decimals + //********************************************************************** + internal static void VarDecMod(ref DecCalc d1, ref DecCalc d2) + { + if ((d2.ulo | d2.umid | d2.uhi) == 0) + throw new DivideByZeroException(); + + if ((d1.ulo | d1.umid | d1.uhi) == 0) + return; + + // In the operation x % y the sign of y does not matter. Result will have the sign of x. + d2.uflags = (d2.uflags & ~SignMask) | (d1.uflags & SignMask); + + int cmp = VarDecCmpSub(in Unsafe.As<DecCalc, decimal>(ref d1), in Unsafe.As<DecCalc, decimal>(ref d2)); + if (cmp == 0) + { + d1.ulo = 0; + d1.umid = 0; + d1.uhi = 0; + if (d2.uflags > d1.uflags) + d1.uflags = d2.uflags; + return; + } + if ((cmp ^ (int)(d1.uflags & SignMask)) < 0) + return; + + // This piece of code is to work around the fact that Dividing a decimal with 28 digits number by decimal which causes + // causes the result to be 28 digits, can cause to be incorrectly rounded up. + // eg. Decimal.MaxValue / 2 * Decimal.MaxValue will overflow since the division by 2 was rounded instead of being truncked. + DecCalc tmp = d2; + DecAddSub(ref d1, ref tmp, true); + + // Formula: d1 - (RoundTowardsZero(d1 / d2) * d2) + tmp = d1; + VarDecDiv(ref tmp, ref d2); + Truncate(ref Unsafe.As<DecCalc, decimal>(ref tmp)); + VarDecMul(ref tmp, ref d2); + uint flags = d1.uflags; + DecAddSub(ref d1, ref tmp, true); + // See if the result has crossed 0 + if (((flags ^ d1.uflags) & SignMask) != 0) + { + if ((d1.Low | d1.Mid | d1.High) == 0 || d1.Scale == DEC_SCALE_MAX && d1.Low64 == 1 && d1.High == 0) + { + // Certain Remainder operations on decimals with 28 significant digits round + // to [+-]0.0000000000000000000000000001m instead of [+-]0m during the intermediate calculations. + // 'zero' results just need their sign corrected. + d1.uflags ^= SignMask; + } + else + { + // If the division rounds up because it runs out of digits, the multiplied result can end up with a larger + // absolute value and the result of the formula crosses 0. To correct it can add the divisor back. + DecAddSub(ref d1, ref d2, false); + } + } + } + + internal enum RoundingMode + { + ToEven = 0, + AwayFromZero = 1, + Truncate = 2, + Floor = 3, + Ceiling = 4, + } + + // Does an in-place round by the specified scale + internal static void InternalRound(ref DecCalc d, uint scale, RoundingMode mode) + { + // the scale becomes the desired decimal count + d.uflags -= scale << ScaleShift; + + uint remainder, sticky = 0, power; + // First divide the value by constant 10^9 up to three times + while (scale >= MaxInt32Scale) + { + scale -= MaxInt32Scale; + + const uint divisor = TenToPowerNine; + uint n = d.uhi; + if (n == 0) + { + ulong tmp = d.Low64; + ulong div = tmp / divisor; + d.Low64 = div; + remainder = (uint)(tmp - div * divisor); + } + else + { + uint q; + d.uhi = q = n / divisor; + remainder = n - q * divisor; + n = d.umid; + if ((n | remainder) != 0) + { + d.umid = q = (uint)((((ulong)remainder << 32) | n) / divisor); + remainder = n - q * divisor; + } + n = d.ulo; + if ((n | remainder) != 0) + { + d.ulo = q = (uint)((((ulong)remainder << 32) | n) / divisor); + remainder = n - q * divisor; + } + } + power = divisor; + if (scale == 0) + goto checkRemainder; + sticky |= remainder; + } + + { + power = s_powers10[scale]; + // TODO: https://github.com/dotnet/coreclr/issues/3439 + uint n = d.uhi; + if (n == 0) + { + ulong tmp = d.Low64; + if (tmp == 0) + { + if (mode <= RoundingMode.Truncate) + goto done; + remainder = 0; + goto checkRemainder; + } + ulong div = tmp / power; + d.Low64 = div; + remainder = (uint)(tmp - div * power); + } + else + { + uint q; + d.uhi = q = n / power; + remainder = n - q * power; + n = d.umid; + if ((n | remainder) != 0) + { + d.umid = q = (uint)((((ulong)remainder << 32) | n) / power); + remainder = n - q * power; + } + n = d.ulo; + if ((n | remainder) != 0) + { + d.ulo = q = (uint)((((ulong)remainder << 32) | n) / power); + remainder = n - q * power; + } + } + } + +checkRemainder: + if (mode == RoundingMode.Truncate) + goto done; + else if (mode == RoundingMode.ToEven) + { + // To do IEEE rounding, we add LSB of result to sticky bits so either causes round up if remainder * 2 == last divisor. + remainder <<= 1; + if ((sticky | d.ulo & 1) != 0) + remainder++; + if (power >= remainder) + goto done; + } + else if (mode == RoundingMode.AwayFromZero) + { + // Round away from zero at the mid point. + remainder <<= 1; + if (power > remainder) + goto done; + } + else if (mode == RoundingMode.Floor) + { + // Round toward -infinity if we have chopped off a non-zero amount from a negative value. + if ((remainder | sticky) == 0 || !d.IsNegative) + goto done; + } + else + { + Debug.Assert(mode == RoundingMode.Ceiling); + // Round toward infinity if we have chopped off a non-zero amount from a positive value. + if ((remainder | sticky) == 0 || d.IsNegative) + goto done; + } + if (++d.Low64 == 0) + d.uhi++; +done: + return; + } + + internal static uint DecDivMod1E9(ref DecCalc value) + { + ulong high64 = ((ulong)value.uhi << 32) + value.umid; + ulong div64 = high64 / TenToPowerNine; + value.uhi = (uint)(div64 >> 32); + value.umid = (uint)div64; + + ulong num = ((high64 - (uint)div64 * TenToPowerNine) << 32) + value.ulo; + uint div = (uint)(num / TenToPowerNine); + value.ulo = div; + return (uint)num - div * TenToPowerNine; + } + + struct PowerOvfl + { + public readonly uint Hi; + public readonly ulong MidLo; + + public PowerOvfl(uint hi, uint mid, uint lo) + { + Hi = hi; + MidLo = ((ulong)mid << 32) + lo; + } + } + + static readonly PowerOvfl[] PowerOvflValues = new[] + { + // This is a table of the largest values that can be in the upper two + // ULONGs of a 96-bit number that will not overflow when multiplied + // by a given power. For the upper word, this is a table of + // 2^32 / 10^n for 1 <= n <= 8. For the lower word, this is the + // remaining fraction part * 2^32. 2^32 = 4294967296. + // + new PowerOvfl(429496729, 2576980377, 2576980377), // 10^1 remainder 0.6 + new PowerOvfl(42949672, 4123168604, 687194767), // 10^2 remainder 0.16 + new PowerOvfl(4294967, 1271310319, 2645699854), // 10^3 remainder 0.616 + new PowerOvfl(429496, 3133608139, 694066715), // 10^4 remainder 0.1616 + new PowerOvfl(42949, 2890341191, 2216890319), // 10^5 remainder 0.51616 + new PowerOvfl(4294, 4154504685, 2369172679), // 10^6 remainder 0.551616 + new PowerOvfl(429, 2133437386, 4102387834), // 10^7 remainder 0.9551616 + new PowerOvfl(42, 4078814305, 410238783), // 10^8 remainder 0.09991616 + }; + + [StructLayout(LayoutKind.Explicit)] + private struct Buf12 + { + [FieldOffset(0 * 4)] + public uint U0; + [FieldOffset(1 * 4)] + public uint U1; + [FieldOffset(2 * 4)] + public uint U2; + + [FieldOffset(0)] + private ulong ulo64LE; + [FieldOffset(4)] + private ulong uhigh64LE; + + public ulong Low64 + { +#if BIGENDIAN + get => ((ulong)U1 << 32) | U0; + set { U1 = (uint)(value >> 32); U0 = (uint)value; } +#else + get => ulo64LE; + set => ulo64LE = value; +#endif + } + + /// <summary> + /// U1-U2 combined (overlaps with Low64) + /// </summary> + public ulong High64 + { +#if BIGENDIAN + get => ((ulong)U2 << 32) | U1; + set { U2 = (uint)(value >> 32); U1 = (uint)value; } +#else + get => uhigh64LE; + set => uhigh64LE = value; +#endif + } + } + + [StructLayout(LayoutKind.Explicit)] + private struct Buf16 + { + [FieldOffset(0 * 4)] + public uint U0; + [FieldOffset(1 * 4)] + public uint U1; + [FieldOffset(2 * 4)] + public uint U2; + [FieldOffset(3 * 4)] + public uint U3; + + [FieldOffset(0 * 8)] + private ulong ulo64LE; + [FieldOffset(1 * 8)] + private ulong uhigh64LE; + + public ulong Low64 + { +#if BIGENDIAN + get => ((ulong)U1 << 32) | U0; + set { U1 = (uint)(value >> 32); U0 = (uint)value; } +#else + get => ulo64LE; + set => ulo64LE = value; +#endif + } + + public ulong High64 + { +#if BIGENDIAN + get => ((ulong)U3 << 32) | U2; + set { U3 = (uint)(value >> 32); U2 = (uint)value; } +#else + get => uhigh64LE; + set => uhigh64LE = value; +#endif + } + } + + [StructLayout(LayoutKind.Explicit)] + private struct Buf24 + { + [FieldOffset(0 * 4)] + public uint U0; + [FieldOffset(1 * 4)] + public uint U1; + [FieldOffset(2 * 4)] + public uint U2; + [FieldOffset(3 * 4)] + public uint U3; + [FieldOffset(4 * 4)] + public uint U4; + [FieldOffset(5 * 4)] + public uint U5; + + [FieldOffset(0 * 8)] + private ulong ulo64LE; + [FieldOffset(1 * 8)] + private ulong umid64LE; + [FieldOffset(2 * 8)] + private ulong uhigh64LE; + + public ulong Low64 + { +#if BIGENDIAN + get => ((ulong)U1 << 32) | U0; + set { U1 = (uint)(value >> 32); U0 = (uint)value; } +#else + get => ulo64LE; + set => ulo64LE = value; +#endif + } + + public ulong Mid64 + { +#if BIGENDIAN + get => ((ulong)U3 << 32) | U2; + set { U3 = (uint)(value >> 32); U2 = (uint)value; } +#else + get => umid64LE; + set => umid64LE = value; +#endif + } + + public ulong High64 + { +#if BIGENDIAN + get => ((ulong)U5 << 32) | U4; + set { U5 = (uint)(value >> 32); U4 = (uint)value; } +#else + get => uhigh64LE; + set => uhigh64LE = value; +#endif + } + + public int Length => 6; + } + } + } +} |