Add wuffs_base__render_number_f64
diff --git a/internal/cgen/base/f64conv-submodule.c b/internal/cgen/base/f64conv-submodule.c
index e0b79d7..5e48340 100644
--- a/internal/cgen/base/f64conv-submodule.c
+++ b/internal/cgen/base/f64conv-submodule.c
@@ -16,8 +16,8 @@
 
 // ---------------- IEEE 754 Floating Point
 
-#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE 1023
-#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION 500
+#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE 2047
+#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION 800
 
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL is the largest N
 // such that ((10 << N) < (1 << 64)).
@@ -27,14 +27,14 @@
 // fixed precision floating point decimal number, augmented with ±infinity
 // values, but it cannot represent NaN (Not a Number).
 //
-// "High precision" means that the mantissa holds 500 decimal digits. 500 is
+// "High precision" means that the mantissa holds 800 decimal digits. 800 is
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION.
 //
 // An HPD isn't for general purpose arithmetic, only for conversions to and
 // from IEEE 754 double-precision floating point, where the largest and
 // smallest positive, finite values are approximately 1.8e+308 and 4.9e-324.
-// HPD exponents above +1023 mean infinity, below -1023 mean zero. The ±1023
-// bounds are further away from zero than ±(324 + 500), where 500 and 1023 is
+// HPD exponents above +2047 mean infinity, below -2047 mean zero. The ±2047
+// bounds are further away from zero than ±(324 + 800), where 800 and 2047 is
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION and
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.
 //
@@ -56,8 +56,8 @@
 //   - A decimal_point of +4 means "7890."
 //   - A decimal_point of +5 means "78900."
 //
-// As above, a decimal_point higher than +1023 means that the overall value is
-// infinity, lower than -1023 means zero.
+// As above, a decimal_point higher than +2047 means that the overall value is
+// infinity, lower than -2047 means zero.
 //
 // negative is a sign bit. An HPD can distinguish positive and negative zero.
 //
@@ -89,6 +89,48 @@
   }
 }
 
+// wuffs_base__private_implementation__high_prec_dec__assign sets h to
+// represent the number x.
+//
+// Preconditions:
+//  - h is non-NULL.
+static void  //
+wuffs_base__private_implementation__high_prec_dec__assign(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    uint64_t x,
+    bool negative) {
+  uint32_t n = 0;
+
+  // Set h->digits.
+  if (x > 0) {
+    // Calculate the digits, working right-to-left. After we determine n (how
+    // many digits there are), copy from buf to h->digits.
+    //
+    // UINT64_MAX, 18446744073709551615, is 20 digits long. It can be faster to
+    // copy a constant number of bytes than a variable number (20 instead of
+    // n). Make buf large enough (and start writing to it from the middle) so
+    // that can we always copy 20 bytes: the slice buf[(20-n) .. (40-n)].
+    uint8_t buf[40] = {0};
+    uint8_t* ptr = &buf[20];
+    do {
+      uint64_t remaining = x / 10;
+      x -= remaining * 10;
+      ptr--;
+      *ptr = (uint8_t)x;
+      n++;
+      x = remaining;
+    } while (x > 0);
+    memcpy(h->digits, ptr, 20);
+  }
+
+  // Set h's other fields.
+  h->num_digits = n;
+  h->decimal_point = (int32_t)n;
+  h->negative = negative;
+  h->truncated = false;
+  wuffs_base__private_implementation__high_prec_dec__trim(h);
+}
+
 static wuffs_base__status  //
 wuffs_base__private_implementation__high_prec_dec__parse(
     wuffs_base__private_implementation__high_prec_dec* h,
@@ -464,6 +506,11 @@
 // wuffs_base__private_implementation__high_prec_dec__rounded_integer and
 // wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits
 // have the same preconditions.
+//
+// wuffs_base__private_implementation__high_prec_dec__lshift keeps the first
+// two preconditions but not the last two. Its shift argument is signed and
+// does not need to be "small": zero is a no-op, positive means left shift and
+// negative means right shift.
 
 static void  //
 wuffs_base__private_implementation__high_prec_dec__small_lshift(
@@ -577,6 +624,254 @@
   wuffs_base__private_implementation__high_prec_dec__trim(h);
 }
 
+static void  //
+wuffs_base__private_implementation__high_prec_dec__lshift(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t shift) {
+  if (shift > 0) {
+    while (shift > +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) {
+      wuffs_base__private_implementation__high_prec_dec__small_lshift(
+          h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL);
+      shift -= WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;
+    }
+    wuffs_base__private_implementation__high_prec_dec__small_lshift(
+        h, ((uint32_t)(+shift)));
+  } else if (shift < 0) {
+    while (shift < -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) {
+      wuffs_base__private_implementation__high_prec_dec__small_rshift(
+          h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL);
+      shift += WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;
+    }
+    wuffs_base__private_implementation__high_prec_dec__small_rshift(
+        h, ((uint32_t)(-shift)));
+  }
+}
+
+// --------
+
+// wuffs_base__private_implementation__high_prec_dec__round_etc rounds h's
+// number. For those functions that take an n argument, rounding produces at
+// most n digits (which is not necessarily at most n decimal places). Negative
+// n values are ignored, as well as any n greater than or equal to h's number
+// of digits. The etc__round_just_enough function implicitly chooses an n to
+// implement WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION.
+//
+// Preconditions:
+//  - h is non-NULL.
+//  - h->decimal_point is "not extreme".
+//
+// "Not extreme" means within
+// ±WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_down(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t n) {
+  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {
+    return;
+  }
+  h->num_digits = (uint32_t)(n);
+  wuffs_base__private_implementation__high_prec_dec__trim(h);
+}
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_up(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t n) {
+  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {
+    return;
+  }
+
+  for (n--; n >= 0; n--) {
+    if (h->digits[n] < 9) {
+      h->digits[n]++;
+      h->num_digits = (uint32_t)(n + 1);
+      return;
+    }
+  }
+
+  // The number is all 9s. Change to a single 1 and adjust the decimal point.
+  h->digits[0] = 1;
+  h->num_digits = 1;
+  h->decimal_point++;
+}
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_nearest(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t n) {
+  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {
+    return;
+  }
+  bool up = h->digits[n] >= 5;
+  if ((h->digits[n] == 5) && ((n + 1) == ((int32_t)(h->num_digits)))) {
+    up = h->truncated ||  //
+         ((n > 0) && ((h->digits[n - 1] & 1) != 0));
+  }
+
+  if (up) {
+    wuffs_base__private_implementation__high_prec_dec__round_up(h, n);
+  } else {
+    wuffs_base__private_implementation__high_prec_dec__round_down(h, n);
+  }
+}
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t exp2,
+    uint64_t mantissa) {
+  // The magic numbers 52 and 53 in this function are because IEEE 754 double
+  // precision has 52 mantissa bits.
+  //
+  // Let f be the floating point number represented by exp2 and mantissa (and
+  // also the number in h): the number (mantissa * (2 ** (exp2 - 52))).
+  //
+  // If f is zero, we can return early.
+  if (mantissa == 0) {
+    return;
+  }
+
+  // The smallest normal f has an exp2 of -1022 and a mantissa of (1 << 52).
+  // Subnormal numbers have the same exp2 but a smaller mantissa.
+  static const int32_t min_incl_normal_exp2 = -1022;
+  static const uint64_t min_incl_normal_mantissa = 0x0010000000000000ul;
+
+  // Compute lower and upper bounds such that any number between them (possibly
+  // inclusive) will round to f. First, the lower bound. Our number f is:
+  //   ((mantissa + 0)         * (2 ** (  exp2 - 52)))
+  //
+  // The next lowest floating point number is:
+  //   ((mantissa - 1)         * (2 ** (  exp2 - 52)))
+  // unless (mantissa - 1) drops the (1 << 52) bit and exp2 is not the
+  // min_incl_normal_exp2. Either way, call it:
+  //   ((l_mantissa)           * (2 ** (l_exp2 - 52)))
+  //
+  // The lower bound is halfway between them (noting that 52 became 53):
+  //   (((2 * l_mantissa) + 1) * (2 ** (l_exp2 - 53)))
+  int32_t l_exp2 = exp2;
+  uint64_t l_mantissa = mantissa - 1;
+  if ((exp2 > min_incl_normal_exp2) && (mantissa <= min_incl_normal_mantissa)) {
+    l_exp2 = exp2 - 1;
+    l_mantissa = (2 * mantissa) - 1;
+  }
+  wuffs_base__private_implementation__high_prec_dec lower;
+  wuffs_base__private_implementation__high_prec_dec__assign(
+      &lower, (2 * l_mantissa) + 1, false);
+  wuffs_base__private_implementation__high_prec_dec__lshift(&lower,
+                                                            l_exp2 - 53);
+
+  // Next, the upper bound. Our number f is:
+  //   ((mantissa + 0)       * (2 ** (exp2 - 52)))
+  //
+  // The next highest floating point number is:
+  //   ((mantissa + 1)       * (2 ** (exp2 - 52)))
+  //
+  // The upper bound is halfway between them (noting that 52 became 53):
+  //   (((2 * mantissa) + 1) * (2 ** (exp2 - 53)))
+  wuffs_base__private_implementation__high_prec_dec upper;
+  wuffs_base__private_implementation__high_prec_dec__assign(
+      &upper, (2 * mantissa) + 1, false);
+  wuffs_base__private_implementation__high_prec_dec__lshift(&upper, exp2 - 53);
+
+  // The lower and upper bounds are possible outputs only if the original
+  // mantissa is even, so that IEEE round-to-even would round to the original
+  // mantissa and not its neighbors.
+  bool inclusive = (mantissa & 1) == 0;
+
+  // As we walk the digits, we want to know whether rounding up would fall
+  // within the upper bound. This is tracked by upper_delta:
+  //  - When -1, the digits of h and upper are the same so far.
+  //  - When +0, we saw a difference of 1 between h and upper on a previous
+  //    digit and subsequently only 9s for h and 0s for upper. Thus, rounding
+  //    up may fall outside of the bound if !inclusive.
+  //  - When +1, the difference is greater than 1 and we know that rounding up
+  //    falls within the bound.
+  //
+  // This is a state machine with three states. The numerical value for each
+  // state (-1, +0 or +1) isn't important, other than their order.
+  int upper_delta = -1;
+
+  // We can now figure out the shortest number of digits required. Walk the
+  // digits until h has distinguished itself from lower or upper.
+  //
+  // The zi and zd variables are indexes and digits, for z in l (lower), h (the
+  // number) and u (upper).
+  //
+  // The lower, h and upper numbers may have their decimal points at different
+  // places. In this case, upper is the longest, so we iterate ui starting from
+  // 0 and iterate li and hi starting from either 0 or -1.
+  int32_t ui = 0;
+  for (;; ui++) {
+    // Calculate hd, the middle number's digit.
+    int32_t hi = ui - upper.decimal_point + h->decimal_point;
+    if (hi >= ((int32_t)(h->num_digits))) {
+      break;
+    }
+    uint8_t hd = (((uint32_t)hi) < h->num_digits) ? h->digits[hi] : 0;
+
+    // Calculate ld, the lower bound's digit.
+    int32_t li = ui - upper.decimal_point + lower.decimal_point;
+    uint8_t ld = (((uint32_t)li) < lower.num_digits) ? lower.digits[li] : 0;
+
+    // We can round down (truncate) if lower has a different digit than h or if
+    // lower is inclusive and is exactly the result of rounding down (i.e. we
+    // have reached the final digit of lower).
+    bool can_round_down =
+        (ld != hd) ||  //
+        (inclusive && ((li + 1) == ((int32_t)(lower.num_digits))));
+
+    // Calculate ud, the upper bound's digit, and update upper_delta.
+    uint8_t ud = (((uint32_t)ui) < upper.num_digits) ? upper.digits[ui] : 0;
+    if (upper_delta < 0) {
+      if ((hd + 1) < ud) {
+        // For example:
+        // h     = 12345???
+        // upper = 12347???
+        upper_delta = +1;
+      } else if (hd != ud) {
+        // For example:
+        // h     = 12345???
+        // upper = 12346???
+        upper_delta = +0;
+      }
+    } else if (upper_delta == 0) {
+      if ((hd != 9) || (ud != 0)) {
+        // For example:
+        // h     = 1234598?
+        // upper = 1234600?
+        upper_delta = +1;
+      }
+    }
+
+    // We can round up if upper has a different digit than h and either upper
+    // is inclusive or upper is bigger than the result of rounding up.
+    bool can_round_up =
+        (upper_delta > 0) ||    //
+        ((upper_delta == 0) &&  //
+         (inclusive || ((ui + 1) < ((int32_t)(upper.num_digits)))));
+
+    // If we can round either way, round to nearest. If we can round only one
+    // way, do it. If we can't round, continue the loop.
+    if (can_round_down) {
+      if (can_round_up) {
+        wuffs_base__private_implementation__high_prec_dec__round_nearest(
+            h, hi + 1);
+        return;
+      } else {
+        wuffs_base__private_implementation__high_prec_dec__round_down(h,
+                                                                      hi + 1);
+        return;
+      }
+    } else {
+      if (can_round_up) {
+        wuffs_base__private_implementation__high_prec_dec__round_up(h, hi + 1);
+        return;
+      }
+    }
+  }
+}
+
 // --------
 
 // The wuffs_base__private_implementation__etc_powers_of_10 tables were printed
@@ -1262,3 +1557,300 @@
     return ret;
   } while (0);
 }
+
+// --------
+
+static inline size_t  //
+wuffs_base__private_implementation__render_inf(wuffs_base__slice_u8 dst,
+                                               bool neg,
+                                               uint32_t options) {
+  if (neg) {
+    if (dst.len < 4) {
+      return 0;
+    }
+    wuffs_base__store_u32le__no_bounds_check(dst.ptr, 0x666E492D);  // '-Inf'le.
+    return 4;
+  }
+
+  if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
+    if (dst.len < 4) {
+      return 0;
+    }
+    wuffs_base__store_u32le__no_bounds_check(dst.ptr, 0x666E492B);  // '+Inf'le.
+    return 4;
+  }
+
+  if (dst.len < 3) {
+    return 0;
+  }
+  wuffs_base__store_u24le__no_bounds_check(dst.ptr, 0x666E49);  // 'Inf'le.
+  return 3;
+}
+
+static inline size_t  //
+wuffs_base__private_implementation__render_nan(wuffs_base__slice_u8 dst) {
+  if (dst.len < 3) {
+    return 0;
+  }
+  wuffs_base__store_u24le__no_bounds_check(dst.ptr, 0x4E614E);  // 'NaN'le.
+  return 3;
+}
+
+static size_t  //
+wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(
+    wuffs_base__slice_u8 dst,
+    wuffs_base__private_implementation__high_prec_dec* h,
+    uint32_t precision,
+    uint32_t options) {
+  size_t n = (h->negative ||
+              (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN))
+                 ? 1
+                 : 0;
+  if (h->decimal_point <= 0) {
+    n += 1;
+  } else {
+    n += (size_t)(h->decimal_point);
+  }
+  if (precision > 0) {
+    n += precision + 1;  // +1 for the '.'.
+  }
+
+  // Don't modify dst if the formatted number won't fit.
+  if (n > dst.len) {
+    return 0;
+  }
+
+  // Align-left or align-right.
+  uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)
+                     ? &dst.ptr[dst.len - n]
+                     : &dst.ptr[0];
+
+  // Leading "±".
+  if (h->negative) {
+    *ptr++ = '-';
+  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
+    *ptr++ = '+';
+  }
+
+  // Integral digits.
+  if (h->decimal_point <= 0) {
+    *ptr++ = '0';
+  } else {
+    uint32_t m =
+        wuffs_base__u32__min(h->num_digits, (uint32_t)(h->decimal_point));
+    uint32_t i = 0;
+    for (; i < m; i++) {
+      *ptr++ = (uint8_t)('0' | h->digits[i]);
+    }
+    for (; i < (uint32_t)(h->decimal_point); i++) {
+      *ptr++ = '0';
+    }
+  }
+
+  // Separator and then fractional digits.
+  if (precision > 0) {
+    *ptr++ =
+        (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA)
+            ? ','
+            : '.';
+    uint32_t i = 0;
+    for (; i < precision; i++) {
+      uint32_t j = ((uint32_t)(h->decimal_point)) + i;
+      *ptr++ = (uint8_t)('0' | ((j < h->num_digits) ? h->digits[j] : 0));
+    }
+  }
+
+  return n;
+}
+
+static size_t  //
+wuffs_base__private_implementation__high_prec_dec__render_exponent_present(
+    wuffs_base__slice_u8 dst,
+    wuffs_base__private_implementation__high_prec_dec* h,
+    uint32_t precision,
+    uint32_t options) {
+  int32_t exp = 0;
+  if (h->num_digits > 0) {
+    exp = h->decimal_point - 1;
+  }
+  bool negative_exp = exp < 0;
+  if (negative_exp) {
+    exp = -exp;
+  }
+
+  size_t n = (h->negative ||
+              (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN))
+                 ? 4
+                 : 3;  // Mininum 3 bytes: first digit and then "e±".
+  if (precision > 0) {
+    n += precision + 1;  // +1 for the '.'.
+  }
+  n += (exp < 100) ? 2 : 3;
+
+  // Don't modify dst if the formatted number won't fit.
+  if (n > dst.len) {
+    return 0;
+  }
+
+  // Align-left or align-right.
+  uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)
+                     ? &dst.ptr[dst.len - n]
+                     : &dst.ptr[0];
+
+  // Leading "±".
+  if (h->negative) {
+    *ptr++ = '-';
+  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
+    *ptr++ = '+';
+  }
+
+  // Integral digit.
+  if (h->num_digits > 0) {
+    *ptr++ = (uint8_t)('0' | h->digits[0]);
+  } else {
+    *ptr++ = '0';
+  }
+
+  // Separator and then fractional digits.
+  if (precision > 0) {
+    *ptr++ =
+        (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA)
+            ? ','
+            : '.';
+    uint32_t i = 1;
+    uint32_t j = wuffs_base__u32__min(h->num_digits, precision + 1);
+    for (; i < j; i++) {
+      *ptr++ = (uint8_t)('0' | h->digits[i]);
+    }
+    for (; i <= precision; i++) {
+      *ptr++ = '0';
+    }
+  }
+
+  // Exponent: "e±" and then 2 or 3 digits.
+  *ptr++ = 'e';
+  *ptr++ = negative_exp ? '-' : '+';
+  if (exp < 10) {
+    *ptr++ = '0';
+    *ptr++ = (uint8_t)('0' | exp);
+  } else if (exp < 100) {
+    *ptr++ = (uint8_t)('0' | (exp / 10));
+    *ptr++ = (uint8_t)('0' | (exp % 10));
+  } else {
+    int32_t e = exp / 100;
+    exp -= e * 100;
+    *ptr++ = (uint8_t)('0' | e);
+    *ptr++ = (uint8_t)('0' | (exp / 10));
+    *ptr++ = (uint8_t)('0' | (exp % 10));
+  }
+
+  return n;
+}
+
+WUFFS_BASE__MAYBE_STATIC size_t  //
+wuffs_base__render_number_f64(wuffs_base__slice_u8 dst,
+                              double x,
+                              uint32_t precision,
+                              uint32_t options) {
+  // Decompose x (64 bits) into negativity (1 bit), base-2 exponent (11 bits
+  // with a -1023 bias) and mantissa (52 bits).
+  uint64_t bits = wuffs_base__ieee_754_bit_representation__from_f64(x);
+  bool neg = (bits >> 63) != 0;
+  int32_t exp2 = ((int32_t)(bits >> 52)) & 0x7FF;
+  uint64_t man = bits & 0x000FFFFFFFFFFFFFul;
+
+  // Apply the exponent bias and set the implicit top bit of the mantissa,
+  // unless x is subnormal. Also take care of Inf and NaN.
+  if (exp2 == 0x7FF) {
+    if (man != 0) {
+      return wuffs_base__private_implementation__render_nan(dst);
+    }
+    return wuffs_base__private_implementation__render_inf(dst, neg, options);
+  } else if (exp2 == 0) {
+    exp2 = -1022;
+  } else {
+    exp2 -= 1023;
+    man |= 0x0010000000000000ul;
+  }
+
+  // Ensure that precision isn't too large.
+  if (precision > 4095) {
+    precision = 4095;
+  }
+
+  // Convert from the (neg, exp2, man) tuple to an HPD.
+  wuffs_base__private_implementation__high_prec_dec h;
+  wuffs_base__private_implementation__high_prec_dec__assign(&h, man, neg);
+  if (h.num_digits > 0) {
+    wuffs_base__private_implementation__high_prec_dec__lshift(
+        &h, exp2 - 52);  // 52 mantissa bits.
+  }
+
+  // Handle the "%e" and "%f" formats.
+  switch (options & (WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT |
+                     WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT)) {
+    case WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT:  // The "%"f" format.
+      if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {
+        wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+            &h, exp2, man);
+        int32_t p = ((int32_t)(h.num_digits)) - h.decimal_point;
+        precision = ((uint32_t)(wuffs_base__i32__max(0, p)));
+      } else {
+        wuffs_base__private_implementation__high_prec_dec__round_nearest(
+            &h, ((int32_t)precision) + h.decimal_point);
+      }
+      return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(
+          dst, &h, precision, options);
+
+    case WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT:  // The "%e" format.
+      if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {
+        wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+            &h, exp2, man);
+        precision = (h.num_digits > 0) ? (h.num_digits - 1) : 0;
+      } else {
+        wuffs_base__private_implementation__high_prec_dec__round_nearest(
+            &h, ((int32_t)precision) + 1);
+      }
+      return wuffs_base__private_implementation__high_prec_dec__render_exponent_present(
+          dst, &h, precision, options);
+  }
+
+  // We have the "%g" format and so precision means the number of significant
+  // digits, not the number of digits after the decimal separator. Perform
+  // rounding and determine whether to use "%e" or "%f".
+  int32_t e_threshold = 0;
+  if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {
+    wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+        &h, exp2, man);
+    precision = h.num_digits;
+    e_threshold = 6;
+  } else {
+    if (precision == 0) {
+      precision = 1;
+    }
+    wuffs_base__private_implementation__high_prec_dec__round_nearest(
+        &h, ((int32_t)precision));
+    e_threshold = ((int32_t)precision);
+    int32_t nd = ((int32_t)(h.num_digits));
+    if ((e_threshold > nd) && (nd >= h.decimal_point)) {
+      e_threshold = nd;
+    }
+  }
+
+  // Use the "%e" format if the exponent is large.
+  int32_t e = h.decimal_point - 1;
+  if ((e < -4) || (e_threshold <= e)) {
+    uint32_t p = wuffs_base__u32__min(precision, h.num_digits);
+    return wuffs_base__private_implementation__high_prec_dec__render_exponent_present(
+        dst, &h, (p > 0) ? (p - 1) : 0, options);
+  }
+
+  // Use the "%f" format otherwise.
+  int32_t p = ((int32_t)precision);
+  if (p > h.decimal_point) {
+    p = ((int32_t)(h.num_digits));
+  }
+  precision = ((uint32_t)(wuffs_base__i32__max(0, p - h.decimal_point)));
+  return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(
+      dst, &h, precision, options);
+}
diff --git a/internal/cgen/base/fundamental-public.h b/internal/cgen/base/fundamental-public.h
index f69e2e4..55c0d40 100644
--- a/internal/cgen/base/fundamental-public.h
+++ b/internal/cgen/base/fundamental-public.h
@@ -281,6 +281,46 @@
 // inline attribute to guide optimizations such as inlining, to avoid the
 // -Wunused-function warning, and we like to compile with -Wall -Werror.
 
