gawk-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[SCM] gawk branch, gawk-5.3-stable, updated. gawk-4.1.0-5497-g21b4a113


From: Arnold Robbins
Subject: [SCM] gawk branch, gawk-5.3-stable, updated. gawk-4.1.0-5497-g21b4a113
Date: Thu, 8 Aug 2024 00:07:41 -0400 (EDT)

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "gawk".

The branch, gawk-5.3-stable has been updated
       via  21b4a1139494fd22530d42ce8dfa784cd7c7c8d2 (commit)
      from  891341aadec38fe62f7f4224ca53e20a36614386 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://git.sv.gnu.org/cgit/gawk.git/commit/?id=21b4a1139494fd22530d42ce8dfa784cd7c7c8d2

commit 21b4a1139494fd22530d42ce8dfa784cd7c7c8d2
Author: Arnold D. Robbins <arnold@skeeve.com>
Date:   Thu Aug 8 07:06:46 2024 +0300

    Squashed commit of the following:
    
    commit 8a88c089d0aab4cb416b36a985cb4fd3367cf6ae
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Aug 6 21:42:45 2024 +0300
    
        Fix some additional printf corner cases and add tests.
    
    commit f76ca80261ac84e129c1b553ff35a0ae0959f524
    Merge: c8bfdff2 891341aa
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Mon Aug 5 21:18:07 2024 +0300
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit c8bfdff2b370d7b98c95894ef5290adb416ebedf
    Merge: 267d7fce d60aa167
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 30 19:45:26 2024 +0300
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit 267d7fcedb22f44f346777367ac3f6162ebcf01e
    Merge: c2a59fef 84d2a184
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sun Jul 28 18:41:17 2024 +0300
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit c2a59fef9a5b8cc315050bfb56305e58d358f325
    Merge: e7e229d9 16284502
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Fri Jul 26 11:58:46 2024 +0300
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit e7e229d94e0b7f31f53faa8956888f82bb4dbb97
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 23 22:30:48 2024 +0300
    
        Printf refactoring finished!
    
    commit 7731d9fd9fa4d0a27e15c8da5cbd1e4d3a515b0f
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sun Jul 21 21:37:21 2024 +0300
    
        Start on simplifying the code.
    
    commit 35f3fd8439e6a7d168c3221a4326e0eaef594c70
    Merge: 8700b5dd f4b9d38f
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sat Jul 20 22:51:36 2024 +0300
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit 8700b5dd514cc70fc29c3df5c40d2a8935c43307
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sat Jul 20 22:41:52 2024 +0300
    
        More printf fixes.
    
    commit 8d1495bf9b1b502a69e3ce593c4e1bbf30ccff1f
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Wed Jul 17 19:13:18 2024 +0300
    
        Start on simplification.
    
    commit 7fdd89f3b30bd6158d7e5e19966e8b0cb72e0077
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Wed Jul 17 12:00:56 2024 +0300
    
        Update NEWS.
    
    commit 6cb660788af1d2e1a04b00f84c5c4af638a85ab3
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 16 20:51:33 2024 +0300
    
        Continue cleaning up printf code.
    
    commit 74f3b30538985e5d66cf2932cec33ff044170127
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 16 08:08:41 2024 +0300
    
        Considerable code cleanup.
    
    commit 057d4307ed06d0247c8756e1d18312f37e22fe90
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 16 06:17:49 2024 +0300
    
        Passing tests. Cleanup still needed.
    
    commit 3575846a3d015fa7132a398e5c4779d6e623f2d6
    Merge: 964a4516 8d55806d
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sun Jul 14 16:04:05 2024 +0300
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit 964a4516d5cf3e7ec4191f3559b0358d6562c762
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sun Jul 14 15:56:05 2024 +0300
    
        Checkpoint commit. -M and all but printf-corners passing.
    
    commit 75f7cf551720c1a240626c10f37972cf3563ddc3
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sun Jul 14 14:37:37 2024 +0300
    
        Checkpiont commit, passing all tests!
    
    commit fd33a7cd1eb2c5372e30d5e79a3164cd2fdc133e
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sat Jul 13 22:21:40 2024 +0300
    
        More printf progress.
    
    commit 8f14d054736a4d1b5daee636dbfebac923ba7fdf
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Thu Jul 11 18:13:10 2024 +0300
    
        Continuing on printf, good progress.
    
    commit 61a8624fdf8827582b00d962f6d6a8ad5a5f5264
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Thu Jul 11 17:47:38 2024 +0300
    
        Update pc/Makefile.tst.
    
    commit 60699f3c1a830a8ba4d5c41a69b7e25639ed27e3
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Thu Jul 11 16:43:19 2024 +0300
    
        More test cases in printf-corners test.
    
    commit a7f5d4361defc621dc32242f4135e5573bcb360d
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Thu Jul 11 13:32:08 2024 +0300
    
        Add new test case, printf-corners.
    
    commit 1cd00fb6721736922f01c19d2455ebe1d5b3950c
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Thu Jul 11 13:25:30 2024 +0300
    
        Continuing to improve printf.
    
    commit a6726a26cc61aaee4043cd3cae3237f46df27c48
    Merge: dd7bd9eb d998e324
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Thu Jul 11 12:22:54 2024 +0300
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit dd7bd9eb8b92426bd6134935a20361211f1de3c2
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 9 20:58:41 2024 +0300
    
        More fixes in printf.
    
    commit e7028c03c5ff4dbe05cb83d02706f4752622f600
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 9 20:17:32 2024 +0300
    
        More printf fixes. Checkpoint commit.
    
    commit 7b9e9bca203c92acaa6b3cd107764be319994038
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Sun Jul 7 22:21:31 2024 +0300
    
        Continuing to refactor printf.
    
    commit dde70cc78f2cfc69439c35713575158d14366567
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Fri Jul 5 16:56:21 2024 +0300
    
        Progress with printf. Checkpoint commit.
    
    commit 8d18169d7124cee926d1755e64c6eb5ae3edef20
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 2 21:50:10 2024 +0300
    
        Start refactoring format_tree.
    
    commit 95fc5822f3f0951df7371000e6cf255df0138643
    Merge: ecf1b719 888281ff
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Tue Jul 2 03:01:21 2024 -0600
    
        Merge branch 'gawk-5.3-stable' into stable/printf-rework
    
    commit ecf1b719040196b391d0aadae276036c893d97c4
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Thu Jun 27 11:13:37 2024 +0300
    
        Improvement in hsprint test.
    
    commit e3de4360243b7842c849f26d9e9093b25a0fa1fa
    Author: Arnold D. Robbins <arnold@skeeve.com>
    Date:   Wed Jun 26 19:04:24 2024 +0300
    
        Split printf functions into a new file.

diff --git a/ChangeLog b/ChangeLog
index 24d29e4a..358d3274 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,16 +1,122 @@
+2024-08-06         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c (format_unsigned_integer, format_signed_integer):
+       Additional fixes. New added tests pass.
+
 2024-07-26         Arnold D. Robbins     <arnold@skeeve.com>
 
        * re.c (check_bracket_exp): Make the code actually work.
 
+2024-07-23         Arnold D. Robbins     <arnold@skeeve.com>
+
+       Finish cleaning up the printf refactoring. All tests run, both
+       regular and -M. Test suite is valgrind clean!
+
+       Summary of changes:
+       * printf.c: New file.
+       (format_args): Moved here and renamed from format_tree.  Considerably
+       simplified by moving code out into new routines.
+       (struct flags): New struct.
+       (add_thousands, do_printf, do_sprintf, format_nan_inf, mbc_byte_count,
+       mbc_char_count, out_of_range, printf_common, reverse): Moved to here.
+       (add_alt_format, add_plus_or_space_and_fill, adjust_flags, 
compute_zero_flag,
+       fill_to_field_width, format_float, format_integer_digits,
+       format_mpg_integer_digits, format_out_of_range, format_signed_integer,
+       format_unsigned_integer, zero_fill_to_precision): New routines.
+
+2024-07-21         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Simplify printing of signed integers; move several
+       common tasks out into separate routines, and use char * instead
+       of const char * to avoid a lot of casting.
+
 2024-07-20         Tim Rice              <trice@posteo.net>
 
        * io.c (csvscan): Set the sentinel if we found CR-LF.
 
