mirror of
git://sourceware.org/git/glibc.git
synced 2026-09-08 23:58:31 +08:00
stdio-common: Handle subnormal values in the printf format tests
The reference implementation used to verify the a and A conversions assumed the value it was given was normal, splitting it into a significand of the full width and an exponent. That is not true below the smallest normal value, where the exponent can go no lower and the significand loses bits from the top instead, which is what makes the leading hexadecimal digit of a subnormal come out as zero. Given a subnormal it would have produced a normalized result such as 0x1p-1070 where we print 0x0.000000000001p-1022. Telling the two apart needs the minimum exponent for the type, which was not among the data the test program supplies, so add it as a MINEXP definition reported in a record of its own next to the working precision. Then stop normalizing once that exponent is reached, and pad the digits produced on the left, as there are no longer enough of them to fill the field on their own. The remaining conversions are unaffected: they work from the value itself and never needed it decomposed. How far the exponent has to be shifted to sit after the significand depends on the working precision, so hold MINEXP as reported and combine the two only once a value is due to be converted, rather than requiring the records to arrive in a particular order. Where no MINEXP record arrives the type has no subnormals and no clamping is applied. None of the values iterated over were subnormal, so this could not be observed. Add DBL_TRUE_MIN and LDBL_TRUE_MIN to cover it, which also exercises the smallest exponent with the remaining conversions. How many bits the leading hexadecimal digit holds varies with the type, one for a 53 bit significand and four for a 64 bit one, so both are needed: the wider case lands on a different exponent than the minimum for the type, with LDBL_TRUE_MIN coming out as 0x0.000000000000001p-16385 rather than at the p-16382 that the leading digit of a normal value would sit at. One sign is enough for either, as nothing in the sign handling depends on the value being subnormal, and the records these produce are among the most expensive in the test suite. Tested on x86_64-linux-gnu, where all 672 results pass. Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
This commit is contained in:
committed by
Adhemerval Zanella
parent
1d2f28b459
commit
71e40f25cf
@@ -26,10 +26,11 @@
|
||||
#define REF_FMT ".35e"
|
||||
#define REF_VAL(v) (v)
|
||||
#define PREC DBL_MANT_DIG
|
||||
#define MINEXP DBL_MIN_EXP
|
||||
typedef double type_t;
|
||||
static const type_t vals[] =
|
||||
{ -HUGE_VAL, -DBL_MAX, -DBL_MIN, -0.0, -NAN, NAN, 0, DBL_MIN,
|
||||
99.9, DBL_MAX, HUGE_VAL };
|
||||
{ -HUGE_VAL, -DBL_MAX, -DBL_MIN, -0.0, -NAN, NAN, 0, DBL_TRUE_MIN,
|
||||
DBL_MIN, 99.9, DBL_MAX, HUGE_VAL };
|
||||
static const char length[] = "";
|
||||
|
||||
#include "tst-printf-format-skeleton.c"
|
||||
|
||||
@@ -27,10 +27,11 @@
|
||||
#define REF_FMT ".35Le"
|
||||
#define REF_VAL(v) (v)
|
||||
#define PREC LDBL_MANT_DIG
|
||||
#define MINEXP LDBL_MIN_EXP
|
||||
typedef long double type_t;
|
||||
static const type_t vals[] =
|
||||
{ -HUGE_VAL, -LDBL_MAX, -LDBL_MIN, -0.0, -NAN, NAN, 0, LDBL_MIN,
|
||||
99.9L, LDBL_MAX, HUGE_VAL };
|
||||
{ -HUGE_VAL, -LDBL_MAX, -LDBL_MIN, -0.0, -NAN, NAN, 0, LDBL_TRUE_MIN,
|
||||
LDBL_MIN, 99.9L, LDBL_MAX, HUGE_VAL };
|
||||
static const char length[] = "L";
|
||||
|
||||
#ifndef TIMEOUT
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
the argument type handled; usually for floating-point
|
||||
conversions only, but it may be required for 128-bit or
|
||||
wider integer data types as well.
|
||||
MINEXP [optional] Minimum exponent integer constant. Set to the
|
||||
*_MIN_EXP value for the argument type handled, so that
|
||||
subnormal values can be told apart from normal ones.
|
||||
|
||||
Typedefs:
|
||||
type_t Variadic function argument type. Define to the promoted
|
||||
@@ -79,6 +82,11 @@
|
||||
#ifndef PREC
|
||||
# define PREC 0
|
||||
#endif
|
||||
/* Set to the minimum exponent for the type handled; zero where there is
|
||||
no such thing, in which case no value can be subnormal. */
|
||||
#ifndef MINEXP
|
||||
# define MINEXP 0
|
||||
#endif
|
||||
|
||||
/* The list of conversions permitted for the '#' flag, the '0' flag,
|
||||
and precision respectively. */
|
||||
@@ -294,8 +302,13 @@ do_printf_flags (char *fmt, size_t idx, const char *l, char c, type_t val)
|
||||
|
||||
prec:<PREC>
|
||||
|
||||
is produced at the beginning. Then for each VAL from VALS a block
|
||||
of records is produced starting with:
|
||||
is produced at the beginning, followed by this one if MINEXP is
|
||||
nonzero:
|
||||
|
||||
minexp:<MINEXP>
|
||||
|
||||
Then for each VAL from VALS a block of records is produced starting
|
||||
with:
|
||||
|
||||
val:<VAL>
|
||||
|
||||
@@ -328,6 +341,12 @@ do_test (int argc, char *argv[])
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (MINEXP != 0 && printf ("minexp:%i\n", MINEXP) < 0)
|
||||
{
|
||||
perror ("printf");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
c = *argv[1];
|
||||
for (v = 0; v < array_length (vals); v++)
|
||||
{
|
||||
|
||||
@@ -286,18 +286,26 @@ 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).
|
||||
def to_significand(fr, mant_bits, min_exp):
|
||||
"""Split the positive Fraction FR into an integer significand 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."""
|
||||
The significand has exactly MANT_BITS bits for a normal value. Below
|
||||
the smallest normal the exponent cannot go any lower, so it stops at
|
||||
MIN_EXP and the significand loses bits from the top instead, which is
|
||||
what makes the leading hexadecimal digit of a subnormal come out zero.
|
||||
MIN_EXP is None where the format has no subnormals. FR comes from a
|
||||
value of this format, so the split is exact either way."""
|
||||
num, den, exp = normalize(fr.numerator, fr.denominator, mant_bits)
|
||||
if min_exp is not None and exp < min_exp:
|
||||
# Subnormal: scale back up to the smallest exponent the format has,
|
||||
# which drops the significand below its full width.
|
||||
den <<= min_exp - exp
|
||||
exp = min_exp
|
||||
return num // den, exp
|
||||
|
||||
|
||||
def render_a(fr, mant_bits, prec, alt, upper):
|
||||
def render_a(fr, mant_bits, min_exp, 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
|
||||
@@ -305,14 +313,15 @@ def render_a(fr, mant_bits, prec, alt, upper):
|
||||
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."""
|
||||
from 8 to f. A subnormal has no bit to put there and so leads with a
|
||||
zero, its exponent being the smallest the format provides."""
|
||||
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
|
||||
mant, exp = to_significand(fr, mant_bits, min_exp)
|
||||
digits = ("%x" % mant).zfill(1 + nfrac)
|
||||
exp += 4 * nfrac
|
||||
|
||||
if prec is None:
|
||||
@@ -351,7 +360,7 @@ def render_e(fr, prec, alt, upper, strip):
|
||||
"-" if exp < 0 else "+", abs(exp))
|
||||
|
||||
|
||||
def convert_float(value, spec, mant_bits, cache):
|
||||
def convert_float(value, spec, mant_bits, min_exp, cache):
|
||||
"""Convert VALUE, an exact Fraction. CACHE memoizes rendered digits for
|
||||
the value currently being converted."""
|
||||
conv = spec.conv
|
||||
@@ -369,7 +378,8 @@ def convert_float(value, spec, mant_bits, cache):
|
||||
body = cache.get(key)
|
||||
if body is None:
|
||||
if conv in "aA":
|
||||
body = render_a(fr, mant_bits, spec.prec, spec.alt, upper)
|
||||
body = render_a(fr, mant_bits, min_exp, 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)
|
||||
@@ -395,11 +405,13 @@ class Block:
|
||||
"""The state a run of records shares: the C type they exercise and the
|
||||
single value they all convert."""
|
||||
|
||||
__slots__ = ("mant_bits", "val_text", "value", "special",
|
||||
"neg_zero", "cache")
|
||||
__slots__ = ("mant_bits", "type_min_exp", "min_exp", "val_text",
|
||||
"value", "special", "neg_zero", "cache")
|
||||
|
||||
def __init__(self):
|
||||
self.mant_bits = 0
|
||||
self.type_min_exp = None
|
||||
self.min_exp = None
|
||||
self.set_value("")
|
||||
|
||||
def set_value(self, text):
|
||||
@@ -413,6 +425,12 @@ class Block:
|
||||
def interpret(self, conv):
|
||||
"""Interpret the value text as the C type conversion CONV takes."""
|
||||
if conv in FLOAT_CONVS:
|
||||
# *_MIN_EXP is the exponent of the smallest normal value with
|
||||
# the radix point before the significand; shift it to sit after
|
||||
# the significand, as to_significand expects. Where it was not
|
||||
# reported the type has no subnormals.
|
||||
self.min_exp = (None if self.type_min_exp is None
|
||||
else self.type_min_exp - self.mant_bits)
|
||||
lowered = self.val_text.lower()
|
||||
if "inf" in lowered or "nan" in lowered:
|
||||
self.special = "nan" if "nan" in lowered else "inf"
|
||||
@@ -437,7 +455,7 @@ class Block:
|
||||
if self.special is not None:
|
||||
return convert_special(self.special, self.neg_zero, spec)
|
||||
return convert_float(self.value, spec, self.mant_bits,
|
||||
self.cache)
|
||||
self.min_exp, self.cache)
|
||||
if conv in INT_CONVS:
|
||||
return convert_int(self.value, spec)
|
||||
if conv == "c":
|
||||
@@ -484,6 +502,8 @@ def main():
|
||||
|
||||
if raw.startswith("prec:"):
|
||||
block.mant_bits = int(raw[5:])
|
||||
elif raw.startswith("minexp:"):
|
||||
block.type_min_exp = int(raw[7:])
|
||||
elif raw.startswith("val:"):
|
||||
block.set_value(raw[4:])
|
||||
elif raw.startswith("%"):
|
||||
|
||||
Reference in New Issue
Block a user