+static inline int8_t  //
+wuffs_base__i8__min(int8_t x, int8_t y) {
+  return x < y ? x : y;
+}
+
+static inline int8_t  //
+wuffs_base__i8__max(int8_t x, int8_t y) {
+  return x > y ? x : y;
+}
+
+static inline int16_t  //
+wuffs_base__i16__min(int16_t x, int16_t y) {
+  return x < y ? x : y;
+}
+
+static inline int16_t  //
+wuffs_base__i16__max(int16_t x, int16_t y) {
+  return x > y ? x : y;
+}
+
+static inline int32_t  //
+wuffs_base__i32__min(int32_t x, int32_t y) {
+  return x < y ? x : y;
+}
+
+static inline int32_t  //
+wuffs_base__i32__max(int32_t x, int32_t y) {
+  return x > y ? x : y;
+}
+
+static inline int64_t  //
+wuffs_base__i64__min(int64_t x, int64_t y) {
+  return x < y ? x : y;
+}
+
+static inline int64_t  //
+wuffs_base__i64__max(int64_t x, int64_t y) {
+  return x > y ? x : y;
+}
+
 static inline uint8_t  //
 wuffs_base__u8__min(uint8_t x, uint8_t y) {
   return x < y ? x : y;
diff --git a/internal/cgen/base/i64conv-submodule.c b/internal/cgen/base/i64conv-submodule.c
index 86335d2..6c43523 100644
--- a/internal/cgen/base/i64conv-submodule.c
+++ b/internal/cgen/base/i64conv-submodule.c
@@ -359,7 +359,7 @@
   if (neg) {
     ptr -= 1;
     ptr[0] = '-';
-  } else if (options & WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN) {
+  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
     ptr -= 1;
     ptr[0] = '+';
   }
@@ -368,7 +368,7 @@
   if (n > dst.len) {
     return 0;
   }
-  memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT)
+  memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)
                         ? (dst.len - n)
                         : 0),
          ptr, n);
diff --git a/internal/cgen/base/strconv-public.h b/internal/cgen/base/strconv-public.h
index 4f80c2a..734d60c 100644
--- a/internal/cgen/base/strconv-public.h
+++ b/internal/cgen/base/strconv-public.h
@@ -16,20 +16,49 @@
 
 // ---------------- String Conversions
 
-// Options (bitwise or'ed together) for wuffs_base__render_number_etc
-// functions.
+// Options (bitwise or'ed together) for wuffs_base__render_number_xxx
+// functions. The XXX options apply to both integer and floating point. The FXX
+// options apply only to floating point.
 
-#define WUFFS_BASE__RENDER_NUMBER__DEFAULT_OPTIONS ((uint32_t)0x00000000)
+#define WUFFS_BASE__RENDER_NUMBER_XXX__DEFAULT_OPTIONS ((uint32_t)0x00000000)
 
-// WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT means to render to the right side
+// WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT means to render to the right side
 // (higher indexes) of the destination slice, leaving any untouched bytes on
 // the left side (lower indexes). The default is vice versa: rendering on the
 // left with slack on the right.
-#define WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT ((uint32_t)0x00000001)
+#define WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT ((uint32_t)0x00000001)
 
-// WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN means to render the unnecessary
-// leading "+" for non-negative numbers: "+0" and "+123", not "0" and "123".
-#define WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN ((uint32_t)0x00000002)
+// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN means to render the leading
+// "+" for non-negative numbers: "+0" and "+12.3" instead of "0" and "12.3".
+#define WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN ((uint32_t)0x00000002)
+
+// WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA means to render
+// one-and-a-half as "1,5" instead of "1.5".
+#define WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA \
+  ((uint32_t)0x00000100)
+
+// WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ETC means whether to never
+// (EXPONENT_ABSENT, equivalent to printf's "%f") or to always
+// (EXPONENT_PRESENT, equivalent to printf's "%e") render a floating point
+// number as "1.23e+05" instead of "123000".
+//
+// Having both bits set is the same has having neither bit set, where the
+// notation used depends on whether the exponent is sufficiently large: "0.5"
+// is preferred over "5e-01" but "5e-09" is preferred over "0.000000005".
+#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT ((uint32_t)0x00000200)
+#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT ((uint32_t)0x00000400)
+
+// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION means to render the
+// smallest number of digits so that parsing the resultant string will recover
+// the same double-precision floating point number.
+//
+// For example, double-precision cannot distinguish between 0.3 and
+// 0.299999999999999988897769753748434595763683319091796875, so when this bit
+// is set, rendering the latter will produce "0.3" but rendering
+// 0.3000000000000000444089209850062616169452667236328125 will produce
+// "0.30000000000000004".
+#define WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION \
+  ((uint32_t)0x00000800)
 
 // ---------------- IEEE 754 Floating Point
 
@@ -148,6 +177,38 @@
 #define WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL 20
 #define WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL 21
 
+// wuffs_base__render_number_f64 writes the decimal encoding of x to dst and
+// returns the number of bytes written. If dst is shorter than the entire
+// encoding, it returns 0 (and no bytes are written).
+//
+// For those familiar with C's printf or Go's fmt.Printf functions:
+//  - "%e" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT option.
+//  - "%f" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT  option.
+//  - "%g" means neither or both bits are set.
+//
+// The precision argument controls the number of digits rendered, excluding the
+// exponent (the "e+05" in "1.23e+05"):
+//  - for "%e" and "%f" it is the number of digits after the decimal separator,
+//  - for "%g" it is the number of significant digits (and trailing zeroes are
+//    removed).
+//
+// A precision of 6 gives similar output to printf's defaults.
+//
+// A precision greater than 4095 is equivalent to 4095.
+//
+// The precision argument is ignored when the
+// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION option is set. This is
+// similar to Go's strconv.FormatFloat with a negative (i.e. non-sensical)
+// precision, but there is no corresponding feature in C's printf.
+//
+// Extreme values of x will be rendered as "NaN", "Inf" (or "+Inf" if the
+// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN option is set) or "-Inf".
+WUFFS_BASE__MAYBE_STATIC size_t  //
+wuffs_base__render_number_f64(wuffs_base__slice_u8 dst,
+                              double x,
+                              uint32_t precision,
+                              uint32_t options);
+
 // wuffs_base__render_number_i64 writes the decimal encoding of x to dst and
 // returns the number of bytes written. If dst is shorter than the entire
 // encoding, it returns 0 (and no bytes are written).
diff --git a/internal/cgen/data/data.go b/internal/cgen/data/data.go
index e39b9c6..ae40721 100644
--- a/internal/cgen/data/data.go
+++ b/internal/cgen/data/data.go
@@ -29,14 +29,15 @@
 	""
 
 const BaseF64ConvSubmoduleC = "" +
-	"// ---------------- IEEE 754 Floating Point\n\n#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE 1023\n#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION 500\n\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL is the largest N\n// such that ((10 << N) < (1 << 64)).\n#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL 60\n\n// wuffs_base__private_implementation__high_prec_dec (abbreviated as HPD) is a\n// fixed precision floating point decimal number, augmented with ±infinity\n// values, but it cannot represent NaN (Not a Number).\n//\n// \"High precision\" means that the mantissa holds 500 decimal digits. 500 is\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION.\n//\n// An HPD isn't for general purpose arithmetic, only for conversions to and\n// from IEEE 754 double-precision floating point, where the largest and\n// smallest positive, finite values are approximately 1.8e+308 and 4.9e-324.\n// HPD exponents above +1023 mean infinity, below -1023 mean zero. Th" +
-	"e ±1023\n// bounds are further away from zero than ±(324 + 500), where 500 and 1023 is\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION and\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.\n//\n// digits[.. num_digits] are the number's digits in big-endian order. The\n// uint8_t values are in the range [0 ..= 9], not ['0' ..= '9'], where e.g. '7'\n// is the ASCII value 0x37.\n//\n// decimal_point is the index (within digits) of the decimal point. It may be\n// negative or be larger than num_digits, in which case the explicit digits are\n// padded with implicit zeroes.\n//\n// For example, if num_digits is 3 and digits is \"\\x07\\x08\\x09\":\n//   - A decimal_point of -2 means \".00789\"\n//   - A decimal_point of -1 means \".0789\"\n//   - A decimal_point of +0 means \".789\"\n//   - A decimal_point of +1 means \"7.89\"\n//   - A decimal_point of +2 means \"78.9\"\n//   - A decimal_point of +3 means \"789.\"\n//   - A decimal_point of +4 means \"7890.\"\n//   - A decimal_point of +5 means \"78900.\"\n//\n// As above, a" +
-	" decimal_point higher than +1023 means that the overall value is\n// infinity, lower than -1023 means zero.\n//\n// negative is a sign bit. An HPD can distinguish positive and negative zero.\n//\n// truncated is whether there are more than\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION digits, and at\n// least one of those extra digits are non-zero. The existence of long-tail\n// digits can affect rounding.\n//\n// The \"all fields are zero\" value is valid, and represents the number +0.\ntypedef struct {\n  uint32_t num_digits;\n  int32_t decimal_point;\n  bool negative;\n  bool truncated;\n  uint8_t digits[WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION];\n} wuffs_base__private_implementation__high_prec_dec;\n\n// wuffs_base__private_implementation__high_prec_dec__trim trims trailing\n// zeroes from the h->digits[.. h->num_digits] slice. They have no benefit,\n// since we explicitly track h->decimal_point.\n//\n// Preconditions:\n//  - h is non-NULL.\nstatic inline void  //\nwuffs_base__private_implementation_" +
-	"_high_prec_dec__trim(\n    wuffs_base__private_implementation__high_prec_dec* h) {\n  while ((h->num_digits > 0) && (h->digits[h->num_digits - 1] == 0)) {\n    h->num_digits--;\n  }\n}\n\nstatic wuffs_base__status  //\nwuffs_base__private_implementation__high_prec_dec__parse(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    wuffs_base__slice_u8 s) {\n  if (!h) {\n    return wuffs_base__make_status(wuffs_base__error__bad_receiver);\n  }\n  h->num_digits = 0;\n  h->decimal_point = 0;\n  h->negative = false;\n  h->truncated = false;\n\n  uint8_t* p = s.ptr;\n  uint8_t* q = s.ptr + s.len;\n\n  for (; (p < q) && (*p == '_'); p++) {\n  }\n  if (p >= q) {\n    return wuffs_base__make_status(wuffs_base__error__bad_argument);\n  }\n\n  // Parse sign.\n  do {\n    if (*p == '+') {\n      p++;\n    } else if (*p == '-') {\n      h->negative = true;\n      p++;\n    } else {\n      break;\n    }\n    for (; (p < q) && (*p == '_'); p++) {\n    }\n  } while (0);\n\n  // Parse digits.\n  uint32_t nd = 0;\n  int32_t dp = 0;\n  bool saw_digits = false;\n  " +
-	"bool saw_non_zero_digits = false;\n  bool saw_dot = false;\n  for (; p < q; p++) {\n    if (*p == '_') {\n      // No-op.\n\n    } else if ((*p == '.') || (*p == ',')) {\n      // As per https://en.wikipedia.org/wiki/Decimal_separator, both '.' or\n      // ',' are commonly used. We just parse either, regardless of LOCALE.\n      if (saw_dot) {\n        return wuffs_base__make_status(wuffs_base__error__bad_argument);\n      }\n      saw_dot = true;\n      dp = (int32_t)nd;\n\n    } else if ('0' == *p) {\n      if (!saw_dot && !saw_non_zero_digits && saw_digits) {\n        // We don't allow unnecessary leading zeroes: \"000123\" or \"0644\".\n        return wuffs_base__make_status(wuffs_base__error__bad_argument);\n      }\n      saw_digits = true;\n      if (nd == 0) {\n        // Track leading zeroes implicitly.\n        dp--;\n      } else if (nd <\n                 WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n        h->digits[nd++] = 0;\n      } else {\n        // Long-tail zeroes are ignored.\n      }\n\n    } else if (('" +
-	"0' < *p) && (*p <= '9')) {\n      if (!saw_dot && !saw_non_zero_digits && saw_digits) {\n        // We don't allow unnecessary leading zeroes: \"000123\" or \"0644\".\n        return wuffs_base__make_status(wuffs_base__error__bad_argument);\n      }\n      saw_digits = true;\n      saw_non_zero_digits = true;\n      if (nd < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n        h->digits[nd++] = (uint8_t)(*p - '0');\n      } else {\n        // Long-tail non-zeroes set the truncated bit.\n        h->truncated = true;\n      }\n\n    } else {\n      break;\n    }\n  }\n\n  if (!saw_digits) {\n    return wuffs_base__make_status(wuffs_base__error__bad_argument);\n  }\n  if (!saw_dot) {\n    dp = (int32_t)nd;\n  }\n\n  // Parse exponent.\n  if ((p < q) && ((*p == 'E') || (*p == 'e'))) {\n    p++;\n    for (; (p < q) && (*p == '_'); p++) {\n    }\n    if (p >= q) {\n      return wuffs_base__make_status(wuffs_base__error__bad_argument);\n    }\n\n    int32_t exp_sign = +1;\n    if (*p == '+') {\n      p++;\n    } else if (*p == '-') {\n      " +
-	"exp_sign = -1;\n      p++;\n    }\n\n    int32_t exp = 0;\n    const int32_t exp_large =\n        WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE +\n        WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION;\n    bool saw_exp_digits = false;\n    for (; p < q; p++) {\n      if (*p == '_') {\n        // No-op.\n      } else if (('0' <= *p) && (*p <= '9')) {\n        saw_exp_digits = true;\n        if (exp < exp_large) {\n          exp = (10 * exp) + ((int32_t)(*p - '0'));\n        }\n      } else {\n        break;\n      }\n    }\n    if (!saw_exp_digits) {\n      return wuffs_base__make_status(wuffs_base__error__bad_argument);\n    }\n    dp += exp_sign * exp;\n  }\n\n  // Finish.\n  if (p != q) {\n    return wuffs_base__make_status(wuffs_base__error__bad_argument);\n  }\n  h->num_digits = nd;\n  if (nd == 0) {\n    h->decimal_point = 0;\n  } else if (dp <\n             -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n    h->decimal_point =\n        -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__" +
-	"RANGE - 1;\n  } else if (dp >\n             +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n    h->decimal_point =\n        +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE + 1;\n  } else {\n    h->decimal_point = dp;\n  }\n  wuffs_base__private_implementation__high_prec_dec__trim(h);\n  return wuffs_base__make_status(NULL);\n}\n\n" +
+	"// ---------------- IEEE 754 Floating Point\n\n#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE 2047\n#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION 800\n\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL is the largest N\n// such that ((10 << N) < (1 << 64)).\n#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL 60\n\n// wuffs_base__private_implementation__high_prec_dec (abbreviated as HPD) is a\n// fixed precision floating point decimal number, augmented with ±infinity\n// values, but it cannot represent NaN (Not a Number).\n//\n// \"High precision\" means that the mantissa holds 800 decimal digits. 800 is\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION.\n//\n// An HPD isn't for general purpose arithmetic, only for conversions to and\n// from IEEE 754 double-precision floating point, where the largest and\n// smallest positive, finite values are approximately 1.8e+308 and 4.9e-324.\n// HPD exponents above +2047 mean infinity, below -2047 mean zero. Th" +
+	"e ±2047\n// bounds are further away from zero than ±(324 + 800), where 800 and 2047 is\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION and\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.\n//\n// digits[.. num_digits] are the number's digits in big-endian order. The\n// uint8_t values are in the range [0 ..= 9], not ['0' ..= '9'], where e.g. '7'\n// is the ASCII value 0x37.\n//\n// decimal_point is the index (within digits) of the decimal point. It may be\n// negative or be larger than num_digits, in which case the explicit digits are\n// padded with implicit zeroes.\n//\n// For example, if num_digits is 3 and digits is \"\\x07\\x08\\x09\":\n//   - A decimal_point of -2 means \".00789\"\n//   - A decimal_point of -1 means \".0789\"\n//   - A decimal_point of +0 means \".789\"\n//   - A decimal_point of +1 means \"7.89\"\n//   - A decimal_point of +2 means \"78.9\"\n//   - A decimal_point of +3 means \"789.\"\n//   - A decimal_point of +4 means \"7890.\"\n//   - A decimal_point of +5 means \"78900.\"\n//\n// As above, a" +
+	" decimal_point higher than +2047 means that the overall value is\n// infinity, lower than -2047 means zero.\n//\n// negative is a sign bit. An HPD can distinguish positive and negative zero.\n//\n// truncated is whether there are more than\n// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION digits, and at\n// least one of those extra digits are non-zero. The existence of long-tail\n// digits can affect rounding.\n//\n// The \"all fields are zero\" value is valid, and represents the number +0.\ntypedef struct {\n  uint32_t num_digits;\n  int32_t decimal_point;\n  bool negative;\n  bool truncated;\n  uint8_t digits[WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION];\n} wuffs_base__private_implementation__high_prec_dec;\n\n// wuffs_base__private_implementation__high_prec_dec__trim trims trailing\n// zeroes from the h->digits[.. h->num_digits] slice. They have no benefit,\n// since we explicitly track h->decimal_point.\n//\n// Preconditions:\n//  - h is non-NULL.\nstatic inline void  //\nwuffs_base__private_implementation_" +
+	"_high_prec_dec__trim(\n    wuffs_base__private_implementation__high_prec_dec* h) {\n  while ((h->num_digits > 0) && (h->digits[h->num_digits - 1] == 0)) {\n    h->num_digits--;\n  }\n}\n\n// wuffs_base__private_implementation__high_prec_dec__assign sets h to\n// represent the number x.\n//\n// Preconditions:\n//  - h is non-NULL.\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__assign(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    uint64_t x,\n    bool negative) {\n  uint32_t n = 0;\n\n  // Set h->digits.\n  if (x > 0) {\n    // Calculate the digits, working right-to-left. After we determine n (how\n    // many digits there are), copy from buf to h->digits.\n    //\n    // UINT64_MAX, 18446744073709551615, is 20 digits long. It can be faster to\n    // copy a constant number of bytes than a variable number (20 instead of\n    // n). Make buf large enough (and start writing to it from the middle) so\n    // that can we always copy 20 bytes: the slice buf[(20-n) .. (40-n)].\n    uint8_t buf[40] = {0};" +
+	"\n    uint8_t* ptr = &buf[20];\n    do {\n      uint64_t remaining = x / 10;\n      x -= remaining * 10;\n      ptr--;\n      *ptr = (uint8_t)x;\n      n++;\n      x = remaining;\n    } while (x > 0);\n    memcpy(h->digits, ptr, 20);\n  }\n\n  // Set h's other fields.\n  h->num_digits = n;\n  h->decimal_point = (int32_t)n;\n  h->negative = negative;\n  h->truncated = false;\n  wuffs_base__private_implementation__high_prec_dec__trim(h);\n}\n\nstatic wuffs_base__status  //\nwuffs_base__private_implementation__high_prec_dec__parse(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    wuffs_base__slice_u8 s) {\n  if (!h) {\n    return wuffs_base__make_status(wuffs_base__error__bad_receiver);\n  }\n  h->num_digits = 0;\n  h->decimal_point = 0;\n  h->negative = false;\n  h->truncated = false;\n\n  uint8_t* p = s.ptr;\n  uint8_t* q = s.ptr + s.len;\n\n  for (; (p < q) && (*p == '_'); p++) {\n  }\n  if (p >= q) {\n    return wuffs_base__make_status(wuffs_base__error__bad_argument);\n  }\n\n  // Parse sign.\n  do {\n    if (*p == '+') {\n      p++;\n  " +
+	"  } else if (*p == '-') {\n      h->negative = true;\n      p++;\n    } else {\n      break;\n    }\n    for (; (p < q) && (*p == '_'); p++) {\n    }\n  } while (0);\n\n  // Parse digits.\n  uint32_t nd = 0;\n  int32_t dp = 0;\n  bool saw_digits = false;\n  bool saw_non_zero_digits = false;\n  bool saw_dot = false;\n  for (; p < q; p++) {\n    if (*p == '_') {\n      // No-op.\n\n    } else if ((*p == '.') || (*p == ',')) {\n      // As per https://en.wikipedia.org/wiki/Decimal_separator, both '.' or\n      // ',' are commonly used. We just parse either, regardless of LOCALE.\n      if (saw_dot) {\n        return wuffs_base__make_status(wuffs_base__error__bad_argument);\n      }\n      saw_dot = true;\n      dp = (int32_t)nd;\n\n    } else if ('0' == *p) {\n      if (!saw_dot && !saw_non_zero_digits && saw_digits) {\n        // We don't allow unnecessary leading zeroes: \"000123\" or \"0644\".\n        return wuffs_base__make_status(wuffs_base__error__bad_argument);\n      }\n      saw_digits = true;\n      if (nd == 0) {\n        // Track leading " +
+	"zeroes implicitly.\n        dp--;\n      } else if (nd <\n                 WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n        h->digits[nd++] = 0;\n      } else {\n        // Long-tail zeroes are ignored.\n      }\n\n    } else if (('0' < *p) && (*p <= '9')) {\n      if (!saw_dot && !saw_non_zero_digits && saw_digits) {\n        // We don't allow unnecessary leading zeroes: \"000123\" or \"0644\".\n        return wuffs_base__make_status(wuffs_base__error__bad_argument);\n      }\n      saw_digits = true;\n      saw_non_zero_digits = true;\n      if (nd < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n        h->digits[nd++] = (uint8_t)(*p - '0');\n      } else {\n        // Long-tail non-zeroes set the truncated bit.\n        h->truncated = true;\n      }\n\n    } else {\n      break;\n    }\n  }\n\n  if (!saw_digits) {\n    return wuffs_base__make_status(wuffs_base__error__bad_argument);\n  }\n  if (!saw_dot) {\n    dp = (int32_t)nd;\n  }\n\n  // Parse exponent.\n  if ((p < q) && ((*p == 'E') || (*p == 'e'))) {\n  " +
+	"  p++;\n    for (; (p < q) && (*p == '_'); p++) {\n    }\n    if (p >= q) {\n      return wuffs_base__make_status(wuffs_base__error__bad_argument);\n    }\n\n    int32_t exp_sign = +1;\n    if (*p == '+') {\n      p++;\n    } else if (*p == '-') {\n      exp_sign = -1;\n      p++;\n    }\n\n    int32_t exp = 0;\n    const int32_t exp_large =\n        WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE +\n        WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION;\n    bool saw_exp_digits = false;\n    for (; p < q; p++) {\n      if (*p == '_') {\n        // No-op.\n      } else if (('0' <= *p) && (*p <= '9')) {\n        saw_exp_digits = true;\n        if (exp < exp_large) {\n          exp = (10 * exp) + ((int32_t)(*p - '0'));\n        }\n      } else {\n        break;\n      }\n    }\n    if (!saw_exp_digits) {\n      return wuffs_base__make_status(wuffs_base__error__bad_argument);\n    }\n    dp += exp_sign * exp;\n  }\n\n  // Finish.\n  if (p != q) {\n    return wuffs_base__make_status(wuffs_base__error__bad_argument);\n  }\n  h->n" +
+	"um_digits = nd;\n  if (nd == 0) {\n    h->decimal_point = 0;\n  } else if (dp <\n             -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n    h->decimal_point =\n        -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE - 1;\n  } else if (dp >\n             +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n    h->decimal_point =\n        +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE + 1;\n  } else {\n    h->decimal_point = dp;\n  }\n  wuffs_base__private_implementation__high_prec_dec__trim(h);\n  return wuffs_base__make_status(NULL);\n}\n\n" +
 	"" +
 	"// --------\n\n// The etc__hpd_left_shift and etc__powers_of_5 tables were printed by\n// script/print-hpd-left-shift.go. That script has an optional -comments flag,\n// whose output is not copied here, which prints further detail.\n//\n// These tables are used in\n// wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits.\n\n// wuffs_base__private_implementation__hpd_left_shift[i] encodes the number of\n// new digits created after multiplying a positive integer by (1 << i): the\n// additional length in the decimal representation. For example, shifting \"234\"\n// by 3 (equivalent to multiplying by 8) will produce \"1872\". Going from a\n// 3-length string to a 4-length string means that 1 new digit was added (and\n// existing digits may have changed).\n//\n// Shifting by i can add either N or N-1 new digits, depending on whether the\n// original positive integer compares >= or < to the i'th power of 5 (as 10\n// equals 2 * 5). Comparison is lexicographic, not numerical.\n//\n// For example, shifting by 4 (i.e. mul" +
 	"tiplying by 16) can add 1 or 2 new\n// digits, depending on a lexicographic comparison to (5 ** 4), i.e. \"625\":\n//  - (\"1\"      << 4) is \"16\",       which adds 1 new digit.\n//  - (\"5678\"   << 4) is \"90848\",    which adds 1 new digit.\n//  - (\"624\"    << 4) is \"9984\",     which adds 1 new digit.\n//  - (\"62498\"  << 4) is \"999968\",   which adds 1 new digit.\n//  - (\"625\"    << 4) is \"10000\",    which adds 2 new digits.\n//  - (\"625001\" << 4) is \"10000016\", which adds 2 new digits.\n//  - (\"7008\"   << 4) is \"112128\",   which adds 2 new digits.\n//  - (\"99\"     << 4) is \"1584\",     which adds 2 new digits.\n//\n// Thus, when i is 4, N is 2 and (5 ** i) is \"625\". This etc__hpd_left_shift\n// array encodes this as:\n//  - etc__hpd_left_shift[4] is 0x1006 = (2 << 11) | 0x0006.\n//  - etc__hpd_left_shift[5] is 0x1009 = (? << 11) | 0x0009.\n// where the ? isn't relevant for i == 4.\n//\n// The high 5 bits of etc__hpd_left_shift[i] is N, the higher of the two\n// possible number of new digits. The low 11 bits are an offset into the\n//" +
