public static void GetBytes(ref BufferWriter writer, double value) { var resultBuilder = StringBuilder.Create(); try { var doubleValue = new IeeeDouble(value); if (doubleValue.IsSpecial()) { if (!HandleSpecialValues(doubleValue, ref resultBuilder)) { throw new InvalidOperationException("not support special double value:" + value); } } else if (!ToShortestIeeeNumber(value, ref resultBuilder, DtoaMode.Shortest)) { throw new InvalidOperationException("not support double value:" + value); } var resultBuilderLength = resultBuilder.Length; var destination = writer.GetSpan(resultBuilderLength); resultBuilder.ReadableSpan.CopyTo(destination); writer.Advance(resultBuilderLength); } finally { resultBuilder.Dispose(); } }
// Returns true if the guess is the correct double. // Returns false, when guess is either correct or the next-lower double. private static bool ComputeGuess(ReadOnlySpan <byte> trimmed, int exponent, out double guess) { if (trimmed.Length == 0) { guess = default; return(true); } if (exponent + trimmed.Length - 1 >= KMaxDecimalPower) { guess = IeeeDouble.Infinity(); return(true); } if (exponent + trimmed.Length <= KMinDecimalPower) { guess = default; return(true); } if (DoubleStrToDouble(trimmed, exponent, out guess) || DiyFpStrToDouble(trimmed, exponent, out guess)) { return(true); } return(guess == IeeeDouble.Infinity()); }
// Provides a decimal representation of v. // Returns true if it succeeds, otherwise the result cannot be trusted. // There will be *length digits inside the buffer (not null-terminated). // If the function returns true then // v == (double) (buffer * 10^decimal_exponent). // The digits in the buffer are the shortest representation possible: no // 0.09999999999999999 instead of 0.1. The shorter representation will even be // chosen even if the longer one would be closer to v. // The last digit will be closest to the actual v. That is, even if several // digits might correctly yield 'v' when read again, the closest will be // computed. private static bool Grisu3(double v, FastDtoaMode mode, Span <byte> buffer, out int length, out int decimalExponent) { var w = new IeeeDouble(v).AsNormalizedDiyFp(); // boundary_minus and boundary_plus are the boundaries between v and its // closest floating-point neighbors. Any number strictly between // boundary_minus and boundary_plus will round to v when convert to a double. // Grisu3 will never output representations that lie exactly on a boundary. DiyFp boundaryMinus, boundaryPlus; switch (mode) { case FastDtoaMode.FastDtoaShortest: new IeeeDouble(v).NormalizedBoundaries(out boundaryMinus, out boundaryPlus); break; case FastDtoaMode.FastDtoaShortestSingle: { var singleV = (float)v; new IeeeSingle(singleV).NormalizedBoundaries(out boundaryMinus, out boundaryPlus); break; } default: throw new Exception("Invalid Mode."); } var tenMkMinimalBinaryExponent = KMinimalTargetExponent - (w.e + DiyFp.kSignificandSize); PowersOfTenCache.GetCachedPowerForBinaryExponentRange(tenMkMinimalBinaryExponent, out var tenMk, out var mk); // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a // 64 bit significand and ten_mk is thus only precise up to 64 bits. // The DiyFp::Times procedure rounds its result, and ten_mk is approximated // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now // off by a small amount. // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w. // In other words: let f = scaled_w.f() and e = scaled_w.e(), then // (f-1) * 2^e < w*10^k < (f+1) * 2^e var scaledW = DiyFp.Times(ref w, ref tenMk); // In theory it would be possible to avoid some recomputations by computing // the difference between w and boundary_minus/plus (a power of 2) and to // compute scaled_boundary_minus/plus by subtracting/adding from // scaled_w. However the code becomes much less readable and the speed // enhancements are not terrific. var scaledBoundaryMinus = DiyFp.Times(ref boundaryMinus, ref tenMk); var scaledBoundaryPlus = DiyFp.Times(ref boundaryPlus, ref tenMk); // DigitGen will generate the digits of scaled_w. Therefore we have // v == (double) (scaled_w * 10^-mk). // Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an // integer than it will be updated. For instance if scaled_w == 1.23 then // the buffer will be filled with "123" und the decimal_exponent will be // decreased by 2. var result = DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, buffer, out length, out var kappa); decimalExponent = -mk + kappa; return(result); }
public static bool TryParseToFloat(ReadOnlySpan <byte> buffer, int exponent, out float value) { Span <byte> copyBuffer = stackalloc byte[KMaxSignificantDecimalDigits]; var leftTrimmed = TrimLeadingZeros(buffer); var rightTrimmed = TrimTrailingZeros(leftTrimmed); var shouldCut = rightTrimmed.Length > KMaxSignificantDecimalDigits; bool isCorrect; double doubleValue; var exponent1 = exponent + leftTrimmed.Length - rightTrimmed.Length; if (shouldCut) { CutToMaxSignificantDigits(rightTrimmed, exponent1, copyBuffer, out exponent); isCorrect = ComputeGuess(copyBuffer, exponent, out doubleValue); } else { isCorrect = ComputeGuess(rightTrimmed, exponent1, out doubleValue); } value = (float)doubleValue; if (value == doubleValue) { // This shortcut triggers for integer values. return(true); } // We must catch double-rounding. Say the double has been rounded up, and is // now a boundary of a float, and rounds up again. This is why we have to // look at previous too. // Example (in decimal numbers): // input: 12349 // high-precision (4 digits): 1235 // low-precision (3 digits): // when read from input: 123 // when rounded from high precision: 124. // To do this we simply look at the neigbors of the correct result and see // if they would round to the same float. If the guess is not correct we have // to look at four values (since two different doubles could be the correct // double). var doubleNext = new IeeeDouble(doubleValue).NextDouble(); var doublePrevious = new IeeeDouble(doubleValue).PreviousDouble(); var f1 = (float)doublePrevious; var f3 = (float)doubleNext; float f4; if (isCorrect) { f4 = f3; } else { var doubleNext2 = new IeeeDouble(doubleNext).NextDouble(); f4 = (float)doubleNext2; } // If the guess doesn't lie near a single-precision boundary we can simply // return its float-value. return(f1 == f4); }
// 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 DiyFpStrToDouble(ReadOnlySpan <byte> 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 const int errorB = kDenominator / 2; var errorAb = error == 0 ? 0 : 1; // We round up to 1. const int 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 = IeeeDouble.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 >>= shiftAmount; 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. const 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++; } // 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 IeeeDouble(roundedInput).Value(); // 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(halfWay - error >= precisionBits || precisionBits >= halfWay + error); }