File: System\Data\SQLTypes\SQLDecimal.cs
Web Access
Project: src\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
 
namespace System.Data.SqlTypes
{
    /// <summary>
    /// Represents a fixed precision and scale numeric value between -10<superscript term='38'/>
    /// -1 and 10<superscript term='38'/> -1 to be stored in or retrieved from a database.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    [XmlSchemaProvider("GetXsdType")]
    public struct SqlDecimal : INullable, IComparable, IXmlSerializable, IEquatable<SqlDecimal>
    {
        // data in CSsNumeric in SQL Server
        // BYTE    m_cbLen;                // # of DWORDs + 1 (1 is for sign)
        // BYTE    m_bPrec;                // precision
        // BYTE    m_bScale;                // scale
        // BYTE    m_bSign;                // NUM_POSITIVE or NUM_NEGATIVE
        // ULONG    m_rgulData [x_culNumeMax];
 
        // NOTE: If any instance fields change, update SqlTypeWorkarounds type in System.Data.SqlClient.
        internal byte _bStatus;      // bit 0: fNotNull, bit 1: fNegative
        internal byte _bLen;         // number of uints used, = (CSsNumeric.m_cbLen - 1) / 4.
        internal byte _bPrec;
        internal byte _bScale;
        internal uint _data1;
        internal uint _data2;
        internal uint _data3;
        internal uint _data4;
 
        private const byte s_NUMERIC_MAX_PRECISION = 38;            // Maximum precision of numeric
        public static readonly byte MaxPrecision = s_NUMERIC_MAX_PRECISION;  // max SS precision
        public static readonly byte MaxScale = s_NUMERIC_MAX_PRECISION;      // max SS scale
 
        private const byte s_bNullMask = 1;    // bit mask for null bit in m_bStatus
        private const byte s_bIsNull = 0;    // is null
        private const byte s_bNotNull = 1;    // is not null
        private const byte s_bReverseNullMask = unchecked((byte)~s_bNullMask);
 
        private const byte s_bSignMask = 2;    // bit mask for sign bit in m_bStatus
        private const byte s_bPositive = 0;    // is positive
        private const byte s_bNegative = 2;    // is negative
        private const byte s_bReverseSignMask = unchecked((byte)~s_bSignMask);
 
        private const uint s_uiZero = 0;
 
        private const int s_cNumeMax = 4;
        private const long s_lInt32Base = ((long)1) << 32;      // 2**32
        private const ulong s_ulInt32Base = ((ulong)1) << 32;     // 2**32
        private const ulong s_ulInt32BaseForMod = s_ulInt32Base - 1;    // 2**32 - 1 (0xFFF...FF)
 
        internal const ulong s_llMax = long.MaxValue;   // Max of Int64
 
        private const uint s_ulBase10 = 10;
 
        private const double s_DUINT_BASE = s_lInt32Base;     // 2**32
        private const double s_DUINT_BASE2 = s_DUINT_BASE * s_DUINT_BASE;  // 2**64
        private const double s_DUINT_BASE3 = s_DUINT_BASE2 * s_DUINT_BASE; // 2**96
        private const double s_DMAX_NUME = 1.0e+38;                  // Max value of numeric
        private const uint s_DBL_DIG = 17;                       // Max decimal digits of double
 
        private const byte s_cNumeDivScaleMin = 6;     // Minimum result scale of numeric division
 
        // Array of multipliers for lAdjust and Ceiling/Floor.
        private static ReadOnlySpan<uint> RgulShiftBase => // 9
        [
            10,
            10 * 10,
            10 * 10 * 10,
            10 * 10 * 10 * 10,
            10 * 10 * 10 * 10 * 10,
            10 * 10 * 10 * 10 * 10 * 10,
            10 * 10 * 10 * 10 * 10 * 10 * 10,
            10 * 10 * 10 * 10 * 10 * 10 * 10 * 10,
            10 * 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10
        ];
 
        #region DecimalHelperTableGenerator
        /*
                // the code below will generate the DecimalHelpers tables
                private static string[] HelperNames = {
                    "DecimalHelpersLo", "DecimalHelpersMid", "DecimalHelpersHi", "DecimalHelpersHiHi",
                };
 
 
                private static void DumpDecimalHelperParts(int index)
                {
                    SqlDecimal sqlDecimalValue = 10;    // start precision=2
 
                    Console.WriteLine("private static readonly UInt32[] {0} = {{", HelperNames[index]);
                    for (int precision = 2; precision <= SqlDecimal.MaxPrecision; precision++){
                        Console.WriteLine("    0x{0,8:x8}, // precision:{1}, value:{2}", sqlDecimalValue.Data[index], precision, sqlDecimalValue.ToString());
                        if (precision < SqlDecimal.MaxPrecision){
                            sqlDecimalValue *= 10;
                        }
                    }
                    sqlDecimalValue = SqlDecimal.MaxValue;
                    int[] data = sqlDecimalValue.Data;
                    UInt32[] udata = { (UInt32)data[0], (UInt32)data[1], (UInt32)data[2], (UInt32)data[3]};
                    bool carry = true;
                    for (int i = 0; i < 4; i++){
                        if (carry){
                            carry = (++udata[i] == 0);
                        }
                    }
                    Console.WriteLine("    0x{0,8:x8}, // precision:{1}+1, value:{2}+1", udata[index], SqlDecimal.MaxPrecision, SqlDecimal.MaxValue.ToString());
 
                    Console.WriteLine("};");
                    Console.WriteLine();
                }
 
 
                public static void CreateDecimalHelperTable()
                {
                    for (int i = 0; i < 4; i++)
                    {
                        DumpDecimalHelperParts(i);
                    }
                }
 
        */
        #endregion
 
        #region DecimalHelperTable
        private static ReadOnlySpan<uint> DecimalHelpersLo =>
        [
            0x0000000a, // precision:2, value:10
            0x00000064, // precision:3, value:100
            0x000003e8, // precision:4, value:1000
            0x00002710, // precision:5, value:10000
            0x000186a0, // precision:6, value:100000
            0x000f4240, // precision:7, value:1000000
            0x00989680, // precision:8, value:10000000
            0x05f5e100, // precision:9, value:100000000
            0x3b9aca00, // precision:10, value:1000000000
            0x540be400, // precision:11, value:10000000000
            0x4876e800, // precision:12, value:100000000000
            0xd4a51000, // precision:13, value:1000000000000
            0x4e72a000, // precision:14, value:10000000000000
            0x107a4000, // precision:15, value:100000000000000
            0xa4c68000, // precision:16, value:1000000000000000
            0x6fc10000, // precision:17, value:10000000000000000
            0x5d8a0000, // precision:18, value:100000000000000000
            0xa7640000, // precision:19, value:1000000000000000000
            0x89e80000, // precision:20, value:10000000000000000000
            0x63100000, // precision:21, value:100000000000000000000
            0xdea00000, // precision:22, value:1000000000000000000000
            0xb2400000, // precision:23, value:10000000000000000000000
            0xf6800000, // precision:24, value:100000000000000000000000
            0xa1000000, // precision:25, value:1000000000000000000000000
            0x4a000000, // precision:26, value:10000000000000000000000000
            0xe4000000, // precision:27, value:100000000000000000000000000
            0xe8000000, // precision:28, value:1000000000000000000000000000
            0x10000000, // precision:29, value:10000000000000000000000000000
            0xa0000000, // precision:30, value:100000000000000000000000000000
            0x40000000, // precision:31, value:1000000000000000000000000000000
            0x80000000, // precision:32, value:10000000000000000000000000000000
            0x00000000, // precision:33, value:100000000000000000000000000000000
            0x00000000, // precision:34, value:1000000000000000000000000000000000
            0x00000000, // precision:35, value:10000000000000000000000000000000000
            0x00000000, // precision:36, value:100000000000000000000000000000000000
            0x00000000, // precision:37, value:1000000000000000000000000000000000000
            0x00000000, // precision:38, value:10000000000000000000000000000000000000
            0x00000000, // precision:38+1, value:99999999999999999999999999999999999999+1
        ];
 
        private static ReadOnlySpan<uint> DecimalHelpersMid =>
        [
            0x00000000, // precision:2, value:10
            0x00000000, // precision:3, value:100
            0x00000000, // precision:4, value:1000
            0x00000000, // precision:5, value:10000
            0x00000000, // precision:6, value:100000
            0x00000000, // precision:7, value:1000000
            0x00000000, // precision:8, value:10000000
            0x00000000, // precision:9, value:100000000
            0x00000000, // precision:10, value:1000000000
            0x00000002, // precision:11, value:10000000000
            0x00000017, // precision:12, value:100000000000
            0x000000e8, // precision:13, value:1000000000000
            0x00000918, // precision:14, value:10000000000000
            0x00005af3, // precision:15, value:100000000000000
            0x00038d7e, // precision:16, value:1000000000000000
            0x002386f2, // precision:17, value:10000000000000000
            0x01634578, // precision:18, value:100000000000000000
            0x0de0b6b3, // precision:19, value:1000000000000000000
            0x8ac72304, // precision:20, value:10000000000000000000
            0x6bc75e2d, // precision:21, value:100000000000000000000
            0x35c9adc5, // precision:22, value:1000000000000000000000
            0x19e0c9ba, // precision:23, value:10000000000000000000000
            0x02c7e14a, // precision:24, value:100000000000000000000000
            0x1bcecced, // precision:25, value:1000000000000000000000000
            0x16140148, // precision:26, value:10000000000000000000000000
            0xdcc80cd2, // precision:27, value:100000000000000000000000000
            0x9fd0803c, // precision:28, value:1000000000000000000000000000
            0x3e250261, // precision:29, value:10000000000000000000000000000
            0x6d7217ca, // precision:30, value:100000000000000000000000000000
            0x4674edea, // precision:31, value:1000000000000000000000000000000
            0xc0914b26, // precision:32, value:10000000000000000000000000000000
            0x85acef81, // precision:33, value:100000000000000000000000000000000
            0x38c15b0a, // precision:34, value:1000000000000000000000000000000000
            0x378d8e64, // precision:35, value:10000000000000000000000000000000000
            0x2b878fe8, // precision:36, value:100000000000000000000000000000000000
            0xb34b9f10, // precision:37, value:1000000000000000000000000000000000000
            0x00f436a0, // precision:38, value:10000000000000000000000000000000000000
            0x098a2240, // precision:38+1, value:99999999999999999999999999999999999999+1
        ];
 
        private static ReadOnlySpan<uint> DecimalHelpersHi =>
        [
            0x00000000, // precision:2, value:10
            0x00000000, // precision:3, value:100
            0x00000000, // precision:4, value:1000
            0x00000000, // precision:5, value:10000
            0x00000000, // precision:6, value:100000
            0x00000000, // precision:7, value:1000000
            0x00000000, // precision:8, value:10000000
            0x00000000, // precision:9, value:100000000
            0x00000000, // precision:10, value:1000000000
            0x00000000, // precision:11, value:10000000000
            0x00000000, // precision:12, value:100000000000
            0x00000000, // precision:13, value:1000000000000
            0x00000000, // precision:14, value:10000000000000
            0x00000000, // precision:15, value:100000000000000
            0x00000000, // precision:16, value:1000000000000000
            0x00000000, // precision:17, value:10000000000000000
            0x00000000, // precision:18, value:100000000000000000
            0x00000000, // precision:19, value:1000000000000000000
            0x00000000, // precision:20, value:10000000000000000000
            0x00000005, // precision:21, value:100000000000000000000
            0x00000036, // precision:22, value:1000000000000000000000
            0x0000021e, // precision:23, value:10000000000000000000000
            0x0000152d, // precision:24, value:100000000000000000000000
            0x0000d3c2, // precision:25, value:1000000000000000000000000
            0x00084595, // precision:26, value:10000000000000000000000000
            0x0052b7d2, // precision:27, value:100000000000000000000000000
            0x033b2e3c, // precision:28, value:1000000000000000000000000000
            0x204fce5e, // precision:29, value:10000000000000000000000000000
            0x431e0fae, // precision:30, value:100000000000000000000000000000
            0x9f2c9cd0, // precision:31, value:1000000000000000000000000000000
            0x37be2022, // precision:32, value:10000000000000000000000000000000
            0x2d6d415b, // precision:33, value:100000000000000000000000000000000
            0xc6448d93, // precision:34, value:1000000000000000000000000000000000
            0xbead87c0, // precision:35, value:10000000000000000000000000000000000
            0x72c74d82, // precision:36, value:100000000000000000000000000000000000
            0x7bc90715, // precision:37, value:1000000000000000000000000000000000000
            0xd5da46d9, // precision:38, value:10000000000000000000000000000000000000
            0x5a86c47a, // precision:38+1, value:99999999999999999999999999999999999999+1
        ];
 
        private static ReadOnlySpan<uint> DecimalHelpersHiHi =>
        [
            0x00000000, // precision:2, value:10
            0x00000000, // precision:3, value:100
            0x00000000, // precision:4, value:1000
            0x00000000, // precision:5, value:10000
            0x00000000, // precision:6, value:100000
            0x00000000, // precision:7, value:1000000
            0x00000000, // precision:8, value:10000000
            0x00000000, // precision:9, value:100000000
            0x00000000, // precision:10, value:1000000000
            0x00000000, // precision:11, value:10000000000
            0x00000000, // precision:12, value:100000000000
            0x00000000, // precision:13, value:1000000000000
            0x00000000, // precision:14, value:10000000000000
            0x00000000, // precision:15, value:100000000000000
            0x00000000, // precision:16, value:1000000000000000
            0x00000000, // precision:17, value:10000000000000000
            0x00000000, // precision:18, value:100000000000000000
            0x00000000, // precision:19, value:1000000000000000000
            0x00000000, // precision:20, value:10000000000000000000
            0x00000000, // precision:21, value:100000000000000000000
            0x00000000, // precision:22, value:1000000000000000000000
            0x00000000, // precision:23, value:10000000000000000000000
            0x00000000, // precision:24, value:100000000000000000000000
            0x00000000, // precision:25, value:1000000000000000000000000
            0x00000000, // precision:26, value:10000000000000000000000000
            0x00000000, // precision:27, value:100000000000000000000000000
            0x00000000, // precision:28, value:1000000000000000000000000000
            0x00000000, // precision:29, value:10000000000000000000000000000
            0x00000001, // precision:30, value:100000000000000000000000000000
            0x0000000c, // precision:31, value:1000000000000000000000000000000
            0x0000007e, // precision:32, value:10000000000000000000000000000000
            0x000004ee, // precision:33, value:100000000000000000000000000000000
            0x0000314d, // precision:34, value:1000000000000000000000000000000000
            0x0001ed09, // precision:35, value:10000000000000000000000000000000000
            0x00134261, // precision:36, value:100000000000000000000000000000000000
            0x00c097ce, // precision:37, value:1000000000000000000000000000000000000
            0x0785ee10, // precision:38, value:10000000000000000000000000000000000000
            0x4b3b4ca8, // precision:38+1, value:99999999999999999999999999999999999999+1
        ];
        #endregion
 