@@ -50,10 +51,21 @@
 	"" +
 	"// --------\n\n// wuffs_base__private_implementation__high_prec_dec__rounded_integer returns\n// the integral (non-fractional) part of h, provided that it is 18 or fewer\n// decimal digits. For 19 or more digits, it returns UINT64_MAX. Note that:\n//   - (1 << 53) is    9007199254740992, which has 16 decimal digits.\n//   - (1 << 56) is   72057594037927936, which has 17 decimal digits.\n//   - (1 << 59) is  576460752303423488, which has 18 decimal digits.\n//   - (1 << 63) is 9223372036854775808, which has 19 decimal digits.\n// and that IEEE 754 double precision has 52 mantissa bits.\n//\n// That integral part is rounded-to-even: rounding 7.5 or 8.5 both give 8.\n//\n// h's negative bit is ignored: rounding -8.6 returns 9.\n//\n// See below for preconditions.\nstatic uint64_t  //\nwuffs_base__private_implementation__high_prec_dec__rounded_integer(\n    wuffs_base__private_implementation__high_prec_dec* h) {\n  if ((h->num_digits == 0) || (h->decimal_point < 0)) {\n    return 0;\n  } else if (h->decimal_point > 18) {\n    return U" +
 	"INT64_MAX;\n  }\n\n  uint32_t dp = (uint32_t)(h->decimal_point);\n  uint64_t n = 0;\n  uint32_t i = 0;\n  for (; i < dp; i++) {\n    n = (10 * n) + ((i < h->num_digits) ? h->digits[i] : 0);\n  }\n\n  bool round_up = false;\n  if (dp < h->num_digits) {\n    round_up = h->digits[dp] >= 5;\n    if ((h->digits[dp] == 5) && (dp + 1 == h->num_digits)) {\n      // We are exactly halfway. If we're truncated, round up, otherwise round\n      // to even.\n      round_up = h->truncated ||  //\n                 ((dp > 0) && (1 & h->digits[dp - 1]));\n    }\n  }\n  if (round_up) {\n    n++;\n  }\n\n  return n;\n}\n\n// wuffs_base__private_implementation__high_prec_dec__small_xshift shifts h's\n// number (where 'x' is 'l' or 'r' for left or right) by a small shift value.\n//\n// Preconditions:\n//  - h is non-NULL.\n//  - h->decimal_point is \"not extreme\".\n//  - shift is non-zero.\n//  - shift is \"a small shift\".\n//\n// \"Not extreme\" means within\n// ±WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.\n//\n// \"A small shift\" means not more than\n/" +
-	"/ WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL.\n//\n// wuffs_base__private_implementation__high_prec_dec__rounded_integer and\n// wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits\n// have the same preconditions.\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__small_lshift(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    uint32_t shift) {\n  if (h->num_digits == 0) {\n    return;\n  }\n  uint32_t num_new_digits =\n      wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits(\n          h, shift);\n  uint32_t rx = h->num_digits - 1;                   // Read  index.\n  uint32_t wx = h->num_digits - 1 + num_new_digits;  // Write index.\n  uint64_t n = 0;\n\n  // Repeat: pick up a digit, put down a digit, right to left.\n  while (((int32_t)rx) >= 0) {\n    n += ((uint64_t)(h->digits[rx])) << shift;\n    uint64_t quo = n / 10;\n    uint64_t rem = n - (10 * quo);\n    if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n      h->d" +
-	"igits[wx] = (uint8_t)rem;\n    } else if (rem > 0) {\n      h->truncated = true;\n    }\n    n = quo;\n    wx--;\n    rx--;\n  }\n\n  // Put down leading digits, right to left.\n  while (n > 0) {\n    uint64_t quo = n / 10;\n    uint64_t rem = n - (10 * quo);\n    if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n      h->digits[wx] = (uint8_t)rem;\n    } else if (rem > 0) {\n      h->truncated = true;\n    }\n    n = quo;\n    wx--;\n  }\n\n  // Finish.\n  h->num_digits += num_new_digits;\n  if (h->num_digits >\n      WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n    h->num_digits = WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION;\n  }\n  h->decimal_point += (int32_t)num_new_digits;\n  wuffs_base__private_implementation__high_prec_dec__trim(h);\n}\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__small_rshift(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    uint32_t shift) {\n  uint32_t rx = 0;  // Read  index.\n  uint32_t wx = 0;  // Write index.\n  uint64_t n =" +
-	" 0;\n\n  // Pick up enough leading digits to cover the first shift.\n  while ((n >> shift) == 0) {\n    if (rx < h->num_digits) {\n      // Read a digit.\n      n = (10 * n) + h->digits[rx++];\n    } else if (n == 0) {\n      // h's number used to be zero and remains zero.\n      return;\n    } else {\n      // Read sufficient implicit trailing zeroes.\n      while ((n >> shift) == 0) {\n        n = 10 * n;\n        rx++;\n      }\n      break;\n    }\n  }\n  h->decimal_point -= ((int32_t)(rx - 1));\n  if (h->decimal_point <\n      -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n    // After the shift, h's number is effectively zero.\n    h->num_digits = 0;\n    h->decimal_point = 0;\n    h->negative = false;\n    h->truncated = false;\n    return;\n  }\n\n  // Repeat: pick up a digit, put down a digit, left to right.\n  uint64_t mask = (((uint64_t)(1)) << shift) - 1;\n  while (rx < h->num_digits) {\n    uint8_t new_digit = ((uint8_t)(n >> shift));\n    n = (10 * (n & mask)) + h->digits[rx++];\n    h->digits[wx++] = new_digi" +
-	"t;\n  }\n\n  // Put down trailing digits, left to right.\n  while (n > 0) {\n    uint8_t new_digit = ((uint8_t)(n >> shift));\n    n = 10 * (n & mask);\n    if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n      h->digits[wx++] = new_digit;\n    } else if (new_digit > 0) {\n      h->truncated = true;\n    }\n  }\n\n  // Finish.\n  h->num_digits = wx;\n  wuffs_base__private_implementation__high_prec_dec__trim(h);\n}\n\n" +
+	"/ WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL.\n//\n// wuffs_base__private_implementation__high_prec_dec__rounded_integer and\n// wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits\n// have the same preconditions.\n//\n// wuffs_base__private_implementation__high_prec_dec__lshift keeps the first\n// two preconditions but not the last two. Its shift argument is signed and\n// does not need to be \"small\": zero is a no-op, positive means left shift and\n// negative means right shift.\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__small_lshift(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    uint32_t shift) {\n  if (h->num_digits == 0) {\n    return;\n  }\n  uint32_t num_new_digits =\n      wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits(\n          h, shift);\n  uint32_t rx = h->num_digits - 1;                   // Read  index.\n  uint32_t wx = h->num_digits - 1 + num_new_digits;  // Write index.\n  uint64_t n = 0;\n\n  // Repeat: pick up " +
+	"a digit, put down a digit, right to left.\n  while (((int32_t)rx) >= 0) {\n    n += ((uint64_t)(h->digits[rx])) << shift;\n    uint64_t quo = n / 10;\n    uint64_t rem = n - (10 * quo);\n    if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n      h->digits[wx] = (uint8_t)rem;\n    } else if (rem > 0) {\n      h->truncated = true;\n    }\n    n = quo;\n    wx--;\n    rx--;\n  }\n\n  // Put down leading digits, right to left.\n  while (n > 0) {\n    uint64_t quo = n / 10;\n    uint64_t rem = n - (10 * quo);\n    if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n      h->digits[wx] = (uint8_t)rem;\n    } else if (rem > 0) {\n      h->truncated = true;\n    }\n    n = quo;\n    wx--;\n  }\n\n  // Finish.\n  h->num_digits += num_new_digits;\n  if (h->num_digits >\n      WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n    h->num_digits = WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION;\n  }\n  h->decimal_point += (int32_t)num_new_digits;\n  wuffs_base__private_implementation__high_pre" +
+	"c_dec__trim(h);\n}\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__small_rshift(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    uint32_t shift) {\n  uint32_t rx = 0;  // Read  index.\n  uint32_t wx = 0;  // Write index.\n  uint64_t n = 0;\n\n  // Pick up enough leading digits to cover the first shift.\n  while ((n >> shift) == 0) {\n    if (rx < h->num_digits) {\n      // Read a digit.\n      n = (10 * n) + h->digits[rx++];\n    } else if (n == 0) {\n      // h's number used to be zero and remains zero.\n      return;\n    } else {\n      // Read sufficient implicit trailing zeroes.\n      while ((n >> shift) == 0) {\n        n = 10 * n;\n        rx++;\n      }\n      break;\n    }\n  }\n  h->decimal_point -= ((int32_t)(rx - 1));\n  if (h->decimal_point <\n      -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n    // After the shift, h's number is effectively zero.\n    h->num_digits = 0;\n    h->decimal_point = 0;\n    h->negative = false;\n    h->truncated = false;\n    return;\n  }\n\n  " +
+	"// Repeat: pick up a digit, put down a digit, left to right.\n  uint64_t mask = (((uint64_t)(1)) << shift) - 1;\n  while (rx < h->num_digits) {\n    uint8_t new_digit = ((uint8_t)(n >> shift));\n    n = (10 * (n & mask)) + h->digits[rx++];\n    h->digits[wx++] = new_digit;\n  }\n\n  // Put down trailing digits, left to right.\n  while (n > 0) {\n    uint8_t new_digit = ((uint8_t)(n >> shift));\n    n = 10 * (n & mask);\n    if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) {\n      h->digits[wx++] = new_digit;\n    } else if (new_digit > 0) {\n      h->truncated = true;\n    }\n  }\n\n  // Finish.\n  h->num_digits = wx;\n  wuffs_base__private_implementation__high_prec_dec__trim(h);\n}\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__lshift(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    int32_t shift) {\n  if (shift > 0) {\n    while (shift > +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) {\n      wuffs_base__private_implementation__high_prec_dec__small_lshift(\n         " +
+	" h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL);\n      shift -= WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;\n    }\n    wuffs_base__private_implementation__high_prec_dec__small_lshift(\n        h, ((uint32_t)(+shift)));\n  } else if (shift < 0) {\n    while (shift < -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) {\n      wuffs_base__private_implementation__high_prec_dec__small_rshift(\n          h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL);\n      shift += WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;\n    }\n    wuffs_base__private_implementation__high_prec_dec__small_rshift(\n        h, ((uint32_t)(-shift)));\n  }\n}\n\n" +
+	"" +
+	"// --------\n\n// wuffs_base__private_implementation__high_prec_dec__round_etc rounds h's\n// number. For those functions that take an n argument, rounding produces at\n// most n digits (which is not necessarily at most n decimal places). Negative\n// n values are ignored, as well as any n greater than or equal to h's number\n// of digits. The etc__round_just_enough function implicitly chooses an n to\n// implement WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION.\n//\n// Preconditions:\n//  - h is non-NULL.\n//  - h->decimal_point is \"not extreme\".\n//\n// \"Not extreme\" means within\n// ±WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__round_down(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    int32_t n) {\n  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {\n    return;\n  }\n  h->num_digits = (uint32_t)(n);\n  wuffs_base__private_implementation__high_prec_dec__trim(h);\n}\n\nstatic void  //\nwuffs_base__private_implementation__hi" +
+	"gh_prec_dec__round_up(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    int32_t n) {\n  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {\n    return;\n  }\n\n  for (n--; n >= 0; n--) {\n    if (h->digits[n] < 9) {\n      h->digits[n]++;\n      h->num_digits = (uint32_t)(n + 1);\n      return;\n    }\n  }\n\n  // The number is all 9s. Change to a single 1 and adjust the decimal point.\n  h->digits[0] = 1;\n  h->num_digits = 1;\n  h->decimal_point++;\n}\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__round_nearest(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    int32_t n) {\n  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {\n    return;\n  }\n  bool up = h->digits[n] >= 5;\n  if ((h->digits[n] == 5) && ((n + 1) == ((int32_t)(h->num_digits)))) {\n    up = h->truncated ||  //\n         ((n > 0) && ((h->digits[n - 1] & 1) != 0));\n  }\n\n  if (up) {\n    wuffs_base__private_implementation__high_prec_dec__round_up(h, n);\n  } else {\n    wuffs_base__private_implementation__high_prec_dec__round_do" +
+	"wn(h, n);\n  }\n}\n\nstatic void  //\nwuffs_base__private_implementation__high_prec_dec__round_just_enough(\n    wuffs_base__private_implementation__high_prec_dec* h,\n    int32_t exp2,\n    uint64_t mantissa) {\n  // The magic numbers 52 and 53 in this function are because IEEE 754 double\n  // precision has 52 mantissa bits.\n  //\n  // Let f be the floating point number represented by exp2 and mantissa (and\n  // also the number in h): the number (mantissa * (2 ** (exp2 - 52))).\n  //\n  // If f is zero, we can return early.\n  if (mantissa == 0) {\n    return;\n  }\n\n  // The smallest normal f has an exp2 of -1022 and a mantissa of (1 << 52).\n  // Subnormal numbers have the same exp2 but a smaller mantissa.\n  static const int32_t min_incl_normal_exp2 = -1022;\n  static const uint64_t min_incl_normal_mantissa = 0x0010000000000000ul;\n\n  // Compute lower and upper bounds such that any number between them (possibly\n  // inclusive) will round to f. First, the lower bound. Our number f is:\n  //   ((mantissa + 0)         * (2 ** ( " +
+	" exp2 - 52)))\n  //\n  // The next lowest floating point number is:\n  //   ((mantissa - 1)         * (2 ** (  exp2 - 52)))\n  // unless (mantissa - 1) drops the (1 << 52) bit and exp2 is not the\n  // min_incl_normal_exp2. Either way, call it:\n  //   ((l_mantissa)           * (2 ** (l_exp2 - 52)))\n  //\n  // The lower bound is halfway between them (noting that 52 became 53):\n  //   (((2 * l_mantissa) + 1) * (2 ** (l_exp2 - 53)))\n  int32_t l_exp2 = exp2;\n  uint64_t l_mantissa = mantissa - 1;\n  if ((exp2 > min_incl_normal_exp2) && (mantissa <= min_incl_normal_mantissa)) {\n    l_exp2 = exp2 - 1;\n    l_mantissa = (2 * mantissa) - 1;\n  }\n  wuffs_base__private_implementation__high_prec_dec lower;\n  wuffs_base__private_implementation__high_prec_dec__assign(\n      &lower, (2 * l_mantissa) + 1, false);\n  wuffs_base__private_implementation__high_prec_dec__lshift(&lower,\n                                                            l_exp2 - 53);\n\n  // Next, the upper bound. Our number f is:\n  //   ((mantissa + 0)       * (2 **" +
+	" (exp2 - 52)))\n  //\n  // The next highest floating point number is:\n  //   ((mantissa + 1)       * (2 ** (exp2 - 52)))\n  //\n  // The upper bound is halfway between them (noting that 52 became 53):\n  //   (((2 * mantissa) + 1) * (2 ** (exp2 - 53)))\n  wuffs_base__private_implementation__high_prec_dec upper;\n  wuffs_base__private_implementation__high_prec_dec__assign(\n      &upper, (2 * mantissa) + 1, false);\n  wuffs_base__private_implementation__high_prec_dec__lshift(&upper, exp2 - 53);\n\n  // The lower and upper bounds are possible outputs only if the original\n  // mantissa is even, so that IEEE round-to-even would round to the original\n  // mantissa and not its neighbors.\n  bool inclusive = (mantissa & 1) == 0;\n\n  // As we walk the digits, we want to know whether rounding up would fall\n  // within the upper bound. This is tracked by upper_delta:\n  //  - When -1, the digits of h and upper are the same so far.\n  //  - When +0, we saw a difference of 1 between h and upper on a previous\n  //    digit and subsequen" +
+	"tly only 9s for h and 0s for upper. Thus, rounding\n  //    up may fall outside of the bound if !inclusive.\n  //  - When +1, the difference is greater than 1 and we know that rounding up\n  //    falls within the bound.\n  //\n  // This is a state machine with three states. The numerical value for each\n  // state (-1, +0 or +1) isn't important, other than their order.\n  int upper_delta = -1;\n\n  // We can now figure out the shortest number of digits required. Walk the\n  // digits until h has distinguished itself from lower or upper.\n  //\n  // The zi and zd variables are indexes and digits, for z in l (lower), h (the\n  // number) and u (upper).\n  //\n  // The lower, h and upper numbers may have their decimal points at different\n  // places. In this case, upper is the longest, so we iterate ui starting from\n  // 0 and iterate li and hi starting from either 0 or -1.\n  int32_t ui = 0;\n  for (;; ui++) {\n    // Calculate hd, the middle number's digit.\n    int32_t hi = ui - upper.decimal_point + h->decimal_point;\n    if (" +
+	"hi >= ((int32_t)(h->num_digits))) {\n      break;\n    }\n    uint8_t hd = (((uint32_t)hi) < h->num_digits) ? h->digits[hi] : 0;\n\n    // Calculate ld, the lower bound's digit.\n    int32_t li = ui - upper.decimal_point + lower.decimal_point;\n    uint8_t ld = (((uint32_t)li) < lower.num_digits) ? lower.digits[li] : 0;\n\n    // We can round down (truncate) if lower has a different digit than h or if\n    // lower is inclusive and is exactly the result of rounding down (i.e. we\n    // have reached the final digit of lower).\n    bool can_round_down =\n        (ld != hd) ||  //\n        (inclusive && ((li + 1) == ((int32_t)(lower.num_digits))));\n\n    // Calculate ud, the upper bound's digit, and update upper_delta.\n    uint8_t ud = (((uint32_t)ui) < upper.num_digits) ? upper.digits[ui] : 0;\n    if (upper_delta < 0) {\n      if ((hd + 1) < ud) {\n        // For example:\n        // h     = 12345???\n        // upper = 12347???\n        upper_delta = +1;\n      } else if (hd != ud) {\n        // For example:\n        // h     = 123" +
+	"45???\n        // upper = 12346???\n        upper_delta = +0;\n      }\n    } else if (upper_delta == 0) {\n      if ((hd != 9) || (ud != 0)) {\n        // For example:\n        // h     = 1234598?\n        // upper = 1234600?\n        upper_delta = +1;\n      }\n    }\n\n    // We can round up if upper has a different digit than h and either upper\n    // is inclusive or upper is bigger than the result of rounding up.\n    bool can_round_up =\n        (upper_delta > 0) ||    //\n        ((upper_delta == 0) &&  //\n         (inclusive || ((ui + 1) < ((int32_t)(upper.num_digits)))));\n\n    // If we can round either way, round to nearest. If we can round only one\n    // way, do it. If we can't round, continue the loop.\n    if (can_round_down) {\n      if (can_round_up) {\n        wuffs_base__private_implementation__high_prec_dec__round_nearest(\n            h, hi + 1);\n        return;\n      } else {\n        wuffs_base__private_implementation__high_prec_dec__round_down(h,\n                                                              " +
+	"        hi + 1);\n        return;\n      }\n    } else {\n      if (can_round_up) {\n        wuffs_base__private_implementation__high_prec_dec__round_up(h, hi + 1);\n        return;\n      }\n    }\n  }\n}\n\n" +
 	"" +
 	"// --------\n\n// The wuffs_base__private_implementation__etc_powers_of_10 tables were printed\n// by script/print-mpb-powers-of-10.go. That script has an optional -comments\n// flag, whose output is not copied here, which prints further detail.\n//\n// These tables are used in\n// wuffs_base__private_implementation__medium_prec_bin__assign_from_hpd.\n\n// wuffs_base__private_implementation__big_powers_of_10 contains approximations\n// to the powers of 10, ranging from 1e-348 to 1e+340, with the exponent\n// stepping by 8: -348, -340, -332, ..., -12, -4, +4, +12, ..., +340. Each step\n// consists of three uint32_t elements. There are 87 triples, 87 * 3 = 261.\n//\n// For example, the third approximation, for 1e-332, consists of the uint32_t\n// triple (0x3055AC76, 0x8B16FB20, 0xFFFFFB72). The first two of that triple\n// are a little-endian uint64_t value: 0x8B16FB203055AC76. The last one is an\n// int32_t value: -1166. Together, they represent the approximation:\n//   1e-332 ≈ 0x8B16FB203055AC76 * (2 ** -1166)\n// Similarly," +
 	" the (0x00000000, 0x9C400000, 0xFFFFFFCE) uint32_t triple means:\n//   1e+4   ≈ 0x9C40000000000000 * (2 **   -50)  // This approx'n is exact.\n// Similarly, the (0xD4C4FB27, 0xED63A231, 0x000000A2) uint32_t triple means:\n//   1e+68  ≈ 0xED63A231D4C4FB27 * (2 **   162)\nstatic const uint32_t\n    wuffs_base__private_implementation__big_powers_of_10[261] = {\n        0x081C0288, 0xFA8FD5A0, 0xFFFFFB3C, 0xA23EBF76, 0xBAAEE17F, 0xFFFFFB57,\n        0x3055AC76, 0x8B16FB20, 0xFFFFFB72, 0x5DCE35EA, 0xCF42894A, 0xFFFFFB8C,\n        0x55653B2D, 0x9A6BB0AA, 0xFFFFFBA7, 0x3D1A45DF, 0xE61ACF03, 0xFFFFFBC1,\n        0xC79AC6CA, 0xAB70FE17, 0xFFFFFBDC, 0xBEBCDC4F, 0xFF77B1FC, 0xFFFFFBF6,\n        0x416BD60C, 0xBE5691EF, 0xFFFFFC11, 0x907FFC3C, 0x8DD01FAD, 0xFFFFFC2C,\n        0x31559A83, 0xD3515C28, 0xFFFFFC46, 0xADA6C9B5, 0x9D71AC8F, 0xFFFFFC61,\n        0x23EE8BCB, 0xEA9C2277, 0xFFFFFC7B, 0x4078536D, 0xAECC4991, 0xFFFFFC96,\n        0x5DB6CE57, 0x823C1279, 0xFFFFFCB1, 0x4DFB5637, 0xC2109436, 0xFFFFFCCB,\n        0x3848984F, 0x909" +