+2024-07-20         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Continuing fixes and cleanups.
+
+2024-07-17         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * NEWS: Updated.
+       * printf.c (fill_to_field_width): New function.
+       (format_out_of_range, format_unsigned_integer): Start to use it.
+
+2024-07-16         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Continue cleaning up code and removing now-dead code.
+
+2024-07-16         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * awk.h (format_args): Renamed from format_tree. Remove
+       format_tree_old.
+       * debug.c, main.c, mpfr.c, node.c: Adjusted.
+       * printf.c (format_args): Renamed. Removed all old, factored-out
+       code.
+       (format_tree_old): Removed.
+
+2024-07-16         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Use mpfr_sprintf() just to generate digits.
+       All tests now passing, even with -M.
+
+2024-07-13         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Continued progress against the printf-corners test.
+
+2024-07-11         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Signed and unsigned integer printing and float printing
+       working for current tests. MPFR integer printing also for current
+       tests. A few tests still to go in printf-corners.
+
+2024-07-09         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Signed printing I think is working. Unsigned is better.
+       Checkpoint commit.
+
+2024-07-07         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Signed and unsigned integer printing mostly working now.
+       Still to go: Finish that up, get GMP/MPFR integer printing working.
+
+2024-07-05         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf.c: Lots of cleanup. Floating point format pretty
+       much working. Signed integer printing getting there.
+       Unsigned integer printing still to do.
+
+2024-07-02         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * awk.h (format_tree_new, format_tree_old): Add declarations.
+       (format_tree): Temporarily make into a function.
+       * main.c (format_tree): Declare it.
+       (main): Check PRINTF_NEW environment variable and if so,
+       use the new version. Otherwise use the old one.
+       * printf.c (struct flags): Declare.
+       (format_tree_old): The original version.
+       (format_tree_new): The new one, being refactored.
+       (add_thousands): Use the global `loc' variable, not a parameter.
+       static const char *format_integer(NODE *arg, struct flags *flags);
+       (format_mpg_integer, format_float, format_out_of_range,
+       compute_zero_flag): New functions.
+
 2024-07-02         Arnold D. Robbins     <arnold@skeeve.com>
 
        * re.c (make_regexp): \u escapes also now treated literally
        for do_traditional.
 
+2024-06-26         Arnold D. Robbins     <arnold@skeeve.com>
+
+       Start on printf rework.
+
+       * printf.c: New file, with stuff for printf in it.
+       * builtin.c: Move printf functions into printf.c.
+       * awk.h (efwrite): Add declaration.
+       * Makefile.am (base_source): Added printf.c.
+
 2024-06-21         Arnold D. Robbins     <arnold@skeeve.com>
 
        * io.c (csvscan): Add a missing buffer overrun check. Restores
diff --git a/Makefile.am b/Makefile.am
index 9f5e40c6..a9149e9a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -102,6 +102,7 @@ base_sources = \
        msg.c \
        node.c \
        nonposix.h \
+       printf.c \
        profile.c \
        protos.h \
        re.c \
diff --git a/Makefile.in b/Makefile.in
index 6c92f48a..67b47bb0 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -152,9 +152,9 @@ am__objects_1 = array.$(OBJEXT) awkgram.$(OBJEXT) 
builtin.$(OBJEXT) \
        eval.$(OBJEXT) ext.$(OBJEXT) field.$(OBJEXT) \
        floatcomp.$(OBJEXT) gawkapi.$(OBJEXT) gawkmisc.$(OBJEXT) \
        int_array.$(OBJEXT) io.$(OBJEXT) main.$(OBJEXT) mpfr.$(OBJEXT) \
-       msg.$(OBJEXT) node.$(OBJEXT) profile.$(OBJEXT) re.$(OBJEXT) \
-       replace.$(OBJEXT) str_array.$(OBJEXT) symbol.$(OBJEXT) \
-       version.$(OBJEXT)
+       msg.$(OBJEXT) node.$(OBJEXT) printf.$(OBJEXT) \
+       profile.$(OBJEXT) re.$(OBJEXT) replace.$(OBJEXT) \
+       str_array.$(OBJEXT) symbol.$(OBJEXT) version.$(OBJEXT)
 am_gawk_OBJECTS = $(am__objects_1)
 gawk_OBJECTS = $(am_gawk_OBJECTS)
 gawk_LDADD = $(LDADD)
@@ -212,10 +212,10 @@ am__depfiles_remade = ./$(DEPDIR)/array.Po 
./$(DEPDIR)/awkgram.Po \
        ./$(DEPDIR)/floatcomp.Po ./$(DEPDIR)/gawkapi.Po \
        ./$(DEPDIR)/gawkmisc.Po ./$(DEPDIR)/int_array.Po \
        ./$(DEPDIR)/io.Po ./$(DEPDIR)/main.Po ./$(DEPDIR)/mpfr.Po \
-       ./$(DEPDIR)/msg.Po ./$(DEPDIR)/node.Po ./$(DEPDIR)/profile.Po \
-       ./$(DEPDIR)/re.Po ./$(DEPDIR)/replace.Po \
-       ./$(DEPDIR)/str_array.Po ./$(DEPDIR)/symbol.Po \
-       ./$(DEPDIR)/version.Po
+       ./$(DEPDIR)/msg.Po ./$(DEPDIR)/node.Po ./$(DEPDIR)/printf.Po \
+       ./$(DEPDIR)/profile.Po ./$(DEPDIR)/re.Po \
+       ./$(DEPDIR)/replace.Po ./$(DEPDIR)/str_array.Po \
+       ./$(DEPDIR)/symbol.Po ./$(DEPDIR)/version.Po
 am__mv = mv -f
 COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
        $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
@@ -542,6 +542,7 @@ base_sources = \
        msg.c \
        node.c \
        nonposix.h \
+       printf.c \
        profile.c \
        protos.h \
        re.c \
@@ -740,6 +741,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mpfr.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/msg.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node.Po@am__quote@ # 
am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/printf.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/profile.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/re.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replace.Po@am__quote@ # 
am--include-marker
@@ -1166,6 +1168,7 @@ distclean: distclean-recursive
        -rm -f ./$(DEPDIR)/mpfr.Po
        -rm -f ./$(DEPDIR)/msg.Po
        -rm -f ./$(DEPDIR)/node.Po
+       -rm -f ./$(DEPDIR)/printf.Po
        -rm -f ./$(DEPDIR)/profile.Po
        -rm -f ./$(DEPDIR)/re.Po
        -rm -f ./$(DEPDIR)/replace.Po
@@ -1238,6 +1241,7 @@ maintainer-clean: maintainer-clean-recursive
        -rm -f ./$(DEPDIR)/mpfr.Po
        -rm -f ./$(DEPDIR)/msg.Po
        -rm -f ./$(DEPDIR)/node.Po
+       -rm -f ./$(DEPDIR)/printf.Po
        -rm -f ./$(DEPDIR)/profile.Po
        -rm -f ./$(DEPDIR)/re.Po
        -rm -f ./$(DEPDIR)/replace.Po
diff --git a/NEWS b/NEWS
index 01f25825..900f06a5 100644
--- a/NEWS
+++ b/NEWS
@@ -26,7 +26,11 @@ Changes from 5.3.0 to 5.3.x
 7. The never-documented --nostalgia option has been removed. It was
    causing bug reports.
 
-8. As usual, there have been several minor code cleanups and bug fixes.
+8. The implementation of printf/sprintf has been thoroughly reworked
+   in order to make the code more maintainable and to fix a goodly
+   number of corner cases.
+
+9. As usual, there have been several minor code cleanups and bug fixes.
    See the ChangeLog for details.
 
 Changes from 5.2.x to 5.3.0
diff --git a/awk.h b/awk.h
index 7a07f1ef..9997534d 100644
--- a/awk.h
+++ b/awk.h
@@ -1483,6 +1483,8 @@ extern NODE *make_regnode(NODETYPE type, NODE *exp);
 extern bool validate_qualified_name(char *token);
 /* builtin.c */
 extern void efflush(FILE *fp, const char *from, struct redirect *rp);
+extern void efwrite(const void *ptr, size_t size, size_t count, FILE *fp, 
const char *from,
+               struct redirect *rp, bool flush);
 extern double double_to_int(double d);
 extern NODE *do_exp(int nargs);
 extern NODE *do_fflush(int nargs);
@@ -1514,7 +1516,7 @@ extern NODE *do_sub(int nargs, unsigned int flags);
 extern NODE *call_sub(const char *name, int nargs);
 extern NODE *call_match(int nargs);
 extern NODE *call_split_func(const char *name, int nargs);
-extern NODE *format_tree(const char *, size_t, NODE **, long);
+extern NODE *format_args(const char *, size_t, NODE **, long);
 extern NODE *do_lshift(int nargs);
 extern NODE *do_rshift(int nargs);
 extern NODE *do_and(int nargs);
diff --git a/builtin.c b/builtin.c
index 275456b3..3c4c8f91 100644
--- a/builtin.c
+++ b/builtin.c
@@ -61,11 +61,6 @@
 #define SIZE_MAX ((size_t) -1)
 #endif
 
-#define DEFAULT_G_PRECISION 6
-
-static size_t mbc_byte_count(const char *ptr, size_t numchars);
-static size_t mbc_char_count(const char *ptr, size_t numbytes);
-
 /* Can declare these, since we always use the random shipped with gawk */
 extern char *initstate(unsigned long seed, char *state, long n);
 extern char *setstate(char *state);
@@ -78,7 +73,6 @@ extern NODE **fields_arr;
 extern bool output_is_tty;
 extern FILE *output_fp;
 
-static const char *add_thousands(const char *original, struct lconv *loc);
 static int gawk_system(const char *command);
 
 #define POP_TWO_SCALARS(s1, s2) \
@@ -156,7 +150,7 @@ efflush(FILE *fp, const char *from, struct redirect *rp)
 
 /* efwrite --- like fwrite, but with error checking */
 
-static void
+void
 efwrite(const void *ptr,
        size_t size,
        size_t count,
@@ -656,1195 +650,6 @@ do_log(int nargs)
        return make_number((AWKNUM) d);
 }
 
-
-#ifdef HAVE_MPFR
-
-/*
- * mpz2mpfr --- convert an arbitrary-precision integer to a float
- *     without any loss of precision. The returned value is only
- *     good for temporary use.
- */
-
-
-static mpfr_ptr
-mpz2mpfr(mpz_ptr zi)
-{
-       size_t prec;
-       static mpfr_t mpfrval;
-       static bool inited = false;
-       int tval;
-
-       /* estimate minimum precision for exact conversion */
-       prec = mpz_sizeinbase(zi, 2);   /* most significant 1 bit position 
starting at 1 */
-       prec -= (size_t) mpz_scan1(zi, 0);      /* least significant 1 bit 
index starting at 0 */
-       if (prec < MPFR_PREC_MIN)
-               prec = MPFR_PREC_MIN;
-       else if (prec > MPFR_PREC_MAX)
-               prec = MPFR_PREC_MAX;
-
-       if (! inited) {
-               mpfr_init2(mpfrval, prec);
-               inited = true;
-       } else
-               mpfr_set_prec(mpfrval, prec);
-       tval = mpfr_set_z(mpfrval, zi, ROUND_MODE);
-       IEEE_FMT(mpfrval, tval);
-       return mpfrval;
-}
-#endif
-
-/*
- * format_tree() formats arguments of sprintf,
- * and accordingly to a fmt_string providing a format like in
- * printf family from C library.  Returns a string node which value
- * is a formatted string.  Called by  sprintf function.
- *
- * It is one of the uglier parts of gawk.  Thanks to Michal Jaegermann
- * for taming this beast and making it compatible with ANSI C.
- */
-
-NODE *
-format_tree(
-       const char *fmt_string,
-       size_t n0,
-       NODE **the_args,
-       long num_args)
-{
-/* copy 'l' bytes from 's' to 'obufout' checking for space in the process */
-/* difference of pointers should be of ptrdiff_t type, but let us be kind */
-#define bchunk(s, l) if (l) { \
-       while ((l) > ofre) { \
-               size_t olen = obufout - obuf; \
-               erealloc(obuf, char *, osiz * 2, "format_tree"); \
-               ofre += osiz; \
-               osiz *= 2; \
-               obufout = obuf + olen; \
-       } \
-       memcpy(obufout, s, (size_t) (l)); \
-       obufout += (l); \
-       ofre -= (l); \
-}
-
-/* copy one byte from 's' to 'obufout' checking for space in the process */
-#define bchunk_one(s) { \
-       if (ofre < 1) { \
-               size_t olen = obufout - obuf; \
-               erealloc(obuf, char *, osiz * 2, "format_tree"); \
-               ofre += osiz; \
-               osiz *= 2; \
-               obufout = obuf + olen; \
-       } \
-       *obufout++ = *s; \
-       --ofre; \
-}
-
-/* Is there space for something L big in the buffer? */
-#define chksize(l)  if ((l) >= ofre) { \
-       size_t olen = obufout - obuf; \
-       size_t delta = osiz+l-ofre; \
-       erealloc(obuf, char *, osiz + delta, "format_tree"); \
-       obufout = obuf + olen; \
-       ofre += delta; \
-       osiz += delta; \
-}
-
-       size_t cur_arg = 0;
-       NODE *r = NULL;
-       int i, nc;
-       bool toofew = false;
-       char *obuf, *obufout;
-       size_t osiz, ofre, olen_final;
-       const char *chbuf;
-       const char *s0, *s1;
-       int cs1;
-       NODE *arg;
-       long fw, prec, argnum;
-       bool used_dollar;
-       bool lj, alt, have_prec, need_format;
-       long *cur = NULL;
-       uintmax_t uval;
-       bool sgn;
-       int base;
-       /*
-        * Although this is an array, the elements serve two different
-        * purposes. The first element is the general buffer meant
-        * to hold the entire result string.  The second one is a
-        * temporary buffer for large floating point values. They
-        * could just as easily be separate variables, and the
-        * code might arguably be clearer.
-        */
-       struct {
-               char *buf;
-               size_t bufsize;
-               char stackbuf[30];
-       } cpbufs[2];
-#define cpbuf  cpbufs[0].buf
-       char *cend = &cpbufs[0].stackbuf[sizeof(cpbufs[0].stackbuf)];
-       char *cp;
-       const char *fill;
-       AWKNUM tmpval = 0.0;
-       char signchar = '\0';
-       size_t len;
-       bool zero_flag = false;
-       bool quote_flag = false;
-       int ii, jj;
-       char *chp;
-       size_t copy_count, char_count;
-       char *nan_inf_val;
-       bool magic_posix_flag;
-#ifdef HAVE_MPFR
-       mpz_ptr zi;
-       mpfr_ptr mf;
-#endif
-       enum { MP_NONE = 0, MP_INT_WITH_PREC = 1, MP_INT_WITHOUT_PREC, MP_FLOAT 
} fmt_type;
-
-       static const char sp[] = " ";
-       static const char zero_string[] = "0";
-       static const char lchbuf[] = "0123456789abcdef";
-       static const char Uchbuf[] = "0123456789ABCDEF";
-       static const char bad_modifiers[] = "hjlLtz";
-       static bool warned[sizeof(bad_modifiers)-1];    // auto-init to zero
-
-       bool modifier_seen[sizeof(bad_modifiers)-1];
-#define modifier_index(c)  (strchr(bad_modifiers, c) - bad_modifiers)
-
-#define INITIAL_OUT_SIZE       64
-       emalloc(obuf, char *, INITIAL_OUT_SIZE, "format_tree");
-       obufout = obuf;
-       osiz = INITIAL_OUT_SIZE;
-       ofre = osiz - 1;
-
-       cur_arg = 1;
-
-       {
-               size_t k;
-               for (k = 0; k < sizeof(cpbufs)/sizeof(cpbufs[0]); k++) {
-                       cpbufs[k].bufsize = sizeof(cpbufs[k].stackbuf);
-                       cpbufs[k].buf = cpbufs[k].stackbuf;
-               }
-       }
-
-       /*
-        * The point of this goop is to grow the buffer
-        * holding the converted number, so that large
-        * values don't overflow a fixed length buffer.
-        */
-#define PREPEND(CH) do {       \
-       if (cp == cpbufs[0].buf) {      \
-               char *prev = cpbufs[0].buf;     \
-               emalloc(cpbufs[0].buf, char *, 2*cpbufs[0].bufsize, \
-                       "format_tree"); \
-               memcpy((cp = cpbufs[0].buf+cpbufs[0].bufsize), prev,    \
-                      cpbufs[0].bufsize);      \
-               cpbufs[0].bufsize *= 2; \
-               if (prev != cpbufs[0].stackbuf) \
-                       efree(prev);    \
-               cend = cpbufs[0].buf+cpbufs[0].bufsize; \
-       }       \
-       *--cp = (CH);   \
-} while(0)
-
-       /*
-        * Check first for use of `count$'.
-        * If plain argument retrieval was used earlier, choke.
-        *      Otherwise, return the requested argument.
-        * If not `count$' now, but it was used earlier, choke.
-        * If this format is more than total number of args, choke.
-        * Otherwise, return the current argument.
-        */
-#define parse_next_arg() { \
-       if (argnum > 0) { \
-               if (cur_arg > 1) { \
-                       msg(_("fatal: must use `count$' on all formats or 
none")); \
-                       goto out; \
-               } \
-               arg = the_args[argnum]; \
-       } else if (used_dollar) { \
-               msg(_("fatal: must use `count$' on all formats or none")); \
-               arg = 0; /* shutup the compiler */ \
-               goto out; \
-       } else if (cur_arg >= num_args) { \
-               arg = 0; /* shutup the compiler */ \
-               toofew = true; \
-               break; \
-       } else { \
-               arg = the_args[cur_arg]; \
-               cur_arg++; \
-       } \
-}
-
-       need_format = false;
-       used_dollar = false;
-
-       s0 = s1 = fmt_string;
-       while (n0-- > 0) {
-               if (*s1 != '%') {
-                       s1++;
-                       continue;
-               }
-               need_format = true;
-               bchunk(s0, s1 - s0);
-               s0 = s1;
-               cur = &fw;
-               fw = 0;
-               prec = 0;
-               base = 0;
-               argnum = 0;
-               base = 0;
-               have_prec = false;
-               signchar = '\0';
-               zero_flag = false;
-               quote_flag = false;
-               nan_inf_val = NULL;
-#ifdef HAVE_MPFR
-               mf = NULL;
-               zi = NULL;
-#endif
-               fmt_type = MP_NONE;
-
-               lj = alt = false;
-               memset(modifier_seen, 0, sizeof(modifier_seen));
-               magic_posix_flag = false;
-               fill = sp;
-               cp = cend;
-               chbuf = lchbuf;
-               s1++;
-
-retry:
-               if (n0-- == 0)  /* ran out early! */
-                       break;
-
-               switch (cs1 = *s1++) {
-               case (-1):      /* dummy case to allow for checking */
-check_pos:
-                       if (cur != &fw)
-                               break;          /* reject as a valid format */
-                       goto retry;
-               case '%':
-                       need_format = false;
-                       /*
-                        * 29 Oct. 2002:
-                        * The C99 standard pages 274 and 279 seem to imply that
-                        * since there's no arg converted, the field width 
doesn't
-                        * apply.  The code already was that way, but this
-                        * comment documents it, at least in the code.
-                        */
-                       if (do_lint) {
-                               const char *msg = NULL;
-
-                               if (fw && ! have_prec)
-                                       msg = _("field width is ignored for 
`%%' specifier");
-                               else if (fw == 0 && have_prec)
-                                       msg = _("precision is ignored for `%%' 
specifier");
-                               else if (fw && have_prec)
-                                       msg = _("field width and precision are 
ignored for `%%' specifier");
-
-                               if (msg != NULL)
-                                       lintwarn("%s", msg);
-                       }
-                       bchunk_one("%");
-                       s0 = s1;
-                       break;
-
-               case '0':
-                       /*
-                        * Only turn on zero_flag if we haven't seen
-                        * the field width or precision yet.  Otherwise,
-                        * screws up floating point formatting.
-                        */
-                       if (cur == & fw)
-                               zero_flag = true;
-                       if (lj)
-                               goto retry;
-                       /* fall through */
-               case '1':
-               case '2':
-               case '3':
-               case '4':
-               case '5':
-               case '6':
-               case '7':
-               case '8':
-               case '9':
-                       if (cur == NULL)
-                               break;
-                       if (prec >= 0)
-                               *cur = cs1 - '0';
-                       /*
-                        * with a negative precision *cur is already set
-                        * to -1, so it will remain negative, but we have
-                        * to "eat" precision digits in any case
-                        */
-                       while (n0 > 0 && *s1 >= '0' && *s1 <= '9') {
-                               --n0;
-                               *cur = *cur * 10 + *s1++ - '0';
-                       }
-                       if (prec < 0)   /* negative precision is discarded */
-                               have_prec = false;
-                       if (cur == &prec)
-                               cur = NULL;
-                       if (n0 == 0)    /* badly formatted control string */
-                               continue;
-                       goto retry;
-               case '$':
-                       if (do_traditional) {
-                               msg(_("fatal: `$' is not permitted in awk 
formats"));
-                               goto out;
-                       }
-
-                       if (cur == &fw) {
-                               argnum = fw;
-                               fw = 0;
-                               used_dollar = true;
-                               if (argnum <= 0) {
-                                       msg(_("fatal: argument index with `$' 
must be > 0"));
-                                       goto out;
-                               }
-                               if (argnum >= num_args) {
-                                       msg(_("fatal: argument index %ld 
greater than total number of supplied arguments"), argnum);
-                                       goto out;
-                               }
-                       } else {
-                               msg(_("fatal: `$' not permitted after period in 
format"));
-                               goto out;
-                       }
-
-                       goto retry;
-               case '*':
-                       if (cur == NULL)
-                               break;
-                       if (! do_traditional && used_dollar && ! 
isdigit((unsigned char) *s1)) {
-                               fatal(_("fatal: must use `count$' on all 
formats or none"));
-                               break;  /* silence warnings */
-                       } else if (! do_traditional && isdigit((unsigned char) 
*s1)) {
-                               int val = 0;
-
-                               for (; n0 > 0 && *s1 && isdigit((unsigned char) 
*s1); s1++, n0--) {
-                                       val *= 10;
-                                       val += *s1 - '0';
-                               }
-                               if (*s1 != '$') {
-                                       msg(_("fatal: no `$' supplied for 
positional field width or precision"));
-                                       goto out;
-                               } else {
-                                       s1++;
-                                       n0--;
-                               }
-                               // val could be less than zero if someone 
provides a field width
-                               // so large that it causes integer overflow. 
Mainly fuzzers do this,
-                               // but let's try to be good anyway.
-                               if (val < 0 || val >= num_args) {
-                                       toofew = true;
-                                       break;
-                               }
-                               arg = the_args[val];
-                       } else {
-                               parse_next_arg();
-                       }
-                       (void) force_number(arg);
-                       *cur = get_number_si(arg);
-                       if (*cur < 0 && cur == &fw) {
-                               *cur = -*cur;
-                               lj = true;
-                       }
-                       if (cur == &prec) {
-                               if (*cur >= 0)
-                                       have_prec = true;
-                               else
-                                       have_prec = false;
-                               cur = NULL;
-                       }
-                       goto retry;
-               case ' ':               /* print ' ' or '-' */
-                                       /* 'space' flag is ignored */
-                                       /* if '+' already present  */
-                       if (signchar != false)
-                               goto check_pos;
-                       /* FALL THROUGH */
-               case '+':               /* print '+' or '-' */
-                       signchar = cs1;
-                       goto check_pos;
-               case '-':
-                       if (prec < 0)
-                               break;
-                       if (cur == &prec) {
-                               prec = -1;
-                               goto retry;
-                       }
-                       fill = sp;      /* if left justified then other */
-                       lj = true;      /* filling is ignored */
-                       goto check_pos;
-               case '.':
-                       if (cur != &fw)
-                               break;
-                       cur = &prec;
-                       have_prec = true;
-                       goto retry;
-               case '#':
-                       alt = true;
-                       goto check_pos;
-               case '\'':
-#if defined(HAVE_LOCALE_H)
-                       quote_flag = true;
-                       goto check_pos;
-#else
-                       goto retry;
-#endif
-               case 'h':
-               case 'j':
-               case 'l':
-               case 'L':
-               case 't':
-               case 'z':
-                       if (modifier_seen[modifier_index(cs1)])
-                               break;
-                       else {
-                               int ind = modifier_index(cs1);
-
-                               if (do_lint && ! warned[ind]) {
-                                       lintwarn(_("`%c' is meaningless in awk 
formats; ignored"), cs1);
-                                       warned[ind] = true;
-                               }
-                               if (do_posix) {
-                                       msg(_("fatal: `%c' is not permitted in 
POSIX awk formats"), cs1);
-                                       goto out;
-                               }
-                       }
-                       modifier_seen[modifier_index(cs1)] = true;
-                       goto retry;
-
-               case 'P':
-                       if (magic_posix_flag)
-                               break;
-                       magic_posix_flag = true;
-                       goto retry;
-               case 'c':
-                       need_format = false;
-                       parse_next_arg();
-                       /* user input that looks numeric is numeric */
-                       fixtype(arg);
-                       if ((arg->flags & NUMBER) != 0) {
-                               uval = get_number_uj(arg);
-                               if (gawk_mb_cur_max > 1) {
-                                       char buf[100];
-                                       wchar_t wc;
-                                       mbstate_t mbs;
-                                       size_t count;
-
-                                       memset(& mbs, 0, sizeof(mbs));
-
-                                       /* handle systems with too small 
wchar_t */
-                                       if (sizeof(wchar_t) < 4 && uval > 
0xffff) {
-                                               if (do_lint)
-                                                       lintwarn(
-                                               _("[s]printf: value %g is too 
big for %%c format"),
-                                                                       
arg->numbr);
-
-                                               goto out0;
-                                       }
-
-                                       wc = uval;
-
-                                       count = wcrtomb(buf, wc, & mbs);
-                                       if (count == 0
-                                           || count == (size_t) -1) {
-                                               if (do_lint)
-                                                       lintwarn(
-                                               _("[s]printf: value %g is not a 
valid wide character"),
-                                                                       
arg->numbr);
-
-                                               goto out0;
-                                       }
-
-                                       memcpy(cpbuf, buf, count);
-                                       prec = count;
-                                       cp = cpbuf;
-                                       goto pr_tail;
-                               }
-out0:
-                               ;
-                               /* else,
-                                       fall through */
-
-                               cpbuf[0] = uval;
-                               prec = 1;
-                               cp = cpbuf;
-                               goto pr_tail;
-                       }
-                       /*
-                        * As per POSIX, only output first character of a
-                        * string value.  Thus, we ignore any provided
-                        * precision, forcing it to 1.  (Didn't this
-                        * used to work? 6/2003.)
-                        */
-                       cp = arg->stptr;
-                       prec = 1;
-                       /*
-                        * First character can be multiple bytes if
-                        * it's a multibyte character. Grr.
-                        */
-                       if (gawk_mb_cur_max > 1) {
-                               mbstate_t state;
-                               size_t count;
-
-                               memset(& state, 0, sizeof(state));
-                               count = mbrlen(cp, arg->stlen, & state);
-                               if (count != (size_t) -1 && count != (size_t) 
-2 && count > 0) {
-                                       prec = count;
-                                       /* may need to increase fw so that 
padding happens, see pr_tail code */
-                                       if (fw > 0)
-                                               fw += count - 1;
-                               }
-                       }
-                       goto pr_tail;
-               case 's':
-                       need_format = false;
-                       parse_next_arg();
-                       arg = force_string(arg);
-                       if (fw == 0 && ! have_prec)
-                               prec = arg->stlen;
-                       else {
-                               char_count = mbc_char_count(arg->stptr, 
arg->stlen);
-                               if (! have_prec || prec > char_count)
-                                       prec = char_count;
-                       }
-                       cp = arg->stptr;
-                       goto pr_tail;
-               case 'd':
-               case 'i':
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-
-                       /*
-                        * Check for Nan or Inf.
-                        */
-                       if (out_of_range(arg))
-                               goto out_of_range;
-#ifdef HAVE_MPFR
-                       if (is_mpg_float(arg))
-                               goto mpf0;
-                       else if (is_mpg_integer(arg))
-                               goto mpz0;
-                       else
-#endif
-                       tmpval = double_to_int(arg->numbr);
-
-                       /*
-                        * ``The result of converting a zero value with a
-                        * precision of zero is no characters.''
-                        */
-                       if (have_prec && prec == 0 && tmpval == 0)
-                               goto pr_tail;
-
-                       if (tmpval < 0) {
-                               tmpval = -tmpval;
-                               sgn = true;
-                       } else {
-                               if (tmpval == -0.0)
-                                       /* avoid printing -0 */
-                                       tmpval = 0.0;
-                               sgn = false;
-                       }
-                       /*
-                        * Use snprintf return value to tell if there
-                        * is enough room in the buffer or not.
-                        */
-                       while ((i = snprintf(cpbufs[1].buf,
-                                            cpbufs[1].bufsize, "%.0f",
-                                            tmpval)) >=
-                              cpbufs[1].bufsize) {
-                               if (cpbufs[1].buf == cpbufs[1].stackbuf)
-                                       cpbufs[1].buf = NULL;
-                               if (i > 0) {
-                                       cpbufs[1].bufsize += ((i > 
cpbufs[1].bufsize) ?
-                                                             i : 
cpbufs[1].bufsize);
-                               }
-                               else
-                                       cpbufs[1].bufsize *= 2;
-                               assert(cpbufs[1].bufsize > 0);
-                               erealloc(cpbufs[1].buf, char *,
-                                        cpbufs[1].bufsize, "format_tree");
-                       }
-                       if (i < 1)
-                               goto out_of_range;
-#if defined(HAVE_LOCALE_H)
-                       quote_flag = (quote_flag && loc.thousands_sep[0] != 0);
-#endif
-                       chp = &cpbufs[1].buf[i-1];
-                       ii = jj = 0;
-                       do {
-                               PREPEND(*chp);
-                               chp--; i--;
-#if defined(HAVE_LOCALE_H)
-                               if (quote_flag && loc.grouping[ii] && ++jj == 
loc.grouping[ii]) {
-                                       if (i) {        /* only add if more 
digits coming */
-                                               int k;
-                                               const char *ts = 
loc.thousands_sep;
-
-                                               for (k = strlen(ts) - 1; k >= 
0; k--) {
-                                                       PREPEND(ts[k]);
-                                               }
-                                       }
-                                       if (loc.grouping[ii+1] == 0)
-                                               jj = 0;         /* keep using 
current val in loc.grouping[ii] */
-                                       else if (loc.grouping[ii+1] == CHAR_MAX)
-                                               quote_flag = false;
-                                       else {
-                                               ii++;
-                                               jj = 0;
-                                       }
-                               }
-#endif
-                       } while (i > 0);
-
-                       /* add more output digits to match the precision */
-                       if (have_prec) {
-                               while (cend - cp < prec)
-                                       PREPEND('0');
-                       }
-
-                       if (sgn)
-                               PREPEND('-');
-                       else if (signchar)
-                               PREPEND(signchar);
-                       /*
-                        * When to fill with zeroes is of course not simple.
-                        * First: No zero fill if left-justifying.
-                        * Next: There seem to be two cases:
-                        *      A '0' without a precision, e.g. %06d
-                        *      A precision with no field width, e.g. %.10d
-                        * Any other case, we don't want to fill with zeroes.
-                        */
-                       if (! lj
-                           && ((zero_flag && ! have_prec)
-                                || (fw == 0 && have_prec)))
-                               fill = zero_string;
-                       if (prec > fw)
-                               fw = prec;
-                       prec = cend - cp;
-                       if (fw > prec && ! lj && fill != sp
-                           && (*cp == '-' || signchar)) {
-                               bchunk_one(cp);
-                               cp++;
-                               prec--;
-                               fw--;
-                       }
-                       goto pr_tail;
-               case 'X':
-                       chbuf = Uchbuf; /* FALL THROUGH */
-               case 'x':
-                       base += 6;      /* FALL THROUGH */
-               case 'u':
-                       base += 2;      /* FALL THROUGH */
-               case 'o':
-                       base += 8;
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-
-                       if (out_of_range(arg))
-                               goto out_of_range;
-#ifdef HAVE_MPFR
-                       if (is_mpg_integer(arg)) {
-mpz0:
-                               zi = arg->mpg_i;
-
-                               if (cs1 != 'd' && cs1 != 'i') {
-                                       if (mpz_sgn(zi) <= 0) {
-                                               /*
-                                                * Negative value or 0 requires 
special handling.
-                                                * Unlike MPFR, GMP does not 
allow conversion
-                                                * to (u)intmax_t. So we first 
convert GMP type to
-                                                * a MPFR type.
-                                                */
-                                               mf = mpz2mpfr(zi);
-                                               goto mpf1;
-                                       }
-                                       signchar = '\0';        /* Don't print 
'+' */
-                               }
-
-                               /* See comments above about when to fill with 
zeros */
-                               zero_flag = (! lj
-                                                   && ((zero_flag && ! 
have_prec)
-                                                        || (fw == 0 && 
have_prec)));
-
-                               fmt_type = have_prec ? MP_INT_WITH_PREC : 
MP_INT_WITHOUT_PREC;
-                               goto fmt0;
-
-                       } else if (is_mpg_float(arg)) {
-mpf0:
-                               mf = arg->mpg_numbr;
-                               if (! mpfr_number_p(mf)) {
-                                       /* inf or NaN */
-                                       cs1 = 'g';
-                                       fmt_type = MP_FLOAT;
-                                       goto fmt1;
-                               }
-
-                               if (cs1 != 'd' && cs1 != 'i') {
-mpf1:
-                                       /*
-                                        * The output of printf("%#.0x", 0) is 
0 instead of 0x, hence <= in
-                                        * the comparison below.
-                                        */
-                                       if (mpfr_sgn(mf) <= 0) {
-                                               if (! mpfr_fits_intmax_p(mf, 
ROUND_MODE)) {
-                                                       /* -ve number is too 
large */
-                                                       cs1 = 'g';
-                                                       fmt_type = MP_FLOAT;
-                                                       goto fmt1;
-                                               }
-
-                                               tmpval = uval = (uintmax_t) 
mpfr_get_sj(mf, ROUND_MODE);
-                                               if (! alt && have_prec && prec 
== 0 && tmpval == 0)
-                                                       goto pr_tail;   /* 
printf("%.0x", 0) is no characters */
-                                               goto int0;
-                                       }
-                                       signchar = '\0';        /* Don't print 
'+' */
-                               }
-
-                               /* See comments above about when to fill with 
zeros */
-                               zero_flag = (! lj
-                                                   && ((zero_flag && ! 
have_prec)
-                                                        || (fw == 0 && 
have_prec)));
-
-                               (void) mpfr_get_z(mpzval, mf, MPFR_RNDZ);       
/* convert to GMP integer */
-                               fmt_type = have_prec ? MP_INT_WITH_PREC : 
MP_INT_WITHOUT_PREC;
-                               zi = mpzval;
-                               goto fmt0;
-                       } else
-#endif
-                               tmpval = arg->numbr;
-
-                       /*
-                        * ``The result of converting a zero value with a
-                        * precision of zero is no characters.''
-                        *
-                        * If I remember the ANSI C standard, though,
-                        * it says that for octal conversions
-                        * the precision is artificially increased
-                        * to add an extra 0 if # is supplied.
-                        * Indeed, in C,
-                        *      printf("%#.0o\n", 0);
-                        * prints a single 0.
-                        */
-                       if (! alt && have_prec && prec == 0 && tmpval == 0)
-                               goto pr_tail;
-
-                       if (tmpval < 0) {
-                               uval = (uintmax_t) (intmax_t) tmpval;
-                               if ((AWKNUM)(intmax_t)uval != 
double_to_int(tmpval))
-                                       goto out_of_range;
-                       } else {
-                               uval = (uintmax_t) tmpval;
-                               if ((AWKNUM)uval != double_to_int(tmpval))
-                                       goto out_of_range;
-                       }
-#ifdef HAVE_MPFR
-       int0:
-#endif
-#if defined(HAVE_LOCALE_H)
-                       quote_flag = (quote_flag && loc.thousands_sep[0] != 0);
-#endif
-                       /*
-                        * When to fill with zeroes is of course not simple.
-                        * First: No zero fill if left-justifying.
-                        * Next: There seem to be two cases:
-                        *      A '0' without a precision, e.g. %06d
-                        *      A precision with no field width, e.g. %.10d
-                        * Any other case, we don't want to fill with zeroes.
-                        */
-                       if (! lj
-                           && ((zero_flag && ! have_prec)
-                                || (fw == 0 && have_prec)))
-                               fill = zero_string;
-                       ii = jj = 0;
-                       do {
-                               PREPEND(chbuf[uval % base]);
-                               uval /= base;
-#if defined(HAVE_LOCALE_H)
-                               if (base == 10 && quote_flag && 
loc.grouping[ii] && ++jj == loc.grouping[ii]) {
-                                       if (uval) {     /* only add if more 
digits coming */
-                                               int k;
-                                               const char *ts = 
loc.thousands_sep;
-
-                                               for (k = strlen(ts) - 1; k >= 
0; k--) {
-                                                       PREPEND(ts[k]);
-                                               }
-                                       }
-                                       if (loc.grouping[ii+1] == 0)
-                                               jj = 0;     /* keep using 
current val in loc.grouping[ii] */
-                                       else if (loc.grouping[ii+1] == CHAR_MAX)
-                                               quote_flag = false;
-                                       else {
-                                               ii++;
-                                               jj = 0;
-                                       }
-                               }
-#endif
-                       } while (uval > 0);
-
-                       /* add more output digits to match the precision */
-                       if (have_prec) {
-                               while (cend - cp < prec)
-                                       PREPEND('0');
-                       }
-
-                       if (alt && tmpval != 0) {
-                               if (base == 16) {
-                                       PREPEND(cs1);
-                                       PREPEND('0');
-                                       if (fill != sp) {
-                                               bchunk(cp, 2);
-                                               cp += 2;
-                                               fw -= 2;
-                                       }
-                               } else if (base == 8)
-                                       PREPEND('0');
-                       }
-                       base = 0;
-                       if (prec > fw)
-                               fw = prec;
-                       prec = cend - cp;
-       pr_tail:
-                       if (! lj) {
-                               while (fw > prec) {
-                                       bchunk_one(fill);
-                                       fw--;
-                               }
-                       }
-                       copy_count = prec;
-                       if (fw == 0 && ! have_prec)
-                               ;
-                       else if (gawk_mb_cur_max > 1) {
-                               if (cs1 == 's') {
-                                       assert(cp == arg->stptr || cp == cpbuf);
-                                       copy_count = mbc_byte_count(arg->stptr, 
prec);
-                               }
-                               /* prec was set by code for %c */
-                               /* else
-                                       copy_count = prec; */
-                       }
-                       bchunk(cp, copy_count);
-                       while (fw > prec) {
-                               bchunk_one(fill);
-                               fw--;
-                       }
-                       s0 = s1;
-                       break;
-
-     out_of_range:
-                       /*
-                        * out of range - emergency use of %g format,
-                        * or format NaN and INF values.
-                        */
-                       nan_inf_val = format_nan_inf(arg, cs1);
-                       if (do_posix || magic_posix_flag || nan_inf_val == 
NULL) {
-                               if (do_lint && ! do_posix && ! magic_posix_flag)
-                                       lintwarn(_("[s]printf: value %g is out 
of range for `%%%c' format"),
-                                                               (double) 
tmpval, cs1);
-                               tmpval = arg->numbr;
-                               if (strchr("aAeEfFgG", cs1) == NULL)
-                                       cs1 = 'g';
-                               goto fmt1;
-                       } else {
-                               if (do_lint)
-                                       lintwarn(_("[s]printf: value %s is out 
of range for `%%%c' format"),
-                                                               nan_inf_val, 
cs1);
-                               bchunk(nan_inf_val, strlen(nan_inf_val));
-                               s0 = s1;
-                               break;
-                       }
-
-               case 'F':
-#if ! defined(PRINTF_HAS_F_FORMAT) || PRINTF_HAS_F_FORMAT != 1
-                       cs1 = 'f';
-                       /* FALL THROUGH */
-#endif
-               case 'g':
-               case 'G':
-               case 'e':
-               case 'f':
-               case 'E':
-#if defined(PRINTF_HAS_A_FORMAT) && PRINTF_HAS_A_FORMAT == 1
-               case 'A':
-               case 'a':
-               {
-                       static bool warned = false;
-
-                       if (do_lint && tolower(cs1) == 'a' && ! warned) {
-                               warned = true;
-                               lintwarn(_("%%%c format is POSIX standard but 
not portable to other awks"), cs1);
-                       }
-               }
-#endif
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-
-                       if (! is_mpg_number(arg))
-                               tmpval = arg->numbr;
-#ifdef HAVE_MPFR
-                       else if (is_mpg_float(arg)) {
-                               mf = arg->mpg_numbr;
-                               fmt_type = MP_FLOAT;
-                       } else {
-                               /* arbitrary-precision integer, convert to MPFR 
float */
-                               assert(mf == NULL);
-                               mf = mpz2mpfr(arg->mpg_i);
-                               fmt_type = MP_FLOAT;
-                       }
-#endif
-                       if (out_of_range(arg))
-                               goto out_of_range;
-
-     fmt1:
-                       if (! have_prec)
-                               prec = DEFAULT_G_PRECISION;
-#ifdef HAVE_MPFR
-     fmt0:
-#endif
-                       chksize(fw + prec + 11);        /* 11 == slop */
-                       cp = cpbuf;
-                       *cp++ = '%';
-                       if (lj)
-                               *cp++ = '-';
-                       if (signchar)
-                               *cp++ = signchar;
-                       if (alt)
-                               *cp++ = '#';
-                       if (zero_flag)
-                               *cp++ = '0';
-                       if (quote_flag)
-                               *cp++ = '\'';
-
-#if defined(LC_NUMERIC)
-                       if (quote_flag && ! use_lc_numeric)
-                               setlocale(LC_NUMERIC, "");
-#endif
-
-                       bool need_to_add_thousands = false;
-                       switch (fmt_type) {
-#ifdef HAVE_MPFR
-                       case MP_INT_WITH_PREC:
-                               sprintf(cp, "*.*Z%c", cs1);
-                               while ((nc = mpfr_snprintf(obufout, ofre, cpbuf,
-                                            (int) fw, (int) prec, zi)) >= 
(int) ofre)
-                                       chksize(nc)
-                               need_to_add_thousands = true;
-                               break;
-                       case MP_INT_WITHOUT_PREC:
-                               sprintf(cp, "*Z%c", cs1);
-                               while ((nc = mpfr_snprintf(obufout, ofre, cpbuf,
-                                            (int) fw, zi)) >= (int) ofre)
-                                       chksize(nc)
-                               need_to_add_thousands = true;
-                               break;
-                       case MP_FLOAT:
-                               sprintf(cp, "*.*R*%c", cs1);
-                               while ((nc = mpfr_snprintf(obufout, ofre, cpbuf,
-                                            (int) fw, (int) prec, ROUND_MODE, 
mf)) >= (int) ofre)
-                                       chksize(nc)
-                               break;
-#endif
-                       default:
-                               if (have_prec || tolower(cs1) != 'a') {
-                                       sprintf(cp, "*.*%c", cs1);
-                                       while ((nc = snprintf(obufout, ofre, 
cpbuf,
-                                                    (int) fw, (int) prec,
-                                                    (double) tmpval)) >= (int) 
ofre)
-                                               chksize(nc)
-                               } else {
-                                       // For %a and %A, use the default 
precision if it
-                                       // wasn't supplied by the user.
-                                       sprintf(cp, "*%c", cs1);
-                                       while ((nc = snprintf(obufout, ofre, 
cpbuf,
-                                                    (int) fw,
-                                                    (double) tmpval)) >= (int) 
ofre)
-                                               chksize(nc)
-                               }
-                       }
-
-#if defined(LC_NUMERIC)
-                       if (quote_flag && ! use_lc_numeric)
-                               setlocale(LC_NUMERIC, "C");
-#endif
-                       len = strlen(obufout);
-                       if (quote_flag && need_to_add_thousands) {
-                               const char *new_text = add_thousands(obufout, & 
loc);
-
-                               len = strlen(new_text);
-                               chksize(len)
-                               strcpy(obufout, new_text);
-                               free((void *) new_text);
-                       }
-
-                       ofre -= len;
-                       obufout += len;
-                       s0 = s1;
-                       break;
-               default:
-                       if (do_lint && is_alpha(cs1))
-                               lintwarn(_("ignoring unknown format specifier 
character `%c': no argument converted"), cs1);
-                       break;
-               }
-               if (toofew) {
-                       msg("%s\n\t`%s'\n\t%*s%s",
-                             _("fatal: not enough arguments to satisfy format 
string"),
-                             fmt_string, (int) (s1 - fmt_string - 1), "",
-                             _("^ ran out for this one"));
-                       goto out;
-               }
-       }
-       if (do_lint) {
-               if (need_format)
-                       lintwarn(
-                       _("[s]printf: format specifier does not have control 
letter"));
-               if (cur_arg < num_args)
-                       lintwarn(
-                       _("too many arguments supplied for format string"));
-       }
-       bchunk(s0, s1 - s0);
-       olen_final = obufout - obuf;
-#define GIVE_BACK_SIZE (INITIAL_OUT_SIZE * 2)
-       if (ofre > GIVE_BACK_SIZE)
-               erealloc(obuf, char *, olen_final + 1, "format_tree");
-       r = make_str_node(obuf, olen_final, ALREADY_MALLOCED);
-       obuf = NULL;
-out:
-       {
-               size_t k;
-               size_t count = sizeof(cpbufs)/sizeof(cpbufs[0]);
-               for (k = 0; k < count; k++) {
-                       if (cpbufs[k].buf != cpbufs[k].stackbuf)
-                               efree(cpbufs[k].buf);
-               }
-               if (obuf != NULL)
-                       efree(obuf);
-       }
-
-       if (r == NULL)
-               gawk_exit(EXIT_FATAL);
-       return r;
-}
-
-
-/* printf_common --- common code for sprintf and printf */
-
-static NODE *
-printf_common(int nargs)
-{
-       int i;
-       NODE *r, *tmp;
-
-       assert(nargs > 0 && nargs <= max_args);
-       for (i = 1; i <= nargs; i++) {
-               tmp = args_array[nargs - i] = POP();
-               if (tmp->type == Node_var_array) {
-                       while (--i > 0)
-                               DEREF(args_array[nargs - i]);
-                       fatal(_("attempt to use array `%s' in a scalar 
context"), array_vname(tmp));
-               }
-       }
-
-       args_array[0] = force_string(args_array[0]);
-       if (do_lint && (fixtype(args_array[0])->flags & STRING) == 0)
-               lintwarn(_("%s: received non-string format string argument"), 
"printf/sprintf");
-       r = format_tree(args_array[0]->stptr, args_array[0]->stlen, args_array, 
nargs);
-       for (i = 0; i < nargs; i++)
-               DEREF(args_array[i]);
-       return r;
-}
-
-/* do_sprintf --- perform sprintf */
-
-NODE *
-do_sprintf(int nargs)
-{
-       NODE *r;
-
-       if (nargs == 0)
-               fatal(_("sprintf: no arguments"));
-
-       r = printf_common(nargs);
-       if (r == NULL)
-               gawk_exit(EXIT_FATAL);
-       return r;
-}
-
-
-/* do_printf --- perform printf, including redirection */
-
-void
-do_printf(int nargs, int redirtype)
-{
-       FILE *fp = NULL;
-       NODE *tmp;
-       struct redirect *rp = NULL;
-       int errflg = 0;
-       NODE *redir_exp = NULL;
-
-       if (nargs == 0) {
-               if (do_traditional) {
-                       if (do_lint)
-                               lintwarn(_("printf: no arguments"));
-                       if (redirtype != 0) {
-                               redir_exp = TOP();
-                               if (redir_exp->type != Node_val)
-                                       fatal(_("attempt to use array `%s' in a 
scalar context"), array_vname(redir_exp));
-                               rp = redirect(redir_exp, redirtype, & errflg, 
true);
-                               DEREF(redir_exp);
-                               decr_sp();
-                       }
-                       return; /* bwk accepts it silently */
-               }
-               fatal(_("printf: no arguments"));
-       }
-
-       if (redirtype != 0) {
-               redir_exp = PEEK(nargs);
-               if (redir_exp->type != Node_val)
-                       fatal(_("attempt to use array `%s' in a scalar 
context"), array_vname(redir_exp));
-               rp = redirect(redir_exp, redirtype, & errflg, true);
-               if (rp != NULL) {
-                       if ((rp->flag & RED_TWOWAY) != 0 && rp->output.fp == 
NULL) {
-                               if (is_non_fatal_redirect(redir_exp->stptr, 
redir_exp->stlen)) {
-                                       update_ERRNO_int(EBADF);
-                                       return;
-                               }
-                               (void) close_rp(rp, CLOSE_ALL);
-                               fatal(_("printf: attempt to write to closed 
write end of two-way pipe"));
-                       }
-                       fp = rp->output.fp;
-               }
-               else if (errflg) {
-                       update_ERRNO_int(errflg);
-                       return;
-               }
-       } else if (do_debug)    /* only the debugger can change the default 
output */
-               fp = output_fp;
-       else
-               fp = stdout;
-
-       tmp = printf_common(nargs);
-       if (redir_exp != NULL) {
-               DEREF(redir_exp);
-               decr_sp();
-       }
-       if (tmp != NULL) {
-               if (fp == NULL) {
-                       DEREF(tmp);
-                       return;
-               }
-               efwrite(tmp->stptr, sizeof(char), tmp->stlen, fp, "printf", rp, 
true);
-               if (rp != NULL && (rp->flag & RED_TWOWAY) != 0)
-                       rp->output.gawk_fflush(rp->output.fp, 
rp->output.opaque);
-               DEREF(tmp);
-       } else
-               gawk_exit(EXIT_FATAL);
-}
-
 /* do_sqrt --- do the sqrt function */
 
 NODE *
@@ -4446,63 +3251,6 @@ do_typeof(int nargs)
        return make_string(res, strlen(res));
 }
 
-/* mbc_byte_count --- return number of bytes for corresponding numchars 
multibyte characters */
-
-static size_t
-mbc_byte_count(const char *ptr, size_t numchars)
-{
-       mbstate_t cur_state;
-       size_t sum = 0;
-       int mb_len;
-
-       memset(& cur_state, 0, sizeof(cur_state));
-
-       assert(gawk_mb_cur_max > 1);
-       mb_len = mbrlen(ptr, numchars * gawk_mb_cur_max, &cur_state);
-       if (mb_len <= 0)
-               return numchars;        /* no valid m.b. char */
-
-       for (; numchars > 0; numchars--) {
-               mb_len = mbrlen(ptr, numchars * gawk_mb_cur_max, &cur_state);
-               if (mb_len <= 0)
-                       break;
-               sum += mb_len;
-               ptr += mb_len;
-       }
-
-       return sum;
-}
-
-/* mbc_char_count --- return number of m.b. chars in string, up to numbytes 
bytes */
-
-static size_t
-mbc_char_count(const char *ptr, size_t numbytes)
-{
-       mbstate_t cur_state;
-       size_t sum = 0;
-       int mb_len;
-
-       if (gawk_mb_cur_max == 1)
-               return numbytes;
-
-       memset(& cur_state, 0, sizeof(cur_state));
-
-       mb_len = mbrlen(ptr, numbytes, &cur_state);
-       if (mb_len <= 0)
-               return numbytes;        /* no valid m.b. char */
-
-       while (numbytes > 0) {
-               mb_len = mbrlen(ptr, numbytes, &cur_state);
-               if (mb_len <= 0)
-                       break;
-               sum++;
-               ptr += mb_len;
-               numbytes -= mb_len;
-       }
-
-       return sum;
-}
-
 /* sanitize_exit_status --- convert a 16 bit Unix exit status into something 
reasonable */
 
 int
@@ -4525,72 +3273,6 @@ sanitize_exit_status(int status)
        return ret;
 }
 
-/* out_of_range --- return true if a value is out of range */
-
-bool
-out_of_range(NODE *n)
-{
-#ifdef HAVE_MPFR
-       if (is_mpg_integer(n))
-               return false;
-       else if (is_mpg_float(n))
-               return (! mpfr_number_p(n->mpg_numbr));
-       else
-#endif
-               return (isnan(n->numbr) || isinf(n->numbr));
-}
-
-/* format_nan_inf --- format NaN and INF values */
-
-char *
-format_nan_inf(NODE *n, char format)
-{
-       static char buf[100];
-       double val = n->numbr;
-
-#ifdef HAVE_MPFR
-       if (is_mpg_integer(n))
-               return NULL;
-       else if (is_mpg_float(n)) {
-               if (mpfr_nan_p(n->mpg_numbr)) {
-                       strcpy(buf, mpfr_signbit(n->mpg_numbr) != 0 ? "-nan" : 
"+nan");
-
-                       goto fmt;
-               } else if (mpfr_inf_p(n->mpg_numbr)) {
-                       strcpy(buf, mpfr_signbit(n->mpg_numbr) ? "-inf" : 
"+inf");
-
-                       goto fmt;
-               } else
-                       return NULL;
-       }
-       /* else
-               fallthrough */
-#endif
-
-       if (isnan(val)) {
-               strcpy(buf, signbit(val) != 0 ? "-nan" : "+nan");
-
-               // fall through to end
-       } else if (isinf(val)) {
-               strcpy(buf, val < 0 ? "-inf" : "+inf");
-
-               // fall through to end
-       } else
-               return NULL;
-
-#ifdef HAVE_MPFR
-fmt:
-#endif
-       if (isupper(format)) {
-               int i;
-
-               for (i = 0; buf[i] != '\0'; i++)
-                       buf[i] = toupper(buf[i]);
-       }
-       return buf;
-}
-
-
 /* check_symtab_functab --- check if dest is SYMTAB or FUNCTAB, fatal if so */
 
 void
@@ -4617,88 +3299,6 @@ do_mkbool(int nargs)
        return make_bool_node(result);
 }
 
-/* reverse --- reverse the contents of a string in place */
-
-static void
-reverse(char *str)
-{
-       int i, j;
-       char tmp;
-
-       for (i = 0, j = strlen(str) - 1; j > i; i++, j--) {
-               tmp = str[i];
-               str[i] = str[j];
-               str[j] = tmp;
-       }
-}
-
-/* add_thousands --- add the thousands separator. Needed for MPFR %d format */
-
-/*
- * Copy the source string into the destination string, backwards,
- * adding the thousands separator at the right points. Then reverse
- * the string when done. This gives us much cleaner code than trying
- * to work through the string backwards. (We tried it, it was yucky.)
- */
-
-static const char *
-add_thousands(const char *original, struct lconv *loc)
-{
-       size_t orig_len = strlen(original);
-       size_t new_len = orig_len + (orig_len * strlen(loc->thousands_sep)) + 
1;        // worst case
-       char *newbuf;
-       char decimal_point = '\0';
-       const char *dec = NULL;
-       const char *src;
-       char *dest;
-
-       emalloc(newbuf, char *, new_len, "add_thousands");
-       memset(newbuf, '\0', new_len);
-
-       src = original + strlen(original) - 1;
-       dest = newbuf;
-
-       if (loc->decimal_point[0] != '\0') {
-               decimal_point = loc->decimal_point[0];
-               if ((dec = strchr(original, decimal_point)) != NULL) {
-                       while (src >= dec)
-                               *dest++ = *src--;
-               }
-       }
-
-
-       int ii = 0;
-       int jj = 0;
-       do {
-               *dest++ = *src--;
-               if (loc->grouping[ii] && ++jj == loc->grouping[ii]) {
-                       if (src >= original) {  /* only add if more digits 
coming */
-                               const char *ts = loc->thousands_sep;
-                               int k;
-
-                               for (k = strlen(ts) - 1; k >= 0; k--)
-                                       *dest++ = ts[k];
-                       }
-                       if (loc->grouping[ii+1] == 0)
-                               jj = 0;         /* keep using current val in 
loc.grouping[ii] */
-                       else if (loc->grouping[ii+1] == CHAR_MAX) {
-                               // copy in the rest and be done
-                               while (src >= original)
-                                       *dest++ = *src--;
-                               break;
-                       } else {
-                               ii++;
-                               jj = 0;
-                       }
-               }
-       } while (src >= original);
-
-       *dest++ = '\0';
-       reverse(newbuf);
-
-       return newbuf;
-}
-
 /* gawk_system --- specialized version for gawk */
 
 int
diff --git a/debug.c b/debug.c
index 8c5bee6c..40c785f8 100644
--- a/debug.c
+++ b/debug.c
@@ -5227,7 +5227,7 @@ do_print_f(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
 
        PUSH_BINDING(fatal_tag_stack, fatal_tag, fatal_tag_valid);
        if (setjmp(fatal_tag) == 0)
-               r = format_tree(tmp[0]->stptr, tmp[0]->stlen, tmp, i);
+               r = format_args(tmp[0]->stptr, tmp[0]->stlen, tmp, i);
        else {
                /* fatal error, restore exit_val of program */
                exit_val = EXIT_SUCCESS;
diff --git a/mpfr.c b/mpfr.c
index 1cb02c11..370f4088 100644
--- a/mpfr.c
+++ b/mpfr.c
@@ -422,16 +422,16 @@ mpg_format_val(const char *format, int index, NODE *s)
                return make_string(result, strlen(result));
        }
 
-       /* create dummy node for a sole use of format_tree */
+       /* create dummy node for a sole use of format_args */
        dummy[1] = s;
        oflags = s->flags;
 
        if (is_mpg_integer(s) || mpfr_integer_p(s->mpg_numbr)) {
                /* integral value, use %d */
-               r = format_tree("%d", 2, dummy, 2);
+               r = format_args("%d", 2, dummy, 2);
                s->stfmt = STFMT_UNUSED;
        } else {
-               r = format_tree(format, fmt_list[index]->stlen, dummy, 2);
+               r = format_args(format, fmt_list[index]->stlen, dummy, 2);
                assert(r != NULL);
                s->stfmt = index;
        }
diff --git a/node.c b/node.c
index 0d8fea72..181152dd 100644
--- a/node.c
+++ b/node.c
@@ -246,16 +246,16 @@ r_format_val(const char *format, int index, NODE *s)
                NODE *dummy[2], *r;
                unsigned int oflags;
 
-               /* create dummy node for a sole use of format_tree */
+               /* create dummy node for a sole use of format_args */
                dummy[1] = s;
                oflags = s->flags;
 
                if (val == s->numbr) {
                        /* integral value, but outside range of %ld, use %.0f */
-                       r = format_tree("%.0f", 4, dummy, 2);
+                       r = format_args("%.0f", 4, dummy, 2);
                        s->stfmt = STFMT_UNUSED;
                } else {
-                       r = format_tree(format, fmt_list[index]->stlen, dummy, 
2);
+                       r = format_args(format, fmt_list[index]->stlen, dummy, 
2);
                        assert(r != NULL);
                        s->stfmt = index;
                }
diff --git a/pc/ChangeLog b/pc/ChangeLog
index 3d819829..d8c5b983 100644
--- a/pc/ChangeLog
+++ b/pc/ChangeLog
@@ -1,3 +1,7 @@
+2024-07-11         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * Makefile.tst: Regenerated.
+
 2024-06-13         Arnold D. Robbins     <arnold@skeeve.com>
 
        * Makefile.tst: Regenerated.
diff --git a/pc/Makefile.tst b/pc/Makefile.tst
index 2a15eaa9..3bb4ab3b 100644
--- a/pc/Makefile.tst
+++ b/pc/Makefile.tst
@@ -180,7 +180,7 @@ BASIC_TESTS = \
        substr swaplns synerr1 synerr2 synerr3 tailrecurse tradanch \
        trailbs tweakfld uninit2 uninit3 uninit4 uninit5 uninitialized \
        unterm uparrfs uplus wideidx wideidx2 widesub widesub2 widesub3 \
-       match4 strsubscript \
+       match4 strsubscript printf-corners \
        widesub4 wjposer1 zero2 zeroe0 zeroflag
 
 UNIX_TESTS = \
@@ -2613,6 +2613,13 @@ strsubscript:
        @-AWKPATH="$(srcdir)" $(AWK) -f $@.awk  >_$@ 2>&1 || echo EXIT CODE: 
$$? >>_$@
        @-$(CMP) "$(srcdir)"/$@.ok _$@ && rm -f _$@
 
+printf-corners:
+       @echo $@
+       @-AWKPATH="$(srcdir)" $(AWK) -f $@.awk  >_$@ 2>&1 || echo EXIT CODE: 
$$? >>_$@
+       @-if echo "$$GAWK_TEST_ARGS" | egrep -s -e '-M|--bignum' > /dev/null ; \
+       then $(CMP) "$(srcdir)"/$@-mpfr.ok _$@ && rm -f _$@ ; \
+       else $(CMP) "$(srcdir)"/$@.ok _$@ && rm -f _$@ ; fi
+
 widesub4:
        @echo $@ $(ZOS_FAIL)
        @-[ -z "$$GAWKLOCALE" ] && GAWKLOCALE=ENU_USA.1252; export GAWKLOCALE; \
diff --git a/printf.c b/printf.c
new file mode 100644
index 00000000..5c604a92
--- /dev/null
+++ b/printf.c
@@ -0,0 +1,1891 @@
+/*
+ * printf.c - All the code for sprintf/printf.
+ */
+
+/*
+ * Copyright (C) 1986, 1988, 1989, 1991-2024,
+ * the Free Software Foundation, Inc.
+ *
+ * This file is part of GAWK, the GNU implementation of the
+ * AWK Programming Language.
+ *
+ * GAWK is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GAWK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
USA
+ */
+
+
+#include "awk.h"
+
+struct flags {
+       bool left_just;
+       bool alt;
+       bool zero;
+       bool space;
+       bool plus;
+       bool quote;
+       bool have_prec;
+       bool magic_posix_flag;
+       char format;
+       int base;
+       int field_width;
+       int precision;
+       bool negative;
+};
+
+extern int max_args;
+extern NODE **args_array;
+extern FILE *output_fp;
+
+#define DEFAULT_G_PRECISION 6
+
+static size_t mbc_byte_count(const char *ptr, size_t numchars);
+static size_t mbc_char_count(const char *ptr, size_t numbytes);
+static void reverse(char *str);
+static bool compute_zero_flag(struct flags *flags);
+static void adjust_flags(struct flags *flags);
+static char *add_thousands(const char *original);
+static char *format_integer_digits(NODE *arg, struct flags *flags, bool 
*used_float);
+static char *format_mpg_integer_digits(NODE *arg, struct flags *flags, bool 
*used_float);
+static char *format_signed_integer(NODE *arg, struct flags *flags);
+static char *format_unsigned_integer(NODE *arg, struct flags *flags);
+static char *format_float(NODE *arg, struct flags *flags);
+static char *format_out_of_range(NODE *arg, struct flags *flags);
+static char *fill_to_field_width(char *startval, struct flags *flags, int 
fill);
+static char *add_plus_or_space_and_fill(char *number_value, struct flags 
*flags);
+static char *zero_fill_to_precision(char *number_value, struct flags *flags);
+static char *add_alt_format(char *number_value, struct flags *flags);
+
+#ifdef HAVE_MPFR
+
+/*
+ * mpz2mpfr --- convert an arbitrary-precision integer to a float
+ *     without any loss of precision. The returned value is only
+ *     good for temporary use.
+ */
+
+
+static mpfr_ptr
+mpz2mpfr(mpz_ptr zi)
+{
+       size_t prec;
+       static mpfr_t mpfrval;
+       static bool inited = false;
+       int tval;
+
+       /* estimate minimum precision for exact conversion */
+       prec = mpz_sizeinbase(zi, 2);   /* most significant 1 bit position 
starting at 1 */
+       prec -= (size_t) mpz_scan1(zi, 0);      /* least significant 1 bit 
index starting at 0 */
+       if (prec < MPFR_PREC_MIN)
+               prec = MPFR_PREC_MIN;
+       else if (prec > MPFR_PREC_MAX)
+               prec = MPFR_PREC_MAX;
+
+       if (! inited) {
+               mpfr_init2(mpfrval, prec);
+               inited = true;
+       } else
+               mpfr_set_prec(mpfrval, prec);
+       tval = mpfr_set_z(mpfrval, zi, ROUND_MODE);
+       IEEE_FMT(mpfrval, tval);
+       return mpfrval;
+}
+#endif
+
+/*
+ * format_args() formats arguments of sprintf,
+ * and according to a fmt_string providing a format like in
+ * printf family from C library.  Returns a string node whose value
+ * is a formatted string.  Called by sprintf function.
+ *
+ * It is one of the uglier parts of gawk.  Thanks to Michal Jaegermann
+ * for taming this beast and making it compatible with ANSI C.
+ *
+ * July 2024: Refactored into something (more) manageable.
+ */
+
+NODE *
+format_args(
+       const char *fmt_string,
+       size_t n0,
+       NODE *the_args[],
+       long num_args)
+{
+/* copy 'l' bytes from 's' to 'obufout' checking for space in the process */
+/* difference of pointers should be of ptrdiff_t type, but let us be kind */
+#define bchunk(s, l) if (l) { \
+       while ((l) > ofre) { \
+               size_t olen = obufout - obuf; \
+               erealloc(obuf, char *, osiz * 2, "format_args"); \
+               ofre += osiz; \
+               osiz *= 2; \
+               obufout = obuf + olen; \
+       } \
+       memcpy(obufout, s, (size_t) (l)); \
+       obufout += (l); \
+       ofre -= (l); \
+}
+
+/* copy one byte from 's' to 'obufout' checking for space in the process */
+#define bchunk_one(s) { \
+       if (ofre < 1) { \
+               size_t olen = obufout - obuf; \
+               erealloc(obuf, char *, osiz * 2, "format_args"); \
+               ofre += osiz; \
+               osiz *= 2; \
+               obufout = obuf + olen; \
+       } \
+       *obufout++ = *s; \
+       --ofre; \
+}
+
+/* Is there space for something L big in the buffer? */
+#define chksize(l)  if ((l) >= ofre) { \
+       size_t olen = obufout - obuf; \
+       size_t delta = osiz+l-ofre; \
+       erealloc(obuf, char *, osiz + delta, "format_args"); \
+       obufout = obuf + olen; \
+       ofre += delta; \
+       osiz += delta; \
+}
+
+       size_t cur_arg = 0;
+       NODE *r = NULL;
+       bool toofew = false;
+       char *obuf, *obufout;
+       size_t osiz, ofre, olen_final;
+       const char *s0, *s1;
+       int cs1;
+       NODE *arg;
+       long fw, prec, argnum;
+       bool used_dollar;
+       bool lj, alt, have_prec, need_format;
+       long *cur = NULL;
+       uintmax_t uval;
+       int base;
+       bool space_flag;
+       bool plus_flag;
+
+       struct {
+               char *buf;
+               size_t bufsize;
+               char stackbuf[30];
+       } cpbuf;
+       char *cend = &cpbuf.stackbuf[sizeof(cpbuf.stackbuf)];
+       char *cp;
+       const char *fill;
+       char signchar = '\0';
+       size_t len;
+       bool zero_flag = false;
+       bool quote_flag = false;
+       size_t copy_count, char_count;
+       bool magic_posix_flag;
+       struct flags flags;
+
+       static const char sp[] = " ";
+       static const char bad_modifiers[] = "DHhjLltwz";
+       static bool warned[sizeof(bad_modifiers)-1];    // auto-init to zero
+
+       bool modifier_seen[sizeof(bad_modifiers)-1];
+#define modifier_index(c)  (strchr(bad_modifiers, c) - bad_modifiers)
+       const char *formatted = NULL;
+
+#define INITIAL_OUT_SIZE       64
+       emalloc(obuf, char *, INITIAL_OUT_SIZE, "format_args");
+       obufout = obuf;
+       osiz = INITIAL_OUT_SIZE;
+       ofre = osiz - 1;
+
+       cur_arg = 1;
+
+       cpbuf.bufsize = sizeof(cpbuf.stackbuf);
+       cpbuf.buf = cpbuf.stackbuf;
+
+       /*
+        * Check first for use of `count$'.
+        * If plain argument retrieval was used earlier, choke.
+        *      Otherwise, return the requested argument.
+        * If not `count$' now, but it was used earlier, choke.
+        * If this format is more than total number of args, choke.
+        * Otherwise, return the current argument.
+        */
+#define parse_next_arg() { \
+       if (argnum > 0) { \
+               if (cur_arg > 1) { \
+                       msg(_("fatal: must use `count$' on all formats or 
none")); \
+                       goto out; \
+               } \
+               arg = the_args[argnum]; \
+       } else if (used_dollar) { \
+               msg(_("fatal: must use `count$' on all formats or none")); \
+               arg = 0; /* shutup the compiler */ \
+               goto out; \
+       } else if (cur_arg >= num_args) { \
+               arg = 0; /* shutup the compiler */ \
+               toofew = true; \
+               break; \
+       } else { \
+               arg = the_args[cur_arg]; \
+               cur_arg++; \
+       } \
+}
+
+       need_format = false;
+       used_dollar = false;
+
+       s0 = s1 = fmt_string;
+       while (n0-- > 0) {
+               if (*s1 != '%') {
+                       s1++;
+                       continue;
+               }
+               need_format = true;
+               bchunk(s0, s1 - s0);
+               s0 = s1;
+               cur = &fw;
+               fw = 0;
+               prec = 0;
+               base = 0;
+               argnum = 0;
+               base = 0;
+               have_prec = false;
+               signchar = '\0';
+               zero_flag = false;
+               quote_flag = false;
+               space_flag = false;
+               plus_flag = false;
+
+               lj = alt = false;
+               memset(modifier_seen, 0, sizeof(modifier_seen));
+               magic_posix_flag = false;
+               fill = sp;
+               cp = cend;
+               s1++;
+               memset(& flags, 0, sizeof(flags));
+
+retry:
+               if (n0-- == 0)  /* ran out early! */
+                       break;
+
+               switch (cs1 = *s1++) {
+               case (-1):      /* dummy case to allow for checking */
+check_pos:
+                       if (cur != &fw)
+                               break;          /* reject as a valid format */
+                       goto retry;
+               case '%':
+                       need_format = false;
+                       /*
+                        * 29 Oct. 2002:
+                        * The C99 standard pages 274 and 279 seem to imply that
+                        * since there's no arg converted, the field width 
doesn't
+                        * apply.  The code already was that way, but this
+                        * comment documents it, at least in the code.
+                        *
+                        * 27 June 2024:
+                        * This is still the case. The 2023 standard says you 
shouldn't
+                        * have anything between the percents.
+                        */
+                       if (do_lint) {
+                               const char *msg = NULL;
+
+                               if (fw != 0 && ! have_prec)
+                                       msg = _("field width is ignored for 
`%%' specifier");
+                               else if (fw == 0 && have_prec)
+                                       msg = _("precision is ignored for `%%' 
specifier");
+                               else if (fw && have_prec)
+                                       msg = _("field width and precision are 
ignored for `%%' specifier");
+
+                               if (msg != NULL)
+                                       lintwarn("%s", msg);
+                       }
+                       bchunk_one("%");
+                       s0 = s1;
+                       break;
+
+               case '0':
+                       /*
+                        * Only turn on zero_flag if we haven't seen
+                        * the field width or precision yet.  Otherwise,
+                        * screws up floating point formatting.
+                        */
+                       if (cur == & fw)
+                               zero_flag = true;
+                       if (lj)
+                               goto retry;
+                       /* fall through */
+               case '1':
+               case '2':
+               case '3':
+               case '4':
+               case '5':
+               case '6':
+               case '7':
+               case '8':
+               case '9':
+                       if (cur == NULL)
+                               break;
+                       if (prec >= 0)
+                               *cur = cs1 - '0';
+                       /*
+                        * with a negative precision *cur is already set
+                        * to -1, so it will remain negative, but we have
+                        * to "eat" precision digits in any case
+                        */
+                       while (n0 > 0 && *s1 >= '0' && *s1 <= '9') {
+                               --n0;
+                               *cur = *cur * 10 + *s1++ - '0';
+                       }
+                       if (prec < 0)   /* negative precision is discarded */
+                               have_prec = false;
+                       if (cur == &prec)
+                               cur = NULL;
+                       if (n0 == 0)    /* badly formatted control string */
+                               continue;
+                       goto retry;
+               case '$':
+                       if (do_traditional) {
+                               msg(_("fatal: `$' is not permitted in awk 
formats"));
+                               goto out;
+                       }
+
+                       if (cur == &fw) {
+                               argnum = fw;
+                               fw = 0;
+                               used_dollar = true;
+                               if (argnum <= 0) {
+                                       msg(_("fatal: argument index with `$' 
must be > 0"));
+                                       goto out;
+                               }
+                               if (argnum >= num_args) {
+                                       msg(_("fatal: argument index %ld 
greater than total number of supplied arguments"), argnum);
+                                       goto out;
+                               }
+                       } else {
+                               argnum = prec;
+                               prec = 0;
+                               used_dollar = true;
+                               if (argnum <= 0) {
+                                       msg(_("fatal: argument index with `$' 
must be > 0"));
+                                       goto out;
+                               }
+                               if (argnum >= num_args) {
+                                       msg(_("fatal: argument index %ld 
greater than total number of supplied arguments"), argnum);
+                                       goto out;
+                               }
+                       }
+
+                       goto retry;
+               case '*':
+                       if (cur == NULL)
+                               break;
+                       if (! do_traditional && used_dollar && ! 
isdigit((unsigned char) *s1)) {
+                               fatal(_("fatal: must use `count$' on all 
formats or none"));
+                               break;  /* silence warnings */
+                       } else if (! do_traditional && isdigit((unsigned char) 
*s1)) {
+                               int val = 0;
+
+                               for (; n0 > 0 && *s1 && isdigit((unsigned char) 
*s1); s1++, n0--) {
+                                       val *= 10;
+                                       val += *s1 - '0';
+                               }
+                               if (*s1 != '$') {
+                                       msg(_("fatal: no `$' supplied for 
positional field width or precision"));
+                                       goto out;
+                               } else {
+                                       s1++;
+                                       n0--;
+                               }
+                               // val could be less than zero if someone 
provides a field width
+                               // so large that it causes integer overflow. 
Mainly fuzzers do this,
+                               // but let's try to be good anyway.
+                               if (val < 0 || val >= num_args) {
+                                       toofew = true;
+                                       break;
+                               }
+                               arg = the_args[val];
+                       } else {
+                               parse_next_arg();
+                       }
+                       (void) force_number(arg);
+                       *cur = get_number_si(arg);
+                       if (*cur < 0 && cur == &fw) {
+                               *cur = -*cur;
+                               lj = true;
+                       }
+                       if (cur == &prec) {
+                               if (*cur >= 0)
+                                       have_prec = true;
+                               else
+                                       have_prec = false;
+                               cur = NULL;
+                       }
+                       goto retry;
+               case ' ':               /* print ' ' or '-' */
+                                       /* 'space' flag is ignored */
+                                       /* if '+' already present  */
+                       space_flag = true;
+                       if (signchar != '\0')
+                               goto check_pos;
+                       signchar = cs1;
+                       goto check_pos;
+               case '+':               /* print '+' or '-' */
+                       plus_flag = true;
+                       signchar = cs1;
+                       goto check_pos;
+               case '-':
+                       if (prec < 0)
+                               break;
+                       if (cur == &prec) {
+                               prec = -1;
+                               goto retry;
+                       }
+                       fill = sp;      /* if left justified then other */
+                       lj = true;      /* filling is ignored */
+                       goto check_pos;
+               case '.':
+                       if (cur != &fw)
+                               break;
+                       cur = &prec;
+                       have_prec = true;
+                       goto retry;
+               case '#':
+                       alt = true;
+                       goto check_pos;
+               case '\'':
+#if defined(HAVE_LOCALE_H)
+                       quote_flag = true;
+                       goto check_pos;
+#else
+                       goto retry;
+#endif
+               case 'D':
+               case 'H':
+               case 'h':
+               case 'j':
+               case 'L':
+               case 'l':
+               case 't':
+               case 'w':
+               case 'z':
+                       if (modifier_seen[modifier_index(cs1)])
+                               break;
+                       else {
+                               int ind = modifier_index(cs1);
+
+                               if (do_lint && ! warned[ind]) {
+                                       lintwarn(_("`%c' is meaningless in awk 
formats; ignored"), cs1);
+                                       warned[ind] = true;
+                               }
+                               if (do_posix) {
+                                       msg(_("fatal: `%c' is not permitted in 
POSIX awk formats"), cs1);
+                                       goto out;
+                               }
+                       }
+                       modifier_seen[modifier_index(cs1)] = true;
+                       goto retry;
+
+               case 'P':
+                       if (magic_posix_flag)
+                               break;
+                       magic_posix_flag = true;
+                       goto retry;
+               case 'C':               // POSIX 2024 extension, undocumented 
for now
+                       cs1 = 'c';
+                       // FALL THROUGH
+               case 'c':
+                       need_format = false;
+                       parse_next_arg();
+                       /* user input that looks numeric is numeric */
+                       fixtype(arg);
+                       if ((arg->flags & NUMBER) != 0) {
+                               uval = get_number_uj(arg);
+                               if (gawk_mb_cur_max > 1) {
+                                       char buf[100];
+                                       wchar_t wc;
+                                       mbstate_t mbs;
+                                       size_t count;
+
+                                       memset(& mbs, 0, sizeof(mbs));
+
+                                       /* handle systems with too small 
wchar_t */
+                                       if (sizeof(wchar_t) < 4 && uval > 
0xffff) {
+                                               if (do_lint)
+                                                       lintwarn(
+                                               _("[s]printf: value %g is too 
big for %%c format"),
+                                                                       
arg->numbr);
+
+                                               goto out0;
+                                       }
+
+                                       wc = uval;
+
+                                       count = wcrtomb(buf, wc, & mbs);
+                                       if (count == 0
+                                           || count == (size_t) -1) {
+                                               if (do_lint)
+                                                       lintwarn(
+                                               _("[s]printf: value %g is not a 
valid wide character"),
+                                                                       
arg->numbr);
+
+                                               goto out0;
+                                       }
+
+                                       memcpy(cpbuf.buf, buf, count);
+                                       prec = count;
+                                       cp = cpbuf.buf;
+                                       goto pr_tail;
+                               }
+out0:
+                               ;
+                               /* else,
+                                       fall through */
+
+                               cpbuf.buf[0] = uval;
+                               prec = 1;
+                               cp = cpbuf.buf;
+                               goto pr_tail;
+                       }
+                       /*
+                        * As per POSIX, only output the first character of a
+                        * string value.  Thus, we ignore any provided
+                        * precision, forcing it to 1.  (Didn't this
+                        * used to work? 6/2003.)
+                        */
+                       cp = arg->stptr;
+                       prec = 1;
+                       /*
+                        * First character can be multiple bytes if
+                        * it's a multibyte character. Grr.
+                        */
+                       if (gawk_mb_cur_max > 1) {
+                               mbstate_t state;
+                               size_t count;
+
+                               memset(& state, 0, sizeof(state));
+                               count = mbrlen(cp, arg->stlen, & state);
+                               if (count != (size_t) -1 && count != (size_t) 
-2 && count > 0) {
+                                       prec = count;
+                                       /* may need to increase fw so that 
padding happens, see pr_tail code */
+                                       if (fw > 0)
+                                               fw += count - 1;
+                               }
+                       }
+                       goto pr_tail;
+               case 'S':               // POSIX 2024 extension, undocumented 
for now
+                       cs1 = 's';
+                       // FALL THROUGH
+               case 's':
+                       need_format = false;
+                       parse_next_arg();
+                       arg = force_string(arg);
+                       if (fw == 0 && ! have_prec)
+                               prec = arg->stlen;
+                       else {
+                               char_count = mbc_char_count(arg->stptr, 
arg->stlen);
+                               if (! have_prec || prec > char_count)
+                                       prec = char_count;
+                       }
+                       cp = arg->stptr;
+       pr_tail:
+                       if (! lj) {
+                               while (fw > prec) {
+                                       bchunk_one(fill);
+                                       fw--;
+                               }
+                       }
+                       copy_count = prec;
+                       if (fw == 0 && ! have_prec)
+                               ;
+                       else if (gawk_mb_cur_max > 1) {
+                               if (cs1 == 's') {
+                                       assert(cp == arg->stptr || cp == 
cpbuf.buf);
+                                       copy_count = mbc_byte_count(arg->stptr, 
prec);
+                               }
+                               /* prec was set by code for %c */
+                               /* else
+                                       copy_count = prec; */
+                       }
+                       bchunk(cp, copy_count);
+                       while (fw > prec) {
+                               bchunk_one(fill);
+                               fw--;
+                       }
+                       s0 = s1;
+                       break;
+
+               case 'd':
+               case 'i':
+                       need_format = false;
+                       parse_next_arg();
+                       (void) force_number(arg);
+                       base = 10;
+
+#define set_flags() \
+       flags.left_just = lj; \
+       flags.alt = alt; \
+       flags.zero = zero_flag; \
+       flags.space = space_flag; \
+       flags.plus = plus_flag; \
+       flags.quote = quote_flag; \
+       flags.have_prec = have_prec; \
+       flags.format = cs1; \
+       flags.base = base; \
+       flags.field_width = fw; \
+       flags.precision = prec; \
+       flags.negative = false
+
+                       set_flags();
+                       adjust_flags(& flags);
+
+                       formatted = format_signed_integer(arg, & flags);
+                       len = strlen(formatted);
+                       chksize(len)
+                       strcpy(obufout, formatted);
+                       free((void *) formatted);
+
+                       ofre -= len;
+                       obufout += len;
+                       s0 = s1;
+                       break;
+
+               case 'X':
+               case 'x':
+                       base += 6;      /* FALL THROUGH */
+               case 'u':
+                       base += 2;      /* FALL THROUGH */
+               case 'o':
+                       base += 8;
+                       need_format = false;
+                       parse_next_arg();
+                       (void) force_number(arg);
+
+                       set_flags();
+                       adjust_flags(& flags);
+
+                       formatted = format_unsigned_integer(arg, & flags);
+                       len = strlen(formatted);
+                       chksize(len)
+                       strcpy(obufout, formatted);
+                       free((void *) formatted);
+
+                       ofre -= len;
+                       obufout += len;
+                       s0 = s1;
+                       break;
+
+#if defined(PRINTF_HAS_A_FORMAT) && PRINTF_HAS_A_FORMAT == 1
+               case 'A':
+               case 'a':
+               {
+                       static bool warned = false;
+
+                       if (do_lint && tolower(cs1) == 'a' && ! warned) {
+                               warned = true;
+                               lintwarn(_("%%%c format is POSIX standard but 
not portable to other awks"), cs1);
+                       }
+                       base = 6;
+                       goto format_float;
+               }
+#endif
+               case 'F':
+#if ! defined(PRINTF_HAS_F_FORMAT) || PRINTF_HAS_F_FORMAT != 1
+                       cs1 = 'f';
+                       /* FALL THROUGH */
+#endif
+               case 'g':
+               case 'G':
+               case 'e':
+               case 'f':
+               case 'E':
+       format_float:
+                       base += 10;
+                       need_format = false;
+                       parse_next_arg();
+                       (void) force_number(arg);
+
+                       set_flags();
+                       adjust_flags(& flags);
+
+                       formatted = format_float(arg, & flags);
+                       len = strlen(formatted);
+                       chksize(len)
+                       strcpy(obufout, formatted);
+                       free((void *) formatted);
+
+                       ofre -= len;
+                       obufout += len;
+                       s0 = s1;
+                       break;
+               default:
+                       if (do_lint && is_alpha(cs1))
+                               lintwarn(_("ignoring unknown format specifier 
character `%c': no argument converted"), cs1);
+                       break;
+               }
+               if (toofew) {
+                       msg("%s\n\t`%s'\n\t%*s%s",
+                             _("fatal: not enough arguments to satisfy format 
string"),
+                             fmt_string, (int) (s1 - fmt_string - 1), "",
+                             _("^ ran out for this one"));
+                       goto out;
+               }
+       }
+       if (do_lint) {
+               if (need_format)
+                       lintwarn(
+                       _("[s]printf: format specifier does not have control 
letter"));
+               if (cur_arg < num_args)
+                       lintwarn(
+                       _("too many arguments supplied for format string"));
+       }
+       bchunk(s0, s1 - s0);
+       olen_final = obufout - obuf;
+#define GIVE_BACK_SIZE (INITIAL_OUT_SIZE * 2)
+       if (ofre > GIVE_BACK_SIZE)
+               erealloc(obuf, char *, olen_final + 1, "format_args");
+       r = make_str_node(obuf, olen_final, ALREADY_MALLOCED);
+       obuf = NULL;
+out:
+       if (cpbuf.buf != cpbuf.stackbuf)
+               efree(cpbuf.buf);
+
+       if (obuf != NULL)
+               efree(obuf);
+
+       if (r == NULL)
+               gawk_exit(EXIT_FATAL);
+       return r;
+}
+
+
+/* printf_common --- common code for sprintf and printf */
+
+static NODE *
+printf_common(int nargs)
+{
+       int i;
+       NODE *r, *tmp;
+
+       assert(nargs > 0 && nargs <= max_args);
+       for (i = 1; i <= nargs; i++) {
+               tmp = args_array[nargs - i] = POP();
+               if (tmp->type == Node_var_array) {
+                       while (--i > 0)
+                               DEREF(args_array[nargs - i]);
+                       fatal(_("attempt to use array `%s' in a scalar 
context"), array_vname(tmp));
+               }
+       }
+
+       args_array[0] = force_string(args_array[0]);
+       if (do_lint && (fixtype(args_array[0])->flags & STRING) == 0)
+               lintwarn(_("%s: received non-string format string argument"), 
"printf/sprintf");
+       r = format_args(args_array[0]->stptr, args_array[0]->stlen, args_array, 
nargs);
+       for (i = 0; i < nargs; i++)
+               DEREF(args_array[i]);
+       return r;
+}
+
+/* do_sprintf --- perform sprintf */
+
+NODE *
+do_sprintf(int nargs)
+{
+       NODE *r;
+
+       if (nargs == 0)
+               fatal(_("sprintf: no arguments"));
+
+       r = printf_common(nargs);
+       if (r == NULL)
+               gawk_exit(EXIT_FATAL);
+       return r;
+}
+
+
+/* do_printf --- perform printf, including redirection */
+
+void
+do_printf(int nargs, int redirtype)
+{
+       FILE *fp = NULL;
+       NODE *tmp;
+       struct redirect *rp = NULL;
+       int errflg = 0;
+       NODE *redir_exp = NULL;
+
+       if (nargs == 0) {
+               if (do_traditional) {
+                       if (do_lint)
+                               lintwarn(_("printf: no arguments"));
+                       if (redirtype != 0) {
+                               redir_exp = TOP();
+                               if (redir_exp->type != Node_val)
+                                       fatal(_("attempt to use array `%s' in a 
scalar context"), array_vname(redir_exp));
+                               rp = redirect(redir_exp, redirtype, & errflg, 
true);
+                               DEREF(redir_exp);
+                               decr_sp();
+                       }
+                       return; /* bwk accepts it silently */
+               }
+               fatal(_("printf: no arguments"));
+       }
+
+       if (redirtype != 0) {
+               redir_exp = PEEK(nargs);
+               if (redir_exp->type != Node_val)
+                       fatal(_("attempt to use array `%s' in a scalar 
context"), array_vname(redir_exp));
+               rp = redirect(redir_exp, redirtype, & errflg, true);
+               if (rp != NULL) {
+                       if ((rp->flag & RED_TWOWAY) != 0 && rp->output.fp == 
NULL) {
+                               if (is_non_fatal_redirect(redir_exp->stptr, 
redir_exp->stlen)) {
+                                       update_ERRNO_int(EBADF);
+                                       return;
+                               }
+                               (void) close_rp(rp, CLOSE_ALL);
+                               fatal(_("printf: attempt to write to closed 
write end of two-way pipe"));
+                       }
+                       fp = rp->output.fp;
+               }
+               else if (errflg) {
+                       update_ERRNO_int(errflg);
+                       return;
+               }
+       } else if (do_debug)    /* only the debugger can change the default 
output */
+               fp = output_fp;
+       else
+               fp = stdout;
+
+       tmp = printf_common(nargs);
+       if (redir_exp != NULL) {
+               DEREF(redir_exp);
+               decr_sp();
+       }
+       if (tmp != NULL) {
+               if (fp == NULL) {
+                       DEREF(tmp);
+                       return;
+               }
+               efwrite(tmp->stptr, sizeof(char), tmp->stlen, fp, "printf", rp, 
true);
+               if (rp != NULL && (rp->flag & RED_TWOWAY) != 0)
+                       rp->output.gawk_fflush(rp->output.fp, 
rp->output.opaque);
+               DEREF(tmp);
+       } else
+               gawk_exit(EXIT_FATAL);
+}
+
+/* format_integer_digits --- format just the actual value of an integer. 
caller frees return value */
+
+static char *
+format_integer_digits(NODE *arg, struct flags *flags, bool *used_float)
+{
+#define VALUE_SIZE 40
+       char *buf = NULL;
+       size_t buflen;
+       static const char lchbuf[] = "0123456789abcdef";
+       static const char Uchbuf[] = "0123456789ABCDEF";
+       const char *chbuf;
+       char *cp;
+       bool quote_flag = false;
+       uintmax_t uval;
+       int i;
+       double tmpval;
+
+#define growbuffer(buf, buflen, cp) { \
+               erealloc(buf, char *, buflen * 2, "format_integer_xxx"); \
+               cp = buf + buflen; \
+               buflen *= 2; \
+       }
+
+#if defined(HAVE_LOCALE_H)
+       quote_flag = (flags->quote && loc.thousands_sep[0] != '\0');
+#endif
+       emalloc(buf, char *, VALUE_SIZE, "format_integer_digits");
+       buflen = VALUE_SIZE;
+       cp = buf;
+
+       *used_float = false;
+       tmpval = double_to_int(arg->numbr);
+       if (flags->base == 10 && flags->format != 'u') {
+               // signed decimal
+               if (tmpval == -0)
+                       tmpval = 0;
+
+               /*
+                * Use snprintf return value to tell if there
+                * is enough room in the buffer or not.
+                */
+               while ((i = snprintf(buf, buflen, "%.0f", tmpval)) >= buflen) {
+                       if (i > 0)
+                               buflen += (i > buflen) ?  i : buflen;
+                       else
+                               buflen *= 2;
+                       assert(buflen > 0);
+                       erealloc(buf, char *, buflen, "format_args");
+               }
+       } else {
+               // octal or hex or unsigned decimal
+               chbuf = (flags->format == 'X' ? Uchbuf : lchbuf);
+
+               if (tmpval < 0) {
+                       uval = (uintmax_t) (intmax_t) tmpval;
+                       if ((AWKNUM)(intmax_t)uval != double_to_int(tmpval)) {
+                               flags->format = 'g';
+                               free((void *) buf);
+                               *used_float = true;
+
+                               return format_float(arg, flags);
+                       }
+               } else {
+                       uval = (uintmax_t) tmpval;
+                       if ((AWKNUM)uval != double_to_int(tmpval)) {
+                               flags->format = 'g';
+                               free((void *) buf);
+                               *used_float = true;
+
+                               return format_float(arg, flags);
+                       }
+               }
+
+               // generate the digits backwards.
+               do {
+                       if (cp >= buf + buflen)
+                               growbuffer(buf, buflen, cp);
+
+                       *cp++ = chbuf[uval % flags->base];
+                       uval /= flags->base;
+               } while (uval > 0);
+               *cp = '\0';
+
+               // turn it back around
+               reverse(buf);
+       }
+
+       if (flags->base == 10 && quote_flag) {
+               char *with_commas = add_thousands(buf);
+
+               free((void *) buf);
+               buf = with_commas;
+       }
+
+       return buf;
+}
+
+/* format_signed_integer --- format a signed integer (decimal) value. caller 
frees return value */
+
+static char *
+format_signed_integer(NODE *arg, struct flags *flags)
+{
+       char *number_value;
+       size_t val_len;
+       char *buf1 = NULL;
+       char fill = ' ';
+       bool used_float = false;
+
+       if (out_of_range(arg))
+               return format_out_of_range(arg, flags);
+
+       if (is_mpg_integer(arg) || is_mpg_float(arg))
+               number_value = format_mpg_integer_digits(arg, flags, & 
used_float);
+       else
+               number_value = format_integer_digits(arg, flags, & used_float); 
// just digits, possible leading '-'
+
+       if (used_float)
+               return number_value;
+
+       val_len = strlen(number_value);
+
+       // We now have the initial *integer* decimal value in hand.
+       // If it's decimal, we've added commas if appropriate. If it's negative
+       // and decimal, it has a minus sign.
+
+       // check if negative
+       flags->negative = (number_value[0] == '-');
+       
+       // The next step is deal with the rest of the printf flags.
+
+       // add more output digits to match the precision
+       if (flags->have_prec && flags->precision == 0 && is_zero(arg)) {
+               /*
+                * No actual value. But if there is plus or space or wide
+                * field width or left just, we have to handle those.
+                */
+               number_value[0] = '\0';         // value is empty
+
+               return add_plus_or_space_and_fill(number_value, flags);
+       } else if (flags->have_prec &&
+                       (val_len < flags->precision ||
+                        flags->negative && val_len - 1 < flags->precision)) {
+               buf1 = zero_fill_to_precision(number_value, flags);
+               val_len = strlen(buf1);
+
+               if (val_len < flags->field_width)
+                       buf1 = fill_to_field_width(buf1, flags, ' ');
+
+               return buf1;
+       } else if (flags->field_width > val_len) {
+               int fw = flags->field_width;
+               size_t buflen = flags->field_width +
+                       (flags->negative || flags->plus || flags->space) + 1;
+               char *cp;
+
+               if (compute_zero_flag(flags))
+                       fill = '0';
+
+               reverse(number_value);
+               if (flags->negative) {
+                       number_value[val_len - 1] = '\0';       // will restore 
later.
+                       val_len--;
+               }
+               
+               fw -= (flags->negative || flags->space || flags->plus);
+
+               emalloc(buf1, char *, buflen, "format_signed_integer");
+               strcpy(buf1, number_value);
+               free((void *) number_value);
+               cp = buf1 + val_len;
+
+               if (fill == '0')
+                       for (; fw > val_len; fw--)
+                               *cp++ = fill;
+
+               if (flags->negative)
+                       *cp++ = '-';
+               else if (flags->plus)
+                       *cp++ = '+';
+               else if (flags->space)
+                       *cp++ = ' ';
+
+               *cp = '\0';
+
+               reverse(buf1);
+
+               return fill_to_field_width(buf1, flags, ' ');
+       } else if ((flags->plus || flags->space) && ! flags->negative) {
+               emalloc(buf1, char *, val_len + 2, "format_signed_integer");
+               if (flags->plus) {
+                       sprintf(buf1, "+%s", number_value);
+               } else {
+                       sprintf(buf1, " %s", number_value);
+               }
+
+               free((void *) number_value);
+       } else
+               buf1 = number_value;
+
+       return buf1;
+}
+
+/* format_unsigned_integer --- format an unsigned integer value. caller frees 
return value */
+
+static char *
+format_unsigned_integer(NODE *arg, struct flags *flags)
+{
+       char *number_value;
+       char *buf1 = NULL;
+       char fill = ' ';
+       size_t val_len;
+       bool used_float = false;
+
+       if (out_of_range(arg))
+               return format_out_of_range(arg, flags);
+
+       if (is_mpg_integer(arg) || is_mpg_float(arg))
+               number_value = format_mpg_integer_digits(arg, flags, & 
used_float);
+       else
+               number_value = format_integer_digits(arg, flags, & used_float); 
// just digits
+
+       if (used_float)
+               return number_value;
+
+       val_len = strlen(number_value);
+
+       // We now have the initial *integer* decimal, octal, or hex value in 
hand.
+       // If it's decimal, we've added commas if appropriate.
+       
+       // The next step is deal with the rest of the printf flags.
+
+       if (flags->have_prec && flags->precision == 0 && is_zero(arg)
+               && (! flags->alt || flags->base != 8)) {
+                       number_value[0] = '\0';
+
+                       if (flags->field_width > 0)
+                               number_value = 
fill_to_field_width(number_value, flags, ' ');
+
+                       return number_value;
+       }
+
+       // Add more output digits to match the precision
+       if (flags->have_prec && val_len < flags->precision) {
+               // we ignore flags->alt when there's a precision
+               // also plus and space
+               flags->plus = flags->space = false;
+               buf1 = zero_fill_to_precision(number_value, flags);     // 
frees number_value
+               val_len = strlen(buf1);
+               number_value = buf1;
+       }
+
+       if (flags->alt && flags->base != 10 && ! is_zero(arg)) {
+               buf1 = add_alt_format(number_value, flags);
+               flags->alt = false;
+       } else {
+               buf1 = number_value;
+       }
+       val_len = strlen(buf1);
+
+       // continue with buf1
+       if (flags->field_width > val_len) {
+               // pad the field if necessary
+               // when there's a precision, field width padding with zeros is 
not done.
+               if (compute_zero_flag(flags))
+                       fill = '0';
+
+               buf1 = fill_to_field_width(buf1, flags, fill);
+               val_len = strlen(buf1);
+       }
+
+       if (flags->alt && ! is_zero(arg)) {
+               buf1 = add_alt_format(buf1, flags);
+               val_len = strlen(buf1);
+       }
+
+       return buf1;
+}
+
+/* format_out_of_range --- format an out of range value as %g. caller frees 
return value */
+
+static char *
+format_out_of_range(NODE *arg, struct flags *flags)
+{
+       /*
+        * out of range - emergency use of %g format,
+        * or format NaN and INF values.
+        */
+
+       // nan_inf_val points to a static buffer, don't free it.
+       const char *nan_inf_val = format_nan_inf(arg, flags->format);
+
+       if (do_posix || flags->magic_posix_flag || nan_inf_val == NULL) {
+               if (do_lint && ! do_posix && ! flags->magic_posix_flag)
+                       lintwarn(_("[s]printf: value %g is out of range for 
`%%%c' format"),
+                                               (double) arg->numbr,
+                                               flags->format);
+
+               if (strchr("aAeEfFgG", flags->format) == NULL)
+                       flags->format = 'g';
+
+               return format_float(arg, flags);
+       }
+
+       if (do_lint)
+               lintwarn(_("[s]printf: value %s is out of range for `%%%c' 
format"),
+                                       nan_inf_val, flags->format);
+
+       // A NaN or Inf, deal with a field width, if any
+       size_t len = strlen(nan_inf_val);
+       if (flags->field_width > len) {
+               char *buf = estrdup(nan_inf_val, len);
+
+               buf = fill_to_field_width(buf, flags, ' ');     // frees 
original
+
+               return buf;
+       }
+
+       return estrdup(nan_inf_val, len);
+}
+
+/* compute_zero_flag --- return true if we want to fill with zeros */
+
+static bool
+compute_zero_flag(struct flags *flags)
+{
+       bool zero_flag;
+
+       /*
+        * When to fill with zeroes is of course not simple.
+        * First: No zero fill if left-justifying.
+        * Next: There seem to be two cases:
+        *      A '0' without a precision, e.g. %06d
+        *      A precision with no field width, e.g. %.10d
+        * Any other case, we don't want to fill with zeroes.
+        */
+       zero_flag = (! flags->left_just
+                   && ((flags->zero && ! flags->have_prec)
+                        || (flags->field_width == 0 && flags->have_prec)));
+
+       return zero_flag;
+}
+
+/* format_mpg_integer_digits --- format an MPZ or MPFR integer. caller frees 
return value */
+
+/*
+ * It seems that mpfr_snprintf() doesn't exactly mimic snprintf() with respect
+ * to all the flags and width and precision.  So we instead we simply generate
+ * the digits and let the higher level code handle padding and so on.
+ */
+
+static char *
+format_mpg_integer_digits(NODE *arg, struct flags *flags, bool *used_float)
+{
+#ifdef HAVE_MPFR
+       mpz_ptr zi = NULL;
+       mpfr_ptr mf = NULL;
+       char *buf;
+       size_t buflen;
+       char cpbuf[30];
+       int nc;
+       bool quote_flag = false;
+
+       *used_float = false;
+
+#if defined(HAVE_LOCALE_H)
+       quote_flag = (flags->quote && loc.thousands_sep[0] != '\0');
+#endif
+
+       if (is_zero(arg))
+               return estrdup("0", 1);
+
+       if (is_mpg_integer(arg)) {
+               zi = arg->mpg_i;
+
+               if (flags->format != 'd' && flags->format != 'i') {
+                       if (mpz_sgn(zi) <= 0) {
+                               /*
+                                * Negative value or 0 requires special 
handling.
+                                * Unlike MPFR, GMP does not allow conversion
+                                * to (u)intmax_t. So we first convert GMP type 
to
+                                * a MPFR type.
+                                */
+                               mf = mpz2mpfr(zi);
+                               goto mpf1;
+                       }
+               }
+
+
+       } else if (is_mpg_float(arg)) {
+               mf = arg->mpg_numbr;
+               if (! mpfr_number_p(mf)) {
+                       cant_happen("%s", "format_mpg_integer called on nan or 
inf");
+               }
+
+               if (flags->format != 'd' && flags->format != 'i') {
+mpf1:
+                       /*
+                        * The output of printf("%#.0x", 0) is 0 instead of 0x, 
hence <= in
+                        * the comparison below.
+                        */
+                       if (mpfr_sgn(mf) <= 0) {
+                               if (! mpfr_fits_intmax_p(mf, ROUND_MODE)) {
+                                       *used_float = true;
+
+                                       return format_float(arg, flags);
+                               }
+                               goto fmt0;
+                       }
+               }
+
+               (void) mpfr_get_z(mpzval, mf, MPFR_RNDZ);       /* convert to 
GMP integer */
+               zi = mpzval;
+               // fall through
+       }
+fmt0:
+       buflen = flags->field_width + flags->precision + 11;    /* 11 == slop */
+       emalloc(buf, char *, buflen, "format_mpg_integer");
+
+
+#if defined(LC_NUMERIC)
+       if (quote_flag && ! use_lc_numeric)
+               setlocale(LC_NUMERIC, "");
+#endif
+
+       sprintf(cpbuf, "%%Z%c", flags->format);
+       while ((nc = mpfr_snprintf(buf, buflen, cpbuf, zi)) >= (int) buflen) {
+               buflen *= 2;
+               erealloc(buf, char *, buflen, "format_mpg_integer");
+       }
+
+#if defined(LC_NUMERIC)
+       if (quote_flag && ! use_lc_numeric)
+               setlocale(LC_NUMERIC, "C");
+#endif
+
+       if (quote_flag && flags->base == 10) {
+               char *new_text = add_thousands(buf);
+
+               free((void *) buf);
+               buf = new_text;
+       }
+
+       return buf;
+#else
+       cant_happen("%s", "trying to format GMP integer");
+#endif
+}
+
+
+/* adjust_flags --- take care of conflicts between flags */
+
+/*
+ * The flag characters and their meanings are:
+ * 
+ * - The result of the conversion is left-justified within the field. (It is
+ *   right-justified if this flag is not specified.)
+ * 
+ * + The result of a signed conversion always begins with a plus or minus
+ *   sign. (It begins with a sign only when a value with a negative sign
+ *   is converted if this flag is not specified.)
+ *
+ * space   If the first character of a signed conversion is not a sign,
+ *   or if a signed conversion results in no characters, a space is prefixed
+ *   to the result. If the space and + flags both appear, the space flag is 
ignored.
+ * 
+ * # The result is converted to an "alternative form". For o conversion,
+ *   it increases the precision, if and only if necessary, to force the
+ *   first digit of the result to be a zero (if the value and precision are
+ *   both 0, a single 0 is printed). For b conversion, a nonzero result has
+ *   0b prefixed to it. For the optional B conversion as described below,
+ *   a nonzero result has 0B prefixed to it. For x (or X) conversion,
+ *   a nonzero result has 0x (or 0X) prefixed to it. For a, A, e, E, f,
+ *   F, g, and G conversions, the result of converting a floating-point
+ *   number always contains a decimal-point character, even if no digits
+ *   follow it. (Normally, a decimal-point character appears in the result of
+ *   these conversions only if a digit follows it.) For g and G conversions,
+ *   trailing zeros are not removed from the result. For other conversions,
+ *   the behavior is undefined.
+ * 
+ * 0 For b, d, i, o, u, x, X, a, A, e, E, f, F, g, and G conversions,
+ *   leading zeros (following any indication of sign or base) are used to
+ *   pad to the field width rather than performing space padding, except
+ *   when converting an infinity or NaN. If the 0 and - flags both appear,
+ *   the 0 flag is ignored. For d, i, o, u, x, and X conversions, if a
+ *   preision is specified, the 0 flag is ignored. For other conversions,
+ *   the behavior is undefined.
+ */
+
+static void
+adjust_flags(struct flags *flags)
+{
+       if (flags->base == 0)
+               cant_happen("%s", "flags->base == 0");
+
+       if (flags->base == 10 && strchr("cdisu", flags->format) != NULL)
+               flags->alt = false;
+
+       if (flags->field_width < 0) {
+               flags->field_width = -flags->field_width;
+               flags->left_just = true;
+       }
+
+       if (flags->base != 10) {
+               flags->quote = false;
+       }
+
+       if (flags->plus)
+               flags->space = false;
+
+       if (strchr("diouxX", flags->format) != NULL && flags->have_prec)
+               flags->zero = false;
+}
+
+/* format_float --- format a floating point number. caller frees return value 
*/
+
+/*
+ * We let the C and MPFR libraries handle the quote flag, since they
+ * know how to do it for the floating point formats.  Thus here we do NOT
+ * set up a separate quote_flag variable based on the contents of the
+ * struct locale loc.
+ */
+
+static char *
+format_float(NODE *arg, struct flags *flags)
+{
+       char *buf;
+       size_t buflen;
+       char *cp;
+       char cpbuf[100];
+       
+       double tmpval;
+#ifdef HAVE_MPFR
+       mpfr_ptr mf = NULL;
+#endif
+       int nc;
+       bool mpfr_format = false;
+
+       if (out_of_range(arg))
+               return format_out_of_range(arg, flags);
+
+       (void) force_number(arg);
+
+       if (! is_mpg_number(arg))
+               tmpval = arg->numbr;
+#ifdef HAVE_MPFR
+       else if (is_mpg_float(arg)) {
+               mf = arg->mpg_numbr;
+               mpfr_format = true;
+       } else {
+               /* arbitrary-precision integer, convert to MPFR float */
+               assert(mf == NULL);
+               mf = mpz2mpfr(arg->mpg_i);
+               mpfr_format = true;
+       }
+#endif
+
+       if (! flags->have_prec)
+               flags->precision = DEFAULT_G_PRECISION;
+
+
+       buflen = flags->field_width + flags->precision + 11;    /* 11 == slop */
+       emalloc(buf, char *, buflen, "format_float");
+
+       int signchar = '\0';
+       if (flags->plus)
+               signchar = '+';
+       else if (flags->space)
+               signchar = ' ';
+
+       cp = cpbuf;
+       *cp++ = '%';
+       if (flags->left_just)
+               *cp++ = '-';
+       if (signchar)
+               *cp++ = signchar;
+       if (flags->alt)
+               *cp++ = '#';
+       if (flags->zero)
+               *cp++ = '0';
+       if (flags->quote)
+               *cp++ = '\'';
+
+#if defined(LC_NUMERIC)
+       if (flags->quote && ! use_lc_numeric)
+               setlocale(LC_NUMERIC, "");
+#endif
+
+       if (mpfr_format) {
+#ifdef HAVE_MPFR
+               sprintf(cp, "*.*R*%c", flags->format);
+               while ((nc = mpfr_snprintf(buf, buflen, cpbuf,
+                            flags->field_width, flags->precision, ROUND_MODE, 
mf)) >= (int) buflen) {
+                       erealloc(buf, char *, buflen * 2, "format_float");
+                       buflen *= 2;
+               }
+#else
+               cant_happen("trying to format GMP/MPFR number");
+#endif
+       } else {
+               if (flags->have_prec || tolower(flags->format) != 'a') {
+                       sprintf(cp, "*.*%c", flags->format);
+                       while ((nc = snprintf(buf, buflen, cpbuf,
+                                    flags->field_width, flags->precision,
+                                    (double) tmpval)) >= (int) buflen) {
+                               erealloc(buf, char *, buflen * 2, 
"format_float");
+                               buflen *= 2;
+                       }
+               } else {
+                       // For %a and %A, use the default precision if it
+                       // wasn't supplied by the user.
+                       sprintf(cp, "*%c", flags->format);
+                       while ((nc = snprintf(buf, buflen, cpbuf,
+                                    flags->field_width,
+                                    (double) tmpval)) >= (int) buflen) {
+                               erealloc(buf, char *, buflen * 2, 
"format_float");
+                               buflen *= 2;
+                       }
+               }
+       }
+
+#if defined(LC_NUMERIC)
+       if (flags->quote && ! use_lc_numeric)
+               setlocale(LC_NUMERIC, "C");
+#endif
+
+       return buf;
+}
+
+/* mbc_byte_count --- return number of bytes for corresponding numchars 
multibyte characters */
+
+static size_t
+mbc_byte_count(const char *ptr, size_t numchars)
+{
+       mbstate_t cur_state;
+       size_t sum = 0;
+       int mb_len;
+
+       memset(& cur_state, 0, sizeof(cur_state));
+
+       assert(gawk_mb_cur_max > 1);
+       mb_len = mbrlen(ptr, numchars * gawk_mb_cur_max, &cur_state);
+       if (mb_len <= 0)
+               return numchars;        /* no valid m.b. char */
+
+       for (; numchars > 0; numchars--) {
+               mb_len = mbrlen(ptr, numchars * gawk_mb_cur_max, &cur_state);
+               if (mb_len <= 0)
+                       break;
+               sum += mb_len;
+               ptr += mb_len;
+       }
+
+       return sum;
+}
+
+/* mbc_char_count --- return number of m.b. chars in string, up to numbytes 
bytes */
+
+static size_t
+mbc_char_count(const char *ptr, size_t numbytes)
+{
+       mbstate_t cur_state;
+       size_t sum = 0;
+       int mb_len;
+
+       if (gawk_mb_cur_max == 1)
+               return numbytes;
+
+       memset(& cur_state, 0, sizeof(cur_state));
+
+       mb_len = mbrlen(ptr, numbytes, &cur_state);
+       if (mb_len <= 0)
+               return numbytes;        /* no valid m.b. char */
+
+       while (numbytes > 0) {
+               mb_len = mbrlen(ptr, numbytes, &cur_state);
+               if (mb_len <= 0)
+                       break;
+               sum++;
+               ptr += mb_len;
+               numbytes -= mb_len;
+       }
+
+       return sum;
+}
+
+/* out_of_range --- return true if a value is out of range */
+
+bool
+out_of_range(NODE *n)
+{
+#ifdef HAVE_MPFR
+       if (is_mpg_integer(n))
+               return false;
+       else if (is_mpg_float(n))
+               return (! mpfr_number_p(n->mpg_numbr));
+       else
+#endif
+               return (isnan(n->numbr) || isinf(n->numbr));
+}
+
+/* format_nan_inf --- format NaN and INF values. return value is to a static 
buffer */
+
+char *
+format_nan_inf(NODE *n, char format)
+{
+       static char buf[100];
+       double val = n->numbr;
+       enum possible { PLUS_NAN, MINUS_NAN, PLUS_INF, MINUS_INF, NONE } result 
= NONE;
+       static const char *strings[] = { "+nan", "-nan", "+inf", "-inf" };
+
+#ifdef HAVE_MPFR
+       if (is_mpg_integer(n))
+               return NULL;
+       else if (is_mpg_float(n)) {
+               if (mpfr_nan_p(n->mpg_numbr)) {
+                       result = (mpfr_signbit(n->mpg_numbr) != 0 ? MINUS_NAN : 
PLUS_NAN);
+               } else if (mpfr_inf_p(n->mpg_numbr)) {
+                       result = (mpfr_signbit(n->mpg_numbr) != 0 ? MINUS_INF : 
PLUS_INF);
+               } else
+                       return NULL;
+       }
+       /* else
+               fallthrough */
+#endif
+
+       if (result == NONE) {
+               if (isnan(val)) {
+                       result = (signbit(val) != 0 ? MINUS_NAN : PLUS_NAN);
+               } else if (isinf(val)) {
+                       result = (val < 0 ? MINUS_INF : PLUS_INF);
+               } else
+                       return NULL;
+       }
+
+       strcpy(buf, strings[(int) result]);
+
+       if (isupper(format)) {
+               int i;
+
+               for (i = 0; buf[i] != '\0'; i++)
+                       buf[i] = toupper(buf[i]);
+       }
+
+       return buf;
+}
+
+
+/* reverse --- reverse the contents of a string in place */
+
+static void
+reverse(char *str)
+{
+       int i, j;
+       char tmp;
+
+       for (i = 0, j = strlen(str) - 1; j > i; i++, j--) {
+               tmp = str[i];
+               str[i] = str[j];
+               str[j] = tmp;
+       }
+}
+
+/* add_thousands --- add the thousands separator. caller frees the return 
value */
+
+/*
+ * Copy the source string into the destination string, backwards,
+ * adding the thousands separator at the right points. Then reverse
+ * the string when done. This gives us much cleaner code than trying
+ * to work through the string backwards. (We tried it, it was yucky.)
+ */
+
+static char *
+add_thousands(const char *original)
+{
+       size_t orig_len = strlen(original);
+       size_t new_len = orig_len + 1;
+       char *newbuf;
+       const char *src;
+       char *dest;
+
+       emalloc(newbuf, char *, new_len, "add_thousands");
+       memset(newbuf, '\0', new_len);
+
+#if defined(HAVE_LOCALE_H)
+       new_len = orig_len + (orig_len * strlen(loc.thousands_sep)) + 1;        
// worst case
+       erealloc(newbuf, char *, new_len, "add_thousands");
+       memset(newbuf, '\0', new_len);
+
+       src = original + strlen(original) - 1;
+       dest = newbuf;
+
+       if (loc.decimal_point[0] != '\0') {
+               const char *dec = NULL;
+
+               if ((dec = strchr(original, loc.decimal_point[0])) != NULL) {
+                       while (src >= dec)
+                               *dest++ = *src--;
+               }
+       }
+
+
+       int ii = 0;
+       int jj = 0;
+       do {
+               *dest++ = *src--;
+               if (loc.grouping[ii] && ++jj == loc.grouping[ii]) {
+                       if (src >= original) {  /* only add if more digits 
coming */
+                               const char *ts = loc.thousands_sep;
+                               int k;
+
+                               for (k = strlen(ts) - 1; k >= 0; k--)
+                                       *dest++ = ts[k];
+                       }
+                       if (loc.grouping[ii+1] == 0)
+                               jj = 0;         /* keep using current val in 
loc.grouping[ii] */
+                       else if (loc.grouping[ii+1] == CHAR_MAX) {
+                               // copy in the rest and be done
+                               while (src >= original)
+                                       *dest++ = *src--;
+                               break;
+                       } else {
+                               ii++;
+                               jj = 0;
+                       }
+               }
+       } while (src >= original);
+
+       *dest++ = '\0';
+       reverse(newbuf);
+#else
+       strcpy(newbuf, original);
+#endif
+
+       return newbuf;
+}
+
+/* fill_to_field_width --- handle justification and padding, frees startval if 
necessary, caller frees return */
+
+static char *
+fill_to_field_width(char *startval, struct flags *flags, int fill)
+{
+       size_t l = strlen(startval);
+       char *buf;
+       char *cp;
+       int fw = flags->field_width;
+
+       if (l >= fw)    // nothing to do
+               return startval;
+
+       emalloc(buf, char *, fw + 1, "fill_to_field_width");
+       cp = buf;
+
+       if (flags->left_just) {
+               strcpy(buf, startval);
+               cp += l;
+               fw -= l;
+               for (; fw > 0; fw--)
+                       *cp++ = fill;
+               *cp = '\0';
+       } else {
+               for (; fw > l; fw--)
+                       *cp++ = fill;
+               strcpy(cp, startval);
+       }
+
+       free((void *) startval);
+
+       return buf;
+}
+
+/* add_plus_or_space_and_fill --- what it says */
+
+static char *
+add_plus_or_space_and_fill(char *number_value, struct flags *flags)
+{
+       char *buf1;
+       char *cp;
+       size_t buflen;
+       int fw = flags->field_width;
+
+       buflen = flags->field_width + strlen(number_value) +
+               (flags->space || flags->plus || flags->negative) + 1;
+
+       emalloc(buf1, char *, buflen, "add_plus_or_space_and_fill");
+       cp = buf1;
+
+       if (flags->left_just) {
+               if (flags->negative) {
+                       *cp++ = '-';
+                       fw--;
+               } else if (flags->plus) {
+                       *cp++ = '+';
+                       fw--;
+               } else if (flags->space) {
+                       *cp++ = ' ';
+                       fw--;
+               }
+               for (; fw > 0; fw--)
+                       *cp++ = ' ';
+               *cp = '\0';
+       } else if (fw > 0 || flags->plus || flags->space) {
+               char final = ' ';
+               if (flags->negative)
+                       final = '-';
+               else if (flags->plus)
+                       final = '+';
+
+               for (fw--; fw > 0; fw--)
+                       *cp++ = ' ';
+               *cp++ = final;
+               *cp = '\0';
+       } else
+               buf1[0] = '\0';
+
+       strcat(buf1, number_value);
+
+       free((void *) number_value);
+
+       return buf1;
+
+}
+
+/* zero_fill_to_precision --- what it says */
+
+static char *
+zero_fill_to_precision(char *number_value, struct flags *flags)
+{
+       char *buf1;
+       char *cp, *src;
+       size_t buflen;
+       int prec = flags->precision;
+       size_t val_len = strlen(number_value);
+
+       buflen = flags->precision + 1;  // we know val_len < precision
+
+       emalloc(buf1, char *, buflen, "zero_fill_to_precision");
+       cp = buf1;
+       src = number_value;
+
+       if (flags->negative) {
+               *cp++ = '-';
+               src++;
+               val_len--;
+       } else if (flags->plus) {
+               *cp++ = '+';
+       } else if (flags->space) {
+               *cp++ = ' ';
+       }
+
+       for (; prec > val_len; prec--)
+               *cp++ = '0';
+
+       strcpy(cp, src);
+       free((void *) number_value);
+
+       return buf1;
+}
+
+/* add_alt_format --- deal with zero padding and the alt flag */
+
+static char *
+add_alt_format(char *number_value, struct flags *flags)
+{
+       int fw;
+       bool zero_flag;
+       char *buf, *cp;
+       size_t buflen;
+       size_t val_len = strlen(number_value);
+
+       assert(flags->alt);
+       assert(flags->base != 10);
+
+       zero_flag = compute_zero_flag(flags);
+
+       buflen = flags->field_width;
+       if (buflen < val_len)
+               buflen = val_len;
+       buflen += 3;
+
+       emalloc(buf, char *, buflen, "add_alt_format");
+       cp = buf;
+
+       fw = flags->field_width;
+       if (flags->base == 16) {
+               *cp++ = '0';
+               *cp++ = flags->format;
+               fw -= 2;
+
+               if (zero_flag) {
+                       fw -= val_len;
+                       for (; fw > 0; fw--)
+                               *cp++ = '0';
+                       // fall through
+               }
+
+               strcpy(cp, number_value);
+       } else if (number_value[0] != '0') {
+               sprintf(buf, "0%s", number_value);
+       } else {
+               strcpy(buf, number_value);
+       }
+
+       free((void *) number_value);
+
+       return buf;
+}
diff --git a/test/ChangeLog b/test/ChangeLog
index 47ac9661..963bd31e 100644
--- a/test/ChangeLog
+++ b/test/ChangeLog
@@ -1,3 +1,31 @@
+2024-08-06         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf-corners.awk, printf-corners.ok, printf-corners-mpfr: Add
+       six additional test cases. Thanks to Maciej W. Rozycki
+       <macro@redhat.com>.
+
+2024-07-23         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * printf-corners.awk, printf-corners.ok, printf-corners-mpfr: Add
+       an additional test case.
+
+2024-07-05         Maciej W. Rozycki     <macro@redhat.com>
+
+       * printf-corners.awk, printf-corners.ok, printf-corners-mpfr: Remove
+       cases with "inf" input.  Add further cases.
+
+2024-07-11         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * Makefile.am (EXTRA_DIST): New test, printf-corners.
+       * printf-corners.awk, printf-corners.ok, printf-corners-mpfr.ok: New
+       files.
+
+2024-06-27         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * hsprint.awk: Add headers to output, fix printing of
+       format string.
+       * hsprint.ok: Updated.
+
 2024-06-13         Arnold D. Robbins     <arnold@skeeve.com>
 
        * Makefile.am (EXTRA_DIST): Add asortsymtab-mpfr.ok.
diff --git a/test/Makefile.am b/test/Makefile.am
index 0f1d5178..878404fc 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1051,6 +1051,9 @@ EXTRA_DIST = \
        prdupval.ok \
        prec.awk \
        prec.ok \
+       printf-corners.awk \
+       printf-corners.ok \
+       printf-corners-mpfr.ok \
        printf0.awk \
        printf0.ok \
        printf1.awk \
@@ -1549,7 +1552,7 @@ BASIC_TESTS = \
        substr swaplns synerr1 synerr2 synerr3 tailrecurse tradanch \
        trailbs tweakfld uninit2 uninit3 uninit4 uninit5 uninitialized \
        unterm uparrfs uplus wideidx wideidx2 widesub widesub2 widesub3 \
-       match4 strsubscript \
+       match4 strsubscript printf-corners \
        widesub4 wjposer1 zero2 zeroe0 zeroflag
 
 UNIX_TESTS = \
diff --git a/test/Makefile.in b/test/Makefile.in
index 69649f08..c4a054e4 100644
--- a/test/Makefile.in
+++ b/test/Makefile.in
@@ -1315,6 +1315,9 @@ EXTRA_DIST = \
        prdupval.ok \
        prec.awk \
        prec.ok \
+       printf-corners.awk \
+       printf-corners.ok \
+       printf-corners-mpfr.ok \
        printf0.awk \
        printf0.ok \
        printf1.awk \
@@ -1813,7 +1816,7 @@ BASIC_TESTS = \
        substr swaplns synerr1 synerr2 synerr3 tailrecurse tradanch \
        trailbs tweakfld uninit2 uninit3 uninit4 uninit5 uninitialized \
        unterm uparrfs uplus wideidx wideidx2 widesub widesub2 widesub3 \
-       match4 strsubscript \
+       match4 strsubscript printf-corners \
        widesub4 wjposer1 zero2 zeroe0 zeroflag
 
 UNIX_TESTS = \
@@ -4430,6 +4433,13 @@ strsubscript:
        @-AWKPATH="$(srcdir)" $(AWK) -f $@.awk  >_$@ 2>&1 || echo EXIT CODE: 
$$? >>_$@
        @-$(CMP) "$(srcdir)"/$@.ok _$@ && rm -f _$@
 
+printf-corners:
+       @echo $@
+       @-AWKPATH="$(srcdir)" $(AWK) -f $@.awk  >_$@ 2>&1 || echo EXIT CODE: 
$$? >>_$@
+       @-if echo "$$GAWK_TEST_ARGS" | egrep -s -e '-M|--bignum' > /dev/null ; \
+       then $(CMP) "$(srcdir)"/$@-mpfr.ok _$@ && rm -f _$@ ; \
+       else $(CMP) "$(srcdir)"/$@.ok _$@ && rm -f _$@ ; fi
+
 widesub4:
        @echo $@ $(ZOS_FAIL)
        @-[ -z "$$GAWKLOCALE" ] && GAWKLOCALE=en_US.UTF-8; export GAWKLOCALE; \
diff --git a/test/Maketests b/test/Maketests
index 3538d141..cb6992a1 100644
--- a/test/Maketests
+++ b/test/Maketests
@@ -1298,6 +1298,13 @@ strsubscript:
        @-AWKPATH="$(srcdir)" $(AWK) -f $@.awk  >_$@ 2>&1 || echo EXIT CODE: 
$$? >>_$@
        @-$(CMP) "$(srcdir)"/$@.ok _$@ && rm -f _$@
 
+printf-corners:
+       @echo $@
+       @-AWKPATH="$(srcdir)" $(AWK) -f $@.awk  >_$@ 2>&1 || echo EXIT CODE: 
$$? >>_$@
+       @-if echo "$$GAWK_TEST_ARGS" | egrep -s -e '-M|--bignum' > /dev/null ; \
+       then $(CMP) "$(srcdir)"/$@-mpfr.ok _$@ && rm -f _$@ ; \
+       else $(CMP) "$(srcdir)"/$@.ok _$@ && rm -f _$@ ; fi
+
 widesub4:
        @echo $@ $(ZOS_FAIL)
        @-[ -z "$$GAWKLOCALE" ] && GAWKLOCALE=en_US.UTF-8; export GAWKLOCALE; \
diff --git a/test/hsprint.awk b/test/hsprint.awk
index facc109d..471d9a89 100644
--- a/test/hsprint.awk
+++ b/test/hsprint.awk
@@ -10,62 +10,73 @@
 
 
 BEGIN {
-    zero = "0";
-    alt  = "#";
-    spc  = " ";
-    plus = "+";
-    just = "-";
-    value[0] = 45;
-    value[1] = 45;
-    value[2] = 45;
-    value[3] = 12.678;
-    value[4] = 12.678;
-    value[5] = 12.678;
-    value[6] = "zap";
-    value[7] = "*";
-    value[8] = -3.4567;
-    value[9] = -3.4567;
-    value[10]= -3.4567;
-    value[11]= -3.4567;
-    oper[0]  = "5d";
-    oper[1]  = "5o";
-    oper[2]  = "5x";
-    oper[3]  = "7.2f";
-    oper[4]  = "10.2e";
-    oper[5]  = "10.4g";
-    oper[6]  = "5s";
-    oper[7]  = "5c";
-    oper[8]  = "7.1G";
-    oper[9]  = "7.2f";
-    oper[10] = "10.2e";
-    oper[11] = "10.4g";
+    zero = "0"
+    alt  = "#"
+    spc  = " "
+    plus = "+"
+    just = "-"
+    value[0] = 45
+    value[1] = 45
+    value[2] = 45
+    value[3] = 12.678
+    value[4] = 12.678
+    value[5] = 12.678
+    value[6] = "zap"
+    value[7] = "*"
+    value[8] = -3.4567
+    value[9] = -3.4567
+    value[10]= -3.4567
+    value[11]= -3.4567
+    oper[0]  = "5d"
+    oper[1]  = "5o"
+    oper[2]  = "5x"
+    oper[3]  = "7.2f"
+    oper[4]  = "10.2e"
+    oper[5]  = "10.4g"
+    oper[6]  = "5s"
+    oper[7]  = "5c"
+    oper[8]  = "7.1G"
+    oper[9]  = "7.2f"
+    oper[10] = "10.2e"
+    oper[11] = "10.4g"
+
+    header_fmt[0] = "fl\\fmt|%5s|%5s|%5s|%7s|%10s|%10s|\n"
+    header_fmt[1] = "fl\\fmt|%5s|%5s|%7s|%7s|%10s|%10s|\n"
+    header_sep[0] = "------+-----+-----+-----+-------+----------+----------+\n"
+    header_sep[1] = 
"------+-----+-----+-------+-------+----------+----------+\n"
 
     
     for (r = 0; r < 12; r += 6) {
+       printf(header_fmt[int(r / 6)],
+                  oper[r], oper[r+1], oper[r+2], oper[r+3], oper[r+4], 
oper[r+5])
+       printf(header_sep[int(r / 6)])
        for (j = 2; j > 0; --j) {
            for (p = 2; p > 0; --p) {
                for (s = 2; s > 0; --s) {
                    for (a = 2; a > 0; --a) {
                        for (z = 2; z > 0; --z) {
                            fmt = "%" substr(just,j,1) substr(plus,p,1) \
-                             substr(spc,s,1) substr(alt,a,1) substr(zero,z,1);
+                             substr(spc,s,1) substr(alt,a,1) substr(zero,z,1)
                            fstr = sprintf(\
                                     "%6s|%s%s|%s%s|%s%s|%s%s|%s%s|%s%s|\n",
-                                          "%" fmt, 
+                                          # "%" fmt, 
+                                          fmt, 
                                           fmt, oper[r],
                                           fmt, oper[r+1],
                                           fmt, oper[r+2],
                                           fmt, oper[r+3],
                                           fmt, oper[r+4],
-                                          fmt, oper[r+5]);
+                                          fmt, oper[r+5])
+#                          xstr = fstr; sub("\n", " +++\n", xstr)
+#                          printf("%s", xstr)
                            printf(fstr, value[r],   value[r+1],
                                         value[r+2], value[r+3],
-                                        value[r+4], value[r+5]);
+                                        value[r+4], value[r+5])
                        }
                    }
                }
            }
        }
-       print "";
+       print ""
     }
 }
diff --git a/test/hsprint.ok b/test/hsprint.ok
index f0a9e9cb..bc68cf41 100644
--- a/test/hsprint.ok
+++ b/test/hsprint.ok
@@ -1,66 +1,70 @@
-    %|   45|   55|   2d|  12.68|  1.27e+01|     12.68|
-   %0|00045|00055|0002d|0012.68|001.27e+01|0000012.68|
-   %#|   45|  055| 0x2d|  12.68|  1.27e+01|     12.68|
-  %#0|00045|00055|0x02d|0012.68|001.27e+01|0000012.68|
-   % |   45|   55|   2d|  12.68|  1.27e+01|     12.68|
-  % 0| 0045|00055|0002d| 012.68| 01.27e+01| 000012.68|
-  % #|   45|  055| 0x2d|  12.68|  1.27e+01|     12.68|
- % #0| 0045|00055|0x02d| 012.68| 01.27e+01| 000012.68|
-   %+|  +45|   55|   2d| +12.68| +1.27e+01|    +12.68|
-  %+0|+0045|00055|0002d|+012.68|+01.27e+01|+000012.68|
-  %+#|  +45|  055| 0x2d| +12.68| +1.27e+01|    +12.68|
- %+#0|+0045|00055|0x02d|+012.68|+01.27e+01|+000012.68|
-  %+ |  +45|   55|   2d| +12.68| +1.27e+01|    +12.68|
- %+ 0|+0045|00055|0002d|+012.68|+01.27e+01|+000012.68|
- %+ #|  +45|  055| 0x2d| +12.68| +1.27e+01|    +12.68|
-%+ #0|+0045|00055|0x02d|+012.68|+01.27e+01|+000012.68|
-   %-|45   |55   |2d   |12.68  |1.27e+01  |12.68     |
-  %-0|45   |55   |2d   |12.68  |1.27e+01  |12.68     |
-  %-#|45   |055  |0x2d |12.68  |1.27e+01  |12.68     |
- %-#0|45   |055  |0x2d |12.68  |1.27e+01  |12.68     |
-  %- | 45  |55   |2d   | 12.68 | 1.27e+01 | 12.68    |
- %- 0| 45  |55   |2d   | 12.68 | 1.27e+01 | 12.68    |
- %- #| 45  |055  |0x2d | 12.68 | 1.27e+01 | 12.68    |
-%- #0| 45  |055  |0x2d | 12.68 | 1.27e+01 | 12.68    |
-  %-+|+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
- %-+0|+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
- %-+#|+45  |055  |0x2d |+12.68 |+1.27e+01 |+12.68    |
-%-+#0|+45  |055  |0x2d |+12.68 |+1.27e+01 |+12.68    |
- %-+ |+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
-%-+ 0|+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
-%-+ #|+45  |055  |0x2d |+12.68 |+1.27e+01 |+12.68    |
+fl\fmt|   5d|   5o|   5x|   7.2f|     10.2e|     10.4g|
+------+-----+-----+-----+-------+----------+----------+
+     %|   45|   55|   2d|  12.68|  1.27e+01|     12.68|
+    %0|00045|00055|0002d|0012.68|001.27e+01|0000012.68|
+    %#|   45|  055| 0x2d|  12.68|  1.27e+01|     12.68|
+   %#0|00045|00055|0x02d|0012.68|001.27e+01|0000012.68|
+    % |   45|   55|   2d|  12.68|  1.27e+01|     12.68|
+   % 0| 0045|00055|0002d| 012.68| 01.27e+01| 000012.68|
+   % #|   45|  055| 0x2d|  12.68|  1.27e+01|     12.68|
+  % #0| 0045|00055|0x02d| 012.68| 01.27e+01| 000012.68|
+    %+|  +45|   55|   2d| +12.68| +1.27e+01|    +12.68|
+   %+0|+0045|00055|0002d|+012.68|+01.27e+01|+000012.68|
+   %+#|  +45|  055| 0x2d| +12.68| +1.27e+01|    +12.68|
+  %+#0|+0045|00055|0x02d|+012.68|+01.27e+01|+000012.68|
+   %+ |  +45|   55|   2d| +12.68| +1.27e+01|    +12.68|
+  %+ 0|+0045|00055|0002d|+012.68|+01.27e+01|+000012.68|
+  %+ #|  +45|  055| 0x2d| +12.68| +1.27e+01|    +12.68|
+ %+ #0|+0045|00055|0x02d|+012.68|+01.27e+01|+000012.68|
+    %-|45   |55   |2d   |12.68  |1.27e+01  |12.68     |
+   %-0|45   |55   |2d   |12.68  |1.27e+01  |12.68     |
+   %-#|45   |055  |0x2d |12.68  |1.27e+01  |12.68     |
+  %-#0|45   |055  |0x2d |12.68  |1.27e+01  |12.68     |
+   %- | 45  |55   |2d   | 12.68 | 1.27e+01 | 12.68    |
+  %- 0| 45  |55   |2d   | 12.68 | 1.27e+01 | 12.68    |
+  %- #| 45  |055  |0x2d | 12.68 | 1.27e+01 | 12.68    |
+ %- #0| 45  |055  |0x2d | 12.68 | 1.27e+01 | 12.68    |
+   %-+|+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
+  %-+0|+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
+  %-+#|+45  |055  |0x2d |+12.68 |+1.27e+01 |+12.68    |
+ %-+#0|+45  |055  |0x2d |+12.68 |+1.27e+01 |+12.68    |
+  %-+ |+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
+ %-+ 0|+45  |55   |2d   |+12.68 |+1.27e+01 |+12.68    |
+ %-+ #|+45  |055  |0x2d |+12.68 |+1.27e+01 |+12.68    |
 %-+ #0|+45  |055  |0x2d |+12.68 |+1.27e+01 |+12.68    |
 
-    %|  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
-   %0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
-   %#|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
-  %#0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
-   % |  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
-  % 0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
-  % #|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
- % #0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
-   %+|  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
-  %+0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
-  %+#|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
- %+#0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
-  %+ |  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
- %+ 0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
- %+ #|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
-%+ #0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
-   %-|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
-  %-0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
-  %-#|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
- %-#0|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
-  %- |zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
- %- 0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
- %- #|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
-%- #0|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
-  %-+|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
- %-+0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
- %-+#|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
-%-+#0|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
- %-+ |zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
-%-+ 0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
-%-+ #|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
+fl\fmt|   5s|   5c|   7.1G|   7.2f|     10.2e|     10.4g|
+------+-----+-----+-------+-------+----------+----------+
+     %|  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
+    %0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
+    %#|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
+   %#0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
+    % |  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
+   % 0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
+   % #|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
+  % #0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
+    %+|  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
+   %+0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
+   %+#|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
+  %+#0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
+   %+ |  zap|    *|     -3|  -3.46| -3.46e+00|    -3.457|
+  %+ 0|  zap|    *|-000003|-003.46|-03.46e+00|-00003.457|
+  %+ #|  zap|    *|    -3.|  -3.46| -3.46e+00|    -3.457|
+ %+ #0|  zap|    *|-00003.|-003.46|-03.46e+00|-00003.457|
+    %-|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+   %-0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+   %-#|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
+  %-#0|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
+   %- |zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+  %- 0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+  %- #|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
+ %- #0|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
+   %-+|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+  %-+0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+  %-+#|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
+ %-+#0|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
+  %-+ |zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+ %-+ 0|zap  |*    |-3     |-3.46  |-3.46e+00 |-3.457    |
+ %-+ #|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
 %-+ #0|zap  |*    |-3.    |-3.46  |-3.46e+00 |-3.457    |
 
diff --git a/test/printf-corners-mpfr.ok b/test/printf-corners-mpfr.ok
new file mode 100644
index 00000000..5eb24699
--- /dev/null
+++ b/test/printf-corners-mpfr.ok
@@ -0,0 +1,86 @@
+<0>
+<0>
+<00>
+<000>
+<01>
+<01>
+<01>
+<001>
+<0x01>
+<>
+<+>
+<+>
+<>
+<>
+<>
+<>
+<>
+< >
+< >
+<>
+<>
+<>
+<>
+<>
+<+>
+<+>
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+<0.00000>
+<0.00000>
+<0.e+00>
+<0.E+00>
+<0.>
+<0.>
+<0.>
+<0.>
+<-nan>
+<-NAN>
+<-nan>
+<-NAN>
+<-nan>
+<-NAN>
+<-nan>
+<-NAN>
+<-inf                >
+<-INF                >
+<-inf                >
+<-INF                >
+<-inf                >
+<-INF                >
+<-inf                >
+<-INF                >
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<   00045>
+<-01>
+<-012>
+<-001>
+<01>
+<01>
+<012>
+<12>
diff --git a/test/printf-corners.awk b/test/printf-corners.awk
new file mode 100644
index 00000000..2dee4abb
--- /dev/null
+++ b/test/printf-corners.awk
@@ -0,0 +1,88 @@
+BEGIN {
+       printf "<%#.o>\n", 0            # 0
+       printf "<%#.1o>\n", 0           # 0
+       printf "<%#.2o>\n", 0           # 00
+       printf "<%#.3o>\n", 0           # 000
+       printf "<%#.o>\n", 1            # 01
+       printf "<%#.1o>\n", 1           # 01
+       printf "<%#.2o>\n", 1           # 01
+       printf "<%#.3o>\n", 1           # 001
+       printf "<%#.2x>\n", 1           # 0x01
+       printf "<%#.x>\n", 0            # ""
+       printf "<%+.d>\n", 0            # "+"
+       printf "<%+.i>\n", 0            # "+"
+       printf "<%+.u>\n", 0            # ""
+       printf "<%+.o>\n", 0            # ""
+       printf "<%+.x>\n", 0            # ""
+       printf "<%+.X>\n", 0            # ""
+       printf "<%+.s>\n", 0            # ""
+       printf "<% .d>\n", 0            # " "
+       printf "<% .i>\n", 0            # " "
+       printf "<% .u>\n", 0            # ""
+       printf "<% .o>\n", 0            # ""
+       printf "<% .x>\n", 0            # ""
+       printf "<% .X>\n", 0            # ""
+       printf "<% .s>\n", 0            # ""
+       printf "<%+1.d>\n", 0           # "+"
+       printf "<%+1.i>\n", 0           # "+"
+       printf "<%+1.u>\n", 0           # " "
+       printf "<%+1.o>\n", 0           # " "
+       printf "<%+1.x>\n", 0           # " "
+       printf "<%+1.X>\n", 0           # " "
+       printf "<%+1.s>\n", 0           # " "
+       printf "<% 1.d>\n", 0           # " "
+       printf "<% 1.i>\n", 0           # " "
+       printf "<% 1.u>\n", 0           # " "
+       printf "<% 1.o>\n", 0           # " "
+       printf "<% 1.x>\n", 0           # " "
+       printf "<% 1.X>\n", 0           # " "
+       printf "<% 1.s>\n", 0           # " "
+       printf "<%#g>\n", "0"           # "0.00000"
+       printf "<%#G>\n", "0"           # "0.00000"
+       printf "<%#.e>\n", "0"          # "0.e+00"
+       printf "<%#.E>\n", "0"          # "0.E+00"
+       printf "<%#.f>\n", "0"          # "0."
+       printf "<%#.F>\n", "0"          # "0."
+       printf "<%#.g>\n", "0"          # "0."
+       printf "<%#.G>\n", "0"          # "0."
+       printf "<%e>\n", "-nan"         # "-nan"
+       printf "<%E>\n", "-nan"         # "-NAN"
+       printf "<%f>\n", "-nan"         # "-nan"
+       printf "<%F>\n", "-nan"         # "-NAN"
+       printf "<%g>\n", "-nan"         # "-nan"
+       printf "<%G>\n", "-nan"         # "-NAN"
+       printf "<%a>\n", "-nan"         # "-nan"
+       printf "<%A>\n", "-nan"         # "-NAN"
+       printf "<%-20e>\n", "-inf"      # "-inf                "
+       printf "<%-20E>\n", "-inf"      # "-INF                "
+       printf "<%-20f>\n", "-inf"      # "-inf                "
+       printf "<%-20F>\n", "-inf"      # "-INF                "
+       printf "<%-20g>\n", "-inf"      # "-inf                "
+       printf "<%-20G>\n", "-inf"      # "-INF                "
+       printf "<%-20a>\n", "-inf"      # "-inf                "
+       printf "<%-20A>\n", "-inf"      # "-INF                "
+       printf "<%20e>\n", "-inf"       # "                -inf"
+       printf "<%20E>\n", "-inf"       # "                -INF"
+       printf "<%20f>\n", "-inf"       # "                -inf"
+       printf "<%20F>\n", "-inf"       # "                -INF"
+       printf "<%20g>\n", "-inf"       # "                -inf"
+       printf "<%20G>\n", "-inf"       # "                -INF"
+       printf "<%20a>\n", "-inf"       # "                -inf"
+       printf "<%20A>\n", "-inf"       # "                -INF"
+       printf "<%20.20e>\n", "-inf"    # "                -inf"
+       printf "<%20.20E>\n", "-inf"    # "                -INF"
+       printf "<%20.20f>\n", "-inf"    # "                -inf"
+       printf "<%20.20F>\n", "-inf"    # "                -INF"
+       printf "<%20.20g>\n", "-inf"    # "                -inf"
+       printf "<%20.20G>\n", "-inf"    # "                -INF"
+       printf "<%20.20a>\n", "-inf"    # "                -inf"
+       printf "<%20.20A>\n", "-inf"    # "                -INF"
+       printf "<%3$*2$.*1$d>\n", 5, 8, 45      # "   00045"
+       printf "<%.2i>\n", -1           # "-01"
+       printf "<%.3i>\n", -12          # "-012"
+       printf "<%.3i>\n", -1           # "-001"
+       printf "<%.2i>\n", 1            # "01"
+       printf "<%+.2u>\n", 1           # "01"
+       printf "<% .3u>\n", 12          # "012
+       printf "<%+.2u>\n", 12          # "12"
+}
diff --git a/test/printf-corners.ok b/test/printf-corners.ok
new file mode 100644
index 00000000..5eb24699
--- /dev/null
+++ b/test/printf-corners.ok
@@ -0,0 +1,86 @@
+<0>
+<0>
+<00>
+<000>
+<01>
+<01>
+<01>
+<001>
+<0x01>
+<>
+<+>
+<+>
+<>
+<>
+<>
+<>
+<>
+< >
+< >
+<>
+<>
+<>
+<>
+<>
+<+>
+<+>
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+< >
+<0.00000>
+<0.00000>
+<0.e+00>
+<0.E+00>
+<0.>
+<0.>
+<0.>
+<0.>
+<-nan>
+<-NAN>
+<-nan>
+<-NAN>
+<-nan>
+<-NAN>
+<-nan>
+<-NAN>
+<-inf                >
+<-INF                >
+<-inf                >
+<-INF                >
+<-inf                >
+<-INF                >
+<-inf                >
+<-INF                >
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<                -inf>
+<                -INF>
+<   00045>
+<-01>
+<-012>
+<-001>
+<01>
+<01>
+<012>
+<12>

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                   |  106 +++
 Makefile.am                 |    1 +
 Makefile.in                 |   18 +-
 NEWS                        |    6 +-
 awk.h                       |    4 +-
 builtin.c                   | 1402 +-------------------------------
 debug.c                     |    2 +-
 mpfr.c                      |    6 +-
 node.c                      |    6 +-
 pc/ChangeLog                |    4 +
 pc/Makefile.tst             |    9 +-
 printf.c                    | 1891 +++++++++++++++++++++++++++++++++++++++++++
 test/ChangeLog              |   28 +
 test/Makefile.am            |    5 +-
 test/Makefile.in            |   12 +-
 test/Maketests              |    7 +
 test/hsprint.awk            |   79 +-
 test/hsprint.ok             |  128 +--
 test/printf-corners-mpfr.ok |   86 ++
 test/printf-corners.awk     |   88 ++
 test/printf-corners.ok      |   86 ++
 21 files changed, 2458 insertions(+), 1516 deletions(-)
 create mode 100644 printf.c
 create mode 100644 test/printf-corners-mpfr.ok
 create mode 100644 test/printf-corners.awk
 create mode 100644 test/printf-corners.ok


hooks/post-receive
-- 
gawk



reply via email to

[Prev in Thread] Current Thread [Next in Thread]