[PATCH 4/7] stdio-common: Add printf format tests for the a and A conversions
Matt Turner <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Message-ID | <[email protected]> |
The a and A conversions were left out when the formatted printf output
tests were added, because gawk produces output that differs from ours,
using insufficient precision where none has been given and choosing a
different exponent otherwise. Verification no longer goes through AWK,
and computing the reference output directly makes them straightforward,
so cover them now.
The significand is written out as it stands, which means the leading
hexadecimal digit holds whatever bits are left over once the remaining
ones are grouped into whole digits: one bit for a 53 bit significand, so
the digit is 1, and four for a 64 bit one, so it runs from 8 to f.
Rounding to a requested precision can carry out of that digit, in which
case the result is re-expressed with one digit fewer and the exponent
raised by four rather than the integer part being widened. The 0x
prefix precedes any '0' flag padding, as it does for the integer
hexadecimal conversions.
Unlike the remaining floating-point conversions these produce different
digits for an omitted precision than for one of 6, so key the memoized
digits on the precision as given rather than as defaulted.
Tested on x86_64-linux-gnu, where all 672 results pass.
---
stdio-common/Makefile | 2 +-
stdio-common/tst-printf-format.py | 106 ++++++++++++++++++++++++------
2 files changed, 88 insertions(+), 20 deletions(-)
diff --git ./stdio-common/Makefile ./stdio-common/Makefile
index 537962ad86..b06e42b7e5 100644
--- ./stdio-common/Makefile
+++ ./stdio-common/Makefile
@@ -28,7 +28,7 @@ xprintf-funcs := p as d f s sn v vas vd vf vs vsn
# List of data types and formats for individual per-conversion printf tests.
fmt-convs := double ldouble
-fmts := E e F f G g
+fmts := A a E e F f G g
# List of data types grouping all conversions in single printf tests.
nonfmt-convs := c char int llong long s short
diff --git ./stdio-common/tst-printf-format.py ./stdio-common/tst-printf-format.py
index a24ea5d402..dc4fbae1da 100644
--- ./stdio-common/tst-printf-format.py
+++ ./stdio-common/tst-printf-format.py
@@ -39,9 +39,12 @@ import sys
from fractions import Fraction
# Conversions grouped by the C type of the corresponding argument.
-FLOAT_CONVS = frozenset("eEfFgG")
+FLOAT_CONVS = frozenset("aAeEfFgG")
INT_CONVS = frozenset("bBdiouxX")
+# Conversions whose output is in upper case.
+UPPER_CONVS = frozenset("AEFG")
+
# The conversion specifier selects the base an integer is written in.
INT_FORMATS = {"b": "b", "B": "b", "o": "o", "u": "d", "x": "x", "X": "X"}
@@ -80,18 +83,11 @@ def scale_ratio(num, den, power):
return num, den * 10 ** -power
-def decimal_to_binary(text, mant_bits):
- """Convert the decimal string TEXT to the nearest value having MANT_BITS
- of significand, returned as an exact Fraction.
+def normalize(num, den, mant_bits):
+ """Scale the positive ratio NUM/DEN into [2**(mant_bits-1), 2**mant_bits).
- The generator prints reference values with enough digits to reproduce
- the original, so this recovers the exact value converted."""
- fr = Fraction(text)
- if fr == 0:
- return fr
- neg = fr < 0
- num, den = (-fr.numerator if neg else fr.numerator), fr.denominator
- # Scale so that 2**(mant_bits-1) <= num/den < 2**mant_bits.
+ Return the scaled pair along with the exponent EXP that was taken out,
+ so that the original value is num/den * 2**exp."""
exp = num.bit_length() - den.bit_length() - mant_bits
if exp >= 0:
den <<= exp
@@ -103,6 +99,21 @@ def decimal_to_binary(text, mant_bits):
while num < den << (mant_bits - 1):
num <<= 1
exp -= 1
+ return num, den, exp
+
+
+def decimal_to_binary(text, mant_bits):
+ """Convert the decimal string TEXT to the nearest value having MANT_BITS
+ of significand, returned as an exact Fraction.
+
+ The generator prints reference values with enough digits to reproduce
+ the original, so this recovers the exact value converted."""
+ fr = Fraction(text)
+ if fr == 0:
+ return fr
+ neg = fr < 0
+ num, den = (-fr.numerator if neg else fr.numerator), fr.denominator
+ num, den, exp = normalize(num, den, mant_bits)
mant = round_ratio(num, den)
if mant >> mant_bits:
mant >>= 1
@@ -256,7 +267,7 @@ def convert_special(kind, neg, spec):
"""Convert an infinity or a NaN. The field is padded with spaces
whether or not the '0' flag was given, and the alternative form has no
effect."""
- body = kind.upper() if spec.conv in "EFG" else kind
+ body = kind.upper() if spec.conv in UPPER_CONVS else kind
return spec.pad(body, zero_ok=False, sign=spec.sign_of(neg))
@@ -275,6 +286,57 @@ def render_f(fr, prec, alt, strip):
return whole
+def to_significand(fr, mant_bits):
+ """Split the positive Fraction FR into an integer significand of exactly
+ MANT_BITS bits and a power of two, returned as (significand, exponent).
+
+ FR comes from a value of that many significand bits, so the split is
+ exact. Subnormals are not among the values the generator uses and are
+ not handled here."""
+ num, den, exp = normalize(fr.numerator, fr.denominator, mant_bits)
+ return num // den, exp
+
+
+def render_a(fr, mant_bits, prec, alt, upper):
+ """Render the non-negative Fraction FR in the style of 'a'.
+
+ The number of bits the leading hexadecimal digit holds is whatever is
+ left over once the remaining significand bits are grouped into whole
+ digits, which is how the significand ends up written out without any
+ shifting. For a 53 bit significand that leaves one bit, so the leading
+ digit is 1; for a 64 bit one it leaves four, so the leading digit runs
+ from 8 to f."""
+ lead_bits = ((mant_bits - 1) % 4) + 1
+ nfrac = (mant_bits - lead_bits) // 4
+ if fr == 0:
+ digits, exp = "0" * (1 + nfrac), 0
+ else:
+ mant, exp = to_significand(fr, mant_bits)
+ digits = "%x" % mant
+ exp += 4 * nfrac
+
+ if prec is None:
+ whole, frac = digits[0], digits[1:].rstrip("0")
+ elif prec < nfrac:
+ value = round_ratio(int(digits, 16), 16 ** (nfrac - prec))
+ if value >= 16 ** (1 + prec):
+ # Rounding carried out of the leading digit; re-express with
+ # one digit fewer rather than widening the integer part.
+ value //= 16
+ exp += 4
+ text = ("%x" % value).zfill(1 + prec)
+ whole, frac = text[0], text[1:]
+ else:
+ whole, frac = digits[0], digits[1:].ljust(prec, "0")
+
+ # The "0x" prefix is not included: like the one the '#' flag produces
+ # for the integer hexadecimal conversions it precedes any '0' flag
+ # padding, so the caller pads it along with the sign.
+ body = whole + "." + frac if frac or alt else whole
+ text = "%sp%s%d" % (body, "-" if exp < 0 else "+", abs(exp))
+ return text.upper() if upper else text
+
+
def render_e(fr, prec, alt, upper, strip):
"""Render the non-negative Fraction FR in the style of 'e'."""
if fr == 0:
@@ -289,24 +351,26 @@ def render_e(fr, prec, alt, upper, strip):
"-" if exp < 0 else "+", abs(exp))
-def convert_float(value, spec, cache):
+def convert_float(value, spec, mant_bits, cache):
"""Convert VALUE, an exact Fraction. CACHE memoizes rendered digits for
the value currently being converted."""
conv = spec.conv
neg = value < 0 or (value == 0 and spec.neg_zero)
fr = -value if value < 0 else value
prec = 6 if spec.prec is None else spec.prec
- upper = conv in "EFG"
+ upper = conv in UPPER_CONVS
# The generator iterates a large number of flag and field width
# combinations over each value, so the same digits are called for over
# and over. They depend only on the magnitude, the precision and the
# alternative form, which is what keeps the wider types inexpensive
# despite the arithmetic being exact.
- key = (conv, prec, spec.alt)
+ key = (conv, spec.prec, spec.alt)
body = cache.get(key)
if body is None:
- if conv in "gG":
+ if conv in "aA":
+ body = render_a(fr, mant_bits, spec.prec, spec.alt, upper)
+ elif conv in "gG":
sig = 1 if prec == 0 else prec
_, exp = decimal_digits(fr, sig) if fr != 0 else ("", 0)
if -4 <= exp < sig:
@@ -321,7 +385,10 @@ def convert_float(value, spec, cache):
body = render_f(fr, prec, spec.alt, strip=False)
cache[key] = body
- return spec.pad(body, zero_ok=True, sign=spec.sign_of(neg))
+ sign = spec.sign_of(neg)
+ if conv in "aA":
+ sign += "0X" if upper else "0x"
+ return spec.pad(body, zero_ok=True, sign=sign)
class Block:
@@ -369,7 +436,8 @@ class Block:
if conv in FLOAT_CONVS:
if self.special is not None:
return convert_special(self.special, self.neg_zero, spec)
- return convert_float(self.value, spec, self.cache)
+ return convert_float(self.value, spec, self.mant_bits,
+ self.cache)
if conv in INT_CONVS:
return convert_int(self.value, spec)
if conv == "c":
--
2.54.0