private static bool FastDtoa(double v, FastDtoaMode mode, Span <byte> buffer, out int length, out int decimalPoint) { switch (mode) { case FastDtoaMode.FastDtoaShortest: case FastDtoaMode.FastDtoaShortestSingle: var result = Grisu3(v, mode, buffer, out length, out var decimalExponent); decimalPoint = result ? length + decimalExponent : -1; return(result); // case FastDtoaMode.FAST_DTOA_PRECISION: //result = Grisu3Counted(v, requested_digits, buffer, length, &decimal_exponent); default: throw new Exception("unreachable code."); } }
// 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); }