@@ -84,7 +96,17 @@
 	".decimal_point);\n      uint32_t shift =\n          (n < num_powers)\n              ? powers[n]\n              : WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;\n\n      wuffs_base__private_implementation__high_prec_dec__small_rshift(&h,\n                                                                      shift);\n      if (h.decimal_point <\n          -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n        goto zero;\n      }\n      exp2 += (int32_t)shift;\n    }\n    // ...then we shift left, putting us in [½ .. 1].\n    while (h.decimal_point <= 0) {\n      uint32_t shift;\n      if (h.decimal_point == 0) {\n        if (h.digits[0] >= 5) {\n          break;\n        }\n        shift = (h.digits[0] <= 2) ? 2 : 1;\n      } else {\n        uint32_t n = (uint32_t)(-h.decimal_point);\n        shift = (n < num_powers)\n                    ? powers[n]\n                    : WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;\n      }\n\n      wuffs_base__private_implementation__high_prec_dec__small_lshif" +
 	"t(&h,\n                                                                      shift);\n      if (h.decimal_point >\n          +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) {\n        goto infinity;\n      }\n      exp2 -= (int32_t)shift;\n    }\n\n    // We're in the range [½ .. 1] but f64 uses [1 .. 2].\n    exp2--;\n\n    // The minimum normal exponent is (f64_bias + 1).\n    while ((f64_bias + 1) > exp2) {\n      uint32_t n = (uint32_t)((f64_bias + 1) - exp2);\n      if (n > WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) {\n        n = WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;\n      }\n      wuffs_base__private_implementation__high_prec_dec__small_rshift(&h, n);\n      exp2 += (int32_t)n;\n    }\n\n    // Check for overflow.\n    if ((exp2 - f64_bias) >= 0x07FF) {  // (1 << 11) - 1.\n      goto infinity;\n    }\n\n    // Extract 53 bits for the mantissa (in base-2).\n    wuffs_base__private_implementation__high_prec_dec__small_lshift(&h, 53);\n    uint64_t man2 =\n        wuffs_base__privat" +
 	"e_implementation__high_prec_dec__rounded_integer(&h);\n\n    // Rounding might have added one bit. If so, shift and re-check overflow.\n    if ((man2 >> 53) != 0) {\n      man2 >>= 1;\n      exp2++;\n      if ((exp2 - f64_bias) >= 0x07FF) {  // (1 << 11) - 1.\n        goto infinity;\n      }\n    }\n\n    // Handle subnormal numbers.\n    if ((man2 >> 52) == 0) {\n      exp2 = f64_bias;\n    }\n\n    // Pack the bits and return.\n    uint64_t exp2_bits =\n        (uint64_t)((exp2 - f64_bias) & 0x07FF);             // (1 << 11) - 1.\n    uint64_t bits = (man2 & 0x000FFFFFFFFFFFFF) |           // (1 << 52) - 1.\n                    (exp2_bits << 52) |                     //\n                    (h.negative ? 0x8000000000000000 : 0);  // (1 << 63).\n\n    wuffs_base__result_f64 ret;\n    ret.status.repr = NULL;\n    ret.value = wuffs_base__ieee_754_bit_representation__to_f64(bits);\n    return ret;\n  } while (0);\n\nzero:\n  do {\n    uint64_t bits = h.negative ? 0x8000000000000000 : 0;\n\n    wuffs_base__result_f64 ret;\n    ret.status.repr = " +
-	"NULL;\n    ret.value = wuffs_base__ieee_754_bit_representation__to_f64(bits);\n    return ret;\n  } while (0);\n\ninfinity:\n  do {\n    uint64_t bits = h.negative ? 0xFFF0000000000000 : 0x7FF0000000000000;\n\n    wuffs_base__result_f64 ret;\n    ret.status.repr = NULL;\n    ret.value = wuffs_base__ieee_754_bit_representation__to_f64(bits);\n    return ret;\n  } while (0);\n}\n" +
+	"NULL;\n    ret.value = wuffs_base__ieee_754_bit_representation__to_f64(bits);\n    return ret;\n  } while (0);\n\ninfinity:\n  do {\n    uint64_t bits = h.negative ? 0xFFF0000000000000 : 0x7FF0000000000000;\n\n    wuffs_base__result_f64 ret;\n    ret.status.repr = NULL;\n    ret.value = wuffs_base__ieee_754_bit_representation__to_f64(bits);\n    return ret;\n  } while (0);\n}\n\n" +
+	"" +
+	"// --------\n\nstatic inline size_t  //\nwuffs_base__private_implementation__render_inf(wuffs_base__slice_u8 dst,\n                                               bool neg,\n                                               uint32_t options) {\n  if (neg) {\n    if (dst.len < 4) {\n      return 0;\n    }\n    wuffs_base__store_u32le__no_bounds_check(dst.ptr, 0x666E492D);  // '-Inf'le.\n    return 4;\n  }\n\n  if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {\n    if (dst.len < 4) {\n      return 0;\n    }\n    wuffs_base__store_u32le__no_bounds_check(dst.ptr, 0x666E492B);  // '+Inf'le.\n    return 4;\n  }\n\n  if (dst.len < 3) {\n    return 0;\n  }\n  wuffs_base__store_u24le__no_bounds_check(dst.ptr, 0x666E49);  // 'Inf'le.\n  return 3;\n}\n\nstatic inline size_t  //\nwuffs_base__private_implementation__render_nan(wuffs_base__slice_u8 dst) {\n  if (dst.len < 3) {\n    return 0;\n  }\n  wuffs_base__store_u24le__no_bounds_check(dst.ptr, 0x4E614E);  // 'NaN'le.\n  return 3;\n}\n\nstatic size_t  //\nwuffs_base__private_implementation__high" +
+	"_prec_dec__render_exponent_absent(\n    wuffs_base__slice_u8 dst,\n    wuffs_base__private_implementation__high_prec_dec* h,\n    uint32_t precision,\n    uint32_t options) {\n  size_t n = (h->negative ||\n              (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN))\n                 ? 1\n                 : 0;\n  if (h->decimal_point <= 0) {\n    n += 1;\n  } else {\n    n += (size_t)(h->decimal_point);\n  }\n  if (precision > 0) {\n    n += precision + 1;  // +1 for the '.'.\n  }\n\n  // Don't modify dst if the formatted number won't fit.\n  if (n > dst.len) {\n    return 0;\n  }\n\n  // Align-left or align-right.\n  uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)\n                     ? &dst.ptr[dst.len - n]\n                     : &dst.ptr[0];\n\n  // Leading \"±\".\n  if (h->negative) {\n    *ptr++ = '-';\n  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {\n    *ptr++ = '+';\n  }\n\n  // Integral digits.\n  if (h->decimal_point <= 0) {\n    *ptr++ = '0';\n  } else {\n    uint32_t m =\n" +
+	"        wuffs_base__u32__min(h->num_digits, (uint32_t)(h->decimal_point));\n    uint32_t i = 0;\n    for (; i < m; i++) {\n      *ptr++ = (uint8_t)('0' | h->digits[i]);\n    }\n    for (; i < (uint32_t)(h->decimal_point); i++) {\n      *ptr++ = '0';\n    }\n  }\n\n  // Separator and then fractional digits.\n  if (precision > 0) {\n    *ptr++ =\n        (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA)\n            ? ','\n            : '.';\n    uint32_t i = 0;\n    for (; i < precision; i++) {\n      uint32_t j = ((uint32_t)(h->decimal_point)) + i;\n      *ptr++ = (uint8_t)('0' | ((j < h->num_digits) ? h->digits[j] : 0));\n    }\n  }\n\n  return n;\n}\n\nstatic size_t  //\nwuffs_base__private_implementation__high_prec_dec__render_exponent_present(\n    wuffs_base__slice_u8 dst,\n    wuffs_base__private_implementation__high_prec_dec* h,\n    uint32_t precision,\n    uint32_t options) {\n  int32_t exp = 0;\n  if (h->num_digits > 0) {\n    exp = h->decimal_point - 1;\n  }\n  bool negative_exp = exp < 0;\n  if (negative_exp) {\n" +
+	"    exp = -exp;\n  }\n\n  size_t n = (h->negative ||\n              (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN))\n                 ? 4\n                 : 3;  // Mininum 3 bytes: first digit and then \"e±\".\n  if (precision > 0) {\n    n += precision + 1;  // +1 for the '.'.\n  }\n  n += (exp < 100) ? 2 : 3;\n\n  // Don't modify dst if the formatted number won't fit.\n  if (n > dst.len) {\n    return 0;\n  }\n\n  // Align-left or align-right.\n  uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)\n                     ? &dst.ptr[dst.len - n]\n                     : &dst.ptr[0];\n\n  // Leading \"±\".\n  if (h->negative) {\n    *ptr++ = '-';\n  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {\n    *ptr++ = '+';\n  }\n\n  // Integral digit.\n  if (h->num_digits > 0) {\n    *ptr++ = (uint8_t)('0' | h->digits[0]);\n  } else {\n    *ptr++ = '0';\n  }\n\n  // Separator and then fractional digits.\n  if (precision > 0) {\n    *ptr++ =\n        (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPA" +
+	"RATOR_IS_A_COMMA)\n            ? ','\n            : '.';\n    uint32_t i = 1;\n    uint32_t j = wuffs_base__u32__min(h->num_digits, precision + 1);\n    for (; i < j; i++) {\n      *ptr++ = (uint8_t)('0' | h->digits[i]);\n    }\n    for (; i <= precision; i++) {\n      *ptr++ = '0';\n    }\n  }\n\n  // Exponent: \"e±\" and then 2 or 3 digits.\n  *ptr++ = 'e';\n  *ptr++ = negative_exp ? '-' : '+';\n  if (exp < 10) {\n    *ptr++ = '0';\n    *ptr++ = (uint8_t)('0' | exp);\n  } else if (exp < 100) {\n    *ptr++ = (uint8_t)('0' | (exp / 10));\n    *ptr++ = (uint8_t)('0' | (exp % 10));\n  } else {\n    int32_t e = exp / 100;\n    exp -= e * 100;\n    *ptr++ = (uint8_t)('0' | e);\n    *ptr++ = (uint8_t)('0' | (exp / 10));\n    *ptr++ = (uint8_t)('0' | (exp % 10));\n  }\n\n  return n;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_f64(wuffs_base__slice_u8 dst,\n                              double x,\n                              uint32_t precision,\n                              uint32_t options) {\n  // Decompose x (64 bits) into " +
+	"negativity (1 bit), base-2 exponent (11 bits\n  // with a -1023 bias) and mantissa (52 bits).\n  uint64_t bits = wuffs_base__ieee_754_bit_representation__from_f64(x);\n  bool neg = (bits >> 63) != 0;\n  int32_t exp2 = ((int32_t)(bits >> 52)) & 0x7FF;\n  uint64_t man = bits & 0x000FFFFFFFFFFFFFul;\n\n  // Apply the exponent bias and set the implicit top bit of the mantissa,\n  // unless x is subnormal. Also take care of Inf and NaN.\n  if (exp2 == 0x7FF) {\n    if (man != 0) {\n      return wuffs_base__private_implementation__render_nan(dst);\n    }\n    return wuffs_base__private_implementation__render_inf(dst, neg, options);\n  } else if (exp2 == 0) {\n    exp2 = -1022;\n  } else {\n    exp2 -= 1023;\n    man |= 0x0010000000000000ul;\n  }\n\n  // Ensure that precision isn't too large.\n  if (precision > 4095) {\n    precision = 4095;\n  }\n\n  // Convert from the (neg, exp2, man) tuple to an HPD.\n  wuffs_base__private_implementation__high_prec_dec h;\n  wuffs_base__private_implementation__high_prec_dec__assign(&h, man, neg);\n  if (h.n" +
+	"um_digits > 0) {\n    wuffs_base__private_implementation__high_prec_dec__lshift(\n        &h, exp2 - 52);  // 52 mantissa bits.\n  }\n\n  // Handle the \"%e\" and \"%f\" formats.\n  switch (options & (WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT |\n                     WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT)) {\n    case WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT:  // The \"%\"f\" format.\n      if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {\n        wuffs_base__private_implementation__high_prec_dec__round_just_enough(\n            &h, exp2, man);\n        int32_t p = ((int32_t)(h.num_digits)) - h.decimal_point;\n        precision = ((uint32_t)(wuffs_base__i32__max(0, p)));\n      } else {\n        wuffs_base__private_implementation__high_prec_dec__round_nearest(\n            &h, ((int32_t)precision) + h.decimal_point);\n      }\n      return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(\n          dst, &h, precision, options);\n\n    case WUFFS_BASE__RENDER_NUMBER_FXX__" +
+	"EXPONENT_PRESENT:  // The \"%e\" format.\n      if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {\n        wuffs_base__private_implementation__high_prec_dec__round_just_enough(\n            &h, exp2, man);\n        precision = (h.num_digits > 0) ? (h.num_digits - 1) : 0;\n      } else {\n        wuffs_base__private_implementation__high_prec_dec__round_nearest(\n            &h, ((int32_t)precision) + 1);\n      }\n      return wuffs_base__private_implementation__high_prec_dec__render_exponent_present(\n          dst, &h, precision, options);\n  }\n\n  // We have the \"%g\" format and so precision means the number of significant\n  // digits, not the number of digits after the decimal separator. Perform\n  // rounding and determine whether to use \"%e\" or \"%f\".\n  int32_t e_threshold = 0;\n  if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {\n    wuffs_base__private_implementation__high_prec_dec__round_just_enough(\n        &h, exp2, man);\n    precision = h.num_digits;\n    e_threshold = 6;\n  } el" +
+	"se {\n    if (precision == 0) {\n      precision = 1;\n    }\n    wuffs_base__private_implementation__high_prec_dec__round_nearest(\n        &h, ((int32_t)precision));\n    e_threshold = ((int32_t)precision);\n    int32_t nd = ((int32_t)(h.num_digits));\n    if ((e_threshold > nd) && (nd >= h.decimal_point)) {\n      e_threshold = nd;\n    }\n  }\n\n  // Use the \"%e\" format if the exponent is large.\n  int32_t e = h.decimal_point - 1;\n  if ((e < -4) || (e_threshold <= e)) {\n    uint32_t p = wuffs_base__u32__min(precision, h.num_digits);\n    return wuffs_base__private_implementation__high_prec_dec__render_exponent_present(\n        dst, &h, (p > 0) ? (p - 1) : 0, options);\n  }\n\n  // Use the \"%f\" format otherwise.\n  int32_t p = ((int32_t)precision);\n  if (p > h.decimal_point) {\n    p = ((int32_t)(h.num_digits));\n  }\n  precision = ((uint32_t)(wuffs_base__i32__max(0, p - h.decimal_point)));\n  return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(\n      dst, &h, precision, options);\n}\n" +
 	""
 
 const BaseI64ConvSubmoduleC = "" +
