// Generates 'requested_digits' after the decimal point. It might omit // trailing '0's. If the input number is too small then no digits at all are // generated (ex.: 2 fixed digits for 0.00001). // // Input verifies: 1 <= (numerator + delta) / denominator < 10. static void BignumToFixed( int requested_digits, ref int decimal_point, Bignum numerator, Bignum denominator, DtoaBuilder buffer) { // Note that we have to look at more than just the requested_digits, since // a number could be rounded up. Example: v=0.5 with requested_digits=0. // Even though the power of v equals 0 we can't just stop here. if (-(decimal_point) > requested_digits) { // The number is definitively too small. // Ex: 0.001 with requested_digits == 1. // Set decimal-point to -requested_digits. This is what Gay does. // Note that it should not have any effect anyways since the string is // empty. decimal_point = -requested_digits; buffer.Reset(); return; } if (-decimal_point == requested_digits) { // We only need to verify if the number rounds down or up. // Ex: 0.04 and 0.06 with requested_digits == 1. Debug.Assert(decimal_point == -requested_digits); // Initially the fraction lies in range (1, 10]. Multiply the denominator // by 10 so that we can compare more easily. denominator.Times10(); if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0) { // If the fraction is >= 0.5 then we have to include the rounded // digit. buffer[0] = '1'; decimal_point++; } else { // Note that we caught most of similar cases earlier. buffer.Reset(); } } else { // The requested digits correspond to the digits after the point. // The variable 'needed_digits' includes the digits before the point. int needed_digits = (decimal_point) + requested_digits; GenerateCountedDigits(needed_digits, ref decimal_point, numerator, denominator, buffer); } }
// Let v = numerator / denominator < 10. // Then we generate 'count' digits of d = x.xxxxx... (without the decimal point) // from left to right. Once 'count' digits have been produced we decide wether // to round up or down. Remainders of exactly .5 round upwards. Numbers such // as 9.999999 propagate a carry all the way, and change the // exponent (decimal_point), when rounding upwards. static void GenerateCountedDigits( int count, ref int decimal_point, Bignum numerator, Bignum denominator, DtoaBuilder buffer) { Debug.Assert(count >= 0); for (int i = 0; i < count - 1; ++i) { uint d = numerator.DivideModuloIntBignum(denominator); Debug.Assert(d <= 9); // digit is a uint and therefore always positive. // digit = numerator / denominator (integer division). // numerator = numerator % denominator. buffer.Append((char)(d + '0')); // Prepare for next iteration. numerator.Times10(); } // Generate the last digit. uint digit = numerator.DivideModuloIntBignum(denominator); if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0) { digit++; } buffer.Append((char)(digit + '0')); // Correct bad digits (in case we had a sequence of '9's). Propagate the // carry until we hat a non-'9' or til we reach the first digit. for (int i = count - 1; i > 0; --i) { if (buffer[i] != '0' + 10) { break; } buffer[i] = '0'; buffer[i - 1]++; } if (buffer[0] == '0' + 10) { // Propagate a carry past the top place. buffer[0] = '1'; decimal_point++; } }
// This routine multiplies numerator/denominator so that its values lies in the // range 1-10. That is after a call to this function we have: // 1 <= (numerator + delta_plus) /denominator < 10. // Let numerator the input before modification and numerator' the argument // after modification, then the output-parameter decimal_point is such that // numerator / denominator * 10^estimated_power == // numerator' / denominator' * 10^(decimal_point - 1) // In some cases estimated_power was too low, and this is already the case. We // then simply adjust the power so that 10^(k-1) <= v < 10^k (with k == // estimated_power) but do not touch the numerator or denominator. // Otherwise the routine multiplies the numerator and the deltas by 10. private static void FixupMultiply10( int estimated_power, bool is_even, out int decimal_point, Bignum numerator, Bignum denominator, Bignum delta_minus, Bignum delta_plus) { bool in_range; if (is_even) { in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0; } else { in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) > 0; } if (in_range) { // Since numerator + delta_plus >= denominator we already have // 1 <= numerator/denominator < 10. Simply update the estimated_power. decimal_point = estimated_power + 1; } else { decimal_point = estimated_power; numerator.Times10(); if (Bignum.Equal(delta_minus, delta_plus)) { delta_minus.Times10(); delta_plus.AssignBignum(delta_minus); } else { delta_minus.Times10(); delta_plus.Times10(); } } }
// The procedure starts generating digits from the left to the right and stops // when the generated digits yield the shortest decimal representation of v. A // decimal representation of v is a number lying closer to v than to any other // double, so it converts to v when read. // // This is true if d, the decimal representation, is between m- and m+, the // upper and lower boundaries. d must be strictly between them if !is_even. // m- := (numerator - delta_minus) / denominator // m+ := (numerator + delta_plus) / denominator // // Precondition: 0 <= (numerator+delta_plus) / denominator < 10. // If 1 <= (numerator+delta_plus) / denominator < 10 then no leading 0 digit // will be produced. This should be the standard precondition. private static void GenerateShortestDigits( Bignum numerator, Bignum denominator, Bignum delta_minus, Bignum delta_plus, bool is_even, DtoaBuilder buffer) { // Small optimization: if delta_minus and delta_plus are the same just reuse // one of the two bignums. if (Bignum.Equal(delta_minus, delta_plus)) { delta_plus = delta_minus; } buffer.Reset(); while (true) { uint digit; digit = numerator.DivideModuloIntBignum(denominator); // digit = numerator / denominator (integer division). // numerator = numerator % denominator. buffer.Append((char)(digit + '0')); // Can we stop already? // If the remainder of the division is less than the distance to the lower // boundary we can stop. In this case we simply round down (discarding the // remainder). // Similarly we test if we can round up (using the upper boundary). bool in_delta_room_minus; bool in_delta_room_plus; if (is_even) { in_delta_room_minus = Bignum.LessEqual(numerator, delta_minus); } else { in_delta_room_minus = Bignum.Less(numerator, delta_minus); } if (is_even) { in_delta_room_plus = Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0; } else { in_delta_room_plus = Bignum.PlusCompare(numerator, delta_plus, denominator) > 0; } if (!in_delta_room_minus && !in_delta_room_plus) { // Prepare for next iteration. numerator.Times10(); delta_minus.Times10(); // We optimized delta_plus to be equal to delta_minus (if they share the // same value). So don't multiply delta_plus if they point to the same // object. if (delta_minus != delta_plus) { delta_plus.Times10(); } } else if (in_delta_room_minus && in_delta_room_plus) { // Let's see if 2*numerator < denominator. // If yes, then the next digit would be < 5 and we can round down. int compare = Bignum.PlusCompare(numerator, numerator, denominator); if (compare < 0) { // Remaining digits are less than .5. -> Round down (== do nothing). } else if (compare > 0) { // Remaining digits are more than .5 of denominator. . Round up. // Note that the last digit could not be a '9' as otherwise the whole // loop would have stopped earlier. // We still have an assert here in case the preconditions were not // satisfied. buffer[buffer.Length - 1]++; } else { // Halfway case. // TODO(floitsch): need a way to solve half-way cases. // For now let's round towards even (since this is what Gay seems to // do). if ((buffer[buffer.Length - 1] - '0') % 2 == 0) { // Round down => Do nothing. } else { buffer[buffer.Length - 1]++; } } return; } else if (in_delta_room_minus) { // Round down (== do nothing). return; } else { // in_delta_room_plus // Round up. // Note again that the last digit could not be '9' since this would have // stopped the loop earlier. // We still have an DCHECK here, in case the preconditions were not // satisfied. buffer[buffer.Length - 1]++; return; } } }