TRIO_PRIVATE void TrioWriteDouble TRIO_ARGS6((self, number, flags, width, precision, base),
                                             trio_class_t* self, trio_long_double_t number,
                                             trio_flags_t flags, int width, int precision, int base)
{
	trio_long_double_t integerNumber;
	trio_long_double_t fractionNumber;
	trio_long_double_t workNumber;
	int integerDigits;
	int fractionDigits;
	int exponentDigits;
	int workDigits;
	int baseDigits;
	int integerThreshold;
	int fractionThreshold;
	int expectedWidth;
	int exponent = 0;
	unsigned int uExponent = 0;
	int exponentBase;
	trio_long_double_t dblBase;
	trio_long_double_t dblFractionBase;
	trio_long_double_t integerAdjust;
	trio_long_double_t fractionAdjust;
	trio_long_double_t workFractionNumber;
	trio_long_double_t workFractionAdjust;
	int fractionDigitsInspect;
	BOOLEAN_T isNegative;
	BOOLEAN_T isExponentNegative = FALSE;
	BOOLEAN_T requireTwoDigitExponent;
	BOOLEAN_T isHex;
	TRIO_CONST char* digits;
#if TRIO_FEATURE_QUOTE
	char* groupingPointer;
#endif
	int i;
	int offset;
	BOOLEAN_T hasOnlyZeroes;
	int leadingFractionZeroes = -1;
	register int trailingZeroes;
	BOOLEAN_T keepTrailingZeroes;
	BOOLEAN_T keepDecimalPoint;
	trio_long_double_t epsilon;
	BOOLEAN_T adjustNumber = FALSE;

	assert(VALID(self));
	assert(VALID(self->OutStream));
	assert(((base >= MIN_BASE) && (base <= MAX_BASE)) || (base == NO_BASE));

	/* Determine sign and look for special quantities */
	switch (trio_fpclassify_and_signbit(number, &isNegative))
	{
		case TRIO_FP_NAN:
			TrioWriteString(self, (flags & FLAGS_UPPER) ? NAN_UPPER : NAN_LOWER, flags, width,
			                precision);
			return;

		case TRIO_FP_INFINITE:
			if (isNegative)
			{
				/* Negative infinity */
				TrioWriteString(self,
				                (flags & FLAGS_UPPER) ? "-" INFINITE_UPPER : "-" INFINITE_LOWER,
				                flags, width, precision);
				return;
			}
			else
			{
				/* Positive infinity */
				TrioWriteString(self, (flags & FLAGS_UPPER) ? INFINITE_UPPER : INFINITE_LOWER,
				                flags, width, precision);
				return;
			}

		default:
			/* Finitude */
			break;
	}

	/* Normal numbers */
	if (flags & FLAGS_LONGDOUBLE)
	{
		baseDigits =
		    (base == 10) ? LDBL_DIG : (int)trio_floor(LDBL_MANT_DIG / TrioLogarithmBase(base));
		epsilon = LDBL_EPSILON;
	}
	else if (flags & FLAGS_SHORT)
	{
		baseDigits = (base == BASE_DECIMAL)
		                 ? FLT_DIG
		                 : (int)trio_floor(FLT_MANT_DIG / TrioLogarithmBase(base));
		epsilon = FLT_EPSILON;
	}
	else
	{
		baseDigits = (base == BASE_DECIMAL)
		                 ? DBL_DIG
		                 : (int)trio_floor(DBL_MANT_DIG / TrioLogarithmBase(base));
		epsilon = DBL_EPSILON;
	}

	digits = (flags & FLAGS_UPPER) ? internalDigitsUpper : internalDigitsLower;
	isHex = (base == BASE_HEX);
	if (base == NO_BASE)
		base = BASE_DECIMAL;
	dblBase = (trio_long_double_t)base;
	keepTrailingZeroes =
	    !((flags & FLAGS_ROUNDING) || ((flags & FLAGS_FLOAT_G) && !(flags & FLAGS_ALTERNATIVE)));

#if TRIO_FEATURE_ROUNDING
	if (flags & FLAGS_ROUNDING)
	{
		precision = baseDigits;
	}
#endif

	if (precision == NO_PRECISION)
	{
		if (isHex)
		{
			keepTrailingZeroes = FALSE;
			precision = FLT_MANT_DIG;
		}
		else
		{
			precision = FLT_DIG;
		}
	}

	if (isNegative)
	{
		number = -number;
	}

	if (isHex)
	{
		flags |= FLAGS_FLOAT_E;
	}

reprocess:

	if (flags & FLAGS_FLOAT_G)
	{
		if (precision == 0)
			precision = 1;

		if ((number < TRIO_SUFFIX_LONG(1.0E-4)) ||
		    (number >= TrioPower(base, (trio_long_double_t)precision)))
		{
			/* Use scientific notation */
			flags |= FLAGS_FLOAT_E;
		}
		else if (number < 1.0)
		{
			/*
			 * Use normal notation. If the integer part of the number is
			 * zero, then adjust the precision to include leading fractional
			 * zeros.
			 */
			workNumber = TrioLogarithm(number, base);
			workNumber = TRIO_FABS(workNumber);
			if (workNumber - trio_floor(workNumber) < epsilon)
				workNumber--;
			leadingFractionZeroes = (int)trio_floor(workNumber);
		}
	}

	if (flags & FLAGS_FLOAT_E)
	{
		/* Scale the number */
		workNumber = TrioLogarithm(number, base);
		if (trio_isinf(workNumber) == -1)
		{
			exponent = 0;
			/* Undo setting */
			if (flags & FLAGS_FLOAT_G)
				flags &= ~FLAGS_FLOAT_E;
		}
		else
		{
			exponent = (int)trio_floor(workNumber);
			workNumber = number;
			/*
			 * The expression A * 10^-B is equivalent to A / 10^B but the former
			 * usually gives better accuracy.
			 */
			workNumber *= TrioPower(dblBase, (trio_long_double_t)-exponent);
			if (trio_isinf(workNumber))
			{
				/*
				 * Scaling is done it two steps to avoid problems with subnormal
				 * numbers.
				 */
				workNumber /= TrioPower(dblBase, (trio_long_double_t)(exponent / 2));
				workNumber /= TrioPower(dblBase, (trio_long_double_t)(exponent - (exponent / 2)));
			}
			number = workNumber;
			isExponentNegative = (exponent < 0);
			uExponent = (isExponentNegative) ? -exponent : exponent;
			if (isHex)
				uExponent *= 4; /* log16(2) */
#if TRIO_FEATURE_QUOTE
			/* No thousand separators */
			flags &= ~FLAGS_QUOTE;
#endif
		}
	}

	integerNumber = trio_floor(number);
	fractionNumber = number - integerNumber;

	/*
	 * Truncated number.
	 *
	 * Precision is number of significant digits for FLOAT_G and number of
	 * fractional digits for others.
	 */
	integerDigits = 1;
	if (integerNumber > epsilon)
	{
		integerDigits += (int)TrioLogarithm(integerNumber, base);
	}

	fractionDigits = precision;
	if (flags & FLAGS_FLOAT_G)
	{
		if (leadingFractionZeroes > 0)
		{
			fractionDigits += leadingFractionZeroes;
		}
		if ((integerNumber > epsilon) || (number <= epsilon))
		{
			fractionDigits -= integerDigits;
		}
	}

	dblFractionBase = TrioPower(base, fractionDigits);

	if (integerNumber < 1.0)
	{
		workNumber = number * dblFractionBase + TRIO_SUFFIX_LONG(0.5);
		if (trio_floor(number * dblFractionBase) != trio_floor(workNumber))
		{
			adjustNumber = TRUE;
			/* Remove a leading fraction zero if fraction is rounded up */
			if ((int)TrioLogarithm(number * dblFractionBase, base) !=
			    (int)TrioLogarithm(workNumber, base))
			{
				--leadingFractionZeroes;
			}
		}
		workNumber /= dblFractionBase;
	}
	else
	{
		workNumber = number + TRIO_SUFFIX_LONG(0.5) / dblFractionBase;
		adjustNumber = (trio_floor(number) != trio_floor(workNumber));
	}
	if (adjustNumber)
	{
		if ((flags & FLAGS_FLOAT_G) && !(flags & FLAGS_FLOAT_E))
		{
			/* The adjustment may require a change to scientific notation */
			if ((workNumber < TRIO_SUFFIX_LONG(1.0E-4)) ||
			    (workNumber >= TrioPower(base, (trio_long_double_t)precision)))
			{
				/* Use scientific notation */
				flags |= FLAGS_FLOAT_E;
				goto reprocess;
			}
		}

		if (flags & FLAGS_FLOAT_E)
		{
			workDigits = 1 + TrioLogarithm(trio_floor(workNumber), base);
			if (integerDigits == workDigits)
			{
				/* Adjust if the same number of digits are used */
				number += TRIO_SUFFIX_LONG(0.5) / dblFractionBase;
				integerNumber = trio_floor(number);
				fractionNumber = number - integerNumber;
			}
			else
			{
				/* Adjust if number was rounded up one digit (ie. 0.99 to 1.00) */
				exponent++;
				isExponentNegative = (exponent < 0);
				uExponent = (isExponentNegative) ? -exponent : exponent;
				if (isHex)
					uExponent *= 4; /* log16(2) */
				workNumber = (number + TRIO_SUFFIX_LONG(0.5) / dblFractionBase) / dblBase;
				integerNumber = trio_floor(workNumber);
				fractionNumber = workNumber - integerNumber;
			}
		}
		else
		{
			if (workNumber > 1.0)
			{
				/* Adjust if number was rounded up one digit (ie. 99 to 100) */
				integerNumber = trio_floor(workNumber);
				fractionNumber = 0.0;
				integerDigits =
				    (integerNumber > epsilon) ? 1 + (int)TrioLogarithm(integerNumber, base) : 1;
				if (flags & FLAGS_FLOAT_G)
				{
					if (flags & FLAGS_ALTERNATIVE)
					{
						fractionDigits = precision;
						if ((integerNumber > epsilon) || (number <= epsilon))
						{
							fractionDigits -= integerDigits;
						}
					}
					else
					{
						fractionDigits = 0;
					}
				}
			}
			else
			{
				integerNumber = trio_floor(workNumber);
				fractionNumber = workNumber - integerNumber;
				if (flags & FLAGS_FLOAT_G)
				{
					if (flags & FLAGS_ALTERNATIVE)
					{
						fractionDigits = precision;
						if (leadingFractionZeroes > 0)
						{
							fractionDigits += leadingFractionZeroes;
						}
						if ((integerNumber > epsilon) || (number <= epsilon))
						{
							fractionDigits -= integerDigits;
						}
					}
				}
			}
		}
	}

	/* Estimate accuracy */
	integerAdjust = fractionAdjust = TRIO_SUFFIX_LONG(0.5);
#if TRIO_FEATURE_ROUNDING
	if (flags & FLAGS_ROUNDING)
	{
		if (integerDigits > baseDigits)
		{
			integerThreshold = baseDigits;
			fractionDigits = 0;
			dblFractionBase = 1.0;
			fractionThreshold = 0;
			precision = 0; /* Disable decimal-point */
			integerAdjust = TrioPower(base, integerDigits - integerThreshold - 1);
			fractionAdjust = 0.0;
		}
		else
		{
			integerThreshold = integerDigits;
			fractionThreshold = fractionDigits - integerThreshold;
			fractionAdjust = 1.0;
		}
	}
	else
#endif
	{
		integerThreshold = INT_MAX;
		fractionThreshold = INT_MAX;
	}

	/*
	 * Calculate expected width.
	 *  sign + integer part + thousands separators + decimal point
	 *  + fraction + exponent
	 */
	fractionAdjust /= dblFractionBase;
	hasOnlyZeroes = (trio_floor((fractionNumber + fractionAdjust) * dblFractionBase) < epsilon);
	keepDecimalPoint = ((flags & FLAGS_ALTERNATIVE) ||
	                    !((precision == 0) || (!keepTrailingZeroes && hasOnlyZeroes)));

	expectedWidth = integerDigits + fractionDigits;

	if (!keepTrailingZeroes)
	{
		trailingZeroes = 0;
		workFractionNumber = fractionNumber;
		workFractionAdjust = fractionAdjust;
		fractionDigitsInspect = fractionDigits;

		if (integerDigits > integerThreshold)
		{
			fractionDigitsInspect = 0;
		}
		else if (fractionThreshold <= fractionDigits)
		{
			fractionDigitsInspect = fractionThreshold + 1;
		}

		trailingZeroes = fractionDigits - fractionDigitsInspect;
		for (i = 0; i < fractionDigitsInspect; i++)
		{
			workFractionNumber *= dblBase;
			workFractionAdjust *= dblBase;
			workNumber = trio_floor(workFractionNumber + workFractionAdjust);
			workFractionNumber -= workNumber;
			offset = (int)trio_fmod(workNumber, dblBase);
			if (offset == 0)
			{
				trailingZeroes++;
			}
			else
			{
				trailingZeroes = 0;
			}
		}
		expectedWidth -= trailingZeroes;
	}

	if (keepDecimalPoint)
	{
		expectedWidth += internalDecimalPointLength;
	}

#if TRIO_FEATURE_QUOTE
	if (flags & FLAGS_QUOTE)
	{
		expectedWidth += TrioCalcThousandSeparatorLength(integerDigits);
	}
#endif

	if (isNegative || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE))
	{
		expectedWidth += sizeof("-") - 1;
	}

	exponentDigits = 0;
	if (flags & FLAGS_FLOAT_E)
	{
		exponentDigits =
		    (uExponent == 0)
		        ? 1
		        : (int)trio_ceil(TrioLogarithm((double)(uExponent + 1), (isHex) ? 10 : base));
	}
	requireTwoDigitExponent = ((base == BASE_DECIMAL) && (exponentDigits == 1));
	if (exponentDigits > 0)
	{
		expectedWidth += exponentDigits;
		expectedWidth += (requireTwoDigitExponent ? sizeof("E+0") - 1 : sizeof("E+") - 1);
	}

	if (isHex)
	{
		expectedWidth += sizeof("0X") - 1;
	}

	/* Output prefixing */
	if (flags & FLAGS_NILPADDING)
	{
		/* Leading zeros must be after sign */
		if (isNegative)
			self->OutStream(self, '-');
		else if (flags & FLAGS_SHOWSIGN)
			self->OutStream(self, '+');
		else if (flags & FLAGS_SPACE)
			self->OutStream(self, ' ');
		if (isHex)
		{
			self->OutStream(self, '0');
			self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x');
		}
		if (!(flags & FLAGS_LEFTADJUST))
		{
			for (i = expectedWidth; i < width; i++)
			{
				self->OutStream(self, '0');
			}
		}
	}
	else
	{
		/* Leading spaces must be before sign */
		if (!(flags & FLAGS_LEFTADJUST))
		{
			for (i = expectedWidth; i < width; i++)
			{
				self->OutStream(self, CHAR_ADJUST);
			}
		}
		if (isNegative)
			self->OutStream(self, '-');
		else if (flags & FLAGS_SHOWSIGN)
			self->OutStream(self, '+');
		else if (flags & FLAGS_SPACE)
			self->OutStream(self, ' ');
		if (isHex)
		{
			self->OutStream(self, '0');
			self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x');
		}
	}

	/* Output the integer part and thousand separators */
	for (i = 0; i < integerDigits; i++)
	{
		workNumber =
		    trio_floor(((integerNumber + integerAdjust) / TrioPower(base, integerDigits - i - 1)));
		if (i > integerThreshold)
		{
			/* Beyond accuracy */
			self->OutStream(self, digits[0]);
		}
		else
		{
			self->OutStream(self, digits[(int)trio_fmod(workNumber, dblBase)]);
		}

#if TRIO_FEATURE_QUOTE
		if (((flags & (FLAGS_FLOAT_E | FLAGS_QUOTE)) == FLAGS_QUOTE) &&
		    TrioFollowedBySeparator(integerDigits - i))
		{
			for (groupingPointer = internalThousandSeparator; *groupingPointer != NIL;
			     groupingPointer++)
			{
				self->OutStream(self, *groupingPointer);
			}
		}
#endif
	}

	/* Insert decimal point and build the fraction part */
	trailingZeroes = 0;

	if (keepDecimalPoint)
	{
		if (internalDecimalPoint)
		{
			self->OutStream(self, internalDecimalPoint);
		}
		else
		{
			for (i = 0; i < internalDecimalPointLength; i++)
			{
				self->OutStream(self, internalDecimalPointString[i]);
			}
		}
	}

	for (i = 0; i < fractionDigits; i++)
	{
		if ((integerDigits > integerThreshold) || (i > fractionThreshold))
		{
			/* Beyond accuracy */
			trailingZeroes++;
		}
		else
		{
			fractionNumber *= dblBase;
			fractionAdjust *= dblBase;
			workNumber = trio_floor(fractionNumber + fractionAdjust);
			if (workNumber > fractionNumber)
			{
				/* fractionNumber should never become negative */
				fractionNumber = 0.0;
				fractionAdjust = 0.0;
			}
			else
			{
				fractionNumber -= workNumber;
			}
			offset = (int)trio_fmod(workNumber, dblBase);
			if (offset == 0)
			{
				trailingZeroes++;
			}
			else
			{
				while (trailingZeroes > 0)
				{
					/* Not trailing zeroes after all */
					self->OutStream(self, digits[0]);
					trailingZeroes--;
				}
				self->OutStream(self, digits[offset]);
			}
		}
	}

	if (keepTrailingZeroes)
	{
		while (trailingZeroes > 0)
		{
			self->OutStream(self, digits[0]);
			trailingZeroes--;
		}
	}

	/* Output exponent */
	if (exponentDigits > 0)
	{
		self->OutStream(self, isHex ? ((flags & FLAGS_UPPER) ? 'P' : 'p')
		                            : ((flags & FLAGS_UPPER) ? 'E' : 'e'));
		self->OutStream(self, (isExponentNegative) ? '-' : '+');

		/* The exponent must contain at least two digits */
		if (requireTwoDigitExponent)
			self->OutStream(self, '0');

		if (isHex)
			base = 10;
		exponentBase = (int)TrioPower(base, exponentDigits - 1);
		for (i = 0; i < exponentDigits; i++)
		{
			self->OutStream(self, digits[(uExponent / exponentBase) % base]);
			exponentBase /= base;
		}
	}
	/* Output trailing spaces */
	if (flags & FLAGS_LEFTADJUST)
	{
		for (i = expectedWidth; i < width; i++)
		{
			self->OutStream(self, CHAR_ADJUST);
		}
	}
}