@@ -103,8 +125,8 @@
 	"" +
 	"// --------\n\n// wuffs_base__render_number__first_hundred contains the decimal encodings of\n// the first one hundred numbers [0 ..= 99].\nstatic const uint8_t wuffs_base__render_number__first_hundred[200] = {\n    '0', '0', '0', '1', '0', '2', '0', '3', '0', '4',  //\n    '0', '5', '0', '6', '0', '7', '0', '8', '0', '9',  //\n    '1', '0', '1', '1', '1', '2', '1', '3', '1', '4',  //\n    '1', '5', '1', '6', '1', '7', '1', '8', '1', '9',  //\n    '2', '0', '2', '1', '2', '2', '2', '3', '2', '4',  //\n    '2', '5', '2', '6', '2', '7', '2', '8', '2', '9',  //\n    '3', '0', '3', '1', '3', '2', '3', '3', '3', '4',  //\n    '3', '5', '3', '6', '3', '7', '3', '8', '3', '9',  //\n    '4', '0', '4', '1', '4', '2', '4', '3', '4', '4',  //\n    '4', '5', '4', '6', '4', '7', '4', '8', '4', '9',  //\n    '5', '0', '5', '1', '5', '2', '5', '3', '5', '4',  //\n    '5', '5', '5', '6', '5', '7', '5', '8', '5', '9',  //\n    '6', '0', '6', '1', '6', '2', '6', '3', '6', '4',  //\n    '6', '5', '6', '6', '6', '7', '6', '8', '6', '9',  //\n    '" +
 	"7', '0', '7', '1', '7', '2', '7', '3', '7', '4',  //\n    '7', '5', '7', '6', '7', '7', '7', '8', '7', '9',  //\n    '8', '0', '8', '1', '8', '2', '8', '3', '8', '4',  //\n    '8', '5', '8', '6', '8', '7', '8', '8', '8', '9',  //\n    '9', '0', '9', '1', '9', '2', '9', '3', '9', '4',  //\n    '9', '5', '9', '6', '9', '7', '9', '8', '9', '9',  //\n};\n\nstatic size_t  //\nwuffs_base__private_implementation__render_number_u64(wuffs_base__slice_u8 dst,\n                                                      uint64_t x,\n                                                      uint32_t options,\n                                                      bool neg) {\n  uint8_t buf[WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL];\n  uint8_t* ptr = &buf[0] + sizeof(buf);\n\n  while (x >= 100) {\n    size_t index = (x % 100) * 2;\n    x /= 100;\n    uint8_t s0 = wuffs_base__render_number__first_hundred[index + 0];\n    uint8_t s1 = wuffs_base__render_number__first_hundred[index + 1];\n    ptr -= 2;\n    ptr[0] = s0;\n    ptr[1] = s1;\n  }\n\n  if (x < 10) {\n " +
-	"   ptr -= 1;\n    ptr[0] = (uint8_t)('0' + x);\n  } else {\n    size_t index = x * 2;\n    uint8_t s0 = wuffs_base__render_number__first_hundred[index + 0];\n    uint8_t s1 = wuffs_base__render_number__first_hundred[index + 1];\n    ptr -= 2;\n    ptr[0] = s0;\n    ptr[1] = s1;\n  }\n\n  if (neg) {\n    ptr -= 1;\n    ptr[0] = '-';\n  } else if (options & WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN) {\n    ptr -= 1;\n    ptr[0] = '+';\n  }\n\n  size_t n = sizeof(buf) - ((size_t)(ptr - &buf[0]));\n  if (n > dst.len) {\n    return 0;\n  }\n  memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT)\n                        ? (dst.len - n)\n                        : 0),\n         ptr, n);\n  return n;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_i64(wuffs_base__slice_u8 dst,\n                              int64_t x,\n                              uint32_t options) {\n  uint64_t u = (uint64_t)x;\n  bool neg = x < 0;\n  if (neg) {\n    u = 1 + ~u;\n  }\n  return wuffs_base__private_implementation__render_number_u64(d" +
-	"st, u, options,\n                                                               neg);\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_u64(wuffs_base__slice_u8 dst,\n                              uint64_t x,\n                              uint32_t options) {\n  return wuffs_base__private_implementation__render_number_u64(dst, x, options,\n                                                               false);\n}\n\n" +
+	"   ptr -= 1;\n    ptr[0] = (uint8_t)('0' + x);\n  } else {\n    size_t index = x * 2;\n    uint8_t s0 = wuffs_base__render_number__first_hundred[index + 0];\n    uint8_t s1 = wuffs_base__render_number__first_hundred[index + 1];\n    ptr -= 2;\n    ptr[0] = s0;\n    ptr[1] = s1;\n  }\n\n  if (neg) {\n    ptr -= 1;\n    ptr[0] = '-';\n  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {\n    ptr -= 1;\n    ptr[0] = '+';\n  }\n\n  size_t n = sizeof(buf) - ((size_t)(ptr - &buf[0]));\n  if (n > dst.len) {\n    return 0;\n  }\n  memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)\n                        ? (dst.len - n)\n                        : 0),\n         ptr, n);\n  return n;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_i64(wuffs_base__slice_u8 dst,\n                              int64_t x,\n                              uint32_t options) {\n  uint64_t u = (uint64_t)x;\n  bool neg = x < 0;\n  if (neg) {\n    u = 1 + ~u;\n  }\n  return wuffs_base__private_implementation__render_numb" +
+	"er_u64(dst, u, options,\n                                                               neg);\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_u64(wuffs_base__slice_u8 dst,\n                              uint64_t x,\n                              uint32_t options) {\n  return wuffs_base__private_implementation__render_number_u64(dst, x, options,\n                                                               false);\n}\n\n" +
 	"" +
 	"// ---------------- Hexadecimal\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len2 = src.len / 2;\n  size_t len = dst.len < src_len2 ? dst.len : src_len2;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[0]] << 4) |\n                   (wuffs_base__parse_number__hexadecimal_digits[s[1]] & 0x0F));\n    d += 1;\n    s += 2;\n  }\n\n  return len;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len4 = src.len / 4;\n  size_t len = dst.len < src_len4 ? dst.len : src_len4;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[2]] << 4) |\n                   (wuffs_base__parse_number__hexa" +
 	"decimal_digits[s[3]] & 0x0F));\n    d += 1;\n    s += 4;\n  }\n\n  return len;\n}\n" +
@@ -231,8 +253,9 @@
 	"" +
 	"// --------\n\n// Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a\n// second. See https://github.com/OculusVR/Flicks\ntypedef int64_t wuffs_base__flicks;\n\n#define WUFFS_BASE__FLICKS_PER_SECOND ((uint64_t)705600000)\n#define WUFFS_BASE__FLICKS_PER_MILLISECOND ((uint64_t)705600)\n\n" +
 	"" +
-	"// ---------------- Numeric Types\n\n// The helpers below are functions, instead of macros, because their arguments\n// can be an expression that we shouldn't evaluate more than once.\n//\n// They are static, so that linking multiple wuffs .o files won't complain about\n// duplicate function definitions.\n//\n// They are explicitly marked inline, even if modern compilers don't use the\n// inline attribute to guide optimizations such as inlining, to avoid the\n// -Wunused-function warning, and we like to compile with -Wall -Werror.\n\nstatic inline uint8_t  //\nwuffs_base__u8__min(uint8_t x, uint8_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint8_t  //\nwuffs_base__u8__max(uint8_t x, uint8_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__min(uint16_t x, uint16_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__max(uint16_t x, uint16_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint32_t  //\nwuffs_base__u32__min(uint32_t x, uint32_t y) {\n  return x < y ? x : y;\n}\n" +
-	"\nstatic inline uint32_t  //\nwuffs_base__u32__max(uint32_t x, uint32_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__min(uint64_t x, uint64_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__max(uint64_t x, uint64_t y) {\n  return x > y ? x : y;\n}\n\n" +
+	"// ---------------- Numeric Types\n\n// The helpers below are functions, instead of macros, because their arguments\n// can be an expression that we shouldn't evaluate more than once.\n//\n// They are static, so that linking multiple wuffs .o files won't complain about\n// duplicate function definitions.\n//\n// They are explicitly marked inline, even if modern compilers don't use the\n// inline attribute to guide optimizations such as inlining, to avoid the\n// -Wunused-function warning, and we like to compile with -Wall -Werror.\n\nstatic inline int8_t  //\nwuffs_base__i8__min(int8_t x, int8_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline int8_t  //\nwuffs_base__i8__max(int8_t x, int8_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline int16_t  //\nwuffs_base__i16__min(int16_t x, int16_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline int16_t  //\nwuffs_base__i16__max(int16_t x, int16_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline int32_t  //\nwuffs_base__i32__min(int32_t x, int32_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline " +
+	"int32_t  //\nwuffs_base__i32__max(int32_t x, int32_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline int64_t  //\nwuffs_base__i64__min(int64_t x, int64_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline int64_t  //\nwuffs_base__i64__max(int64_t x, int64_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint8_t  //\nwuffs_base__u8__min(uint8_t x, uint8_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint8_t  //\nwuffs_base__u8__max(uint8_t x, uint8_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__min(uint16_t x, uint16_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__max(uint16_t x, uint16_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint32_t  //\nwuffs_base__u32__min(uint32_t x, uint32_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint32_t  //\nwuffs_base__u32__max(uint32_t x, uint32_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__min(uint64_t x, uint64_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__m" +
+	"ax(uint64_t x, uint64_t y) {\n  return x > y ? x : y;\n}\n\n" +
 	"" +
 	"// --------\n\n// Saturating arithmetic (sat_add, sat_sub) branchless bit-twiddling algorithms\n// are per https://locklessinc.com/articles/sat_arithmetic/\n//\n// It is important that the underlying types are unsigned integers, as signed\n// integer arithmetic overflow is undefined behavior in C.\n\nstatic inline uint8_t  //\nwuffs_base__u8__sat_add(uint8_t x, uint8_t y) {\n  uint8_t res = (uint8_t)(x + y);\n  res |= (uint8_t)(-(res < x));\n  return res;\n}\n\nstatic inline uint8_t  //\nwuffs_base__u8__sat_sub(uint8_t x, uint8_t y) {\n  uint8_t res = (uint8_t)(x - y);\n  res &= (uint8_t)(-(res <= x));\n  return res;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__sat_add(uint16_t x, uint16_t y) {\n  uint16_t res = (uint16_t)(x + y);\n  res |= (uint16_t)(-(res < x));\n  return res;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__sat_sub(uint16_t x, uint16_t y) {\n  uint16_t res = (uint16_t)(x - y);\n  res &= (uint16_t)(-(res <= x));\n  return res;\n}\n\nstatic inline uint32_t  //\nwuffs_base__u32__sat_add(uint32_t x, uint32_t y) {\n  uint32" +
 	"_t res = (uint32_t)(x + y);\n  res |= (uint32_t)(-(res < x));\n  return res;\n}\n\nstatic inline uint32_t  //\nwuffs_base__u32__sat_sub(uint32_t x, uint32_t y) {\n  uint32_t res = (uint32_t)(x - y);\n  res &= (uint32_t)(-(res <= x));\n  return res;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__sat_add(uint64_t x, uint64_t y) {\n  uint64_t res = (uint64_t)(x + y);\n  res |= (uint64_t)(-(res < x));\n  return res;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__sat_sub(uint64_t x, uint64_t y) {\n  uint64_t res = (uint64_t)(x - y);\n  res &= (uint64_t)(-(res <= x));\n  return res;\n}\n\n" +
@@ -417,7 +440,9 @@
 	""
 
 const BaseStrConvPublicH = "" +
-	"// ---------------- String Conversions\n\n// Options (bitwise or'ed together) for wuffs_base__render_number_etc\n// functions.\n\n#define WUFFS_BASE__RENDER_NUMBER__DEFAULT_OPTIONS ((uint32_t)0x00000000)\n\n// WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT means to render to the right side\n// (higher indexes) of the destination slice, leaving any untouched bytes on\n// the left side (lower indexes). The default is vice versa: rendering on the\n// left with slack on the right.\n#define WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT ((uint32_t)0x00000001)\n\n// WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN means to render the unnecessary\n// leading \"+\" for non-negative numbers: \"+0\" and \"+123\", not \"0\" and \"123\".\n#define WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN ((uint32_t)0x00000002)\n\n" +
+	"// ---------------- String Conversions\n\n// Options (bitwise or'ed together) for wuffs_base__render_number_xxx\n// functions. The XXX options apply to both integer and floating point. The FXX\n// options apply only to floating point.\n\n#define WUFFS_BASE__RENDER_NUMBER_XXX__DEFAULT_OPTIONS ((uint32_t)0x00000000)\n\n// WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT means to render to the right side\n// (higher indexes) of the destination slice, leaving any untouched bytes on\n// the left side (lower indexes). The default is vice versa: rendering on the\n// left with slack on the right.\n#define WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT ((uint32_t)0x00000001)\n\n// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN means to render the leading\n// \"+\" for non-negative numbers: \"+0\" and \"+12.3\" instead of \"0\" and \"12.3\".\n#define WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN ((uint32_t)0x00000002)\n\n// WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA means to render\n// one-and-a-half as \"1,5\" instead of \"1.5\".\n#define " +
+	"WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA \\\n  ((uint32_t)0x00000100)\n\n// WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ETC means whether to never\n// (EXPONENT_ABSENT, equivalent to printf's \"%f\") or to always\n// (EXPONENT_PRESENT, equivalent to printf's \"%e\") render a floating point\n// number as \"1.23e+05\" instead of \"123000\".\n//\n// Having both bits set is the same has having neither bit set, where the\n// notation used depends on whether the exponent is sufficiently large: \"0.5\"\n// is preferred over \"5e-01\" but \"5e-09\" is preferred over \"0.000000005\".\n#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT ((uint32_t)0x00000200)\n#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT ((uint32_t)0x00000400)\n\n// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION means to render the\n// smallest number of digits so that parsing the resultant string will recover\n// the same double-precision floating point number.\n//\n// For example, double-precision cannot distinguish between 0.3 and\n// 0.2999999999999" +
+	"99988897769753748434595763683319091796875, so when this bit\n// is set, rendering the latter will produce \"0.3\" but rendering\n// 0.3000000000000000444089209850062616169452667236328125 will produce\n// \"0.30000000000000004\".\n#define WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION \\\n  ((uint32_t)0x00000800)\n\n" +
 	"" +
 	"// ---------------- IEEE 754 Floating Point\n\n// wuffs_base__parse_number_f64 parses the floating point number in s. For\n// example, if s contains the bytes \"1.5\" then it will return the double 1.5.\n//\n// It returns an error if s does not contain a floating point number.\n//\n// It does not necessarily return an error if the conversion is lossy, e.g. if\n// s is \"0.3\", which double-precision floating point cannot represent exactly.\n//\n// Similarly, the returned value may be infinite (and no error returned) even\n// if s was not \"inf\", when the input is nominally finite but sufficiently\n// larger than DBL_MAX, about 1.8e+308.\n//\n// It is similar to the C standard library's strtod function, but:\n//  - Errors are returned in-band (in a result type), not out-of-band (errno).\n//  - It takes a slice (a pointer and length), not a NUL-terminated C string.\n//  - It does not take an optional endptr argument. It does not allow a partial\n//    parse: it returns an error unless all of s is consumed.\n//  - It does not allow whi" +
 	"tespace, leading or otherwise.\n//  - It does not allow unnecessary leading zeroes (\"0\" is valid and its sole\n//    zero is necessary). All of \"00\", \"0644\" and \"00.7\" are invalid.\n//  - It is not affected by i18n / l10n settings such as environment variables.\n//  - Conversely, it always accepts either ',' or '.' as a decimal separator.\n//    In particular, \"3,141,592\" is always invalid but \"3,141\" is always valid\n//    (and approximately π). The caller is responsible for e.g. previously\n//    rejecting or filtering s if it contains a comma, if that is unacceptable\n//    to the caller. For example, JSON numbers always use a dot '.' and never a\n//    comma ',', regardless of the LOCALE environment variable.\n//  - It does allow arbitrary underscores. For example, \"_3.141_592\" would\n//    successfully parse, again approximately π.\n//  - It does allow \"inf\", \"+Infinity\" and \"-NAN\", case insensitive, but it\n//    does not permit \"nan\" to be followed by an integer mantissa.\n//  - It does not allow hexadecimal float" +
@@ -429,8 +454,9 @@
 	"ted C string.\n//  - It does not take an optional endptr argument. It does not allow a partial\n//    parse: it returns an error unless all of s is consumed.\n//  - It does not allow whitespace, leading or otherwise.\n//  - It does not allow a leading '+' or '-'.\n//  - It does not allow unnecessary leading zeroes (\"0\" is valid and its sole\n//    zero is necessary). All of \"00\", \"0644\" and \"007\" are invalid.\n//  - It does not take a base argument (e.g. base 10 vs base 16). Instead, it\n//    always accepts both decimal (e.g \"1234\", \"0d5678\") and hexadecimal (e.g.\n//    \"0x9aBC\"). The caller is responsible for prior filtering of e.g. hex\n//    numbers if they are unwanted. For example, Wuffs' JSON decoder will only\n//    produce a wuffs_base__token for decimal numbers, not hexadecimal.\n//  - It is not affected by i18n / l10n settings such as environment variables.\n//  - It does allow arbitrary underscores, except inside the optional 2-byte\n//    opening \"0d\" or \"0X\" that denotes base-10 or base-16. For example,\n//  " +
 	"  \"__0D_1_002\" would successfully parse as \"one thousand and two\".\nWUFFS_BASE__MAYBE_STATIC wuffs_base__result_u64  //\nwuffs_base__parse_number_u64(wuffs_base__slice_u8 s);\n\n" +
 	"" +
