// this = this * other. public void Multiply(ref DiyFp other) { // Simply "emulates" a 128 bit multiplication. // However: the resulting number only contains 64 bits. The least // significant 64 bits are only used for rounding the most significant 64 // bits. const long kM32 = 0xFFFFFFFFU; ulong a = f >> 32; ulong b = f & kM32; ulong c = other.f >> 32; ulong d = other.f & kM32; ulong ac = a * c; ulong bc = b * c; ulong ad = a * d; ulong bd = b * d; ulong tmp = (bd >> 32) + (ad & kM32) + (bc & kM32); // By adding 1U << 31 to tmp we round the final result. // Halfway cases will be round up. tmp += 1U << 31; ulong result_f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); e += other.e + 64; f = result_f; }
public static bool TryRunDouble(double value, int requestedDigits, ref NumberBuffer number) { var v = value.IsNegative() ? -value : value; Debug.Assert(v > 0); Debug.Assert(v.IsFinite()); int length; int decimalExponent; bool result; if (requestedDigits == -1) { var w = DiyFp.CreateAndGetBoundaries(v, out var boundaryMinus, out var boundaryPlus).Normalize(); result = TryRunShortest(in boundaryMinus, in w, in boundaryPlus, number.DigitsMut, out length, out decimalExponent); } else { var w = new DiyFp(v).Normalize(); result = TryRunCounted(in w, requestedDigits, number.DigitsMut, out length, out decimalExponent); } if (!result) { return(result); } Debug.Assert(requestedDigits == -1 || length == requestedDigits); number.Scale = length + decimalExponent; number.DigitsMut[length] = (byte)'\0'; number.DigitsCount = length; return(result); }
// Returns the two boundaries of first argument. // The bigger boundary (m_plus) is normalized. The lower boundary has the same // exponent as m_plus. internal static void NormalizedBoundaries(long d64, DiyFp mMinus, DiyFp mPlus) { DiyFp v = AsDiyFp(d64); bool significandIsZero = (v.F == KHiddenBit); mPlus.F = (v.F << 1) + 1; mPlus.E = v.E - 1; mPlus.Normalize(); if (significandIsZero && v.E != KDenormalExponent) { // The boundary is closer. Think of v = 1000e10 and v- = 9999e9. // Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but // at a distance of 1e8. // The only exception is for the smallest normal: the largest denormal is // at the same distance as its successor. // Note: denormals have the same exponent as the smallest normals. mMinus.F = (v.F << 2) - 1; mMinus.E = v.E - 2; } else { mMinus.F = (v.F << 1) - 1; mMinus.E = v.E - 1; } mMinus.F = mMinus.F << (mMinus.E - mPlus.E); mMinus.E = mPlus.E; }
private static void CachedPower(int k, out DiyFp cmk, out int decimalExponent) { int index = ((PowerOffset + k - 1) / PowerDecimalExponentDistance) + 1; cmk = new DiyFp(s_CachedPowerSignificands[index], s_CachedPowerBinaryExponents[index]); decimalExponent = s_CachedPowerDecimalExponents[index]; }
public static bool TryRunSingle(float value, int requestedDigits, ref NumberBuffer number) { float v = float.IsNegative(value) ? -value : value; Debug.Assert(v > 0); Debug.Assert(float.IsFinite(v)); int length; int decimalExponent; bool result; if (requestedDigits == -1) { DiyFp w = DiyFp.CreateAndGetBoundaries(v, out DiyFp boundaryMinus, out DiyFp boundaryPlus).Normalize(); result = TryRunShortest(in boundaryMinus, in w, in boundaryPlus, number.Digits, out length, out decimalExponent); } else { DiyFp w = new DiyFp(v).Normalize(); result = TryRunCounted(in w, requestedDigits, number.Digits, out length, out decimalExponent); } if (result) { Debug.Assert((requestedDigits == -1) || (length == requestedDigits)); number.Scale = length + decimalExponent; number.Digits[length] = (byte)('\0'); number.DigitsCount = length; } return(result); }
// Returns a - b. // The exponents of both numbers must be the same and this must be bigger // than other. The result will not be normalized. public static DiyFp Minus(ref DiyFp a, ref DiyFp b) { DiyFp result = a; result.Subtract(ref b); return(result); }
// this = this * other. public void Multiply(ref DiyFp other) { // Simply "emulates" a 128 bit multiplication. // However: the resulting number only contains 64 bits. The least // significant 64 bits are only used for rounding the most significant 64 // bits. const long kM32 = 0xFFFFFFFFU; var a = F >> 32; var b = F & kM32; var c = other.F >> 32; var d = other.F & kM32; var ac = a * c; var bc = b * c; var ad = a * d; var bd = b * d; var tmp = (bd >> 32) + (ad & kM32) + (bc & kM32); // By adding 1U << 31 to tmp we round the final result. // Halfway cases will be round up. tmp += 1U << 31; var result_f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); E += other.E + 64; F = result_f; }
// returns a * b; public static DiyFp Times(ref DiyFp a, ref DiyFp b) { DiyFp result = a; result.Multiply(ref b); return(result); }
public void Minus(ref DiyFp rhs) { Debug.Assert(_e == rhs._e); Debug.Assert(_f >= rhs._f); _f -= rhs._f; }
// this = this * other. private void Multiply(DiyFp other) { // Simply "emulates" a 128 bit multiplication. // However: the resulting number only contains 64 bits. The least // significant 64 bits are only used for rounding the most significant 64 // bits. const long kM32 = 0xFFFFFFFFL; long a = F.UnsignedShift(32); long b = F & kM32; long c = other.F.UnsignedShift(32); long d = other.F & kM32; long ac = a * c; long bc = b * c; long ad = a * d; long bd = b * d; long tmp = bd.UnsignedShift(32) + (ad & kM32) + (bc & kM32); // By adding 1U << 31 to tmp we round the final result. // Halfway cases will be round up. tmp += 1L << 31; long resultF = ac + ad.UnsignedShift(32) + bc.UnsignedShift(32) + tmp.UnsignedShift(32); E += other.E + 64; F = resultF; }
// Reads a DiyFp from the buffer. // The returned DiyFp is not necessarily normalized. // If remaining_decimals is zero then the returned DiyFp is accurate. // Otherwise it has been rounded and has error of at most 1/2 ulp. private static void ReadDiyFp(Vector buffer, out DiyFp result, out int remainingDecimals ) { var significand = ReadUint64(buffer, out var readDigits); if (buffer.length() == readDigits) { result = new DiyFp(significand, 0); remainingDecimals = 0; } else { // Round the significand. if (buffer[readDigits] >= '5') { significand++; } // Compute the binary exponent. var exponent = 0; result = new DiyFp(significand, exponent); remainingDecimals = buffer.length() - readDigits; } }
// Returns a - b. // The exponents of both numbers must be the same and this must be bigger // than other. The result will not be normalized. internal static DiyFp Minus(DiyFp a, DiyFp b) { DiyFp result = new DiyFp(a.F, a.E); result.Subtract(b); return(result); }
// returns a * b; internal static DiyFp Times(DiyFp a, DiyFp b) { DiyFp result = new DiyFp(a.F, a.E); result.Multiply(b); return(result); }
public static DiyFp Normalize(ref DiyFp a) { DiyFp result = a; result.Normalize(); return(result); }
// Computes the two boundaries of value. // // The bigger boundary (mPlus) is normalized. // The lower boundary has the same exponent as mPlus. // // Precondition: // The value encoded by value must be greater than 0. public static DiyFp CreateAndGetBoundaries(float value, out DiyFp mMinus, out DiyFp mPlus) { var result = new DiyFp(value); result.GetBoundaries(SingleImplicitBitIndex, out mMinus, out mPlus); return(result); }
public void NormalizedBoundaries(out DiyFp minus, out DiyFp plus) { var pl = new DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); var mi = f == kDpHiddenBit ? new DiyFp((f << 2) - 1, e - 2) : new DiyFp((f << 1) - 1, e - 1); mi.f <<= mi.e - pl.e; mi.e = pl.e; plus = pl; minus = mi; }
private static unsafe int DigitGen(DiyFp W, DiyFp Mp, ulong delta, char *buffer, ref int K) { var one = new DiyFp((ulong)1 << -Mp.e, Mp.e); var wp_w = Mp - W; var p1 = (uint)(Mp.f >> -one.e); var p2 = Mp.f & (one.f - 1); var kappa = log10(p1) + 1; var length = 0; var divisor = kPow10[kappa - 1]; while (kappa > 0) { var d = p1 / divisor; p1 %= divisor; if (d != 0 || length != 0) { *buffer++ = (char)('0' + d); length++; } kappa--; var tmp = ((ulong)p1 << -one.e) + p2; if (tmp <= delta) { K += kappa; GrisuRound(buffer, delta, tmp, (ulong)divisor << -one.e, wp_w.f); return(length); } divisor /= 10; } var pow10 = 10ul; // kappa = 0 for (;;) { p2 *= 10; delta *= 10; var d1 = (char)(p2 >> -one.e); if (d1 != 0 || length != 0) { *buffer++ = (char)('0' + d1); length++; } p2 &= one.f - 1; kappa--; if (p2 < delta) { K += kappa; GrisuRound(buffer, delta, p2, one.f, wp_w.f * pow10); return(length); } pow10 *= 10; } }
// Computes the two boundaries of this. // The bigger boundary (m_plus) is normalized. The lower boundary has the same // exponent as m_plus. // Precondition: the value encoded by this Single must be greater than 0. public void NormalizedBoundaries(out DiyFp outMMinus, out DiyFp outMPlus) { var v = this.AsDiyFp(); var __ = new DiyFp((v.f << 1) + 1, v.e - 1); var mPlus = DiyFp.Normalize(ref __); var mMinus = LowerBoundaryIsCloser() ? new DiyFp((v.f << 2) - 1, v.e - 2) : new DiyFp((v.f << 1) - 1, v.e - 1); mMinus.f <<= mMinus.e - mPlus.e; mMinus.e = mPlus.e; outMPlus = mPlus; outMMinus = mMinus; }
internal static int GetCachedPower(int e, int alpha, int gamma, DiyFp cMk) { const int kQ = DiyFp.KSignificandSize; double k = System.Math.Ceiling((alpha - e + kQ - 1) * Kd1Log210); int index = (GrisuCacheOffset + (int)k - 1) / CachedPowersSpacing + 1; CachedPower cachedPower = CACHED_POWERS[index]; cMk.F = cachedPower.Significand; cMk.E = cachedPower.BinaryExponent; return(cachedPower.DecimalExponent); }
private static unsafe int Grisu2(double value, char *buffer, out int K) { var v = new DiyFp(value); v.NormalizedBoundaries(out var w_m, out var w_p); var c_mk = GetCachedPower(w_p.e, out K); var W = v.Normalize() * c_mk; var Wp = w_p * c_mk; var Wm = w_m * c_mk; Wm.f++; Wp.f--; return(DigitGen(W, Wp, Wp.f - Wm.f, buffer, ref K)); }
private static unsafe int Grisu2(double value, char *buffer, out int K) { DiyFp diyFp = new DiyFp(value); DiyFp minus; DiyFp plus; diyFp.NormalizedBoundaries(out minus, out plus); DiyFp cachedPower = GetCachedPower(plus.e, out K); DiyFp w = diyFp.Normalize() * cachedPower; DiyFp mp = plus * cachedPower; DiyFp diyFp2 = minus * cachedPower; diyFp2.f++; mp.f--; return(DigitGen(w, mp, mp.f - diyFp2.f, buffer, ref K)); }
public static void GenerateNormalizedDiyFp(double value, out DiyFp result) { Debug.Assert(value > 0.0); long f = ExtractFractionAndBiasedExponent(value, out int e); while ((f & (1L << 52)) == 0) { f <<= 1; e--; } int lengthDelta = (SignificandLength - 53); f <<= lengthDelta; e -= lengthDelta; result = new DiyFp((ulong)(f), e); }
public static ulong DiyFpToUint64(DiyFp diyFp) { var significand = diyFp.f; var exponent = diyFp.e; while (significand > KHiddenBit + KSignificandMask) { significand >>= 1; exponent++; } if (exponent >= KMaxExponent) { return(KInfinity); } if (exponent < KDenormalExponent) { return(0); } while (exponent > KDenormalExponent && (significand & KHiddenBit) == 0) { significand <<= 1; exponent--; } ulong biasedExponent; if (exponent == KDenormalExponent && (significand & KHiddenBit) == 0) { biasedExponent = 0; } else { biasedExponent = (ulong)(exponent + KExponentBias); } return((significand & KSignificandMask) | (biasedExponent << KPhysicalSignificandSize)); }
public void Multiply(ref DiyFp rhs) { ulong lf = _f; ulong rf = rhs._f; uint a = (uint)(lf >> 32); uint b = (uint)(lf); uint c = (uint)(rf >> 32); uint d = (uint)(rf); ulong ac = ((ulong)(a) * c); ulong bc = ((ulong)(b) * c); ulong ad = ((ulong)(a) * d); ulong bd = ((ulong)(b) * d); ulong tmp = (bd >> 32) + (uint)(ad) + (uint)(bc); tmp += (1UL << 31); _f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); _e += (rhs._e + SignificandLength); }
// If the function returns true then the result is the correct double. // Otherwise it is either the correct double or the double that is just below // the correct double. private static bool DiyFpStrtod(Vector buffer, int exponent, out double result ) { ReadDiyFp(buffer, out var input, out var remainingDecimals); // Since we may have dropped some digits the input is not accurate. // If remaining_decimals is different than 0 than the error is at most // .5 ulp (unit in the last place). // We don't want to deal with fractions and therefore keep a common // denominator. const int kDenominatorLog = 3; const int kDenominator = 1 << kDenominatorLog; // Move the remaining decimals into the exponent. exponent += remainingDecimals; var error = (ulong)(remainingDecimals == 0 ? 0 : kDenominator / 2); var oldE = input.E; input.Normalize(); error <<= oldE - input.E; if (exponent < PowersOfTenCache.KMinDecimalExponent) { result = 0.0; return(true); } PowersOfTenCache.GetCachedPowerForDecimalExponent(exponent, out var cachedPower, out var cachedDecimalExponent); if (cachedDecimalExponent != exponent) { var adjustmentExponent = exponent - cachedDecimalExponent; var adjustmentPower = AdjustmentPowerOfTen(adjustmentExponent); input.Multiply(ref adjustmentPower); if (KMaxUint64DecimalDigits - buffer.length() >= adjustmentExponent) { // The product of input with the adjustment power fits into a 64 bit // integer. } else { // The adjustment power is exact. There is hence only an error of 0.5. error += kDenominator / 2; } } input.Multiply(ref cachedPower); // The error introduced by a multiplication of a*b equals // error_a + error_b + error_a*error_b/2^64 + 0.5 // Substituting a with 'input' and b with 'cached_power' we have // error_b = 0.5 (all cached powers have an error of less than 0.5 ulp), // error_ab = 0 or 1 / kDenominator > error_a*error_b/ 2^64 var errorB = kDenominator / 2; var errorAb = (error == 0 ? 0 : 1); // We round up to 1. var fixedError = kDenominator / 2; error += (ulong)(errorB + errorAb + fixedError); oldE = input.E; input.Normalize(); error <<= oldE - input.E; // See if the double's significand changes if we add/subtract the error. var orderOfMagnitude = DiyFp.KSignificandSize + input.E; var effectiveSignificandSize = Double.SignificandSizeForOrderOfMagnitude(orderOfMagnitude); var precisionDigitsCount = DiyFp.KSignificandSize - effectiveSignificandSize; if (precisionDigitsCount + kDenominatorLog >= DiyFp.KSignificandSize) { // This can only happen for very small denormals. In this case the // half-way multiplied by the denominator exceeds the range of an uint64. // Simply shift everything to the right. var shiftAmount = (precisionDigitsCount + kDenominatorLog) - DiyFp.KSignificandSize + 1; input.F = (input.F >> shiftAmount); input.E = (input.E + shiftAmount); // We add 1 for the lost precision of error, and kDenominator for // the lost precision of input.f(). error = (error >> shiftAmount) + 1 + kDenominator; precisionDigitsCount -= shiftAmount; } // We use uint64_ts now. This only works if the DiyFp uses uint64_ts too. ulong one64 = 1; var precisionBitsMask = (one64 << precisionDigitsCount) - 1; var precisionBits = input.F & precisionBitsMask; var halfWay = one64 << (precisionDigitsCount - 1); precisionBits *= kDenominator; halfWay *= kDenominator; var roundedInput = new DiyFp(input.F >> precisionDigitsCount, input.E + precisionDigitsCount); if (precisionBits >= halfWay + error) { roundedInput.F = (roundedInput.F + 1); } // If the last_bits are too close to the half-way case than we are too // inaccurate and round down. In this case we return false so that we can // fall back to a more precise algorithm. result = new Double(roundedInput).value(); if (halfWay - error < precisionBits && precisionBits < halfWay + error) { // Too imprecise. The caller will have to fall back to a slower version. // However the returned number is guaranteed to be either the correct // double, or the next-lower double. return(false); } return(true); }
public IeeeDouble(DiyFp d) { d64 = DiyFpToUint64(d); }
private static bool DigitGen(ref DiyFp mp, int precision, byte *digits, out int length, out int k) { // Split the input mp to two parts. Part 1 is integral. Part 2 can be used to calculate // fractional. // // mp: the input DiyFp scaled by cached power. // K: final kappa. // p1: part 1. // p2: part 2. Debug.Assert(precision > 0); Debug.Assert(digits != null); Debug.Assert(mp.e >= Alpha); Debug.Assert(mp.e <= Gamma); ulong mpF = mp.f; int mpE = mp.e; var one = new DiyFp(1UL << -mpE, mpE); ulong oneF = one.f; int oneNegE = -one.e; ulong ulp = 1; uint p1 = (uint)(mpF >> oneNegE); ulong p2 = mpF & (oneF - 1); // When p2 (fractional part) is zero, we can predicate if p1 is good to produce the numbers in requested digit count: // // - When requested digit count >= 11, p1 is not be able to exhaust the count as 10^(11 - 1) > uint.MaxValue >= p1. // - When p1 < 10^(count - 1), p1 is not be able to exhaust the count. // - Otherwise, p1 may have chance to exhaust the count. if ((p2 == 0) && ((precision >= 11) || (p1 < s_CachedPowerOfTen[precision - 1]))) { length = 0; k = 0; return(false); } // Note: The code in the paper simply assigns div to Ten9 and kappa to 10 directly. // That means we need to check if any leading zero of the generated // digits during the while loop, which hurts the performance. // // Now if we can estimate what the div and kappa, we do not need to check the leading zeros. // The idea is to find the biggest power of 10 that is less than or equal to the given number. // Then we don't need to worry about the leading zeros and we can get 10% performance gain. int index = 0; BiggestPowerTenLessThanOrEqualTo(p1, (DiyFp.SignificandLength - oneNegE), out uint div, out int kappa); kappa++; // Produce integral. while (kappa > 0) { int d = (int)(Math.DivRem(p1, div, out p1)); digits[index] = (byte)('0' + d); index++; precision--; kappa--; if (precision == 0) { break; } div /= 10; } // End up here if we already exhausted the digit count. if (precision == 0) { ulong rest = ((ulong)(p1) << oneNegE) + p2; length = index; k = kappa; return(RoundWeed( digits, index, rest, ((ulong)(div)) << oneNegE, ulp, ref k )); } // We have to generate digits from part2 if we have requested digit count left // and part2 is greater than ulp. while ((precision > 0) && (p2 > ulp)) { p2 *= 10; int d = (int)(p2 >> oneNegE); digits[index] = (byte)('0' + d); index++; precision--; kappa--; p2 &= (oneF - 1); ulp *= 10; } // If we haven't exhausted the requested digit counts, the Grisu3 algorithm fails. if (precision != 0) { length = 0; k = 0; return(false); } length = index; k = kappa; return(RoundWeed(digits, index, p2, oneF, ulp, ref k)); }
// this = this - other. // The exponents of both numbers must be the same and the significand of this // must be bigger than the significand of other. // The result will not be normalized. public void Subtract(ref DiyFp other) => F -= other.F;
public static bool Run(double value, int precision, ref NumberBuffer number) { // ======================================================================================================================================== // This implementation is based on the paper: http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf // You must read this paper to fully understand the code. // // Deviation: Instead of generating shortest digits, we generate the digits according to the input count. // Therefore, we do not need m+ and m- which are used to determine the exact range of values. // ======================================================================================================================================== // // Overview: // // The idea of Grisu3 is to leverage additional bits and cached power of ten to produce the digits. // We need to create a handmade floating point data structure DiyFp to extend the bits of double. // We also need to cache the powers of ten for digits generation. By choosing the correct index of powers // we need to start with, we can eliminate the expensive big num divide operation. // // Grisu3 is imprecision for some numbers. Fortunately, the algorithm itself can determine that and give us // a success/fail flag. We may fall back to other algorithms (For instance, Dragon4) if it fails. // // w: the normalized DiyFp from the input value. // mk: The index of the cached powers. // cmk: The cached power. // D: Product: w * cmk. // kappa: A factor used for generating digits. See step 5 of the Grisu3 procedure in the paper. // Handle sign bit. if (double.IsNegative(value)) { value = -value; number.IsNegative = true; } else { number.IsNegative = false; } // Step 1: Determine the normalized DiyFp w. DiyFp.GenerateNormalizedDiyFp(value, out DiyFp w); // Step 2: Find the cached power of ten. // Compute the proper index mk. int mk = KComp(w.e + DiyFp.SignificandLength); // Retrieve the cached power of ten. CachedPower(mk, out DiyFp cmk, out int decimalExponent); // Step 3: Scale the w with the cached power of ten. DiyFp.Multiply(ref w, ref cmk, out DiyFp D); // Step 4: Generate digits. bool isSuccess = DigitGen(ref D, precision, number.GetDigitsPointer(), out int length, out int kappa); if (isSuccess) { number.Digits[precision] = (byte)('\0'); number.Scale = (length - decimalExponent + kappa); } return(isSuccess); }
// this = this - other. // The exponents of both numbers must be the same and the significand of this // must be bigger than the significand of other. // The result will not be normalized. public void Subtract(ref DiyFp other) { f -= other.f; }