/* ---------------------------------------------------------------------------- DblToRgbPrecise() Uses big integer arithmetic to get the sequence of digits. */ public static void DblToRgbPrecise(double dbl, byte[] mantissa, out int exponent, out int mantissaSize) { BigInteger biNum = new BigInteger(); BigInteger biDen = new BigInteger(); BigInteger biHi = new BigInteger(); BigInteger biLo = new BigInteger(); BigInteger biT = new BigInteger(); BigInteger biHiLo; byte bT; bool fPow2; int ib, cu; int wExp10, wExp2, w1, w2; int c2Num, c2Den, c5Num, c5Den; double dblT; //uint *rgu = stackalloc uint[2]; uint rgu0, rgu1; uint dblHi, dblLo; dblHi = DblHi(dbl); dblLo = DblLo(dbl); // Caller should take care of 0, negative and non-finite values. Debug.Assert(!IsSpecial(dbl)); Debug.Assert(0 < dbl); // Init the Denominator, Hi error and Lo error bigints. biDen.InitFromDigits(1, 0, 1); biHi.InitFromDigits(1, 0, 1); wExp2 = (int)(((dblHi & 0x7FF00000) >> 20) - 1075); rgu1 = dblHi & 0x000FFFFF; rgu0 = dblLo; cu = 2; fPow2 = false; if (wExp2 == -1075) { // dbl is denormalized. Debug.Assert(0 == (dblHi & 0x7FF00000)); if (0 == rgu1) { cu = 1; } // Get dblT such that dbl / dblT is a power of 2 and 1 <= dblT < 2. // First multiply by a power of 2 to get a normalized value. dblT = BitConverter.Int64BitsToDouble(0x4FF00000L << 32); dblT *= dbl; Debug.Assert(0 != (DblHi(dblT) & 0x7FF00000)); // This is the power of 2. w1 = (int)((DblHi(dblT) & 0x7FF00000) >> 20) - (256 + 1023); dblHi = DblHi(dblT); dblHi &= 0x000FFFFF; dblHi |= 0x3FF00000; dblT = BitConverter.Int64BitsToDouble((long)dblHi << 32 | DblLo(dblT)); // Adjust wExp2 because we don't have the implicit bit. wExp2++; } else { // Get dblT such that dbl / dblT is a power of 2 and 1 <= dblT < 2. // First multiply by a power of 2 to get a normalized value. dblHi &= 0x000FFFFF; dblHi |= 0x3FF00000; dblT = BitConverter.Int64BitsToDouble((long)dblHi << 32 | dblLo); // This is the power of 2. w1 = wExp2 + 52; if (0 == rgu0 && 0 == rgu1 && wExp2 > -1074) { // Power of 2 bigger than smallest normal. The next smaller // representable value is closer than the next larger value. rgu1 = 0x00200000; wExp2--; fPow2 = true; } else { // Normalized and not a power of 2 or the smallest normal. The // representable values on either side are the same distance away. rgu1 |= 0x00100000; } } // Compute an approximation to the base 10 log. This is borrowed from // David ----'s paper. Debug.Assert(1 <= dblT && dblT < 2); dblT = (dblT - 1.5) * 0.289529654602168 + 0.1760912590558 + w1 * 0.301029995663981; wExp10 = (int)dblT; if (dblT < 0 && dblT != wExp10) { wExp10--; } if (wExp2 >= 0) { c2Num = wExp2; c2Den = 0; } else { c2Num = 0; c2Den = -wExp2; } if (wExp10 >= 0) { c5Num = 0; c5Den = wExp10; c2Den += wExp10; } else { c2Num -= wExp10; c5Num = -wExp10; c5Den = 0; } if (c2Num > 0 && c2Den > 0) { w1 = c2Num < c2Den ? c2Num : c2Den; c2Num -= w1; c2Den -= w1; } // We need a bit for the Hi and Lo values. c2Num++; c2Den++; // Initialize biNum and multiply by powers of 5. if (c5Num > 0) { Debug.Assert(0 == c5Den); biHi.MulPow5(c5Num); biNum.InitFromBigint(biHi); if (1 == cu) { biNum.MulAdd(rgu0, 0); } else { biNum.MulAdd(rgu1, 0); biNum.ShiftLeft(32); if (0 != rgu0) { biT.InitFromBigint(biHi); biT.MulAdd(rgu0, 0); biNum.Add(biT); } } } else { Debug.Assert(cu <= 2); biNum.InitFromDigits(rgu0, rgu1, cu); if (c5Den > 0) { biDen.MulPow5(c5Den); } } // BigInteger.DivRem only works if the 4 high bits of the divisor are 0. // It works most efficiently if there are exactly 4 zero high bits. // Adjust c2Den and c2Num to guarantee this. w1 = CbitZeroLeft(biDen[biDen.Length - 1]); w1 = (w1 + 28 - c2Den) & 0x1F; c2Num += w1; c2Den += w1; // Multiply by powers of 2. Debug.Assert(c2Num > 0 && c2Den > 0); biNum.ShiftLeft(c2Num); if (c2Num > 1) { biHi.ShiftLeft(c2Num - 1); } biDen.ShiftLeft(c2Den); Debug.Assert(0 == (biDen[biDen.Length - 1] & 0xF0000000)); Debug.Assert(0 != (biDen[biDen.Length - 1] & 0x08000000)); // Get biHiLo and handle the power of 2 case where biHi needs to be doubled. if (fPow2) { biHiLo = biLo; biHiLo.InitFromBigint(biHi); biHi.ShiftLeft(1); } else { biHiLo = biHi; } for (ib = 0; ; ) { bT = (byte)biNum.DivRem(biDen); if (0 == ib && 0 == bT) { // Our estimate of wExp10 was too big. Oh well. wExp10--; goto LSkip; } // w1 = sign(biNum - biHiLo). w1 = biNum.CompareTo(biHiLo); // w2 = sign(biNum + biHi - biDen). if (biDen.CompareTo(biHi) < 0) { w2 = 1; } else { // biT.InitFromBigint(biDen); biT.Subtract(biHi); w2 = biNum.CompareTo(biT); } // if (biNum + biHi == biDen && even) if (0 == w2 && 0 == (dblLo & 1)) { // Rounding up this digit produces exactly (biNum + biHi) which // StrToDbl will round down to dbl. if (bT == 9) { goto LRoundUp9; } if (w1 > 0) { bT++; } mantissa[ib++] = bT; break; } // if (biNum < biHiLo || biNum == biHiLo && even) if (w1 < 0 || 0 == w1 && 0 == (dblLo & 1)) { // if (biNum + biHi > biDen) if (w2 > 0) { // Decide whether to round up. biNum.ShiftLeft(1); w2 = biNum.CompareTo(biDen); if ((w2 > 0 || 0 == w2 && (0 != (bT & 1))) && bT++ == 9) { goto LRoundUp9; } } mantissa[ib++] = bT; break; } // if (biNum + biHi > biDen) if (w2 > 0) { // Round up and be done with it. if (bT != 9) { mantissa[ib++] = (byte)(bT + 1); break; } goto LRoundUp9; } // Save the digit. mantissa[ib++] = bT; LSkip: biNum.MulAdd(10, 0); biHi.MulAdd(10, 0); if ((object) biHiLo != (object) biHi) { biHiLo.MulAdd(10, 0); } continue; LRoundUp9: while (ib > 0) { if (mantissa[--ib] != 9) { mantissa[ib++]++; goto LReturn; } } wExp10++; mantissa[ib++] = 1; break; } LReturn: exponent = wExp10 + 1; mantissaSize = ib; }