-	"// --------\n\n#define WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL 20\n#define WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL 21\n\n// wuffs_base__render_number_i64 writes the decimal encoding of x to dst and\n// returns the number of bytes written. If dst is shorter than the entire\n// encoding, it returns 0 (and no bytes are written).\n//\n// dst will never be too short if its length is at least 20, also known as\n// WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_i64(wuffs_base__slice_u8 dst,\n                              int64_t x,\n                              uint32_t options);\n\n// wuffs_base__render_number_u64 writes the decimal encoding of x to dst and\n// returns the number of bytes written. If dst is shorter than the entire\n// encoding, it returns 0 (and no bytes are written).\n//\n// dst will never be too short if its length is at least 21, also known as\n// WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_u64(wuffs_bas" +
-	"e__slice_u8 dst,\n                              uint64_t x,\n                              uint32_t options);\n\n" +
+	"// --------\n\n#define WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL 20\n#define WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL 21\n\n// wuffs_base__render_number_f64 writes the decimal encoding of x to dst and\n// returns the number of bytes written. If dst is shorter than the entire\n// encoding, it returns 0 (and no bytes are written).\n//\n// For those familiar with C's printf or Go's fmt.Printf functions:\n//  - \"%e\" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT option.\n//  - \"%f\" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT  option.\n//  - \"%g\" means neither or both bits are set.\n//\n// The precision argument controls the number of digits rendered, excluding the\n// exponent (the \"e+05\" in \"1.23e+05\"):\n//  - for \"%e\" and \"%f\" it is the number of digits after the decimal separator,\n//  - for \"%g\" it is the number of significant digits (and trailing zeroes are\n//    removed).\n//\n// A precision of 6 gives similar output to printf's defaults.\n//\n// A precision greater than 4095 is equivalent to 4095.\n//\n// The " +
+	"precision argument is ignored when the\n// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION option is set. This is\n// similar to Go's strconv.FormatFloat with a negative (i.e. non-sensical)\n// precision, but there is no corresponding feature in C's printf.\n//\n// Extreme values of x will be rendered as \"NaN\", \"Inf\" (or \"+Inf\" if the\n// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN option is set) or \"-Inf\".\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_f64(wuffs_base__slice_u8 dst,\n                              double x,\n                              uint32_t precision,\n                              uint32_t options);\n\n// wuffs_base__render_number_i64 writes the decimal encoding of x to dst and\n// returns the number of bytes written. If dst is shorter than the entire\n// encoding, it returns 0 (and no bytes are written).\n//\n// dst will never be too short if its length is at least 20, also known as\n// WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__ren" +
+	"der_number_i64(wuffs_base__slice_u8 dst,\n                              int64_t x,\n                              uint32_t options);\n\n// wuffs_base__render_number_u64 writes the decimal encoding of x to dst and\n// returns the number of bytes written. If dst is shorter than the entire\n// encoding, it returns 0 (and no bytes are written).\n//\n// dst will never be too short if its length is at least 21, also known as\n// WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_u64(wuffs_base__slice_u8 dst,\n                              uint64_t x,\n                              uint32_t options);\n\n" +
 	"" +
 	"// ---------------- Hexadecimal\n\n// wuffs_base__hexadecimal__decode2 converts \"6A6b\" to \"jk\", where e.g. 'j' is\n// U+006A. There are 2 source bytes for every destination byte.\n//\n// It returns the number of dst bytes written: the minimum of dst.len and\n// (src.len / 2). Excess source bytes are ignored.\n//\n// It assumes that the src bytes are two hexadecimal digits (0-9, A-F, a-f),\n// repeated. It may write nonsense bytes if not, although it will not read or\n// write out of bounds.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src);\n\n// wuffs_base__hexadecimal__decode4 converts \"\\\\x6A\\\\x6b\" to \"jk\", where e.g.\n// 'j' is U+006A. There are 4 source bytes for every destination byte.\n//\n// It returns the number of dst bytes written: the minimum of dst.len and\n// (src.len / 4). Excess source bytes are ignored.\n//\n// It assumes that the src bytes are two ignored bytes and then two hexadecimal\n// digits (0-9, A-F, a" +
 	"-f), repeated. It may write nonsense bytes if not,\n// although it will not read or write out of bounds.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src);\n\n" +
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index c4db729..e0161ee 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -354,6 +354,46 @@
 // inline attribute to guide optimizations such as inlining, to avoid the
 // -Wunused-function warning, and we like to compile with -Wall -Werror.
 
+static inline int8_t  //
+wuffs_base__i8__min(int8_t x, int8_t y) {
+  return x < y ? x : y;
+}
+
+static inline int8_t  //
+wuffs_base__i8__max(int8_t x, int8_t y) {
+  return x > y ? x : y;
+}
+
+static inline int16_t  //
+wuffs_base__i16__min(int16_t x, int16_t y) {
+  return x < y ? x : y;
+}
+
+static inline int16_t  //
+wuffs_base__i16__max(int16_t x, int16_t y) {
+  return x > y ? x : y;
+}
+
+static inline int32_t  //
+wuffs_base__i32__min(int32_t x, int32_t y) {
+  return x < y ? x : y;
+}
+
+static inline int32_t  //
+wuffs_base__i32__max(int32_t x, int32_t y) {
+  return x > y ? x : y;
+}
+
+static inline int64_t  //
+wuffs_base__i64__min(int64_t x, int64_t y) {
+  return x < y ? x : y;
+}
+
+static inline int64_t  //
+wuffs_base__i64__max(int64_t x, int64_t y) {
+  return x > y ? x : y;
+}
+
 static inline uint8_t  //
 wuffs_base__u8__min(uint8_t x, uint8_t y) {
   return x < y ? x : y;
@@ -3866,20 +3906,49 @@
 
 // ---------------- String Conversions
 
-// Options (bitwise or'ed together) for wuffs_base__render_number_etc
-// functions.
+// Options (bitwise or'ed together) for wuffs_base__render_number_xxx
+// functions. The XXX options apply to both integer and floating point. The FXX
+// options apply only to floating point.
 
-#define WUFFS_BASE__RENDER_NUMBER__DEFAULT_OPTIONS ((uint32_t)0x00000000)
+#define WUFFS_BASE__RENDER_NUMBER_XXX__DEFAULT_OPTIONS ((uint32_t)0x00000000)
 
-// WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT means to render to the right side
+// WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT means to render to the right side
 // (higher indexes) of the destination slice, leaving any untouched bytes on
 // the left side (lower indexes). The default is vice versa: rendering on the
 // left with slack on the right.
-#define WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT ((uint32_t)0x00000001)
+#define WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT ((uint32_t)0x00000001)
 
-// WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN means to render the unnecessary
-// leading "+" for non-negative numbers: "+0" and "+123", not "0" and "123".
-#define WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN ((uint32_t)0x00000002)
+// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN means to render the leading
+// "+" for non-negative numbers: "+0" and "+12.3" instead of "0" and "12.3".
+#define WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN ((uint32_t)0x00000002)
+
+// WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA means to render
+// one-and-a-half as "1,5" instead of "1.5".
+#define WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA \
+  ((uint32_t)0x00000100)
+
+// WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ETC means whether to never
+// (EXPONENT_ABSENT, equivalent to printf's "%f") or to always
+// (EXPONENT_PRESENT, equivalent to printf's "%e") render a floating point
+// number as "1.23e+05" instead of "123000".
+//
+// Having both bits set is the same has having neither bit set, where the
+// notation used depends on whether the exponent is sufficiently large: "0.5"
+// is preferred over "5e-01" but "5e-09" is preferred over "0.000000005".
+#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT ((uint32_t)0x00000200)
+#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT ((uint32_t)0x00000400)
+
+// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION means to render the
+// smallest number of digits so that parsing the resultant string will recover
+// the same double-precision floating point number.
+//
+// For example, double-precision cannot distinguish between 0.3 and
+// 0.299999999999999988897769753748434595763683319091796875, so when this bit
+// is set, rendering the latter will produce "0.3" but rendering
+// 0.3000000000000000444089209850062616169452667236328125 will produce
+// "0.30000000000000004".
+#define WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION \
+  ((uint32_t)0x00000800)
 
 // ---------------- IEEE 754 Floating Point
 
@@ -3998,6 +4067,38 @@
 #define WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL 20
 #define WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL 21
 
+// wuffs_base__render_number_f64 writes the decimal encoding of x to dst and
+// returns the number of bytes written. If dst is shorter than the entire
+// encoding, it returns 0 (and no bytes are written).
+//
+// For those familiar with C's printf or Go's fmt.Printf functions:
+//  - "%e" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT option.
+//  - "%f" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT  option.
+//  - "%g" means neither or both bits are set.
+//
+// The precision argument controls the number of digits rendered, excluding the
+// exponent (the "e+05" in "1.23e+05"):
+//  - for "%e" and "%f" it is the number of digits after the decimal separator,
+//  - for "%g" it is the number of significant digits (and trailing zeroes are
+//    removed).
+//
+// A precision of 6 gives similar output to printf's defaults.
+//
+// A precision greater than 4095 is equivalent to 4095.
+//
+// The precision argument is ignored when the
+// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION option is set. This is
+// similar to Go's strconv.FormatFloat with a negative (i.e. non-sensical)
+// precision, but there is no corresponding feature in C's printf.
+//
+// Extreme values of x will be rendered as "NaN", "Inf" (or "+Inf" if the
+// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN option is set) or "-Inf".
+WUFFS_BASE__MAYBE_STATIC size_t  //
+wuffs_base__render_number_f64(wuffs_base__slice_u8 dst,
+                              double x,
+                              uint32_t precision,
+                              uint32_t options);
+
 // wuffs_base__render_number_i64 writes the decimal encoding of x to dst and
 // returns the number of bytes written. If dst is shorter than the entire
 // encoding, it returns 0 (and no bytes are written).
@@ -8840,8 +8941,8 @@
 
 // ---------------- IEEE 754 Floating Point
 
-#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE 1023
-#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION 500
+#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE 2047
+#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION 800
 
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL is the largest N
 // such that ((10 << N) < (1 << 64)).
@@ -8851,14 +8952,14 @@
 // fixed precision floating point decimal number, augmented with ±infinity
 // values, but it cannot represent NaN (Not a Number).
 //
-// "High precision" means that the mantissa holds 500 decimal digits. 500 is
+// "High precision" means that the mantissa holds 800 decimal digits. 800 is
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION.
 //
 // An HPD isn't for general purpose arithmetic, only for conversions to and
 // from IEEE 754 double-precision floating point, where the largest and
 // smallest positive, finite values are approximately 1.8e+308 and 4.9e-324.
-// HPD exponents above +1023 mean infinity, below -1023 mean zero. The ±1023
-// bounds are further away from zero than ±(324 + 500), where 500 and 1023 is
+// HPD exponents above +2047 mean infinity, below -2047 mean zero. The ±2047
+// bounds are further away from zero than ±(324 + 800), where 800 and 2047 is
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION and
 // WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.
 //
@@ -8880,8 +8981,8 @@
 //   - A decimal_point of +4 means "7890."
 //   - A decimal_point of +5 means "78900."
 //
-// As above, a decimal_point higher than +1023 means that the overall value is
-// infinity, lower than -1023 means zero.
+// As above, a decimal_point higher than +2047 means that the overall value is
+// infinity, lower than -2047 means zero.
 //
 // negative is a sign bit. An HPD can distinguish positive and negative zero.
 //
@@ -8913,6 +9014,48 @@
   }
 }
 
+// wuffs_base__private_implementation__high_prec_dec__assign sets h to
+// represent the number x.
+//
+// Preconditions:
+//  - h is non-NULL.
+static void  //
+wuffs_base__private_implementation__high_prec_dec__assign(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    uint64_t x,
+    bool negative) {
+  uint32_t n = 0;
+
+  // Set h->digits.
+  if (x > 0) {
+    // Calculate the digits, working right-to-left. After we determine n (how
+    // many digits there are), copy from buf to h->digits.
+    //
+    // UINT64_MAX, 18446744073709551615, is 20 digits long. It can be faster to
+    // copy a constant number of bytes than a variable number (20 instead of
+    // n). Make buf large enough (and start writing to it from the middle) so
+    // that can we always copy 20 bytes: the slice buf[(20-n) .. (40-n)].
+    uint8_t buf[40] = {0};
+    uint8_t* ptr = &buf[20];
+    do {
+      uint64_t remaining = x / 10;
+      x -= remaining * 10;
+      ptr--;
+      *ptr = (uint8_t)x;
+      n++;
+      x = remaining;
+    } while (x > 0);
+    memcpy(h->digits, ptr, 20);
+  }
+
+  // Set h's other fields.
+  h->num_digits = n;
+  h->decimal_point = (int32_t)n;
+  h->negative = negative;
+  h->truncated = false;
+  wuffs_base__private_implementation__high_prec_dec__trim(h);
+}
+
 static wuffs_base__status  //
 wuffs_base__private_implementation__high_prec_dec__parse(
     wuffs_base__private_implementation__high_prec_dec* h,
@@ -9288,6 +9431,11 @@
 // wuffs_base__private_implementation__high_prec_dec__rounded_integer and
 // wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits
 // have the same preconditions.
+//
+// wuffs_base__private_implementation__high_prec_dec__lshift keeps the first
+// two preconditions but not the last two. Its shift argument is signed and
+// does not need to be "small": zero is a no-op, positive means left shift and
+// negative means right shift.
 
 static void  //
 wuffs_base__private_implementation__high_prec_dec__small_lshift(
@@ -9401,6 +9549,254 @@
   wuffs_base__private_implementation__high_prec_dec__trim(h);
 }
 
+static void  //
+wuffs_base__private_implementation__high_prec_dec__lshift(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t shift) {
+  if (shift > 0) {
+    while (shift > +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) {
+      wuffs_base__private_implementation__high_prec_dec__small_lshift(
+          h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL);
+      shift -= WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;
+    }
+    wuffs_base__private_implementation__high_prec_dec__small_lshift(
+        h, ((uint32_t)(+shift)));
+  } else if (shift < 0) {
+    while (shift < -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) {
+      wuffs_base__private_implementation__high_prec_dec__small_rshift(
+          h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL);
+      shift += WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL;
+    }
+    wuffs_base__private_implementation__high_prec_dec__small_rshift(
+        h, ((uint32_t)(-shift)));
+  }
+}
+
+// --------
+
+// wuffs_base__private_implementation__high_prec_dec__round_etc rounds h's
+// number. For those functions that take an n argument, rounding produces at
+// most n digits (which is not necessarily at most n decimal places). Negative
+// n values are ignored, as well as any n greater than or equal to h's number
+// of digits. The etc__round_just_enough function implicitly chooses an n to
+// implement WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION.
+//
+// Preconditions:
+//  - h is non-NULL.
+//  - h->decimal_point is "not extreme".
+//
+// "Not extreme" means within
+// ±WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE.
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_down(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t n) {
+  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {
+    return;
+  }
+  h->num_digits = (uint32_t)(n);
+  wuffs_base__private_implementation__high_prec_dec__trim(h);
+}
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_up(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t n) {
+  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {
+    return;
+  }
+
+  for (n--; n >= 0; n--) {
+    if (h->digits[n] < 9) {
+      h->digits[n]++;
+      h->num_digits = (uint32_t)(n + 1);
+      return;
+    }
+  }
+
+  // The number is all 9s. Change to a single 1 and adjust the decimal point.
+  h->digits[0] = 1;
+  h->num_digits = 1;
+  h->decimal_point++;
+}
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_nearest(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t n) {
+  if ((n < 0) || (h->num_digits <= (uint32_t)n)) {
+    return;
+  }
+  bool up = h->digits[n] >= 5;
+  if ((h->digits[n] == 5) && ((n + 1) == ((int32_t)(h->num_digits)))) {
+    up = h->truncated ||  //
+         ((n > 0) && ((h->digits[n - 1] & 1) != 0));
+  }
+
+  if (up) {
+    wuffs_base__private_implementation__high_prec_dec__round_up(h, n);
+  } else {
+    wuffs_base__private_implementation__high_prec_dec__round_down(h, n);
+  }
+}
+
+static void  //
+wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+    wuffs_base__private_implementation__high_prec_dec* h,
+    int32_t exp2,
+    uint64_t mantissa) {
+  // The magic numbers 52 and 53 in this function are because IEEE 754 double
+  // precision has 52 mantissa bits.
+  //
+  // Let f be the floating point number represented by exp2 and mantissa (and
+  // also the number in h): the number (mantissa * (2 ** (exp2 - 52))).
+  //
+  // If f is zero, we can return early.
+  if (mantissa == 0) {
+    return;
+  }
+
+  // The smallest normal f has an exp2 of -1022 and a mantissa of (1 << 52).
+  // Subnormal numbers have the same exp2 but a smaller mantissa.
+  static const int32_t min_incl_normal_exp2 = -1022;
+  static const uint64_t min_incl_normal_mantissa = 0x0010000000000000ul;
+
+  // Compute lower and upper bounds such that any number between them (possibly
+  // inclusive) will round to f. First, the lower bound. Our number f is:
+  //   ((mantissa + 0)         * (2 ** (  exp2 - 52)))
+  //
+  // The next lowest floating point number is:
+  //   ((mantissa - 1)         * (2 ** (  exp2 - 52)))
+  // unless (mantissa - 1) drops the (1 << 52) bit and exp2 is not the
+  // min_incl_normal_exp2. Either way, call it:
+  //   ((l_mantissa)           * (2 ** (l_exp2 - 52)))
+  //
+  // The lower bound is halfway between them (noting that 52 became 53):
+  //   (((2 * l_mantissa) + 1) * (2 ** (l_exp2 - 53)))
+  int32_t l_exp2 = exp2;
+  uint64_t l_mantissa = mantissa - 1;
+  if ((exp2 > min_incl_normal_exp2) && (mantissa <= min_incl_normal_mantissa)) {
+    l_exp2 = exp2 - 1;
+    l_mantissa = (2 * mantissa) - 1;
+  }
+  wuffs_base__private_implementation__high_prec_dec lower;
+  wuffs_base__private_implementation__high_prec_dec__assign(
+      &lower, (2 * l_mantissa) + 1, false);
+  wuffs_base__private_implementation__high_prec_dec__lshift(&lower,
+                                                            l_exp2 - 53);
+
+  // Next, the upper bound. Our number f is:
+  //   ((mantissa + 0)       * (2 ** (exp2 - 52)))
+  //
+  // The next highest floating point number is:
+  //   ((mantissa + 1)       * (2 ** (exp2 - 52)))
+  //
+  // The upper bound is halfway between them (noting that 52 became 53):
+  //   (((2 * mantissa) + 1) * (2 ** (exp2 - 53)))
+  wuffs_base__private_implementation__high_prec_dec upper;
+  wuffs_base__private_implementation__high_prec_dec__assign(
+      &upper, (2 * mantissa) + 1, false);
+  wuffs_base__private_implementation__high_prec_dec__lshift(&upper, exp2 - 53);
+
+  // The lower and upper bounds are possible outputs only if the original
+  // mantissa is even, so that IEEE round-to-even would round to the original
+  // mantissa and not its neighbors.
+  bool inclusive = (mantissa & 1) == 0;
+
+  // As we walk the digits, we want to know whether rounding up would fall
+  // within the upper bound. This is tracked by upper_delta:
+  //  - When -1, the digits of h and upper are the same so far.
+  //  - When +0, we saw a difference of 1 between h and upper on a previous
+  //    digit and subsequently only 9s for h and 0s for upper. Thus, rounding
+  //    up may fall outside of the bound if !inclusive.
+  //  - When +1, the difference is greater than 1 and we know that rounding up
+  //    falls within the bound.
+  //
+  // This is a state machine with three states. The numerical value for each
+  // state (-1, +0 or +1) isn't important, other than their order.
+  int upper_delta = -1;
+
+  // We can now figure out the shortest number of digits required. Walk the
+  // digits until h has distinguished itself from lower or upper.
+  //
+  // The zi and zd variables are indexes and digits, for z in l (lower), h (the
+  // number) and u (upper).
+  //
+  // The lower, h and upper numbers may have their decimal points at different
+  // places. In this case, upper is the longest, so we iterate ui starting from
+  // 0 and iterate li and hi starting from either 0 or -1.
+  int32_t ui = 0;
+  for (;; ui++) {
+    // Calculate hd, the middle number's digit.
+    int32_t hi = ui - upper.decimal_point + h->decimal_point;
+    if (hi >= ((int32_t)(h->num_digits))) {
+      break;
+    }
+    uint8_t hd = (((uint32_t)hi) < h->num_digits) ? h->digits[hi] : 0;
+
+    // Calculate ld, the lower bound's digit.
+    int32_t li = ui - upper.decimal_point + lower.decimal_point;
+    uint8_t ld = (((uint32_t)li) < lower.num_digits) ? lower.digits[li] : 0;
+
+    // We can round down (truncate) if lower has a different digit than h or if
+    // lower is inclusive and is exactly the result of rounding down (i.e. we
+    // have reached the final digit of lower).
+    bool can_round_down =
+        (ld != hd) ||  //
+        (inclusive && ((li + 1) == ((int32_t)(lower.num_digits))));
+
+    // Calculate ud, the upper bound's digit, and update upper_delta.
+    uint8_t ud = (((uint32_t)ui) < upper.num_digits) ? upper.digits[ui] : 0;
+    if (upper_delta < 0) {
+      if ((hd + 1) < ud) {
+        // For example:
+        // h     = 12345???
+        // upper = 12347???
+        upper_delta = +1;
+      } else if (hd != ud) {
+        // For example:
+        // h     = 12345???
+        // upper = 12346???
+        upper_delta = +0;
+      }
+    } else if (upper_delta == 0) {
+      if ((hd != 9) || (ud != 0)) {
+        // For example:
+        // h     = 1234598?
+        // upper = 1234600?
+        upper_delta = +1;
+      }
+    }
+
+    // We can round up if upper has a different digit than h and either upper
+    // is inclusive or upper is bigger than the result of rounding up.
+    bool can_round_up =
+        (upper_delta > 0) ||    //
+        ((upper_delta == 0) &&  //
+         (inclusive || ((ui + 1) < ((int32_t)(upper.num_digits)))));
+
+    // If we can round either way, round to nearest. If we can round only one
+    // way, do it. If we can't round, continue the loop.
+    if (can_round_down) {
+      if (can_round_up) {
+        wuffs_base__private_implementation__high_prec_dec__round_nearest(
+            h, hi + 1);
+        return;
+      } else {
+        wuffs_base__private_implementation__high_prec_dec__round_down(h,
+                                                                      hi + 1);
+        return;
+      }
+    } else {
+      if (can_round_up) {
+        wuffs_base__private_implementation__high_prec_dec__round_up(h, hi + 1);
+        return;
+      }
+    }
+  }
+}
+
 // --------
 
 // The wuffs_base__private_implementation__etc_powers_of_10 tables were printed