        // note that the algorithm covers a range from -5 to +4 from the initial index
        // at the end of the algorithm the tableindex will point to the greatest value that is
        // less than the current value
        // except (!) if the current value is less than 10 (precision=1). There is no value ins
        // the table that is less than 10. In this case the algorithm terminates earlier.
        //
        // The startindex values have been chosen so that the highest possible index (startindex+5)
        // does not point to a value that has bits in a higher word set
        //
        private const int HelperTableStartIndexLo = 5;
        private const int HelperTableStartIndexMid = 15;
        private const int HelperTableStartIndexHi = 24;
        private const int HelperTableStartIndexHiHi = 33;
 
        private byte CalculatePrecision()
        {
            int tableIndex;
            byte precision;
            ReadOnlySpan<uint> decimalHelpers;
            uint decimalPart;
 
            if (_data4 != 0)
            {
                tableIndex = HelperTableStartIndexHiHi;
                decimalHelpers = DecimalHelpersHiHi;
                decimalPart = _data4;
            }
            else if (_data3 != 0)
            {
                tableIndex = HelperTableStartIndexHi;
                decimalHelpers = DecimalHelpersHi;
                decimalPart = _data3;
            }
            else if (_data2 != 0)
            {
                tableIndex = HelperTableStartIndexMid;
                decimalHelpers = DecimalHelpersMid;
                decimalPart = _data2;
            }
            else
            {
                tableIndex = HelperTableStartIndexLo;
                decimalHelpers = DecimalHelpersLo;
                decimalPart = _data1;
            }
 
 
            // this code will move the index no more than -2 -2 -1 (-5) or +2 +1 +1 (+4)
            // from the initial position
            //
            if (decimalPart < decimalHelpers[tableIndex])
            {
                tableIndex -= 2;
                if (decimalPart < decimalHelpers[tableIndex])
                {
                    tableIndex -= 2;
                    if (decimalPart < decimalHelpers[tableIndex])
                    {
                        tableIndex -= 1;
                    }
                    else
                    {
                        tableIndex += 1;
                    }
                }
                else
                {
                    tableIndex += 1;
                }
            }
            else
            {
                tableIndex += 2;
                if (decimalPart < decimalHelpers[tableIndex])
                {
                    tableIndex -= 1;
                }
                else
                {
                    tableIndex += 1;
                }
            }
            if (decimalPart >= decimalHelpers[tableIndex])
            {
                tableIndex += 1;
                if (tableIndex == 37 && decimalPart >= decimalHelpers[tableIndex])
                {
                    // This can happen only if the actual value is greater than 1E+38,
                    // in which case, tableIndex starts at 33 and ends at 37.
                    // Note that in this case, the actual value will be out of SqlDeicmal's range,
                    // and tableIndex is out of the array boudary. We'll throw later in ctors.
                    tableIndex += 1;
                }
            }
 
            precision = (byte)(tableIndex + 1);
            if (precision > 1)
            {
                // not done yet
                // tableIndex may still be off by one since we didn't look at the lower words
                //
                if (VerifyPrecision((byte)(precision - 1)))
                {
                    precision -= 1;
                }
            }
            Debug.Assert((precision == MaxPrecision + 1) || VerifyPrecision(precision), "Calcualted precision too low?");
            Debug.Assert((precision == 1) || !VerifyPrecision((byte)(precision - 1)), "Calculated precision too high?");
 
            // adjust the precision
            // This might not be correct but is to our best knowledge
            precision = Math.Max(precision, _bScale);
#if DEBUG
            byte bPrec = _bPrec;   // store current value
            _bPrec = MaxPrecision; // BActualPrec does not work if m_bPrec uninitialized!
            byte bActualPrecision = BActualPrec();
            _bPrec = bPrec;  // restore current value
 
            Debug.Assert(precision == bActualPrecision, $"CalculatePrecision={precision}, BActualPrec={bActualPrecision}. Results must be equal!");
#endif
            return precision;
        }
 
        // VerifyPrecision
        //
        // returns true if the current value is less or equal than the max value of the
        // supplied precision.
        //
        private bool VerifyPrecision(byte precision)
        {
            Debug.Assert(precision > 0, "Precision cannot be less than 1");
            Debug.Assert(precision <= MaxPrecision, "Precision > MaxPrecision");
 
            int tableIndex = checked((precision - 1));
            if (_data4 < DecimalHelpersHiHi[tableIndex])
            {
                return true;
            }
            else if (_data4 == DecimalHelpersHiHi[tableIndex])
            {
                if (_data3 < DecimalHelpersHi[tableIndex])
                {
                    return true;
                }
                else if (_data3 == DecimalHelpersHi[tableIndex])
                {
                    if (_data2 < DecimalHelpersMid[tableIndex])
                    {
                        return true;
                    }
                    else if (_data2 == DecimalHelpersMid[tableIndex])
                    {
                        if (_data1 < DecimalHelpersLo[tableIndex])
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
 
 
        // constructor
        // construct a Null
        private SqlDecimal(bool _)
        {
            _bLen =
            _bPrec =
            _bScale = 0;
            _bStatus = 0;
            _data1 =
            _data2 =
            _data3 =
            _data4 = s_uiZero;
        }
 
        public SqlDecimal(decimal value)
        {
            // set the null bit
            _bStatus = s_bNotNull;
 
            // note: using usafe code we could get the data directly out of the structure like that:
            // UInt32 * pInt = (UInt32*) &value;
            // UInt32 sgnscl = *pInt++;
 
            // m_data3 = *pInt++; // high part
            // m_data1 = *pInt++; // lo part
            // m_data2 = *pInt++; // mid part
 
            Span<int> bits = stackalloc int[4];
            decimal.GetBits(value, bits);
            uint sgnscl;
 
            unchecked
            {
                sgnscl = (uint)bits[3];
                _data1 = (uint)bits[0];
                _data2 = (uint)bits[1];
                _data3 = (uint)bits[2];
                _data4 = s_uiZero;
            }
 
            // set the sign bit
            _bStatus |= ((sgnscl & 0x80000000) == 0x80000000) ? s_bNegative : (byte)0;
 
            if (_data3 != 0)
                _bLen = 3;
            else if (_data2 != 0)
                _bLen = 2;
            else
                _bLen = 1;
 
            // Get the scale info from Decimal
            _bScale = (byte)((int)(sgnscl & 0xff0000) >> 16);
 
            // initialize precision
            _bPrec = 0; // The this object cannot be used before all of its fields are assigned to
 
 
            _bPrec = CalculatePrecision();
            // CalculatePrecision adjusts!
            //            if (m_bPrec < m_bScale)
            //                m_bPrec = m_bScale;
        }
 
        public SqlDecimal(int value)
        {
            // set the null bit
            _bStatus = s_bNotNull;
 
            uint uiValue = unchecked((uint)value);
 
            // set the sign bit
            if (value < 0)
            {
                _bStatus |= s_bNegative;
                // The negative of -2147483648 doesn't fit into int32, directly cast to int should work.
                if (value != int.MinValue)
                    uiValue = (uint)(-value);
            }
 
            // set the data
            _data1 = uiValue;
            _data2 = _data3 = _data4 = s_uiZero;
 
            _bLen = 1;
            _bPrec = BGetPrecUI4(_data1);
            _bScale = 0;
        }
 
        public SqlDecimal(long value)
        {
            // set the null bit
            _bStatus = s_bNotNull;
 
            ulong dwl = unchecked((ulong)value);
 
            // set the sign bit
            if (value < 0)
            {
                _bStatus |= s_bNegative;
                // The negative of Int64.MinValue doesn't fit into int64, directly cast to ulong should work.
                if (value != long.MinValue)
                    dwl = (ulong)(-value);
            }
 
            // Copy DWL into bottom 2 UI4s of numeric
            _data1 = (uint)dwl;
            _data2 = (uint)(dwl >> 32);
            _data3 = _data4 = 0;
 
            // Trim any leading zero from the length
            _bLen = (byte)((_data2 == 0) ? 1 : 2);
 
            _bPrec = BGetPrecUI8(dwl);
            _bScale = 0;
 
            AssertValid();
        }
 
        public SqlDecimal(byte bPrecision, byte bScale, bool fPositive, int[] bits)
        {
            CheckValidPrecScale(bPrecision, bScale);
            ArgumentNullException.ThrowIfNull(bits);
            if (bits.Length != 4)
                throw new ArgumentException(SQLResource.InvalidArraySizeMessage, nameof(bits));
 
            _bPrec = bPrecision;
            _bScale = bScale;
            _data1 = (uint)bits[0];
            _data2 = (uint)bits[1];
            _data3 = (uint)bits[2];
            _data4 = (uint)bits[3];
            _bLen = 1;
            for (int i = 3; i >= 0; i--)
            {
                if (bits[i] != 0)
                {
                    _bLen = (byte)(i + 1);
                    break;
                }
            }
 
            // set the null bit
            _bStatus = s_bNotNull;
 
            // set the sign bit
            if (!fPositive)
            {
                _bStatus |= s_bNegative;
            }
 
            // If result is -0, adjust sign to positive.
            if (FZero())
                SetPositive();
 
            if (bPrecision < CalculatePrecision())
                throw new OverflowException(SQLResource.ArithOverflowMessage);
        }
 
        public SqlDecimal(byte bPrecision, byte bScale, bool fPositive, int data1, int data2, int data3, int data4)
        {
            CheckValidPrecScale(bPrecision, bScale);
            _bPrec = bPrecision;
            _bScale = bScale;
 
            _data1 = (uint)data1;
            _data2 = (uint)data2;
            _data3 = (uint)data3;
            _data4 = (uint)data4;
 
            _bLen = 1;
            if (data4 == 0)
                if (data3 == 0)
                    if (data2 == 0)
                        _bLen = 1;
                    else
                        _bLen = 2;
                else
                    _bLen = 3;
            else
                _bLen = 4;
 
            // set the null bit
            _bStatus = s_bNotNull;
 
            // set the sign bit
            if (!fPositive)
            {
                _bStatus |= s_bNegative;
            }
 
            // If result is -0, adjust sign to positive.
            if (FZero())
                SetPositive();
 
            if (bPrecision < CalculatePrecision())
                throw new OverflowException(SQLResource.ArithOverflowMessage);
        }
 
        public SqlDecimal(double dVal) : this(false)
        {
            // set the null bit
            _bStatus = s_bNotNull;
 
            // Split double to sign, integer, and fractional parts
            if (dVal < 0)
            {
                dVal = -dVal;
                _bStatus |= s_bNegative;
            }
 
            // If it will not fit into numeric(NUMERIC_MAX_PRECISION,0), overflow.
            if (dVal >= s_DMAX_NUME)
                throw new OverflowException(SQLResource.ArithOverflowMessage);
 
            double dInt = Math.Floor(dVal);
            double dFrac = dVal - dInt;
 
            _bPrec = s_NUMERIC_MAX_PRECISION;
            _bLen = 1;
            if (dInt > 0.0)
            {
                dVal = Math.Floor(dInt / s_DUINT_BASE);
                _data1 = (uint)(dInt - dVal * s_DUINT_BASE);
                dInt = dVal;
 
                if (dInt > 0.0)
                {
                    dVal = Math.Floor(dInt / s_DUINT_BASE);
                    _data2 = (uint)(dInt - dVal * s_DUINT_BASE);
                    dInt = dVal;
                    _bLen++;
 
                    if (dInt > 0.0)
                    {
                        dVal = Math.Floor(dInt / s_DUINT_BASE);
                        _data3 = (uint)(dInt - dVal * s_DUINT_BASE);
                        dInt = dVal;
                        _bLen++;
 
                        if (dInt > 0.0)
                        {
                            dVal = Math.Floor(dInt / s_DUINT_BASE);
                            _data4 = (uint)(dInt - dVal * s_DUINT_BASE);
                            _bLen++;
                        }
                    }
                }
            }
 
            uint ulLen, ulLenDelta;
            uint ulTemp;
 
            // Get size of the integer part
            ulLen = FZero() ? 0 : (uint)CalculatePrecision();
            Debug.Assert(ulLen <= s_NUMERIC_MAX_PRECISION, "ulLen <= NUMERIC_MAX_PRECISION", "");
 
            // If we got more than 17 decimal digits, zero lower ones.
            if (ulLen > s_DBL_DIG)
            {
                // Divide number by 10 while there are more then 17 digits
                uint ulWrk = ulLen - s_DBL_DIG;
                do
                {
                    ulTemp = DivByULong(10);
                    ulWrk--;
                }
                while (ulWrk > 0);
                ulWrk = ulLen - s_DBL_DIG;
 
                // Round, if necessary. # of digits can change. Cannot be overflow.
                if (ulTemp >= 5)
                {
                    AddULong(1);
                    ulLen = CalculatePrecision() + ulWrk;
                }
 
                // Multiply back
                do
                {
                    MultByULong(10);
                    ulWrk--;
                }
                while (ulWrk > 0);
            }
 
            _bScale = (byte)(ulLen < s_DBL_DIG ? s_DBL_DIG - ulLen : 0);
            _bPrec = (byte)(ulLen + _bScale);
 
            // Add meaningful fractional part - max 9 digits per iteration
            if (_bScale > 0)
            {
                ulLen = _bScale;
                do
                {
                    ulLenDelta = (ulLen >= 9) ? 9 : ulLen;
 
                    dFrac *= RgulShiftBase[(int)ulLenDelta - 1];
                    ulLen -= ulLenDelta;
                    MultByULong(RgulShiftBase[(int)ulLenDelta - 1]);
                    AddULong((uint)dFrac);
                    dFrac -= Math.Floor(dFrac);
                }
                while (ulLen > 0);
            }
 
            // Round, if necessary
            if (dFrac >= 0.5)
            {
                AddULong(1);
            }
 
            if (FZero())
                SetPositive();
 
            AssertValid();
        }
 
        private SqlDecimal(ReadOnlySpan<uint> rglData, byte bLen, byte bPrec, byte bScale, bool fPositive)
        {
            CheckValidPrecScale(bPrec, bScale);
            Debug.Assert(rglData.Length >= 4);
 
            _bLen = bLen;
            _bPrec = bPrec;
            _bScale = bScale;
            _data1 = rglData[0];
            _data2 = rglData[1];
            _data3 = rglData[2];
            _data4 = rglData[3];
 
            // set the null bit
            _bStatus = s_bNotNull;
 
            // set the sign bit
            if (!fPositive)
            {
                _bStatus |= s_bNegative;
            }
 
            // If result is -0, adjust sign to positive.
            if (FZero())
                SetPositive();
        }
 
 
        // INullable
        public bool IsNull
        {
            get { return (_bStatus & s_bNullMask) == s_bIsNull; }
        }
 
        public decimal Value
        {
            get { return ToDecimal(); }
        }
 
        public bool IsPositive
        {
            get
            {
                if (IsNull)
                    throw new SqlNullValueException();
                return (_bStatus & s_bSignMask) == s_bPositive;
            }
        }
 
        private void SetPositive()
        {
            Debug.Assert(!IsNull);
            _bStatus = (byte)(_bStatus & s_bReverseSignMask);
        }
 
        private void SetSignBit(bool fPositive)
        {
            Debug.Assert(!IsNull);
            _bStatus = (byte)(fPositive ? (_bStatus & s_bReverseSignMask) : (_bStatus | s_bNegative));
        }
 
        public byte Precision
        {
            get
            {
                if (IsNull)
                    throw new SqlNullValueException();
                return _bPrec;
            }
        }
 
        public byte Scale
        {
            get
            {
                if (IsNull)
                    throw new SqlNullValueException();
                return _bScale;
            }
        }
 
        public int[] Data
        {
            get
            {
                if (IsNull)
                    throw new SqlNullValueException();
 
                unchecked
                {
                    return new int[4] { (int)_data1, (int)_data2, (int)_data3, (int)_data4 };
                }
            }
        }
 
        public byte[] BinData
        {
            get
            {
                if (IsNull)
                    throw new SqlNullValueException();
 
                int data1 = (int)_data1;
                int data2 = (int)_data2;
                int data3 = (int)_data3;
                int data4 = (int)_data4;
                byte[] rgBinData = new byte[16];
                rgBinData[0] = (byte)(data1 & 0xff);
                data1 >>= 8;
                rgBinData[1] = (byte)(data1 & 0xff);
                data1 >>= 8;
                rgBinData[2] = (byte)(data1 & 0xff);
                data1 >>= 8;
                rgBinData[3] = (byte)(data1 & 0xff);
                rgBinData[4] = (byte)(data2 & 0xff);
                data2 >>= 8;
                rgBinData[5] = (byte)(data2 & 0xff);
                data2 >>= 8;
                rgBinData[6] = (byte)(data2 & 0xff);
                data2 >>= 8;
                rgBinData[7] = (byte)(data2 & 0xff);
                rgBinData[8] = (byte)(data3 & 0xff);
                data3 >>= 8;
                rgBinData[9] = (byte)(data3 & 0xff);
                data3 >>= 8;
                rgBinData[10] = (byte)(data3 & 0xff);
                data3 >>= 8;
                rgBinData[11] = (byte)(data3 & 0xff);
                rgBinData[12] = (byte)(data4 & 0xff);
                data4 >>= 8;
                rgBinData[13] = (byte)(data4 & 0xff);
                data4 >>= 8;
                rgBinData[14] = (byte)(data4 & 0xff);
                data4 >>= 8;
                rgBinData[15] = (byte)(data4 & 0xff);
 
                return rgBinData;
            }
        }
 
        public override string ToString()
        {
            if (IsNull)
                return SQLResource.NullString;
            AssertValid();
 
            // Make local copy of data to avoid modifying input.
            Span<uint> rgulNumeric = [_data1, _data2, _data3, _data4];
            int culLen = _bLen;
            Span<char> pszTmp = stackalloc char[s_NUMERIC_MAX_PRECISION + 1];   //Local Character buffer to hold
            pszTmp.Clear();                                                     //the decimal digits, from the
                                                                                //lowest significant to highest significant
 
            int iDigits = 0; //Number of significant digits
            uint ulRem; //Remainder of a division by x_ulBase10, i.e.,least significant digit
 
            // Build the final numeric string by inserting the sign, reversing
            // the order and inserting the decimal number at the correct position
 
            //Retrieve each digit from the lowest significant digit
            while (culLen > 1 || rgulNumeric[0] != 0)
            {
                MpDiv1(rgulNumeric, ref culLen, s_ulBase10, out ulRem);
                //modulo x_ulBase10 is the lowest significant digit
                pszTmp[iDigits++] = ChFromDigit(ulRem);
            }
 
            // if scale of the number has not been
            // reached pad remaining number with zeros.
            while (iDigits <= _bScale)
            {
                pszTmp[iDigits++] = ChFromDigit(0);
            }
 
 
            int uiResultLen = 0;
            int iCurChar = 0;
            char[] szResult;
 
            // Increment the result length if scale > 0 (need to add '.')
            if (_bScale > 0)
            {
                uiResultLen = 1;
            }
 
            if (IsPositive)
            {
                szResult = new char[uiResultLen + iDigits];
            }
            else
            {
                // Increment the result length if negative (need to add '-')
                szResult = new char[uiResultLen + iDigits + 1];
                szResult[iCurChar++] = '-';
            }
 
            while (iDigits > 0)
            {
                if (iDigits-- == _bScale)
                    szResult[iCurChar++] = '.';
 
                szResult[iCurChar++] = pszTmp[iDigits];
            }
 
            AssertValid();
 
            return new string(szResult);
        }
 
        public static SqlDecimal Parse(string s)
        {
            ArgumentNullException.ThrowIfNull(s);
 
            if (s == SQLResource.NullString)
                return SqlDecimal.Null;
 
            SqlDecimal snResult = SqlDecimal.Null;
 
            char[] rgwchStr = s.ToCharArray();
            int cwchStr = rgwchStr.Length;
 
            int iData;            //index to string
            char usChar;            //current value in string
            int lDecPnt = -1;    //position of decimal point in string
            int iCurChar = 0;
 
            //Must initialize precision and scale to valid values
            snResult._bPrec = 1;
            snResult._bScale = 0;
 
            //Must initialize *this to zero
            snResult.SetToZero();
 
            // Trim trailing blanks.
            while (cwchStr != 0 && rgwchStr[cwchStr - 1] == ' ')
                cwchStr--;
 
            // If string contains only spaces, stop
            if (cwchStr == 0)
                throw new FormatException(SQLResource.FormatMessage);
 
            // Trim leading blanks.
            while (rgwchStr[iCurChar] == ' ')
            {
                iCurChar++;
                cwchStr--;
            }
 
            // Get sign for numeric value.
            if (rgwchStr[iCurChar] == '-')
            {
                snResult.SetSignBit(false);
                iCurChar++;
                cwchStr--;
            }
            else
            {
                snResult.SetSignBit(true);
                if (rgwchStr[iCurChar] == '+')
                {
                    iCurChar++;
                    cwchStr--;
                }
            }
 
            // Hack: Check for "0.". If so, replace by ".0".
            while ((cwchStr > 2) && (rgwchStr[iCurChar] == '0'))
            {
                iCurChar++;
                cwchStr--;
            }
            if (2 == cwchStr && '0' == rgwchStr[iCurChar] && '.' == rgwchStr[iCurChar + 1])
            {
                rgwchStr[iCurChar] = '.';
                rgwchStr[iCurChar + 1] = '0';
            }
 
            // Invalid string?
            if (cwchStr == 0 || cwchStr > s_NUMERIC_MAX_PRECISION + 1)
                throw new FormatException(SQLResource.FormatMessage);
 
            // Trim leading zeros.  (There shouldn't be any except for floats
            // less than 1.  e.g.  0.01)
            while ((cwchStr > 1) && (rgwchStr[iCurChar] == '0'))
            {
                iCurChar++;
                cwchStr--;
            }
 
            // Convert string to numeric value by looping through input string.
            for (iData = 0; iData < cwchStr; iData++)
            {
                usChar = rgwchStr[iCurChar];
                iCurChar++;
 
                if (char.IsAsciiDigit(usChar))
                {
                    usChar -= '0';
                }
                else if (usChar == '.' && lDecPnt < 0)
                {
                    lDecPnt = iData;
                    continue;
                }
                else
                {
                    throw new FormatException(SQLResource.FormatMessage);
                }
 
                snResult.MultByULong(s_ulBase10);
                snResult.AddULong(usChar);
            }
 
            // Save precision and scale.
            if (lDecPnt < 0)
            {
                snResult._bPrec = (byte)iData;
                snResult._bScale = 0;
            }
            else
            {
                snResult._bPrec = (byte)(iData - 1);
                snResult._bScale = (byte)(snResult._bPrec - lDecPnt);
            }
 
            //Check for overflow condition
            if (snResult._bPrec > s_NUMERIC_MAX_PRECISION)
                throw new FormatException(SQLResource.FormatMessage);
 
            // Check for invalid precision for numeric value.
            // e.g., when string is ".", precision will be 0
            if (snResult._bPrec == 0)
                throw new FormatException(SQLResource.FormatMessage);
 
            // If result is -0, adjust sign to positive.
            if (snResult.FZero())
                snResult.SetPositive();
 
            snResult.AssertValid();
 
            return snResult;
        }
 
        public double ToDouble()
        {
            if (IsNull)
                throw new SqlNullValueException();
 
            double dRet;
 
            dRet = _data4;
            dRet = dRet * s_lInt32Base + _data3;
            dRet = dRet * s_lInt32Base + _data2;
            dRet = dRet * s_lInt32Base + _data1;
 
            dRet /= System.Math.Pow(10.0, _bScale);
 
            return IsPositive ? dRet : -dRet;
        }
 
        private decimal ToDecimal()
        {
            if (IsNull)
                throw new SqlNullValueException();
 
            if ((int)_data4 != 0 || _bScale > 28)
                throw new OverflowException(SQLResource.ConversionOverflowMessage);
 
            unchecked
            {
                return new decimal((int)_data1, (int)_data2, (int)_data3, !IsPositive, _bScale);
            }
        }
 
        [CLSCompliant(false)]
        public int WriteTdsValue(Span<uint> destination)
        {
            if (IsNull)
            {
                throw new SqlNullValueException();
            }
 
            ArgumentOutOfRangeException.ThrowIfLessThan(destination.Length, 4, nameof(destination));
 
            destination[0] = _data1;
            destination[1] = _data2;
            destination[2] = _data3;
            destination[3] = _data4;
            return 4;
        }
 
        // Implicit conversion from Decimal to SqlDecimal
        public static implicit operator SqlDecimal(decimal x)
        {
            return new SqlDecimal(x);
        }
 
        // Explicit conversion from Double to SqlDecimal
        public static explicit operator SqlDecimal(double x)
        {
            return new SqlDecimal(x);
        }
 
        // Implicit conversion from long to SqlDecimal
        public static implicit operator SqlDecimal(long x)
        {
            return new SqlDecimal(new decimal(x));
        }
 
        // Explicit conversion from SqlDecimal to Decimal. Throw exception if x is Null.
        public static explicit operator decimal(SqlDecimal x)
        {
            return x.Value;
        }
 
 
        // Unary operators
        public static SqlDecimal operator -(SqlDecimal x)
        {
            if (x.IsNull)
                return Null;
            else
            {
                SqlDecimal s = x;
                if (s.FZero())
                    s.SetPositive();
                else
                    s.SetSignBit(!s.IsPositive);
                return s;
            }
        }
 
 
        // Binary operators
 
        // Arithmetic operators
        public static SqlDecimal operator +(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return Null;
 
            ulong dwlAccum;           //accumulated sum
            bool fMySignPos;         //sign of x was positive at start
            bool fOpSignPos;         // sign of y positive at start
            bool fResSignPos = true; //sign of result should be positive
            int MyScale;    //scale of x
            int OpScale;    //scale of y
            int ResScale;   //scale of result
            int ResPrec;    //precision of result
            int ResInteger; //number of digits for the integer part of result
            int culOp1;     //# of UI4s in x
            int culOp2;     //# of UI4s in y
            int iulData;    //which UI4 we are operating on in x, y
            byte bLen;       // length for the result
 
 
            x.AssertValid();
            y.AssertValid();
 
            fMySignPos = x.IsPositive;
            fOpSignPos = y.IsPositive;
 
            //result scale = max(s1,s2)
            //result precision = max(s1,s2) + max(p1-s1,p2-s2)
            MyScale = x._bScale;
            OpScale = y._bScale;
 
            // Calculate the integer part of the result.
            ResInteger = Math.Max(x._bPrec - MyScale, y._bPrec - OpScale);
            Debug.Assert(ResInteger <= MaxPrecision);
 
            // Calculate the scale of the result.
            ResScale = Math.Max(MyScale, OpScale);
            Debug.Assert(ResScale <= MaxScale);
 
            // Calculate the precision of the result.
            // Add 1 for final carry.
            ResPrec = ResInteger + ResScale + 1;
            ResPrec = Math.Min(MaxPrecision, ResPrec);
 
            // If precision adjusted, scale is reduced to keep the integer part untruncated.
            // But discard the extra carry, only keep the integer part as ResInteger, not ResInteger + 1.
            Debug.Assert(ResPrec - ResInteger >= 0);
            if (ResPrec - ResInteger < ResScale)
                ResScale = ResPrec - ResInteger;
 
            // Adjust both operands to be the same scale as ResScale.
            if (MyScale != ResScale)
                x.AdjustScale(ResScale - MyScale, true);
 
            if (OpScale != ResScale)
                y.AdjustScale(ResScale - OpScale, true);
 
            // When sign of first operand is negative
            // negate all operands including result.
            if (!fMySignPos)
            {
                // fMySignPos = !fMySignPos;
                fOpSignPos = !fOpSignPos;
                fResSignPos = !fResSignPos;
            }
 
            // Initialize operand lengths and pointer.
            culOp1 = x._bLen;
            culOp2 = y._bLen;
 
            Span<uint> rglData1 = [x._data1, x._data2, x._data3, x._data4];
            Span<uint> rglData2 = [y._data1, y._data2, y._data3, y._data4];
 
            if (fOpSignPos)
            {
                dwlAccum = 0;
 
                // CONSIDER: Call AddUlong when possible
 
                // Loop through UI4s adding operands and putting result in *this
                // of the operands and put result in *this
                for (iulData = 0; iulData < culOp1 || iulData < culOp2; iulData++)
                {
                    // None of these DWORDLONG additions can overflow, as dwlAccum comes in < x_lInt32Base
                    if (iulData < culOp1)
                        dwlAccum += rglData1[iulData];
                    if (iulData < culOp2)
                        dwlAccum += rglData2[iulData];
 
                    rglData1[iulData] = unchecked((uint)dwlAccum); // equiv to mod x_lInt32Base
                    dwlAccum >>= 32; // equiv to div x_lInt32Base
                }
 
                //If carry
                if (dwlAccum != 0)
                {
                    Debug.Assert(dwlAccum < s_ulInt32Base);
 
                    //Either overflowed
                    if (iulData == s_cNumeMax)
                        throw new OverflowException(SQLResource.ArithOverflowMessage);
 
                    // Or extended length
                    rglData1[iulData] = (uint)dwlAccum;
                    iulData++;
                }
 
                // Set result length
                bLen = (byte)iulData;
            }
            else
            {
                int iulLastNonZero = 0; // The last nonzero UI
 
                // When second operand is negative, switch operands
                // if operand2 is greater than operand1
                if (x.LAbsCmp(y) < 0)
                {
                    fResSignPos = !fResSignPos;
                    Span<uint> rguiTemp = rglData2;
                    rglData2 = rglData1;
                    rglData1 = rguiTemp;
                    culOp1 = culOp2;
                    culOp2 = x._bLen;
                }
 
                dwlAccum = s_ulInt32Base;
                for (iulData = 0; iulData < culOp1 || iulData < culOp2; iulData++)
                {
                    if (iulData < culOp1)
                        dwlAccum += rglData1[iulData];
                    if (iulData < culOp2)
                        dwlAccum -= rglData2[iulData];
 
                    rglData1[iulData] = unchecked((uint)dwlAccum); // equiv to mod BaseUI4
                    if (rglData1[iulData] != 0)
                        iulLastNonZero = iulData;
                    dwlAccum >>= 32; // equiv to /= BaseUI4
                    dwlAccum += s_ulInt32BaseForMod; // equiv to BaseUI4 - 1
                }
                // Set length based on highest non-zero ULONG
                bLen = (byte)(iulLastNonZero + 1);
            }
 
            SqlDecimal ret = new SqlDecimal(rglData1, bLen, (byte)ResPrec, (byte)ResScale, fResSignPos);
 
            if (ret.FGt10_38() || ret.CalculatePrecision() > s_NUMERIC_MAX_PRECISION)
                throw new OverflowException(SQLResource.ArithOverflowMessage);
 
            if (ret.FZero())
                ret.SetPositive();
 
            ret.AssertValid();
 
            return ret;
        }
 
        public static SqlDecimal operator -(SqlDecimal x, SqlDecimal y)
        {
            return x + (-y);
        }
 
        //    MultNm()
        //
        //    Multiply two numerics.
        //
        //  Parameters:
        //        x    - IN Multiplier
        //        y    - IN Multiplicand
        //
        //    Result scale and precision(same as in SQL Server Manual and Hydra):
        //        scale = s1 + s2
        //        precision = s1 + s2 + (p1 - s1) + (p2 - s2) + 1
        //
        //    Overflow Rules:
        //        If scale is greater than NUMERIC_MAX_PRECISION it is set to
        //    NUMERIC_MAX_PRECISION.  If precision is greater than NUMERIC_MAX_PRECISION
        //    it is set to NUMERIC_MAX_PRECISION, then scale is reduced to keep the
        //    integer part untruncated but keeping a minimum value of x_cNumeDivScaleMin.
        //    For example, if using the above formula, the resulting precision is 46 and
        //    scale is 10, the precision will be reduced to 38. To keep the integral part
        //    untruncated the scale needs be reduced to 2, but since x_cNumeDivScaleMin
        //    is set to 6 currently, resulting scale will be 6.
        //        O_OVERFLOW is returned only if the actual precision is greater than
        //     NUMERIC_MAX_PRECISION or the actual length is greater than x_cbNumeBuf.
        //
        //    Algorithm:
        //        Starting from the lowest significant UI4, for each UI4 of the multiplier
        //    iterate through the UI4s of the multiplicand starting from
        //    the least significant UI4s, multiply the multiplier UI4 with
        //    multiplicand UI4, update the result buffer with the product modulo
        //    x_dwlBaseUI4 at the same index as the multiplicand, and carry the quotient to
        //    add to the next multiplicand UI4.  Until the end of the multiplier data
        //    array is reached.
        //
        public static SqlDecimal operator *(SqlDecimal x, SqlDecimal y)
        {
            x.AssertValid();
            y.AssertValid();
 
            if (x.IsNull || y.IsNull)
                return Null;
 
            //Implementation:
            //        I) Figure result scale,prec
            //        II) Perform mult.
            //        III) Adjust product to result scale,prec
 
            // Local variables for actual multiplication
            int iulPlier;           //index of UI4 in the Multiplier
            uint ulPlier;            //current mutiplier UI4
            ulong dwlAccum;           //accumulated sum
            ulong dwlNextAccum;       //overflow of accumulated sum
            int culCand = y._bLen; //length of multiplicand in UI4s
 
            //Local variables to track scale,precision
            int ActualScale;                    // Scale after mult done
            int ResScale;                       // Final scale we will force result to
            int ResPrec;                        // Final precision we will force result to
            int ResInteger;                     // # of digits in integer part of result (prec-scale)
            int lScaleAdjust;   //How much result scale will be adjusted
            bool fResPositive;  // Result sign
 
            SqlDecimal ret;
 
 
            //I) Figure result prec,scale
            ActualScale = x._bScale + y._bScale;
            ResScale = ActualScale;
            ResInteger = (x._bPrec - x._bScale) + (y._bPrec - y._bScale) + 1;
 
            //result precision = s1 + s2 + (p1 - s1) + (p2 - s2) + 1
            ResPrec = ResScale + ResInteger;
 
            // Downward adjust res prec,scale if either larger than NUMERIC_MAX_PRECISION
            if (ResPrec > s_NUMERIC_MAX_PRECISION)
                ResPrec = s_NUMERIC_MAX_PRECISION;
            if (ResScale > s_NUMERIC_MAX_PRECISION)
                ResScale = s_NUMERIC_MAX_PRECISION;
 
            //
            // It is possible when two large numbers are being multiplied the scale
            // can be reduced to 0 to keep data untruncated; the fix here is to
            // preserve a minimum scale of 6.
            //
            // If overflow, reduce the scale to avoid truncation of data
            ResScale = Math.Min((ResPrec - ResInteger), ResScale);
            // But keep a minimum scale of NUMERIC_MIN_DVSCALE
            ResScale = Math.Max(ResScale, Math.Min(ActualScale, s_cNumeDivScaleMin));
 
            lScaleAdjust = ResScale - ActualScale;
 
            fResPositive = (x.IsPositive == y.IsPositive); //positive if both signs same.
 
            // II) Perform multiplication
 
            ReadOnlySpan<uint> rglData1 = [x._data1, x._data2, x._data3, x._data4];
            ReadOnlySpan<uint> rglData2 = [y._data1, y._data2, y._data3, y._data4];
 
            //Local buffer to hold the result of multiplication.
            //Longer than CReNumeBuf because full precision of multiplication is carried out
            const int x_culNumeMultRes = 9;       // Maximum # UI4s in result buffer in multiplication
            Span<uint> rgulRes = stackalloc uint[x_culNumeMultRes]; //new [] are already initialized to zero
            rgulRes.Clear(); // but spans in core libraries are not for performance reasons so clear it
            int culRes;             // # of UI4s in result
            int idRes = 0;
 
            //Iterate over the bytes of multiplier
            for (iulPlier = 0; iulPlier < x._bLen; iulPlier++)
            {
                ulPlier = rglData1[iulPlier];
                dwlAccum = 0;
 
                //Multiply each UI4 of multiCand by ulPliear and accumulate into result buffer
 
                // Start on correct place in result
                idRes = iulPlier;
 
                for (int iulCand = 0; iulCand < culCand; iulCand++)
                {
                    // dwlAccum = dwlAccum + rgulRes[idRes] + ulPlier*rglData2[iulCand]
                    //        use dwlNextAccum to detect overflow of DWORDLONG
                    dwlNextAccum = dwlAccum + rgulRes[idRes];
                    ulong ulTemp = rglData2[iulCand];
                    dwlAccum = ulPlier * ulTemp;
                    dwlAccum += dwlNextAccum;
                    if (dwlAccum < dwlNextAccum) // indicates dwl addition overflowed
                        dwlNextAccum = s_ulInt32Base; // = maxUI64/x_dwlBaseUI4
                    else
                        dwlNextAccum = 0;
 
                    // Update result and accum
                    rgulRes[idRes++] = unchecked((uint)dwlAccum); // & x_ulInt32BaseForMod); // equiv to mod x_lInt32Base
                    dwlAccum = (dwlAccum >> 32) + dwlNextAccum; // equiv to div BaseUI4 + dwlNAccum
 
                    // dwlNextAccum can't overflow next iteration
                    Debug.Assert(dwlAccum < s_ulInt32Base * 2);
                }
 
                Debug.Assert(dwlAccum < s_ulInt32Base); // can never final accum > 1 more UI4
                if (dwlAccum != 0)
                    rgulRes[idRes++] = (uint)dwlAccum;
            }
            // Skip leading 0s (may exist if we are multiplying by 0)
            for (; (rgulRes[idRes] == 0) && (idRes > 0); idRes--)
                ;
            // Calculate actual result length
            culRes = idRes + 1;
 
            // III) Adjust precision,scale to result prec,scale
            if (lScaleAdjust != 0)
            {
                // If need to decrease scale
                if (lScaleAdjust < 0)
                {
                    Debug.Assert(s_NUMERIC_MAX_PRECISION == ResPrec);
 
                    // have to adjust - might yet end up fitting.
                    // Cannot call AdjustScale - number cannot fit in a numeric, so
                    // have to duplicate code here
 
                    uint ulRem;          //Remainder when downshifting
                    uint ulShiftBase;    //What to multiply by to effect scale adjust
 
                    do
                    {
                        if (lScaleAdjust <= -9)
                        {
                            ulShiftBase = RgulShiftBase[8];
                            lScaleAdjust += 9;
                        }
                        else
                        {
                            ulShiftBase = RgulShiftBase[-lScaleAdjust - 1];
                            lScaleAdjust = 0;
                        }
                        MpDiv1(rgulRes, ref culRes, ulShiftBase, out ulRem);
                    }
                    while (lScaleAdjust != 0);
 
                    // Still do not fit?
                    if (culRes > s_cNumeMax)
                        throw new OverflowException(SQLResource.ArithOverflowMessage);
 
                    for (idRes = culRes; idRes < s_cNumeMax; idRes++)
                        rgulRes[idRes] = 0;
                    ret = new SqlDecimal(rgulRes, (byte)culRes, (byte)ResPrec, (byte)ResScale, fResPositive);
 
                    // Is it greater than 10**38?
                    if (ret.FGt10_38())
                        throw new OverflowException(SQLResource.ArithOverflowMessage);
 
                    ret.AssertValid();
 
                    // If remainder is 5 or above, increment/decrement by 1.
                    if (ulRem >= ulShiftBase / 2)
                        ret.AddULong(1);
                    // After adjusting, if the result is 0 and remainder is less than 5,
                    // set the sign to be positive
                    if (ret.FZero())
                        ret.SetPositive();
 
                    return ret;
                }
 
                // Otherwise call AdjustScale
                if (culRes > s_cNumeMax)    // Do not fit now, so will not fit after asjustement
                    throw new OverflowException(SQLResource.ArithOverflowMessage);
                // NOTE: Have not check for value in the range (10**38..2**128),
                // as we'll call AdjustScale with positive argument, and it'll
                // return "normal" overflow
 
                for (idRes = culRes; idRes < s_cNumeMax; idRes++)
                    rgulRes[idRes] = 0;
                ret = new SqlDecimal(rgulRes, (byte)culRes, (byte)ResPrec, (byte)ActualScale, fResPositive);
 
                if (ret.FZero())
                    ret.SetPositive();
 
                ret.AssertValid();
 
                ret.AdjustScale(lScaleAdjust, true);
 
                return ret;
            }
            else
            {
                if (culRes > s_cNumeMax)
                    throw new OverflowException(SQLResource.ArithOverflowMessage);
 
                for (idRes = culRes; idRes < s_cNumeMax; idRes++)
                    rgulRes[idRes] = 0;
                ret = new SqlDecimal(rgulRes, (byte)culRes, (byte)ResPrec, (byte)ResScale, fResPositive);
 
                // Is it greater than 10**38?
                if (ret.FGt10_38())
                    throw new OverflowException(SQLResource.ArithOverflowMessage);
 
                if (ret.FZero())
                    ret.SetPositive();
 
                ret.AssertValid();
 
                return ret;
            }
        }
 
        //-----------------------------------------------------------
        //DivNm():
        //  Divide numeric by numeric.
        //    The Quotient will be returned in *this
        //
        //Result scale&precision:
        //    NOTE: same as in Hydra but different from SQL Server Manual,
        //            where scale = max(s1+p2-s2+1,x_cNumeDivScaleMin)):
        //        scale = max(s1 + p2 + 1, x_cNumeDivScaleMin);
        //        precision = max(s1 + p2 + 1, x_cNumeDivScaleMin) + p1 + p2 + 1;
        //
        //Overflow Rules:
        //        If scale is greater than NUMERIC_MAX_PRECISION it is set to
        //    NUMERIC_MAX_PRECISION.  If precision is greater than NUMERIC_MAX_PRECISION
        //    it's set to NUMERIC_MAX_PRECISION, then scale is reduced to keep the
        //    integer part untruncated but keeping a minimum value of x_cNumeDivScaleMin.
        //    For example, if using the above formula, the resulting precision is 46 and
        //    scale is 10, the precision will be reduced to 38, to keep the integral part
        //    untruncated the scale needs be recuded to 2, but since x_cNumeDivScaleMin
        //    is set to 6 currently, resulting scale will be 6.
        //        OverflowException is thrown only if the actual precision is greater than
        //    NUMERIC_MAX_PRECISION or actual length is greater than x_cbNumeBuf
        //
        //Algorithm
        //  Call general purpose arbitrary precision division routine with scale = 0.
        //    Scale,prec adjusted later.
        //
        public static SqlDecimal operator /(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return Null;
 
            x.AssertValid();
            y.AssertValid();
 
            // Variables for figuring prec,scale
            int bScaleD;            // Input Scale of dividend (output scale of remainder)
            int bPrecD;             // Input Prec of dividend (output prec of remainder)
            int ResScale;           // Final scale we will force quotient to
            int ResPrec;            // Final precision we will force quotient to
            int ResInteger;         // # of digits in integer part of result (prec-scale)
            int MinScale;           // Temp to help compute ResScale
            int lScaleAdjust;       // How much result scale will be adjusted
            bool fResSignPos;       // sign of result
 
            // Steps:
            //    1) Figure result prec,scale; adjust scale of dividend
            //    2) Compute result remainder/quotient in 0 scale numbers
            //    3) Set result prec,scale and adjust as necessary
 
            // 0) Check for Div by 0
            if (y.FZero())
                throw new DivideByZeroException(SQLResource.DivideByZeroMessage);
 
            // 1) Figure out result prec,scale,sign..
            fResSignPos = (x.IsPositive == y.IsPositive); //sign of result
 
            //scale = max(s1 + p2 + 1, x_cNumeDivScaleMin);
            //precision = max(s1 + p2 + 1, x_cNumeDivScaleMin) + p1 + p2 + 1;
            //For backward compatibility, use exactly the same scheme as in Hydra
            bScaleD = x._bScale;
            bPrecD = x._bPrec;
            ResScale = Math.Max(x._bScale + y._bPrec + 1, s_cNumeDivScaleMin);
            ResInteger = x._bPrec - x._bScale + y._bScale;
            ResPrec = ResScale + x._bPrec + y._bPrec + 1;
            MinScale = Math.Min(ResScale, s_cNumeDivScaleMin);
 
            ResInteger = Math.Min(ResInteger, s_NUMERIC_MAX_PRECISION);
            ResPrec = ResInteger + ResScale;
 
            if (ResPrec > s_NUMERIC_MAX_PRECISION)
                ResPrec = s_NUMERIC_MAX_PRECISION;
 
            // If overflow, reduce the scale to avoid truncation of data
            ResScale = Math.Min((ResPrec - ResInteger), ResScale);
            ResScale = Math.Max(ResScale, MinScale);
 
            //Adjust the scale of the dividend
            lScaleAdjust = ResScale - x._bScale + y._bScale;
            x.AdjustScale(lScaleAdjust, true);
 
            // Step2: Actual Computation
 
            Span<uint> rgulData1 = [x._data1, x._data2, x._data3, x._data4];
            Span<uint> rgulData2 = [y._data1, y._data2, y._data3, y._data4];
 
            // Buffers for arbitrary precision divide
            Span<uint> rgulR = stackalloc uint[s_cNumeMax + 1];
            Span<uint> rgulQ = stackalloc uint[s_cNumeMax];
            rgulR.Clear();
            rgulQ.Clear();
            // # of ULONGs in result
            int culQ, culR;
 
            // Divide mantissas. V is not zero - already checked.
            // Cannot overflow, as Q <= U, R <= V. (and both are positive)
            MpDiv(rgulData1, x._bLen, rgulData2, y._bLen, rgulQ, out culQ, rgulR, out culR);
 
            // Construct the result from Q
            ZeroToMaxLen(rgulQ, culQ);
            SqlDecimal ret = new SqlDecimal(rgulQ, (byte)culQ, (byte)ResPrec, (byte)ResScale, fResSignPos);
 
            if (ret.FZero())
                ret.SetPositive();
 
            ret.AssertValid();
 
            return ret;
        }
 
 
 
        // Implicit conversions
 
        // Implicit conversion from SqlBoolean to SqlDecimal
        public static explicit operator SqlDecimal(SqlBoolean x)
        {
            return x.IsNull ? Null : new SqlDecimal(x.ByteValue);
        }
 
        // Implicit conversion from SqlByte to SqlDecimal
        public static implicit operator SqlDecimal(SqlByte x)
        {
            return x.IsNull ? Null : new SqlDecimal(x.Value);
        }
 
        // Implicit conversion from SqlInt16 to SqlDecimal
        public static implicit operator SqlDecimal(SqlInt16 x)
        {
            return x.IsNull ? Null : new SqlDecimal(x.Value);
        }
 
        // Implicit conversion from SqlInt32 to SqlDecimal
        public static implicit operator SqlDecimal(SqlInt32 x)
        {
            return x.IsNull ? Null : new SqlDecimal(x.Value);
        }
 
        // Implicit conversion from SqlInt64 to SqlDecimal
        public static implicit operator SqlDecimal(SqlInt64 x)
        {
            return x.IsNull ? Null : new SqlDecimal(x.Value);
        }
 
        // Implicit conversion from SqlMoney to SqlDecimal
        public static implicit operator SqlDecimal(SqlMoney x)
        {
            return x.IsNull ? Null : new SqlDecimal(x.ToDecimal());
        }
 
 
        // Explicit conversions
 
        // Explicit conversion from SqlSingle to SqlDecimal
        public static explicit operator SqlDecimal(SqlSingle x)
        {
            return x.IsNull ? SqlDecimal.Null : new SqlDecimal(x.Value);
        }
 
        // Explicit conversion from SqlDouble to SqlDecimal
        public static explicit operator SqlDecimal(SqlDouble x)
        {
            return x.IsNull ? SqlDecimal.Null : new SqlDecimal(x.Value);
        }
 
        // Explicit conversion from SqlString to SqlDecimal
        // Throws FormatException or OverflowException if necessary.
        public static explicit operator SqlDecimal(SqlString x)
        {
            return x.IsNull ? Null : SqlDecimal.Parse(x.Value);
        }
 
        // private methods
 
        //----------------------------------------------------------------------
        // Is this RE numeric valid?
        [Conditional("DEBUG")]
        private void AssertValid()
        {
            if (IsNull)
                return;
 
            // Scale,Prec in range
            Debug.Assert(_bScale <= s_NUMERIC_MAX_PRECISION, "m_bScale <= NUMERIC_MAX_PRECISION", "In AssertValid");
            Debug.Assert(_bScale <= _bPrec, "m_bScale <= m_bPrec", "In AssertValid");
            Debug.Assert(_bScale >= 0, "m_bScale >= 0", "In AssertValid");
            Debug.Assert(_bPrec > 0, "m_bPrec > 0", "In AssertValid");
 
            Debug.Assert(CLenFromPrec(_bPrec) >= _bLen, "CLenFromPrec(m_bPrec) >= m_bLen", "In AssertValid");
            Debug.Assert(_bLen <= s_cNumeMax, "m_bLen <= x_cNumeMax", "In AssertValid");
 
            ReadOnlySpan<uint> rglData = [_data1, _data2, _data3, _data4];
 
            // highest UI4 is non-0 unless value "zero"
            if (rglData[_bLen - 1] == 0)
            {
                Debug.Assert(_bLen == 1, "m_bLen == 1", "In AssertValid");
            }
 
            // All UI4s from length to end are 0
            for (int iulData = _bLen; iulData < s_cNumeMax; iulData++)
                Debug.Assert(rglData[iulData] == 0, "rglData[iulData] == 0", "In AssertValid");
        }
        /*
                // print the data members
                [System.Diagnostics.Conditional("DEBUG")]
                private void Print() {
                    if (IsNull) {
                        Debug.WriteLine("Numeric: Null");
                        return;
                    }
                    Debug.WriteLine("Numeric: data - " + m_data4.ToString() + ", " + m_data3.ToString() + ", " +
                                      m_data2.ToString() + ", " + m_data1.ToString());
                    Debug.WriteLine("\tlen = " + m_bLen.ToString() + ", Prec = " + m_bPrec.ToString() +
                                      ", Scale = " + m_bScale.ToString() + ", Sign = " + IsPositive.ToString());
                }
 
                [System.Diagnostics.Conditional("DEBUG")]
                private void Print(String s) {
                    Debug.WriteLine("*** " + s + " ***");
                    if (IsNull) {
                        Debug.WriteLine("Numeric: Null");
                        return;
                    }
                    Debug.WriteLine("Numeric: data - " + m_data4.ToString() + ", " + m_data3.ToString() + ", " +
                                      m_data2.ToString() + ", " + m_data1.ToString());
                    Debug.WriteLine("\tlen = " + m_bLen.ToString() + ", Prec = " + m_bPrec.ToString() +
                                      ", Scale = " + m_bScale.ToString() + ", Sign = " + IsPositive.ToString());
                }
        */
        // Set all extra uints to zero
        private static void ZeroToMaxLen(Span<uint> rgulData, int cUI4sCur)
        {
            Debug.Assert(rgulData.Length == s_cNumeMax, "rgulData.Length == x_cNumeMax", "Invalid array length");
 
            switch (cUI4sCur)
            {
                case 1:
                    rgulData[1] = rgulData[2] = rgulData[3] = 0;
                    break;
 
                case 2:
                    rgulData[2] = rgulData[3] = 0;
                    break;
 
                case 3:
                    rgulData[3] = 0;
                    break;
            }
        }
 
        // Set all extra uints to zero
        /*
        private void ZeroToMaxLen(int cUI4sCur) {
            switch (cUI4sCur) {
                case 1:
                    m_data2 = m_data3 = m_data4 = x_uiZero;
                    break;
 
                case 2:
                    m_data3 = m_data4 = x_uiZero;
                    break;
 
                case 3:
                    m_data4 = x_uiZero;
                    break;
            }
        }
        */
 
        //Determine the number of uints needed for a numeric given a precision
        //Precision        Length
        //    0            invalid
        //    1-9            1
        //    10-19          2
        //    20-28          3
        //    29-38          4
        private static ReadOnlySpan<byte> RgCLenFromPrec =>
        [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
            2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
            4, 4, 4, 4, 4, 4
        ];
 
        private static byte CLenFromPrec(byte bPrec)
        {
            Debug.Assert(bPrec <= MaxPrecision && bPrec > 0, "bPrec <= MaxPrecision && bPrec > 0",
                           "Invalid numeric precision");
            return RgCLenFromPrec[bPrec - 1];
        }
 
        // check whether is zero
        private bool FZero()
        {
            return (_data1 == 0) && (_bLen <= 1);
        }
 
        // Find the case where we overflowed 10**38, but not 2**128
        private bool FGt10_38()
        {
            return _data4 >= 0x4b3b4ca8L && _bLen == 4 &&
            ((_data4 > 0x4b3b4ca8L) || (_data3 > 0x5a86c47aL) ||
             (_data3 == 0x5a86c47aL) && (_data2 >= 0x098a2240L));
        }
 
        private static bool FGt10_38(Span<uint> rglData)
        {
            Debug.Assert(rglData.Length == 4, "rglData.Length == 4", $"Wrong array length: {rglData.Length}");
 
            return rglData[3] >= 0x4b3b4ca8L &&
            ((rglData[3] > 0x4b3b4ca8L) || (rglData[2] > 0x5a86c47aL) ||
             (rglData[2] == 0x5a86c47aL) && (rglData[1] >= 0x098a2240L));
        }
 
 
        // Powers of ten (used by BGetPrecI4,BGetPrecI8)
        private const uint s_ulT1 = 10;
        private const uint s_ulT2 = 100;
        private const uint s_ulT3 = 1000;
        private const uint s_ulT4 = 10000;
        private const uint s_ulT5 = 100000;
        private const uint s_ulT6 = 1000000;
        private const uint s_ulT7 = 10000000;
        private const uint s_ulT8 = 100000000;
        private const uint s_ulT9 = 1000000000;
        private const ulong s_dwlT10 = 10000000000;
        private const ulong s_dwlT11 = 100000000000;
        private const ulong s_dwlT12 = 1000000000000;
        private const ulong s_dwlT13 = 10000000000000;
        private const ulong s_dwlT14 = 100000000000000;
        private const ulong s_dwlT15 = 1000000000000000;
        private const ulong s_dwlT16 = 10000000000000000;
        private const ulong s_dwlT17 = 100000000000000000;
        private const ulong s_dwlT18 = 1000000000000000000;
        private const ulong s_dwlT19 = 10000000000000000000;
 
        //------------------------------------------------------------------
        //BGetPrecI4
        //    Return the precision(number of significant digits) of a integer
        private static byte BGetPrecUI4(uint value)
        {
            int ret;
 
            // Now do the (almost) binary search
            if (value < s_ulT4)
            {
                if (value < s_ulT2)
                    ret = value >= s_ulT1 ? 2 : 1;
                else
                    ret = value >= s_ulT3 ? 4 : 3;
            }
            else if (value < s_ulT8)
            {
                if (value < s_ulT6)
                    ret = value >= s_ulT5 ? 6 : 5;
                else
                    ret = value >= s_ulT7 ? 8 : 7;
            }
            else
                ret = value >= s_ulT9 ? 10 : 9;
 
            return (byte)ret;
        }
 
        //------------------------------------------------------------------
        //BGetPrecI8
        //    Return the precision (number of significant digits) of an Int8
        private static byte BGetPrecUI8(uint ulU0, uint ulU1)
        {
            ulong dwlVal = ulU0 + (((ulong)ulU1) << 32);
            return BGetPrecUI8(dwlVal);
        }
 
        private static byte BGetPrecUI8(ulong dwlVal)
        {
            int ret;
 
            // Now do the (almost) binary search
            if (dwlVal < s_ulT8)
            {
                uint ulVal = (uint)dwlVal;
 
                if (ulVal < s_ulT4)
                {
                    if (ulVal < s_ulT2)
                        ret = (ulVal >= s_ulT1) ? 2 : 1;
                    else
                        ret = (ulVal >= s_ulT3) ? 4 : 3;
                }
                else
                {
                    if (ulVal < s_ulT6)
                        ret = (ulVal >= s_ulT5) ? 6 : 5;
                    else
                        ret = (ulVal >= s_ulT7) ? 8 : 7;
                }
            }
            else if (dwlVal < s_dwlT16)
            {
                if (dwlVal < s_dwlT12)
                {
                    if (dwlVal < s_dwlT10)
                        ret = (dwlVal >= s_ulT9) ? 10 : 9;
                    else
                        ret = (dwlVal >= s_dwlT11) ? 12 : 11;
                }
                else
                {
                    if (dwlVal < s_dwlT14)
                        ret = (dwlVal >= s_dwlT13) ? 14 : 13;
                    else
                        ret = (dwlVal >= s_dwlT15) ? 16 : 15;
                }
            }
            else
            {
                if (dwlVal < s_dwlT18)
                    ret = (dwlVal >= s_dwlT17) ? 18 : 17;
                else
                    ret = (dwlVal >= s_dwlT19) ? 20 : 19;
            }
 
            return (byte)ret;
        }
 
#if DEBUG
        // This is the old precision routine. It is only used in debug code to verify that both code paths
        // return the same value
 
        //
        //    BActualPrec()
        //
        //    Determine the actual number of significant digits (precision) of a numeric
        //
        //    Parameters:
        //
        //    Complexity:
        //        For small numerics, use simpler routines = O(n)
        //        Else, max 3 divisions of mp by ULONG, then again simpler routine.
        //
        //    Returns:
        //        a byte containing the actual precision
        //
        private byte BActualPrec()
        {
            if (_bPrec == 0 || _bLen < 1)
                return 0;
 
            int ciulU = _bLen;
            int Prec;
            // uint ulRem;
 
            if (ciulU == 1)
            {
                Prec = BGetPrecUI4(_data1);
            }
            else if (ciulU == 2)
            {
                Prec = BGetPrecUI8(_data1, _data2);
            }
            else
            {
                Span<uint> rgulU = [_data1, _data2, _data3, _data4];
                Prec = 0;
                do
                {
                    MpDiv1(rgulU, ref ciulU, 1000000000, out _ /* ulRem */);
                    Prec += 9;
                }
                while (ciulU > 2);
                Debug.Assert(Prec == 9 || Prec == 18 || Prec == 27);
                Prec += BGetPrecUI8(rgulU[0], rgulU[1]);
            }
 
            // If number of significant digits less than scale, return scale
            return (Prec < _bScale ? _bScale : (byte)Prec);
        }
#endif
 
        //    AddULong()
        //
        //    Add ulAdd to this numeric.  The result will be returned in *this.
        //
        //    Parameters:
        //        this    - IN Operand1 & OUT Result
        //        ulAdd    - IN operand2.
        //
        private void AddULong(uint ulAdd)
        {
            ulong dwlAccum = ulAdd;
            int iData;                  // which UI4 in this we are on
            int iDataMax = _bLen; // # of UI4s in this
 
            Span<uint> rguiData = [_data1, _data2, _data3, _data4];
 
            // Add, starting at the LS UI4 until out of UI4s or no carry
            iData = 0;
            do
            {
                dwlAccum += rguiData[iData];
                rguiData[iData] = (uint)dwlAccum;       // equivalent to mod x_dwlBaseUI4
                dwlAccum >>= 32;                        // equivalent to dwlAccum /= x_dwlBaseUI4;
                if (0 == dwlAccum)
                {
                    StoreFromWorkingArray(rguiData);
                    return;
                }
                iData++;
            }
            while (iData < iDataMax);
 
            // There is carry at the end
 
            Debug.Assert(dwlAccum < s_ulInt32Base, "dwlAccum < x_lInt32Base", "");
 
            // Either overflowed
            if (iData == s_cNumeMax)
                throw new OverflowException(SQLResource.ArithOverflowMessage);
 
            // Or need to extend length by 1 UI4
            rguiData[iData] = (uint)dwlAccum;
            _bLen++;
 
            if (FGt10_38(rguiData))
                throw new OverflowException(SQLResource.ArithOverflowMessage);
 
            StoreFromWorkingArray(rguiData);
        }
 
        // multiply by a long integer
        private void MultByULong(uint uiMultiplier)
        {
            int iDataMax = _bLen; // How many UI4s currently in *this
 
            ulong dwlAccum = 0;       // accumulated sum
            ulong dwlNextAccum;       // accumulation past dwlAccum
            int iData;              // which UI4 in *This we are on.
 
            Span<uint> rguiData = [_data1, _data2, _data3, _data4];
 
            for (iData = 0; iData < iDataMax; iData++)
            {
                Debug.Assert(dwlAccum < s_ulInt32Base);
 
                ulong ulTemp = rguiData[iData];
                dwlNextAccum = ulTemp * uiMultiplier;
                dwlAccum += dwlNextAccum;
                if (dwlAccum < dwlNextAccum)        // Overflow of int64 add
                    dwlNextAccum = s_ulInt32Base;   // how much to add to dwlAccum after div x_dwlBaseUI4
                else
                    dwlNextAccum = 0;
                rguiData[iData] = unchecked((uint)dwlAccum); // equivalent to mod x_dwlBaseUI4
                dwlAccum = (dwlAccum >> 32) + dwlNextAccum;  // equivalent to div x_dwlBaseUI4
            }
 
            // If any carry,
            if (dwlAccum != 0)
            {
                // Either overflowed
                Debug.Assert(dwlAccum < s_ulInt32Base, "dwlAccum < x_dwlBaseUI4", "Integer overflow");
                if (iDataMax == s_cNumeMax)
                    throw new OverflowException(SQLResource.ArithOverflowMessage);
 
                // Or extend length by one uint
                rguiData[iDataMax] = (uint)dwlAccum;
                _bLen++;
            }
 
            if (FGt10_38(rguiData))
                throw new OverflowException(SQLResource.ArithOverflowMessage);
 
            StoreFromWorkingArray(rguiData);
        }
 
 
        //    DivByULong()
        //
        //    Divide numeric value by a ULONG.  The result will be returned
        //    in the dividend *this.
        //
        //    Parameters:
        //        this        - IN Dividend & OUT Result
        //        ulDivisor    - IN Divisor
        //    Returns:        - OUT Remainder
        //
        private uint DivByULong(uint iDivisor)
        {
            ulong dwlDivisor = iDivisor;
            ulong dwlAccum = 0;           //Accumulated sum
            uint ulQuotientCur;          // Value of the current UI4 of the quotient
            bool fAllZero = true;    // All of the quotient (so far) has been 0
            int iData;              //Which UI4 currently on
 
            // Check for zero divisor.
            if (dwlDivisor == 0)
                throw new DivideByZeroException(SQLResource.DivideByZeroMessage);
 
            // Copy into array, so that we can iterate through the data
            Span<uint> rguiData = [_data1, _data2, _data3, _data4];
 
            // Start from the MS UI4 of quotient, divide by divisor, placing result
            //        in quotient and carrying the remainder.
            // DWORDLONG sufficient accumulator since:
            //        Accum < Divisor <= 2^32 - 1    at start each loop
            //                                    initially,and mod end previous loop
            //        Accum*2^32 < 2^64 - 2^32
            //                                    multiply both side by 2^32 (x_dwlBaseUI4)
            //        Accum*2^32 + m_rgulData < 2^64
            //                                    rglData < 2^32
            for (iData = _bLen; iData > 0; iData--)
            {
                Debug.Assert(dwlAccum < dwlDivisor);
                dwlAccum = (dwlAccum << 32) + rguiData[iData - 1]; // dwlA*x_dwlBaseUI4 + rglData
                Debug.Assert((dwlAccum / dwlDivisor) < s_ulInt32Base);
                //Update dividend to the quotient.
                ulQuotientCur = (uint)(dwlAccum / dwlDivisor);
                rguiData[iData - 1] = ulQuotientCur;
                //Remainder to be carried to the next lower significant byte.
                dwlAccum %= dwlDivisor;
 
                // While current part of quotient still 0, reduce length
                if (fAllZero && (ulQuotientCur == 0))
                {
                    _bLen--;
                }
                else
                {
                    fAllZero = false;
                }
            }
 
            StoreFromWorkingArray(rguiData);
 
            // If result is 0, preserve sign but set length to 5
            if (fAllZero)
                _bLen = 1;
 
            AssertValid();
 
            // return the remainder
            Debug.Assert(dwlAccum < s_ulInt32Base);
            return (uint)dwlAccum;
        }
 
        //    AdjustScale()
        //
        //    Adjust number of digits to the right of the decimal point.
        //    A positive adjustment increases the scale of the numeric value
        //    while a negative adjustment decreases the scale.  When decreasing
        //    the scale for the numeric value, the remainder is checked and
        //    rounded accordingly.
        //
        internal void AdjustScale(int digits, bool fRound)
        {
            Debug.Assert(!IsNull, "!IsNull", "In AdjustScale");
 
            uint ulRem;                  //Remainder when downshifting
            uint ulShiftBase;            //What to multiply by to effect scale adjust
            bool fNeedRound = false;     //Do we really need to round?
            byte bNewScale, bNewPrec;
            int lAdjust = digits;
 
            //If downshifting causes truncation of data
            if (lAdjust + _bScale < 0)
                throw new SqlTruncateException();
 
            //If uphifting causes scale overflow
            if (lAdjust + _bScale > s_NUMERIC_MAX_PRECISION)
                throw new OverflowException(SQLResource.ArithOverflowMessage);
 
            bNewScale = (byte)(lAdjust + _bScale);
            bNewPrec = (byte)(Math.Min(s_NUMERIC_MAX_PRECISION, Math.Max(1, lAdjust + _bPrec)));
 
            if (lAdjust > 0)
            {
                _bScale = bNewScale;
                _bPrec = bNewPrec;
 
                while (lAdjust > 0)
                {
                    //if lAdjust>=9, downshift by 10^9 each time, otherwise by the full amount
                    if (lAdjust >= 9)
                    {
                        ulShiftBase = RgulShiftBase[8];
                        lAdjust -= 9;
                    }
                    else
                    {
                        ulShiftBase = RgulShiftBase[lAdjust - 1];
                        lAdjust = 0;
                    }
                    MultByULong(ulShiftBase);
                }
            }
            else if (lAdjust < 0)
            {
                do
                {
                    if (lAdjust <= -9)
                    {
                        ulShiftBase = RgulShiftBase[8];
                        lAdjust += 9;
                    }
                    else
                    {
                        ulShiftBase = RgulShiftBase[-lAdjust - 1];
                        lAdjust = 0;
                    }
                    ulRem = DivByULong(ulShiftBase);
                }
                while (lAdjust < 0);
 
                // Do we really need to round?
                fNeedRound = (ulRem >= ulShiftBase / 2);
 
                _bScale = bNewScale;
                _bPrec = bNewPrec;
            }
 
            AssertValid();
 
            // After adjusting, if the result is 0 and remainder is less than 5,
            // set the sign to be positive and return.
            if (fNeedRound && fRound)
            {
                // If remainder is 5 or above, increment/decrement by 1.
                AddULong(1);
            }
            else if (FZero())
                SetPositive();
        }
 
        // Adjust scale of a SqlDecimal
        public static SqlDecimal AdjustScale(SqlDecimal n, int digits, bool fRound)
        {
            if (n.IsNull)
                return SqlDecimal.Null;
 
            SqlDecimal ret = n;
            ret.AdjustScale(digits, fRound);
            return ret;
        }
 
        // Convert to a specific precision and scale
        public static SqlDecimal ConvertToPrecScale(SqlDecimal n, int precision, int scale)
        {
            CheckValidPrecScale(precision, scale);
            n.AssertValid();
 
            if (n.IsNull)
                return SqlDecimal.Null;
 
            SqlDecimal ret = n;
 
            int lScaleAdjust = scale - ret._bScale; //Adjustment to scale
 
            //Adjust scale
            ret.AdjustScale(lScaleAdjust, true);
 
            //The number of bytes storage required by the new precision
            byte cbWithNewPrec = CLenFromPrec((byte)precision);
 
            if (cbWithNewPrec < ret._bLen)
            {
                //if current actual length greater than length corresponding to bNewPrec
                //there must be truncating
                throw new SqlTruncateException();
            }
            else if (cbWithNewPrec == ret._bLen)
            {
                //if the two lengths equal, need to check the actual precision
                if (precision < ret.CalculatePrecision())
                    throw new SqlTruncateException();
            }
 
            //Adjust precision
            ret._bPrec = (byte)precision;
 
            ret.AssertValid();
 
            return ret;
        }
 
        //    LAbsCmp()
        //
        //    Compare the absolute value of two numerics without checking scale
        //
        //  Parameters:
        //        this    - IN Operand1
        //        snumOp    - IN Operand2
        //
        //    Returns:
        //        positive    - |this| > |snumOp|
        //        0            - |this| = |snumOp|
        //        negative    - |this| < |snumOp|
        //
        private int LAbsCmp(SqlDecimal snumOp)
        {
            int iData;  //which UI4 we are operating on
            int culOp;  //#of UI4s on operand
            int culThis; //# of UI4s in this
 
            // If one longer, it is larger
            culOp = snumOp._bLen;
            culThis = _bLen;
            if (culOp != culThis)
                return (culThis > culOp) ? 1 : -1;
 
            ReadOnlySpan<uint> rglData1 = [_data1, _data2, _data3, _data4];
            ReadOnlySpan<uint> rglData2 = [snumOp._data1, snumOp._data2, snumOp._data3, snumOp._data4];
 
            // Loop through numeric value checking each byte for differences.
            iData = culOp - 1;
            do
            {
                // CONSIDER PERF: Could replace second comparison with
                //        cast to LONGLONG and subtract.  Probably not worth it.
                if (rglData1[iData] != rglData2[iData])
                    return ((rglData1[iData] > rglData2[iData]) ? 1 : -1);
                iData--;
            }
            while (iData >= 0);
 
            // All UI4s the same, return 0.
            return 0;
        }
 
        // Move multi-precision number
        private static void MpMove
        (
        ReadOnlySpan<uint> rgulS,      // In    | Source number
        int ciulS,      // In    | # of digits in S
        Span<uint> rgulD,      // Out    | Destination number
        out int ciulD       // Out    | # of digits in D
        )
        {
            ciulD = ciulS;
 
            Debug.Assert(rgulS.Length >= ciulS, "rgulS.Length >= ciulS", "Invalid array length");
            Debug.Assert(rgulD.Length >= ciulS, "rgulD.Length >= ciulS", "Invalid array length");
 
            for (int i = 0; i < ciulS; i++)
                rgulD[i] = rgulS[i];
        }
 
        // Set multi-precision number to one super-digit
        private static void MpSet
        (
        Span<uint> rgulD,      // Out    | Number
        out int ciulD,      // Out    | # of digits in D
        uint iulN        // In    | ULONG to set
        )
        {
            ciulD = 1;
            rgulD[0] = iulN;
        }
 
        // Normalize multi-precision number - remove leading zeroes
        private static void MpNormalize
        (
        ReadOnlySpan<uint> rgulU,      // In   | Number
        ref int ciulU       // InOut| # of digits
        )
        {
            while (ciulU > 1 && rgulU[ciulU - 1] == 0)
                ciulU--;
        }
 
        // Multi-precision one super-digit multiply in place.
        // D = D * X
        // Length can increase
        private static void MpMul1
        (
        Span<uint> piulD,      // InOut| D
        ref int ciulD,      // InOut| # of digits in D
        uint iulX        // In    | X
        )
        {
            Debug.Assert(iulX > s_uiZero);
            uint ulCarry = 0;
            int iData;
            ulong dwlAccum;
 
            for (iData = 0; iData < ciulD; iData++)
            {
                ulong ulTemp = piulD[iData];
                dwlAccum = ulCarry + ulTemp * iulX;
                ulCarry = HI(dwlAccum);
                piulD[iData] = LO(dwlAccum);
            }
 
            // If overflow occurs, increase precision
            if (ulCarry != 0)
            {
                piulD[iData] = ulCarry;
                ciulD++;
            }
        }
 
        // Multi-precision one super-digit divide in place.
        // U = U / D,
        // R = U % D
        // Length of U can decrease
        private static void MpDiv1
        (
        Span<uint> rgulU,      // InOut| U
        ref int ciulU,      // InOut| # of digits in U
        uint iulD,       // In    | D
        out uint iulR        // Out    | R
        )
        {
            Debug.Assert(rgulU.Length == s_cNumeMax);
 
            uint ulCarry = 0;
            ulong dwlAccum;
            ulong ulD = iulD;
            int idU = ciulU;
 
            Debug.Assert(iulD != 0, "iulD != 0", "Divided by zero!");
            Debug.Assert(iulD > 0, "iulD > 0", "Invalid data: less than zero");
            Debug.Assert(ciulU > 0, "ciulU > 0", "No data in the array");
 
            while (idU > 0)
            {
                idU--;
                dwlAccum = (((ulong)ulCarry) << 32) + rgulU[idU];
                rgulU[idU] = (uint)(dwlAccum / ulD);
                ulCarry = (uint)(dwlAccum - rgulU[idU] * ulD);  // (ULONG) (dwlAccum % iulD)
            }
            iulR = ulCarry;
            MpNormalize(rgulU, ref ciulU);
        }
 
        internal static ulong DWL(uint lo, uint hi)
        {
            return lo + (((ulong)hi) << 32);
        }
 
        private static uint HI(ulong x)
        {
            return (uint)(x >> 32);
        }
 
        private static uint LO(ulong x)
        {
            return (uint)x;
        }
 
        // Multi-precision divide.
        // Q = U / D,
        // R = U % D,
        // U and D not changed.
        // It is Ok for U and R to have the same location in memory,
        // but then U will be changed.
        // Assumes that there is enough room in Q and R for results.
 
        // Drawbacks of this implementation:
        //    1)    Need one extra super-digit (ULONG) in R
        //    2)    As it modifies D during work, then it do (probably) unnecessary
        //        work to restore it
        //    3)    Always get Q and R - if R is unnecessary, can be slightly faster
        // Most of this can be fixed if it'll be possible to have a working buffer. But
        // then we'll use alloca() or there will be limit on the upper size of numbers
        // (maybe not a problem in SQL Server).
        //
        private static void MpDiv
        (
        ReadOnlySpan<uint> rgulU,      // In    | U
        int ciulU,      // In    | # of digits in U
        Span<uint> rgulD,      // InOut    | D
        int ciulD,      // In    | # of digits in D
        Span<uint> rgulQ,      // Out    | Q
        out int ciulQ,      // Out    | # of digits in Q
        Span<uint> rgulR,      // Out    | R
        out int ciulR       // Out    | # of digits in R
        )
        {
            Debug.Assert(ciulU > 0, "ciulU > 0", "In method MpDiv");
            Debug.Assert(ciulD > 0, "ciulD > 0", "In method MpDiv");
            Debug.Assert(rgulU.Length == s_cNumeMax);
            Debug.Assert(rgulD.Length == s_cNumeMax);
 
            // Division by zero?
            if (ciulD == 1 && rgulD[0] == 0)
            {
                ciulQ = ciulR = 0;
            }
 
            // Check for simplest case, so it'll be fast
            else if (ciulU == 1 && ciulD == 1)
            {
                MpSet(rgulQ, out ciulQ, rgulU[0] / rgulD[0]);
                MpSet(rgulR, out ciulR, rgulU[0] % rgulD[0]);
            }
 
            // If D > U then do not divide at all
            else if (ciulD > ciulU)
            {
                MpMove(rgulU, ciulU, rgulR, out ciulR);        // R = U
                MpSet(rgulQ, out ciulQ, 0);                    // Q = 0
            }
 
            // Try to divide faster - check for remaining good sizes (8 / 4, 8 / 8)
            else if (ciulU <= 2)
            {
                ulong dwlU, dwlD, dwlT;
 
                dwlU = DWL(rgulU[0], rgulU[1]);
                dwlD = rgulD[0];
                if (ciulD > 1)
                    dwlD += (((ulong)rgulD[1]) << 32);
                dwlT = dwlU / dwlD;
                rgulQ[0] = LO(dwlT);
                rgulQ[1] = HI(dwlT);
                ciulQ = (HI(dwlT) != 0) ? 2 : 1;
                dwlT = dwlU % dwlD;
                rgulR[0] = LO(dwlT);
                rgulR[1] = HI(dwlT);
                ciulR = (HI(dwlT) != 0) ? 2 : 1;
            }
 
            // If we are dividing by one digit - use simpler routine
            else if (ciulD == 1)
            {
                MpMove(rgulU, ciulU, rgulQ, out ciulQ);        // Q = U
                uint remainder;
                MpDiv1(rgulQ, ref ciulQ, rgulD[0], out remainder);     // Q = Q / D, R = Q % D
                rgulR[0] = remainder;
                ciulR = 1;
            }
 
            // Worst case. Knuth, "The Art of Computer Programming", 3rd edition, vol.II, Alg.D, pg 272
            else
            {
                ciulQ = ciulR = 0;
 
                uint D1, ulDHigh, ulDSecond;
                int iulRindex;
 
                if (rgulU != rgulR)
                    MpMove(rgulU, ciulU, rgulR, out ciulR);        // R = U
 
                ciulQ = ciulU - ciulD + 1;
                ulDHigh = rgulD[ciulD - 1];
 
                // D1.    Normalize so high digit of D >= BASE/2 - that guarantee
                //        that QH will not be too far from the correct digit later in D3
                rgulR[ciulU] = 0;
                iulRindex = ciulU;
                D1 = (uint)(s_ulInt32Base / ((ulong)ulDHigh + 1));
                if (D1 > 1)
                {
                    MpMul1(rgulD, ref ciulD, D1);
                    ulDHigh = rgulD[ciulD - 1];
                    MpMul1(rgulR, ref ciulR, D1);
                }
                ulDSecond = rgulD[ciulD - 2];
                // D2 already done - iulRindex initialized before normalization of R.
                // D3-D7. Loop on iulRindex - obtaining digits one-by-one, as "in paper"
                do
                {
                    uint QH, RH;
                    int iulDindex, iulRwork;
                    ulong dwlAccum, dwlMulAccum;
 
                    // D3. Calculate Q hat - estimation of the next digit
                    dwlAccum = DWL(rgulR[iulRindex - 1], rgulR[iulRindex]);
                    if (ulDHigh == rgulR[iulRindex])
                        QH = (uint)(s_ulInt32Base - 1);
                    else
                        QH = (uint)(dwlAccum / ulDHigh);
                    ulong ulTemp = QH;
                    RH = (uint)(dwlAccum - ulTemp * ulDHigh);
 
                    while (ulDSecond * ulTemp > DWL(rgulR[iulRindex - 2], RH))
                    {
                        QH--;
                        if (RH >= (uint)-((int)ulDHigh))
                            break;
                        RH += ulDHigh;
                        ulTemp = QH;
                    }
 
                    // D4. Multiply and subtract: (some digits of) R -= D * QH
                    for (dwlAccum = s_ulInt32Base, dwlMulAccum = 0, iulDindex = 0, iulRwork = iulRindex - ciulD;
                        iulDindex < ciulD; iulDindex++, iulRwork++)
                    {
                        ulong ulTemp2 = rgulD[iulDindex];
                        dwlMulAccum += QH * ulTemp2;
                        dwlAccum += (ulong)rgulR[iulRwork] - LO(dwlMulAccum);
                        dwlMulAccum = HI(dwlMulAccum);
                        rgulR[iulRwork] = LO(dwlAccum);
                        dwlAccum = HI(dwlAccum) + s_ulInt32Base - 1;
                    }
                    dwlAccum += rgulR[iulRwork] - dwlMulAccum;
                    rgulR[iulRwork] = LO(dwlAccum);
                    rgulQ[iulRindex - ciulD] = QH;
 
                    // D5. Test remainder. Carry indicates result<0, therefore QH 1 too large
                    if (HI(dwlAccum) == 0)
                    {
                        // D6. Add back - probability is 2**(-31). R += D. Q[digit] -= 1
                        uint ulCarry;
 
                        rgulQ[iulRindex - ciulD] = QH - 1;
                        for (ulCarry = 0, iulDindex = 0, iulRwork = iulRindex - ciulD;
                            iulDindex < ciulD; iulDindex++, iulRwork++)
                        {
                            dwlAccum = rgulD[iulDindex] + (ulong)rgulR[iulRwork] + ulCarry;
                            ulCarry = HI(dwlAccum);
                            rgulR[iulRwork] = LO(dwlAccum);
                        }
                        rgulR[iulRwork] += ulCarry;
                    }
                    // D7. Loop on iulRindex
                    iulRindex--;
                }
                while (iulRindex >= ciulD);
                // Normalize results
                MpNormalize(rgulQ, ref ciulQ);
                ciulR = ciulD;
                MpNormalize(rgulR, ref ciulR);
                // D8. Unnormalize: Divide D and R to get result
                if (D1 > 1)
                {
                    MpDiv1(rgulD, ref ciulD, D1, out _);
                    MpDiv1(rgulR, ref ciulR, D1, out _);
                }
            }
        }
 
        //    CmpCompareNm()
        //
        //    Compare the value of two numerics
        //
        //    Complexity: O(pn) p: precision  n: length
        //
        //  Parameters:
        //        this    - IN Operand1
        //        snumOp    - IN operand2
        //
        //    Returns:
        //        EComparison.LT    - this < snumOp
        //        EComparison.EQ        - this = snumOp
        //        EComparison.GT    - this > snumOp
        //
        private EComparison CompareNm
        (
        SqlDecimal snumOp
        )
        {
            AssertValid();
            snumOp.AssertValid();
 
            //Signs of the two numeric operands
            int Sign1;
            int Sign2;
 
            int iFinalResult;   //Final result of comparison: positive = greater
                                //than, 0 = equal, negative = less than
 
            //Initialize the sign values to be 1(positive) or -1(negative)
            Sign1 = IsPositive ? 1 : -1;
            Sign2 = snumOp.IsPositive ? 1 : -1;
 
            if (Sign1 != Sign2) //If different sign, the positive one is greater
                return Sign1 == 1 ? EComparison.GT : EComparison.LT;
 
            else
            { //same sign, have to compare absolute values
                //Temporary memory to hold the operand since it is const
                //but its scale may get adjusted during comparison
                int ScaleDiff;
                SqlDecimal snumArg1 = this;
                SqlDecimal snumArg2 = snumOp;
 
                //First make the two operands the same scale if necessary
                ScaleDiff = _bScale - snumOp._bScale;
 
                if (ScaleDiff < 0)
                {
                    //If increasing the scale of operand1 caused an overflow,
                    //then its absolute value is greater than that of operand2.
                    try
                    {
                        snumArg1.AdjustScale(-ScaleDiff, true);
                    }
                    catch (OverflowException)
                    {
                        return (Sign1 > 0) ? EComparison.GT : EComparison.LT;
                    }
                }
                else if (ScaleDiff > 0)
                {
                    //If increasing the scale of operand2 caused an overflow, then
                    //operand1's absolute value is less than that of operand2.
                    try
                    {
                        snumArg2.AdjustScale(ScaleDiff, true);
                    }
                    catch (OverflowException)
                    {
                        return (Sign1 > 0) ? EComparison.LT : EComparison.GT;
                    }
                }
 
                //Compare the absolute value of the two numerics
                //Note: We are sure that scale of arguments is the same,
                //      so LAbsCmp() will not modify its argument.
                int lResult = snumArg1.LAbsCmp(snumArg2);
                if (0 == lResult)
                    return EComparison.EQ;
 
                //if both positive, result same as result from LAbsCmp;
                //if both negative, result reverse of result from LAbsCmp
                iFinalResult = Sign1 * lResult;
 
                if (iFinalResult < 0)
                    return EComparison.LT;
                else
                    return EComparison.GT;
            }
        }
 
        private static void CheckValidPrecScale(byte bPrec, byte bScale)
        {
            if (bPrec < 1 || bPrec > MaxPrecision || bScale > MaxScale || bScale > bPrec)
                throw new SqlTypeException(SQLResource.InvalidPrecScaleMessage);
        }
 
        private static void CheckValidPrecScale(int iPrec, int iScale)
        {
            if (iPrec < 1 || iPrec > MaxPrecision || iScale < 0 || iScale > MaxScale || iScale > iPrec)
                throw new SqlTypeException(SQLResource.InvalidPrecScaleMessage);
        }
 
        // Overloading comparison operators
        public static SqlBoolean operator ==(SqlDecimal x, SqlDecimal y)
        {
            return (x.IsNull || y.IsNull) ? SqlBoolean.Null : new SqlBoolean(x.CompareNm(y) == EComparison.EQ);
        }
 
        public static SqlBoolean operator !=(SqlDecimal x, SqlDecimal y)
        {
            return !(x == y);
        }
 
        public static SqlBoolean operator <(SqlDecimal x, SqlDecimal y)
        {
            return (x.IsNull || y.IsNull) ? SqlBoolean.Null : new SqlBoolean(x.CompareNm(y) == EComparison.LT);
        }
 
        public static SqlBoolean operator >(SqlDecimal x, SqlDecimal y)
        {
            return (x.IsNull || y.IsNull) ? SqlBoolean.Null : new SqlBoolean(x.CompareNm(y) == EComparison.GT);
        }
 
        public static SqlBoolean operator <=(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlBoolean.Null;
            else
            {
                EComparison result = x.CompareNm(y);
                return new SqlBoolean(result == EComparison.LT || result == EComparison.EQ);
            }
        }
 
        public static SqlBoolean operator >=(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlBoolean.Null;
            else
            {
                EComparison result = x.CompareNm(y);
                return new SqlBoolean(result == EComparison.GT || result == EComparison.EQ);
            }
        }
 
 
        //--------------------------------------------------
        // Alternative methods for overloaded operators
        //--------------------------------------------------
 
        // Alternative method for operator +
        public static SqlDecimal Add(SqlDecimal x, SqlDecimal y)
        {
            return x + y;
        }
        // Alternative method for operator -
        public static SqlDecimal Subtract(SqlDecimal x, SqlDecimal y)
        {
            return x - y;
        }
 
        // Alternative method for operator *
        public static SqlDecimal Multiply(SqlDecimal x, SqlDecimal y)
        {
            return x * y;
        }
 
        // Alternative method for operator /
        public static SqlDecimal Divide(SqlDecimal x, SqlDecimal y)
        {
            return x / y;
        }
 
        // Alternative method for operator ==
        public static SqlBoolean Equals(SqlDecimal x, SqlDecimal y)
        {
            return (x == y);
        }
 
        // Alternative method for operator !=
        public static SqlBoolean NotEquals(SqlDecimal x, SqlDecimal y)
        {
            return (x != y);
        }
 
        // Alternative method for operator <
        public static SqlBoolean LessThan(SqlDecimal x, SqlDecimal y)
        {
            return (x < y);
        }
 
        // Alternative method for operator >
        public static SqlBoolean GreaterThan(SqlDecimal x, SqlDecimal y)
        {
            return (x > y);
        }
 
        // Alternative method for operator <=
        public static SqlBoolean LessThanOrEqual(SqlDecimal x, SqlDecimal y)
        {
            return (x <= y);
        }
 
        // Alternative method for operator >=
        public static SqlBoolean GreaterThanOrEqual(SqlDecimal x, SqlDecimal y)
        {
            return (x >= y);
        }
 
        // Alternative method for conversions.
 
        public SqlBoolean ToSqlBoolean()
        {
            return (SqlBoolean)this;
        }
 
        public SqlByte ToSqlByte()
        {
            return (SqlByte)this;
        }
 
        public SqlDouble ToSqlDouble()
        {
            return this;
        }
 
        public SqlInt16 ToSqlInt16()
        {
            return (SqlInt16)this;
        }
 
        public SqlInt32 ToSqlInt32()
        {
            return (SqlInt32)this;
        }
 
        public SqlInt64 ToSqlInt64()
        {
            return (SqlInt64)this;
        }
 
        public SqlMoney ToSqlMoney()
        {
            return (SqlMoney)this;
        }
 
        public SqlSingle ToSqlSingle()
        {
            return this;
        }
 
        public SqlString ToSqlString()
        {
            return (SqlString)this;
        }
 
        private static char ChFromDigit(uint uiDigit)
        {
            Debug.Assert(uiDigit < 10);
            return (char)(uiDigit + '0');
        }
 
        // Store data back from rguiData[] to m_data*
        private void StoreFromWorkingArray(ReadOnlySpan<uint> rguiData)
        {
            Debug.Assert(rguiData.Length == 4);
            _data1 = rguiData[0];
            _data2 = rguiData[1];
            _data3 = rguiData[2];
            _data4 = rguiData[3];
        }
 
        private void SetToZero()
        {
            Debug.Assert(_bPrec >= 1);
            _bLen = 1;
            _data1 =
            _data2 =
            _data3 =
            _data4 = 0;
            _bStatus = (byte)(s_bNotNull | s_bPositive);
            AssertValid();
        }
 
        // Truncate to integer
        private void MakeInteger(out bool fFraction)
        {
            uint ulRem;
            int iAdjust = _bScale;
 
            fFraction = false;
 
            while (iAdjust > 0)
            {
                if (iAdjust >= 9)
                {
                    ulRem = DivByULong(RgulShiftBase[8]);
                    iAdjust -= 9;
                }
                else
                {
                    ulRem = DivByULong(RgulShiftBase[iAdjust - 1]);
                    iAdjust = 0;
                }
 
                // Check for remainder and set fFraction flag.
                if (ulRem != 0)
                    fFraction = true;
            }
 
            _bScale = 0;
            AssertValid();
        }
 
        // Builtin functions
 
        // Abs - absolute value
        public static SqlDecimal Abs(SqlDecimal n)
        {
            n.AssertValid();
 
            if (n.IsNull)
                return SqlDecimal.Null;
 
            n.SetPositive();
            n.AssertValid();
            return n;
        }
 
        // Ceiling - next smallest integer greater than or equal to the numeric
        public static SqlDecimal Ceiling(SqlDecimal n)
        {
            n.AssertValid();
 
            if (n.IsNull)
                return SqlDecimal.Null;
 
            if (n._bScale == 0)
                return n;
 
            bool fFraction;    //Fractional flag
 
            n.MakeInteger(out fFraction);
 
            //When the numeric has fraction and is positive, adjust by adding 1.
            //Otherwise return the integral part.
            if (fFraction && n.IsPositive)
            {
                n.AddULong(1);
            }
 
            if (n.FZero())//if result is zero, sign should be positive
                n.SetPositive();
            n.AssertValid();
            return n;
        }
 
        // Floor - next largest integer smaller or equal to the numeric
        public static SqlDecimal Floor(SqlDecimal n)
        {
            n.AssertValid();
 
            if (n.IsNull)
                return SqlDecimal.Null;
 
            if (n._bScale == 0)
                return n;
 
            bool fFraction;    //Fractional flag
 
            n.MakeInteger(out fFraction);
 
            //When the numeric has fraction and is negative, subtract 1 by calling AddULong(1)
            //Otherwise return the integral part.
            if (fFraction && !n.IsPositive)
            {
                n.AddULong(1);
            }
 
            if (n.FZero())//if result is zero, sign should be positive
                n.SetPositive();
            n.AssertValid();
            return n;
        }
 
        // Sign -   1 if positive, -1 if negative
        public static SqlInt32 Sign(SqlDecimal n)
        {
            n.AssertValid();
 
            if (n.IsNull)
                return SqlInt32.Null;
 
            if (n == new SqlDecimal(0))
                return SqlInt32.Zero;
            else
                return n.IsNull ? SqlInt32.Null :
                    (n.IsPositive ? new SqlInt32(1) : new SqlInt32(-1));
        }
 
        private static SqlDecimal Round(SqlDecimal n, int lPosition, bool fTruncate)
        {
            if (n.IsNull)
                return SqlDecimal.Null;
 
            if (lPosition >= 0)
            {
                //If round to the right of decimal number
                lPosition = Math.Min(s_NUMERIC_MAX_PRECISION, lPosition);
                if (lPosition >= n._bScale)
                    return n;   //No need to round
            }
            else
            {
                //If round to the left of the decimal point
                lPosition = Math.Max(-s_NUMERIC_MAX_PRECISION, lPosition);
 
                //Return +0.00 if truncation of integer part
                if (lPosition < n._bScale - n._bPrec)
                {
                    n.SetToZero();
                    return n;
                }
            }
 
            uint ulRem = 0;                                         // Remainder: the highest significant digit to be truncated
            int lAdjust = Math.Abs(lPosition - n._bScale);    // Precision adjustment
            uint ulLastDivBase = 1;                                 //
 
            //Compute the integral part of the numeric
            while (lAdjust > 0)
            {
                if (lAdjust >= 9)
                {
                    ulRem = n.DivByULong(RgulShiftBase[8]);
                    ulLastDivBase = RgulShiftBase[8];
                    lAdjust -= 9;
                }
                else
                {
                    ulRem = n.DivByULong(RgulShiftBase[lAdjust - 1]);
                    ulLastDivBase = RgulShiftBase[lAdjust - 1];
                    lAdjust = 0;
                }
            }
 
            // The rounding only depends on the first digit after the rounding position
            if (ulLastDivBase > 1)
            {
                ulRem /= (ulLastDivBase / 10);
            }
 
            //If result is zero, return
            if (n.FZero() && (fTruncate || ulRem < 5))
            {
                n.SetPositive();
                n.AssertValid();
                return n;
            }
 
            // Adjust by adding 1 if remainder is larger than 5
            if (ulRem >= 5 && !fTruncate)
                n.AddULong(1);
 
            // Convert back to original scale
            lAdjust = Math.Abs(lPosition - n._bScale);
 
            while (lAdjust-- > 0)
            {
                n.MultByULong(s_ulBase10);
            }
 
            n.AssertValid();
            return n;
        }
 
        // Round - Round the numeric to a specific digit
        public static SqlDecimal Round(SqlDecimal n, int position)
        {
            n.AssertValid();
            return Round(n, position, false);
        }
 
        // Truncate - Truncate the numeric to a specific digit
        public static SqlDecimal Truncate(SqlDecimal n, int position)
        {
            n.AssertValid();
            return Round(n, position, true);
        }
 
        // Power - Compute the power of a numeric
        public static SqlDecimal Power(SqlDecimal n, double exp)
        {
            n.AssertValid();
 
            if (n.IsNull)
                return SqlDecimal.Null;
 
            int scale = n.Scale;
            double dBaseNum = n.ToDouble();
 
            n = new SqlDecimal(Math.Pow(dBaseNum, exp));
            n.AdjustScale(scale - n.Scale, true);
 
            n._bPrec = MaxPrecision;
 
            return n;
        }
 
 
        // IComparable
        // Compares this object to another object, returning an integer that
        // indicates the relationship.
        // Returns a value less than zero if this < object, zero if this = object,
        // or a value greater than zero if this > object.
        // null is considered to be less than any instance.
        // If object is not of same type, this method throws an ArgumentException.
        public int CompareTo(object? value)
        {
            if (value is SqlDecimal i)
            {
                return CompareTo(i);
            }
            throw ADP.WrongType(value!.GetType(), typeof(SqlDecimal));
        }
 
        public int CompareTo(SqlDecimal value)
        {
            // If both Null, consider them equal.
            // Otherwise, Null is less than anything.
            if (IsNull)
                return value.IsNull ? 0 : -1;
            else if (value.IsNull)
                return 1;
 
            if (this < value) return -1;
            if (this > value) return 1;
            return 0;
        }
 
        // Compares this instance with a specified object
        public override bool Equals([NotNullWhen(true)] object? value) =>
            value is SqlDecimal other && Equals(other);
 
        /// <summary>Indicates whether the current instance is equal to another instance of the same type.</summary>
        /// <param name="other">An instance to compare with this instance.</param>
        /// <returns>true if the current instance is equal to the other instance; otherwise, false.</returns>
        public bool Equals(SqlDecimal other) =>
            other.IsNull || IsNull ? other.IsNull && IsNull :
            (this == other).Value;
 
        // For hashing purpose
        public override int GetHashCode()
        {
            if (IsNull)
                return 0;
 
            SqlDecimal ssnumTemp;
            int lActualPrec;
 
            // First, "normalize" numeric, so that values with different
            // scale/precision will have the same representation.
            ssnumTemp = this;
            lActualPrec = ssnumTemp.CalculatePrecision();
            ssnumTemp.AdjustScale(s_NUMERIC_MAX_PRECISION - lActualPrec, true);
 
            // Now evaluate the hash
            int cDwords = ssnumTemp._bLen;
            int ulValue = 0;
            int ulHi;
 
            // Size of CRC window (hashing bytes, ssstr, sswstr, numeric)
            const int x_cbCrcWindow = 4;
            // const int iShiftVal = (sizeof ulValue) * (8*sizeof(char)) - x_cbCrcWindow;
            const int iShiftVal = 4 * 8 - x_cbCrcWindow;
 
            int[] rgiData = ssnumTemp.Data;
 
            for (int i = 0; i < cDwords; i++)
            {
                ulHi = (ulValue >> iShiftVal) & 0xff;
                ulValue <<= x_cbCrcWindow;
                ulValue = ulValue ^ rgiData[i] ^ ulHi;
            }
            return ulValue;
        }
 
        XmlSchema? IXmlSerializable.GetSchema() { return null; }
 
        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            string? isNull = reader.GetAttribute("nil", XmlSchema.InstanceNamespace);
            if (isNull != null && XmlConvert.ToBoolean(isNull))
            {
                // Read the next value.
                reader.ReadElementString();
                _bStatus = (byte)(s_bReverseNullMask & _bStatus);
            }
            else
            {
                SqlDecimal dec = Parse(reader.ReadElementString());
                _bStatus = dec._bStatus;
                _bLen = dec._bLen;
                _bPrec = dec._bPrec;
                _bScale = dec._bScale;
                _data1 = dec._data1;
                _data2 = dec._data2;
                _data3 = dec._data3;
                _data4 = dec._data4;
            }
        }
 
        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            if (IsNull)
            {
                writer.WriteAttributeString("xsi", "nil", XmlSchema.InstanceNamespace, "true");
            }
            else
            {
                writer.WriteString(ToString());
            }
        }
 
        public static XmlQualifiedName GetXsdType(XmlSchemaSet schemaSet)
        {
            return new XmlQualifiedName("decimal", XmlSchema.Namespace);
        }
 
        // These values are defined at last, because they need to call ctors, which use other
        // constant values. Those constants must be defined before callint ctor.
 
        public static readonly SqlDecimal Null = new SqlDecimal(true);
 
        public static readonly SqlDecimal MinValue = SqlDecimal.Parse("-99999999999999999999999999999999999999");
        public static readonly SqlDecimal MaxValue = SqlDecimal.Parse("99999999999999999999999999999999999999");
    } // SqlDecimal
} // namespace System.Data.SqlTypes