@@ -10087,6 +10483,303 @@
   } while (0);
 }
 
+// --------
+
+static inline size_t  //
+wuffs_base__private_implementation__render_inf(wuffs_base__slice_u8 dst,
+                                               bool neg,
+                                               uint32_t options) {
+  if (neg) {
+    if (dst.len < 4) {
+      return 0;
+    }
+    wuffs_base__store_u32le__no_bounds_check(dst.ptr, 0x666E492D);  // '-Inf'le.
+    return 4;
+  }
+
+  if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
+    if (dst.len < 4) {
+      return 0;
+    }
+    wuffs_base__store_u32le__no_bounds_check(dst.ptr, 0x666E492B);  // '+Inf'le.
+    return 4;
+  }
+
+  if (dst.len < 3) {
+    return 0;
+  }
+  wuffs_base__store_u24le__no_bounds_check(dst.ptr, 0x666E49);  // 'Inf'le.
+  return 3;
+}
+
+static inline size_t  //
+wuffs_base__private_implementation__render_nan(wuffs_base__slice_u8 dst) {
+  if (dst.len < 3) {
+    return 0;
+  }
+  wuffs_base__store_u24le__no_bounds_check(dst.ptr, 0x4E614E);  // 'NaN'le.
+  return 3;
+}
+
+static size_t  //
+wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(
+    wuffs_base__slice_u8 dst,
+    wuffs_base__private_implementation__high_prec_dec* h,
+    uint32_t precision,
+    uint32_t options) {
+  size_t n = (h->negative ||
+              (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN))
+                 ? 1
+                 : 0;
+  if (h->decimal_point <= 0) {
+    n += 1;
+  } else {
+    n += (size_t)(h->decimal_point);
+  }
+  if (precision > 0) {
+    n += precision + 1;  // +1 for the '.'.
+  }
+
+  // Don't modify dst if the formatted number won't fit.
+  if (n > dst.len) {
+    return 0;
+  }
+
+  // Align-left or align-right.
+  uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)
+                     ? &dst.ptr[dst.len - n]
+                     : &dst.ptr[0];
+
+  // Leading "±".
+  if (h->negative) {
+    *ptr++ = '-';
+  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
+    *ptr++ = '+';
+  }
+
+  // Integral digits.
+  if (h->decimal_point <= 0) {
+    *ptr++ = '0';
+  } else {
+    uint32_t m =
+        wuffs_base__u32__min(h->num_digits, (uint32_t)(h->decimal_point));
+    uint32_t i = 0;
+    for (; i < m; i++) {
+      *ptr++ = (uint8_t)('0' | h->digits[i]);
+    }
+    for (; i < (uint32_t)(h->decimal_point); i++) {
+      *ptr++ = '0';
+    }
+  }
+
+  // Separator and then fractional digits.
+  if (precision > 0) {
+    *ptr++ =
+        (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA)
+            ? ','
+            : '.';
+    uint32_t i = 0;
+    for (; i < precision; i++) {
+      uint32_t j = ((uint32_t)(h->decimal_point)) + i;
+      *ptr++ = (uint8_t)('0' | ((j < h->num_digits) ? h->digits[j] : 0));
+    }
+  }
+
+  return n;
+}
+
+static size_t  //
+wuffs_base__private_implementation__high_prec_dec__render_exponent_present(
+    wuffs_base__slice_u8 dst,
+    wuffs_base__private_implementation__high_prec_dec* h,
+    uint32_t precision,
+    uint32_t options) {
+  int32_t exp = 0;
+  if (h->num_digits > 0) {
+    exp = h->decimal_point - 1;
+  }
+  bool negative_exp = exp < 0;
+  if (negative_exp) {
+    exp = -exp;
+  }
+
+  size_t n = (h->negative ||
+              (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN))
+                 ? 4
+                 : 3;  // Mininum 3 bytes: first digit and then "e±".
+  if (precision > 0) {
+    n += precision + 1;  // +1 for the '.'.
+  }
+  n += (exp < 100) ? 2 : 3;
+
+  // Don't modify dst if the formatted number won't fit.
+  if (n > dst.len) {
+    return 0;
+  }
+
+  // Align-left or align-right.
+  uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)
+                     ? &dst.ptr[dst.len - n]
+                     : &dst.ptr[0];
+
+  // Leading "±".
+  if (h->negative) {
+    *ptr++ = '-';
+  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
+    *ptr++ = '+';
+  }
+
+  // Integral digit.
+  if (h->num_digits > 0) {
+    *ptr++ = (uint8_t)('0' | h->digits[0]);
+  } else {
+    *ptr++ = '0';
+  }
+
+  // Separator and then fractional digits.
+  if (precision > 0) {
+    *ptr++ =
+        (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA)
+            ? ','
+            : '.';
+    uint32_t i = 1;
+    uint32_t j = wuffs_base__u32__min(h->num_digits, precision + 1);
+    for (; i < j; i++) {
+      *ptr++ = (uint8_t)('0' | h->digits[i]);
+    }
+    for (; i <= precision; i++) {
+      *ptr++ = '0';
+    }
+  }
+
+  // Exponent: "e±" and then 2 or 3 digits.
+  *ptr++ = 'e';
+  *ptr++ = negative_exp ? '-' : '+';
+  if (exp < 10) {
+    *ptr++ = '0';
+    *ptr++ = (uint8_t)('0' | exp);
+  } else if (exp < 100) {
+    *ptr++ = (uint8_t)('0' | (exp / 10));
+    *ptr++ = (uint8_t)('0' | (exp % 10));
+  } else {
+    int32_t e = exp / 100;
+    exp -= e * 100;
+    *ptr++ = (uint8_t)('0' | e);
+    *ptr++ = (uint8_t)('0' | (exp / 10));
+    *ptr++ = (uint8_t)('0' | (exp % 10));
+  }
+
+  return n;
+}
+
+WUFFS_BASE__MAYBE_STATIC size_t  //
+wuffs_base__render_number_f64(wuffs_base__slice_u8 dst,
+                              double x,
+                              uint32_t precision,
+                              uint32_t options) {
+  // Decompose x (64 bits) into negativity (1 bit), base-2 exponent (11 bits
+  // with a -1023 bias) and mantissa (52 bits).
+  uint64_t bits = wuffs_base__ieee_754_bit_representation__from_f64(x);
+  bool neg = (bits >> 63) != 0;
+  int32_t exp2 = ((int32_t)(bits >> 52)) & 0x7FF;
+  uint64_t man = bits & 0x000FFFFFFFFFFFFFul;
+
+  // Apply the exponent bias and set the implicit top bit of the mantissa,
+  // unless x is subnormal. Also take care of Inf and NaN.
+  if (exp2 == 0x7FF) {
+    if (man != 0) {
+      return wuffs_base__private_implementation__render_nan(dst);
+    }
+    return wuffs_base__private_implementation__render_inf(dst, neg, options);
+  } else if (exp2 == 0) {
+    exp2 = -1022;
+  } else {
+    exp2 -= 1023;
+    man |= 0x0010000000000000ul;
+  }
+
+  // Ensure that precision isn't too large.
+  if (precision > 4095) {
+    precision = 4095;
+  }
+
+  // Convert from the (neg, exp2, man) tuple to an HPD.
+  wuffs_base__private_implementation__high_prec_dec h;
+  wuffs_base__private_implementation__high_prec_dec__assign(&h, man, neg);
+  if (h.num_digits > 0) {
+    wuffs_base__private_implementation__high_prec_dec__lshift(
+        &h, exp2 - 52);  // 52 mantissa bits.
+  }
+
+  // Handle the "%e" and "%f" formats.
+  switch (options & (WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT |
+                     WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT)) {
+    case WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT:  // The "%"f" format.
+      if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {
+        wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+            &h, exp2, man);
+        int32_t p = ((int32_t)(h.num_digits)) - h.decimal_point;
+        precision = ((uint32_t)(wuffs_base__i32__max(0, p)));
+      } else {
+        wuffs_base__private_implementation__high_prec_dec__round_nearest(
+            &h, ((int32_t)precision) + h.decimal_point);
+      }
+      return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(
+          dst, &h, precision, options);
+
+    case WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT:  // The "%e" format.
+      if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {
+        wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+            &h, exp2, man);
+        precision = (h.num_digits > 0) ? (h.num_digits - 1) : 0;
+      } else {
+        wuffs_base__private_implementation__high_prec_dec__round_nearest(
+            &h, ((int32_t)precision) + 1);
+      }
+      return wuffs_base__private_implementation__high_prec_dec__render_exponent_present(
+          dst, &h, precision, options);
+  }
+
+  // We have the "%g" format and so precision means the number of significant
+  // digits, not the number of digits after the decimal separator. Perform
+  // rounding and determine whether to use "%e" or "%f".
+  int32_t e_threshold = 0;
+  if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) {
+    wuffs_base__private_implementation__high_prec_dec__round_just_enough(
+        &h, exp2, man);
+    precision = h.num_digits;
+    e_threshold = 6;
+  } else {
+    if (precision == 0) {
+      precision = 1;
+    }
+    wuffs_base__private_implementation__high_prec_dec__round_nearest(
+        &h, ((int32_t)precision));
+    e_threshold = ((int32_t)precision);
+    int32_t nd = ((int32_t)(h.num_digits));
+    if ((e_threshold > nd) && (nd >= h.decimal_point)) {
+      e_threshold = nd;
+    }
+  }
+
+  // Use the "%e" format if the exponent is large.
+  int32_t e = h.decimal_point - 1;
+  if ((e < -4) || (e_threshold <= e)) {
+    uint32_t p = wuffs_base__u32__min(precision, h.num_digits);
+    return wuffs_base__private_implementation__high_prec_dec__render_exponent_present(
+        dst, &h, (p > 0) ? (p - 1) : 0, options);
+  }
+
+  // Use the "%f" format otherwise.
+  int32_t p = ((int32_t)precision);
+  if (p > h.decimal_point) {
+    p = ((int32_t)(h.num_digits));
+  }
+  precision = ((uint32_t)(wuffs_base__i32__max(0, p - h.decimal_point)));
+  return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent(
+      dst, &h, precision, options);
+}
+
 #endif  // !defined(WUFFS_CONFIG__MODULES) ||
         // defined(WUFFS_CONFIG__MODULE__BASE) ||
         // defined(WUFFS_CONFIG__MODULE__BASE__F64CONV)
@@ -10439,7 +11132,7 @@
   if (neg) {
     ptr -= 1;
     ptr[0] = '-';
-  } else if (options & WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN) {
+  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {
     ptr -= 1;
     ptr[0] = '+';
   }
@@ -10448,7 +11141,7 @@
   if (n > dst.len) {
     return 0;
   }
-  memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT)
+  memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)
                         ? (dst.len - n)
                         : 0),
          ptr, n);
diff --git a/script/print-render-number-f64-tests.go b/script/print-render-number-f64-tests.go
new file mode 100644
index 0000000..b4aec2b
--- /dev/null
+++ b/script/print-render-number-f64-tests.go
@@ -0,0 +1,176 @@
+// Copyright 2020 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build ignore
+
+package main
+
+// print-render-number-f64-tests.go prints the
+// test_wuffs_strconv_render_number_f64 test cases.
+//
+// Usage: go run print-render-number-f64-tests.go
+
+import (
+	"fmt"
+	"math"
+	"os"
+	"sort"
+	"strconv"
+)
+
+func main() {
+	if err := main1(); err != nil {
+		os.Stderr.WriteString(err.Error() + "\n")
+		os.Exit(1)
+	}
+}
+
+func main1() error {
+	testCases := append([]uint64(nil), u64TestCases...)
+	for _, f := range f64TestCases {
+		testCases = append(testCases, math.Float64bits(f))
+	}
+
+	sort.Slice(testCases, func(i int, j int) bool {
+		return testCases[i] < testCases[j]
+	})
+
+	for i, tc := range testCases {
+		f := math.Float64frombits(tc)
+
+		if (i > 0) && (tc == testCases[i-1]) {
+			return fmt.Errorf("duplicate test case (f=%g, tc=0x%X)", f, tc)
+		}
+
+		// Check that calling strconv.FormatFloat with a precision of -1 (round
+		// to shortest) does indeed return a string that, when parsed, recovers
+		// the original number.
+		shortest := strconv.FormatFloat(f, 'g', -1, 64)
+		g, err := strconv.ParseFloat(shortest, 64)
+		if err != nil {
+			return fmt.Errorf("ParseFloat failed (f=%g, tc=0x%X): %v", f, tc, err)
+		}
+		equal := tc == math.Float64bits(g)
+		if math.IsNaN(f) {
+			equal = math.IsNaN(g)
+		}
+		if !equal {
+			return fmt.Errorf("round-trip failed (f=%g, tc=0x%X)", f, tc)
+		}
+	}
+
+	for _, tc := range testCases {
+		f := math.Float64frombits(tc)
+		fmt.Printf(`{
+	.x = 0x%016X,
+	.want__e = %s,
+	.want__f = %s,
+	.want_0g = %s,
+	.want_2e = %s,
+	.want_3f = %s,
+	.want_4g = %s,
+},`+"\n",
+			tc,
+			do(f, -1, 'e'),
+			do(f, -1, 'f'),
+			do(f, +0, 'g'),
+			do(f, +2, 'e'),
+			do(f, +3, 'f'),
+			do(f, +4, 'g'),
+		)
+	}
+	return nil
+}
+
+func do(f float64, precision int, format byte) (ret string) {
+	s := strconv.FormatFloat(f, format, precision, 64)
+	for ; len(s) > 50; s = s[50:] {
+		ret += fmt.Sprintf("%q\n\t\t", s[:50])
+	}
+	ret += fmt.Sprintf("%q", s)
+	if ret == `"+Inf"` {
+		ret = `"Inf"`
+	}
+	return ret
+}
+
+var f64TestCases = []float64{
+	// Approximations of e, the base of the natural logarithm.
+	2.7,
+	2.72,
+	2.718,
+	2.7183,
+	2.71828,
+	2.718282,
+	2.7182818,
+	2.71828183,
+
+	// Approximations of N_A, the Avogadro constant.
+	6.0e23,
+	6.02e23,
+	6.022e23,
+	6.0221e23,
+	6.02214e23,
+	6.022141e23,
+	6.0221408e23,
+	6.02214076e23,
+}
+
+var u64TestCases = []uint64{
+	0x0000000000000000,
+	0x0000000000000001,
+	0x0000000000000002,
+	0x0000000000000003,
+	0x000FFFFFFFFFFFFF,
+	0x0010000000000000,
+	0x369C314ABE948EB1,
+	0x3F88000000000000,
+	0x3FD0000000000000,
+	0x3FD3333333333333,
+	0x3FD3333333333334,
+	0x3FD5555555555555,
+	0x3FEFFFFFFFFFFFFF,
+	0x3FF0000000000000,
+	0x3FF0000000000001,
+	0x3FF0000000000002,
+	0x3FF4000000000000,
+	0x3FF8000000000000,
+	0x4008000000000000,
+	0x400921F9F01B866E,
+	0x400921FB54442D11,
+	0x400921FB54442D18,
+	0x400C000000000000,
+	0x4014000000000000,
+	0x4036000000000000,
+	0x4037000000000000,
+	0x4038000000000000,
+	0x40FE240C9FCB0C02,
+	0x4202A05F20000000,
+	0x4330000000000000,
+	0x4330000000000001,
+	0x4330000000000002,
+	0x4340000000000000,
+	0x4340000000000001,
+	0x4340000000000002,
+	0x4415AF1D78B58C40,
+	0x46293E5939A08CEA,
+	0x54B249AD2594C37D,
+	0x7FEFFFFFFFFFFFFF,
+	0x7FF0000000000000,
+	0x7FFFFFFFFFFFFFFF,
+	0x8000000000000000,
+	0xC008000000000000,
+	0xFFF0000000000000,
+	0xFFFFFFFFFFFFFFFF,
+}
diff --git a/test/c/std/json.c b/test/c/std/json.c
index bb5d967..4bd4834 100644
--- a/test/c/std/json.c
+++ b/test/c/std/json.c
@@ -607,11 +607,16 @@
     uint64_t want;
     const char* str;
   } test_cases[] = {
+      // If adding new cases, consider updating u64TestCases in
+      // script/print-render-number-f64-tests.go
+
       {.want = 0x0000000000000000, .str = "+0.0"},
       {.want = 0x0000000000000000, .str = "0"},
       {.want = 0x0000000000000000, .str = "0e0"},
       {.want = 0x0000000000000000, .str = "1e-332"},
       {.want = 0x0000000000000001, .str = "4.9406564584124654e-324"},
+      {.want = 0x0000000000000002, .str = "9.8813129168249309e-324"},
+      {.want = 0x0000000000000003, .str = "1.4821969375237396e-323"},
       {.want = 0x000FFFFFFFFFFFFF, .str = "2.2250738585072009E-308"},
       {.want = 0x0010000000000000, .str = "2.2250738585072014E-308"},
       {.want = 0x369C314ABE948EB1,
@@ -679,6 +684,9 @@
       {.want = 0xFFF0000000000000, .str = "-inf"},
       {.want = 0xFFFFFFFFFFFFFFFF, .str = "-NAN"},
 
+      // If adding new cases, consider updating u64TestCases in
+      // script/print-render-number-f64-tests.go
+
       // We accept either ',' or '.'.
       {.want = 0x3FFC000000000000, .str = "1,75"},
       {.want = 0x3FFC000000000000, .str = "1.75"},
@@ -859,6 +867,702 @@
 }
 
 const char*  //
+test_wuffs_strconv_render_number_f64() {
+  CHECK_FOCUS(__func__);
+
+  struct {
+    uint64_t x;
+    // These want strings come from Go's strconv.FormatFloat.
+    const char* want__e;  // FormatFloat(etc, 'e', -1, etc)
+    const char* want__f;  // FormatFloat(etc, 'f', -1, etc)
+    const char* want_0g;  // FormatFloat(etc, 'g', +0, etc)
+    const char* want_2e;  // FormatFloat(etc, 'e', +2, etc)
+    const char* want_3f;  // FormatFloat(etc, 'f', +3, etc)
+    const char* want_4g;  // FormatFloat(etc, 'g', +4, etc)
+  } test_cases[] = {
+      // These test cases were generated by
+      // script/print-render-number-f64-tests.go
+
+      {
+          .x = 0x0000000000000000,
+          .want__e = "0e+00",
+          .want__f = "0",
+          .want_0g = "0",
+          .want_2e = "0.00e+00",
+          .want_3f = "0.000",
+          .want_4g = "0",
+      },
+      {
+          .x = 0x0000000000000001,
+          .want__e = "5e-324",
+          .want__f = "0.000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000005",
+          .want_0g = "5e-324",
+          .want_2e = "4.94e-324",
+          .want_3f = "0.000",
+          .want_4g = "4.941e-324",
+      },
+      {
+          .x = 0x0000000000000002,
+          .want__e = "1e-323",
+          .want__f = "0.000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "0000000000000000000000001",
+          .want_0g = "1e-323",
+          .want_2e = "9.88e-324",
+          .want_3f = "0.000",
+          .want_4g = "9.881e-324",
+      },
+      {
+          .x = 0x0000000000000003,
+          .want__e = "1.5e-323",
+          .want__f = "0.000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000015",
+          .want_0g = "1e-323",
+          .want_2e = "1.48e-323",
+          .want_3f = "0.000",
+          .want_4g = "1.482e-323",
+      },
+      {
+          .x = 0x000FFFFFFFFFFFFF,
+          .want__e = "2.225073858507201e-308",
+          .want__f = "0.000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "0000000002225073858507201",
+          .want_0g = "2e-308",
+          .want_2e = "2.23e-308",
+          .want_3f = "0.000",
+          .want_4g = "2.225e-308",
+      },
+      {
+          .x = 0x0010000000000000,
+          .want__e = "2.2250738585072014e-308",
+          .want__f = "0.000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000022250738585072014",
+          .want_0g = "2e-308",
+          .want_2e = "2.23e-308",
+          .want_3f = "0.000",
+          .want_4g = "2.225e-308",
+      },
+      {
+          .x = 0x369C314ABE948EB1,
+          .want__e = "1.23456789e-45",
+          .want__f = "0.000000000000000000000000000000000000000000001234"
+                     "56789",
+          .want_0g = "1e-45",
+          .want_2e = "1.23e-45",
+          .want_3f = "0.000",
+          .want_4g = "1.235e-45",
+      },
+      {
+          .x = 0x3F88000000000000,
+          .want__e = "1.171875e-02",
+          .want__f = "0.01171875",
+          .want_0g = "0.01",
+          .want_2e = "1.17e-02",
+          .want_3f = "0.012",
+          .want_4g = "0.01172",
+      },
+      {
+          .x = 0x3FD0000000000000,
+          .want__e = "2.5e-01",
+          .want__f = "0.25",
+          .want_0g = "0.2",
+          .want_2e = "2.50e-01",
+          .want_3f = "0.250",
+          .want_4g = "0.25",
+      },
+      {
+          .x = 0x3FD3333333333333,
+          .want__e = "3e-01",
+          .want__f = "0.3",
+          .want_0g = "0.3",
+          .want_2e = "3.00e-01",
+          .want_3f = "0.300",
+          .want_4g = "0.3",
+      },
+      {
+          .x = 0x3FD3333333333334,
+          .want__e = "3.0000000000000004e-01",
+          .want__f = "0.30000000000000004",
+          .want_0g = "0.3",
+          .want_2e = "3.00e-01",
+          .want_3f = "0.300",
+          .want_4g = "0.3",
+      },
+      {
+          .x = 0x3FD5555555555555,
+          .want__e = "3.333333333333333e-01",
+          .want__f = "0.3333333333333333",
+          .want_0g = "0.3",
+          .want_2e = "3.33e-01",
+          .want_3f = "0.333",
+          .want_4g = "0.3333",
+      },
+      {
+          .x = 0x3FEFFFFFFFFFFFFF,
+          .want__e = "9.999999999999999e-01",
+          .want__f = "0.9999999999999999",
+          .want_0g = "1",
+          .want_2e = "1.00e+00",
+          .want_3f = "1.000",
+          .want_4g = "1",
+      },
+      {
+          .x = 0x3FF0000000000000,
+          .want__e = "1e+00",
+          .want__f = "1",
+          .want_0g = "1",
+          .want_2e = "1.00e+00",
+          .want_3f = "1.000",
+          .want_4g = "1",
+      },
+      {
+          .x = 0x3FF0000000000001,
+          .want__e = "1.0000000000000002e+00",
+          .want__f = "1.0000000000000002",
+          .want_0g = "1",
+          .want_2e = "1.00e+00",
+          .want_3f = "1.000",
+          .want_4g = "1",
+      },
+      {
+          .x = 0x3FF0000000000002,
+          .want__e = "1.0000000000000004e+00",
+          .want__f = "1.0000000000000004",
+          .want_0g = "1",
+          .want_2e = "1.00e+00",
+          .want_3f = "1.000",
+          .want_4g = "1",
+      },
+      {
+          .x = 0x3FF4000000000000,
+          .want__e = "1.25e+00",
+          .want__f = "1.25",
+          .want_0g = "1",
+          .want_2e = "1.25e+00",
+          .want_3f = "1.250",
+          .want_4g = "1.25",
+      },
+      {
+          .x = 0x3FF8000000000000,
+          .want__e = "1.5e+00",
+          .want__f = "1.5",
+          .want_0g = "2",
+          .want_2e = "1.50e+00",
+          .want_3f = "1.500",
+          .want_4g = "1.5",
+      },
+      {
+          .x = 0x400599999999999A,
+          .want__e = "2.7e+00",
+          .want__f = "2.7",
+          .want_0g = "3",
+          .want_2e = "2.70e+00",
+          .want_3f = "2.700",
+          .want_4g = "2.7",
+      },
+      {
+          .x = 0x4005BE76C8B43958,
+          .want__e = "2.718e+00",
+          .want__f = "2.718",
+          .want_0g = "3",
+          .want_2e = "2.72e+00",
+          .want_3f = "2.718",
+          .want_4g = "2.718",
+      },
+      {
+          .x = 0x4005BF0995AAF790,
+          .want__e = "2.71828e+00",
+          .want__f = "2.71828",
+          .want_0g = "3",
+          .want_2e = "2.72e+00",
+          .want_3f = "2.718",
+          .want_4g = "2.718",
+      },
+      {
+          .x = 0x4005BF0A87427F01,
+          .want__e = "2.7182818e+00",
+          .want__f = "2.7182818",
+          .want_0g = "3",
+          .want_2e = "2.72e+00",
+          .want_3f = "2.718",
+          .want_4g = "2.718",
+      },
+      {
+          .x = 0x4005BF0A8B4949CB,
+          .want__e = "2.71828183e+00",
+          .want__f = "2.71828183",
+          .want_0g = "3",
+          .want_2e = "2.72e+00",
+          .want_3f = "2.718",
+          .want_4g = "2.718",
+      },
+      {
+          .x = 0x4005BF0AA21A719B,
+          .want__e = "2.718282e+00",
+          .want__f = "2.718282",
+          .want_0g = "3",
+          .want_2e = "2.72e+00",
+          .want_3f = "2.718",
+          .want_4g = "2.718",
+      },
+      {
+          .x = 0x4005BF141205BC02,
+          .want__e = "2.7183e+00",
+          .want__f = "2.7183",
+          .want_0g = "3",
+          .want_2e = "2.72e+00",
+          .want_3f = "2.718",
+          .want_4g = "2.718",
+      },
+      {
+          .x = 0x4005C28F5C28F5C3,
+          .want__e = "2.72e+00",
+          .want__f = "2.72",
+          .want_0g = "3",
+          .want_2e = "2.72e+00",
+          .want_3f = "2.720",
+          .want_4g = "2.72",
+      },
+      {
+          .x = 0x4008000000000000,
+          .want__e = "3e+00",
+          .want__f = "3",
+          .want_0g = "3",
+          .want_2e = "3.00e+00",
+          .want_3f = "3.000",
+          .want_4g = "3",
+      },
+      {
+          .x = 0x400921F9F01B866E,
+          .want__e = "3.14159e+00",
+          .want__f = "3.14159",
+          .want_0g = "3",
+          .want_2e = "3.14e+00",
+          .want_3f = "3.142",
+          .want_4g = "3.142",
+      },
+      {
+          .x = 0x400921FB54442D11,
+          .want__e = "3.14159265358979e+00",
+          .want__f = "3.14159265358979",
+          .want_0g = "3",
+          .want_2e = "3.14e+00",
+          .want_3f = "3.142",
+          .want_4g = "3.142",
+      },
+      {
+          .x = 0x400921FB54442D18,
+          .want__e = "3.141592653589793e+00",
+          .want__f = "3.141592653589793",
+          .want_0g = "3",
+          .want_2e = "3.14e+00",
+          .want_3f = "3.142",
+          .want_4g = "3.142",
+      },
+      {
+          .x = 0x400C000000000000,
+          .want__e = "3.5e+00",
+          .want__f = "3.5",
+          .want_0g = "4",
+          .want_2e = "3.50e+00",
+          .want_3f = "3.500",
+          .want_4g = "3.5",
+      },
+      {
+          .x = 0x4014000000000000,
+          .want__e = "5e+00",
+          .want__f = "5",
+          .want_0g = "5",
+          .want_2e = "5.00e+00",
+          .want_3f = "5.000",
+          .want_4g = "5",
+      },
+      {
+          .x = 0x4036000000000000,
+          .want__e = "2.2e+01",
+          .want__f = "22",
+          .want_0g = "2e+01",
+          .want_2e = "2.20e+01",
+          .want_3f = "22.000",
+          .want_4g = "22",
+      },
+      {
+          .x = 0x4037000000000000,
+          .want__e = "2.3e+01",
+          .want__f = "23",
+          .want_0g = "2e+01",
+          .want_2e = "2.30e+01",
+          .want_3f = "23.000",
+          .want_4g = "23",
+      },
+      {
+          .x = 0x4038000000000000,
+          .want__e = "2.4e+01",
+          .want__f = "24",
+          .want_0g = "2e+01",
+          .want_2e = "2.40e+01",
+          .want_3f = "24.000",
+          .want_4g = "24",
+      },
+      {
+          .x = 0x40FE240C9FCB0C02,
+          .want__e = "1.23456789012e+05",
+          .want__f = "123456.789012",
+          .want_0g = "1e+05",
+          .want_2e = "1.23e+05",
+          .want_3f = "123456.789",
+          .want_4g = "1.235e+05",
+      },
+      {
+          .x = 0x4202A05F20000000,
+          .want__e = "1e+10",
+          .want__f = "10000000000",
+          .want_0g = "1e+10",
+          .want_2e = "1.00e+10",
+          .want_3f = "10000000000.000",
+          .want_4g = "1e+10",
+      },
+      {
+          .x = 0x4330000000000000,
+          .want__e = "4.503599627370496e+15",
+          .want__f = "4503599627370496",
+          .want_0g = "5e+15",
+          .want_2e = "4.50e+15",
+          .want_3f = "4503599627370496.000",
+          .want_4g = "4.504e+15",
+      },
+      {
+          .x = 0x4330000000000001,
+          .want__e = "4.503599627370497e+15",
+          .want__f = "4503599627370497",
+          .want_0g = "5e+15",
+          .want_2e = "4.50e+15",
+          .want_3f = "4503599627370497.000",
+          .want_4g = "4.504e+15",
+      },
+      {
+          .x = 0x4330000000000002,
+          .want__e = "4.503599627370498e+15",
+          .want__f = "4503599627370498",
+          .want_0g = "5e+15",
+          .want_2e = "4.50e+15",
+          .want_3f = "4503599627370498.000",
+          .want_4g = "4.504e+15",
+      },
+      {
+          .x = 0x4340000000000000,
+          .want__e = "9.007199254740992e+15",
+          .want__f = "9007199254740992",
+          .want_0g = "9e+15",
+          .want_2e = "9.01e+15",
+          .want_3f = "9007199254740992.000",
+          .want_4g = "9.007e+15",
+      },
+      {
+          .x = 0x4340000000000001,
+          .want__e = "9.007199254740994e+15",
+          .want__f = "9007199254740994",
+          .want_0g = "9e+15",
+          .want_2e = "9.01e+15",
+          .want_3f = "9007199254740994.000",
+          .want_4g = "9.007e+15",
+      },
+      {
+          .x = 0x4340000000000002,
+          .want__e = "9.007199254740996e+15",
+          .want__f = "9007199254740996",
+          .want_0g = "9e+15",
+          .want_2e = "9.01e+15",
+          .want_3f = "9007199254740996.000",
+          .want_4g = "9.007e+15",
+      },
+      {
+          .x = 0x4415AF1D78B58C40,
+          .want__e = "1e+20",
+          .want__f = "100000000000000000000",
+          .want_0g = "1e+20",
+          .want_2e = "1.00e+20",
+          .want_3f = "100000000000000000000.000",
+          .want_4g = "1e+20",
+      },
+      {
+          .x = 0x44DFC3842BD1F072,
+          .want__e = "6e+23",
+          .want__f = "600000000000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.00e+23",
+          .want_3f = "600000000000000016777216.000",
+          .want_4g = "6e+23",
+      },
+      {
+          .x = 0x44DFDE9F10A8D361,
+          .want__e = "6.02e+23",
+          .want__f = "602000000000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.02e+23",
+          .want_3f = "601999999999999995805696.000",
+          .want_4g = "6.02e+23",
+      },
+      {
+          .x = 0x44DFE154F457EA13,
+          .want__e = "6.022e+23",
+          .want__f = "602200000000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.02e+23",
+          .want_3f = "602200000000000027262976.000",
+          .want_4g = "6.022e+23",
+      },
+      {
+          .x = 0x44DFE177A620AB35,
+          .want__e = "6.0221e+23",
+          .want__f = "602210000000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.02e+23",
+          .want_3f = "602209999999999995281408.000",
+          .want_4g = "6.022e+23",
+      },
+      {
+          .x = 0x44DFE18586D75EDC,
+          .want__e = "6.02214e+23",
+          .want__f = "602214000000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.02e+23",
+          .want_3f = "602213999999999969067008.000",
+          .want_4g = "6.022e+23",
+      },
+      {
+          .x = 0x44DFE185CA57C517,
+          .want__e = "6.02214076e+23",
+          .want__f = "602214076000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.02e+23",
+          .want_3f = "602214075999999987023872.000",
+          .want_4g = "6.022e+23",
+      },
+      {
+          .x = 0x44DFE185CDE543BC,
+          .want__e = "6.0221408e+23",
+          .want__f = "602214080000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.02e+23",
+          .want_3f = "602214080000000002097152.000",
+          .want_4g = "6.022e+23",
+      },
+      {
+          .x = 0x44DFE185DFA8BCF4,
+          .want__e = "6.022141e+23",
+          .want__f = "602214100000000000000000",
+          .want_0g = "6e+23",
+          .want_2e = "6.02e+23",
+          .want_3f = "602214100000000010354688.000",
+          .want_4g = "6.022e+23",
+      },
+      {
+          .x = 0x46293E5939A08CEA,
+          .want__e = "1e+30",
+          .want__f = "1000000000000000000000000000000",
+          .want_0g = "1e+30",
+          .want_2e = "1.00e+30",
+          .want_3f = "1000000000000000019884624838656.000",
+          .want_4g = "1e+30",
+      },
+      {
+          .x = 0x54B249AD2594C37D,
+          .want__e = "1e+100",
+          .want__f = "10000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "0",
+          .want_0g = "1e+100",
+          .want_2e = "1.00e+100",
+          .want_3f = "10000000000000000159028911097599180468360808563945"
+                     "28138978132755774783877217038106081346998585681510"
+                     "4.000",
+          .want_4g = "1e+100",
+      },
+      {
+          .x = 0x7FEFFFFFFFFFFFFF,
+          .want__e = "1.7976931348623157e+308",
+          .want__f = "17976931348623157000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "00000000000000000000000000000000000000000000000000"
+                     "000000000",
+          .want_0g = "2e+308",
+          .want_2e = "1.80e+308",
+          .want_3f = "17976931348623157081452742373170435679807056752584"
+                     "49965989174768031572607800285387605895586327668781"
+                     "71540458953514382464234321326889464182768467546703"
+                     "53751698604991057655128207624549009038932894407586"
+                     "85084551339423045832369032229481658085593321233482"
+                     "74797826204144723168738177180919299881250404026184"
+                     "124858368.000",
+          .want_4g = "1.798e+308",
+      },
+      {
+          .x = 0x7FF0000000000000,
+          .want__e = "Inf",
+          .want__f = "Inf",
+          .want_0g = "Inf",
+          .want_2e = "Inf",
+          .want_3f = "Inf",
+          .want_4g = "Inf",
+      },
+      {
+          .x = 0x7FFFFFFFFFFFFFFF,
+          .want__e = "NaN",
+          .want__f = "NaN",
+          .want_0g = "NaN",
+          .want_2e = "NaN",
+          .want_3f = "NaN",
+          .want_4g = "NaN",
+      },
+      {
+          .x = 0x8000000000000000,
+          .want__e = "-0e+00",
+          .want__f = "-0",
+          .want_0g = "-0",
+          .want_2e = "-0.00e+00",
+          .want_3f = "-0.000",
+          .want_4g = "-0",
+      },
+      {
+          .x = 0xC008000000000000,
+          .want__e = "-3e+00",
+          .want__f = "-3",
+          .want_0g = "-3",
+          .want_2e = "-3.00e+00",
+          .want_3f = "-3.000",
+          .want_4g = "-3",
+      },
+      {
+          .x = 0xFFF0000000000000,
+          .want__e = "-Inf",
+          .want__f = "-Inf",
+          .want_0g = "-Inf",
+          .want_2e = "-Inf",
+          .want_3f = "-Inf",
+          .want_4g = "-Inf",
+      },
+      {
+          .x = 0xFFFFFFFFFFFFFFFF,
+          .want__e = "NaN",
+          .want__f = "NaN",
+          .want_0g = "NaN",
+          .want_2e = "NaN",
+          .want_3f = "NaN",
+          .want_4g = "NaN",
+      },
+  };
+
+  int tc;
+  for (tc = 0; tc < WUFFS_TESTLIB_ARRAY_SIZE(test_cases); tc++) {
+    double f64 =
+        wuffs_base__ieee_754_bit_representation__to_f64(test_cases[tc].x);
+    int o;
+    for (o = 0; o < 6; o++) {
+      uint32_t precision = 0;
+      uint32_t options = 0;
+      const char* want = NULL;
+      if (o == 0) {
+        options = WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT |
+                  WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION;
+        want = test_cases[tc].want__e;
+      } else if (o == 1) {
+        options = WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT |
+                  WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION;
+        want = test_cases[tc].want__f;
+      } else if (o == 2) {
+        precision = 0;
+        options = 0;
+        want = test_cases[tc].want_0g;
+      } else if (o == 3) {
+        precision = 2;
+        options = WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT;
+        want = test_cases[tc].want_2e;
+      } else if (o == 4) {
+        precision = 3;
+        options = WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT;
+        want = test_cases[tc].want_3f;
+      } else if (o == 5) {
+        precision = 4;
+        options = 0;
+        want = test_cases[tc].want_4g;
+      }
+
+      size_t n = wuffs_base__render_number_f64(g_have_slice_u8, f64, precision,
+                                               options);
+      if (n == 0) {
+        RETURN_FAIL("x=0x%016" PRIX64 ", o=%d: could not render",
+                    test_cases[tc].x, o);
+      } else if (n >= g_have_slice_u8.len) {
+        RETURN_FAIL("x=0x%016" PRIX64 ", o=%d: n is too large",
+                    test_cases[tc].x, o);
+      }
+      g_have_slice_u8.ptr[n] = 0x00;
+      if (strcmp((const char*)(g_have_slice_u8.ptr), want) != 0) {
+        RETURN_FAIL("x=0x%016" PRIX64 ", o=%d: have \"%s\", want \"%s\"",
+                    test_cases[tc].x, o, g_have_slice_u8.ptr, want);
+      }
+    }
+  }
+
+  // Test WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA.
+  {
+    int o;
+    for (o = 0; o < 2; o++) {
+      uint8_t dst[8] = {0};
+      const double f64 = 1.75;
+      const uint32_t precision = 2;
+      size_t n = wuffs_base__render_number_f64(
+          wuffs_base__make_slice_u8(&dst[0], WUFFS_TESTLIB_ARRAY_SIZE(dst)),
+          f64, precision,
+          WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT |
+              (o ? WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA
+                 : 0));
+      if (n != 4) {
+        RETURN_FAIL("DECIMAL_SEPARATOR_IS_A_COMMA, o=%d: n != 4", o);
+      }
+      uint8_t have = dst[1];
+      uint8_t want = o ? ',' : '.';
+      if (have != want) {
+        RETURN_FAIL(
+            "DECIMAL_SEPARATOR_IS_A_COMMA, o=%d: have 0x%02X, want 0x%02X", o,
+            (int)have, (int)want);
+      }
+    }
+  }
+
+  return NULL;
+}
+
+const char*  //
 test_wuffs_strconv_render_number_i64() {
   CHECK_FOCUS(__func__);
 
@@ -909,7 +1613,7 @@
   for (tc = 0; tc < WUFFS_TESTLIB_ARRAY_SIZE(test_cases); tc++) {
     size_t n = wuffs_base__render_number_i64(
         g_have_slice_u8, test_cases[tc].x,
-        WUFFS_BASE__RENDER_NUMBER__DEFAULT_OPTIONS);
+        WUFFS_BASE__RENDER_NUMBER_XXX__DEFAULT_OPTIONS);
     if (n == 0) {
       RETURN_FAIL("%" PRId64 ": could not render", test_cases[tc].x);
     } else if (n >= g_have_slice_u8.len) {
@@ -964,7 +1668,7 @@
   for (tc = 0; tc < WUFFS_TESTLIB_ARRAY_SIZE(test_cases); tc++) {
     size_t n = wuffs_base__render_number_u64(
         g_have_slice_u8, test_cases[tc].x,
-        WUFFS_BASE__RENDER_NUMBER__DEFAULT_OPTIONS);
+        WUFFS_BASE__RENDER_NUMBER_XXX__DEFAULT_OPTIONS);
     if (n == 0) {
       RETURN_FAIL("%" PRIu64 ": could not render", test_cases[tc].x);
     } else if (n >= g_have_slice_u8.len) {
@@ -982,7 +1686,7 @@
     uint8_t dst[5];
     size_t n = wuffs_base__render_number_u64(
         wuffs_base__make_slice_u8(&dst[0], sizeof(dst)), 123456,
-        WUFFS_BASE__RENDER_NUMBER__DEFAULT_OPTIONS);
+        WUFFS_BASE__RENDER_NUMBER_XXX__DEFAULT_OPTIONS);
     if (n != 0) {
       RETURN_FAIL("dst too short: have %zu, want 0", n);
     }
@@ -994,8 +1698,8 @@
     memcpy(&dst[0], "ABCDEFG\x00", 8);
     size_t n = wuffs_base__render_number_u64(
         wuffs_base__make_slice_u8(&dst[0], sizeof(dst) - 1), 1234,
-        WUFFS_BASE__RENDER_NUMBER__ALIGN_RIGHT |
-            WUFFS_BASE__RENDER_NUMBER__LEADING_PLUS_SIGN);
+        WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT |
+            WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN);
     if (n != 5) {
       RETURN_FAIL("ALIGN_RIGHT | LEADING_PLUS_SIGN: have %zu, want 5", n);
     }
@@ -2634,6 +3338,7 @@
     test_wuffs_strconv_parse_number_f64,
     test_wuffs_strconv_parse_number_i64,
     test_wuffs_strconv_parse_number_u64,
+    test_wuffs_strconv_render_number_f64,
     test_wuffs_strconv_render_number_i64,
     test_wuffs_strconv_render_number_u64,
     test_wuffs_strconv_utf_8_next,