Index of /~beebe/pawk

[ICO]NameLast modifiedSize

[PARENTDIR]Parent Directory  -
[TXT]README.html2026-11-10 07:05 71K
[   ]README.sok2026-11-03 09:42 2.2K
[   ]pawk.12026-11-13 10:44 84K
[   ]pawk.pdf2026-11-13 10:45 116K
[   ]pawk.ps2026-11-13 10:45 144K


Pawk: modernized profiling awk
Nelson H. F. Beebe
University of Utah
Department of Mathematics, 110 LCB
155 S 1400 E RM 233
Salt Lake City, UT 84112-0090
USA
Email: beebe@math.utah.edu, beebe@acm.org, beebe@computer.org
ORCID: 0000-0001-7281-4263
WWW URL: https://www.math.utah.edu/~beebe
Telephone: +1 801 581 5254

Original version: Mon Oct 12 16:16:50 2026

Update(s): Mon Oct 12 16:16:50 2026     …     Tue Nov 10 07:05:32 2026


Table of contents


Introduction

The awk programming language was designed and developed in the Unix Research Group at AT&T Bell Laboratories, Murray Hill, NJ, USA, starting in the late 1970s, and culminating with the publication of the 1988 book, The AWK Programming Language (ISBN 0-201-07981-X).

Awk proved quite popular, and was later standardized in successive versions of IEEE POSIX, the Portable Operating System Interface. Awk has been ported to essentially all significant desktop, mainframe, and supercomputer operating systems. Several independent implementations of awk exist, and are in wide use, including oawk (the old pre-book version), followed by the 1988 book version, usually called nawk (for new awk), bawk, gawk (GNU awk), goawk, jawk, mawk, mksawk, tawk, wak, …. Of those, gawk, mawk, and nawk are by far the most common, and are available in almost all operating system software distributions. On many systems, the awk executable is just a symbolic link to either nawk or gawk. Despite the numerous implementations, for many purposes they can be used interchangeably, which is always a healthy situation for programmers.

In 2023, a second edition of the book was published (ISBN 0-13-826972-6), along with an updated source release called onetrueawk. The language is unchanged, but there is now support for Unicode UTF-8 text, and handling of comma-separated-value (CSV) data, commonly available from spreadsheet software.

The awk language is small, and can be learned in just a few evenings; the books are about 200 pages long. This author has several times presented a one-hour class lecture that covers most of the language.

More detail is supplied in the book Effective AWK Programming (four editions (ISBN 0-916151-88-3 (1996), 1-57831-000-8 (1997), 0-596-00070-7 (2001), 1-4919-0461-5 (2015)), also available in GNU Info format as an online document in most Unix-family distributions. It describes gawk, which has served as a fine testbed for new features in the language, including environment variable access, file inclusion, generalized text substitution, loadable libraries, multiple-precision arithmetic via an interface to the MPFR library, persistent interjob storage, and ability to handle records with embedded NUL characters, allowing it to be used with binary files. Gawk has several active mailing lists for developers and users, where language extensions, and application problems and their solutions, are discussed.


Awk basics

Awk was inspired by a simple goal: provide a data filter that can use a text pattern to match some input, and then perform some action at each such match. Either the pattern, or the braced action, but not both, may be omitted. An omitted action means to print the matching input record, and an omitted pattern means that the action applies to every input record. By default, the input text is split into lines, but other possibilities are supported. Two special patterns, BEGIN and END, allow the programmer to gain control before, and after, processing of input.

Records are automatically split into fields, numbered $1, $2, …, $NF, with $0 representing the complete record. Fields are normally separated by whitespace, but the awk field and record separators are easily changed, even part way through an input stream. Input comes either from the Unix standard input, or from one or more files given on the awk command line. Those files are automatically opened, read, and closed, and the current filename, record number within the file, and total number of records read so far, are available as standard awk-maintained variables. The awk command-line arguments are available to the awk program, which can peek at them before processing, or even modify them.

The language includes simple syntax for reading and writing named files, and writing to unnamed command pipelines, making the huge repertoire of Unix tools readily available to the awk programmer.

Here are some simple one-line awk programs:

    /[0-9]/                     # print every line containing at least one decimal digit
    /[0-9][0-9]+/               # print every containing at least two adjacent digits
    { print $2, $1}             # output fields 1 and 2 in revers order
    $3 ~ /[aeiou]/ { print $3 } # print field 3 if it has a vowel
    length($0) > 70             # show lines longer than 70 characters
    FNR <= 5                    # print the first five lines of each input file
    NF < 10                     # print records with fewer than 10 fields
    END { print NR, NF }        # print the counts of records and files

Awk has a number of useful built-in functions for pattern matching, and string handling, and users can also write their own. Awk's syntax is largely based on a subset of that of C, which is familiar to many programmers, and used to write most of the software in Unix-family systems. The syntax and design of C grew out of earlier languages, including Algol, BCPL, and B, and C in turn has influenced later languages, including C++, C#, D, Go, Java, Javascript, Rust, and numerous others.

There is a critical, and extremely powerful, feature of awk that we have not yet mentioned. It has been there since the beginning, and is still missing from most other programming languages. That feature is associative arrays, tables that are indexed like normal predeclared arrays, but have no predeclared sizes, and no predictable storage layout. Importantly, the array indexes can be strings as well as integers. That gives awk the important capability of supporting key–value data, with syntax like this:

    PhoneList["Ali"] = "(880) 555-0112"
    PhoneList["Jan"] = "(880) 555-0123"
    PhoneList["Kim"] = "(880) 555-0134"
    PhoneList["Sal"] = "(880) 555-0145"

    for (name in PhoneList)
        { NumberOwner[PhoneList[name]] = name }

    if ("Billy" in PhoneList)
       { … }
    else
       { … }

Awk programs that use associative arrays can function effectively as easily modified, but simple to create and use, databases, with array index names serving as unique keys into tables of data, such as our example of people and telephone numbers, or with keys that are birthdays, calendar dates, ISBNs of books, ISSNs of journals, office numbers, personal identifiers, product numbers, street addresses, vehicle license plates, and so on.

Probably a majority of uses of awk are simple ones, with just one, or a few, lines in the program. However, this author has also written awk programs of tens of thousands of lines: few other programming languages are as ideally suited for text processing as awk is.


Profiling large awk programs

The efficiency of small programs is rarely of concern to users; all that matters is that the programs do their jobs. The focus on rather short programs in the early days of awk meant that language implementations gave no way to identify the hot spots in longer running awk programs.

In 2002, I added support for run-time profiling of awk programs in a modestly revised source code release. In 2026, I retrofitted those additions into the latest onetrueawk code, adding much improved support for access to high-resolution timers on many different CPU types. The new code will soon be in the linked-to repository.

The pawk code has been routinely built and tested on many different Unix-like operating systems, including several members of the BSD family, GNU Hurd, numerous GNU Linux variants, macOS, and Solaris.

The test CPUs are Alpha, ARM32, ARM64 (including Apple's M1, M2, M3, M4, and M5), HPPA (PA-RISC), IA-64 (Itanium), LA64 (Loongson), M68K, MIPS32, MIPS64, PowerPC, RISC-V, S390x, SPARC64, x86 (i286, i386, i486, i586, i686), and x86_64 (AMD64). They represent systems with big-endian addressing, and little-endian addressing, which has often been a showstopper for porting other packages to new platforms.

The pawk code has been successfully built with numerous C compilers, including aocc, chibicc, clang (versions 3–22), gcc (versions 4–16), icc (versions 10–19), icx, kefir, opencc, pcc, pgcc, sgicc, slimcc, suncc, tcc, and zig cc.

The pawk test suite is extensive, and includes numerous validation tests from gawk and nawk. As a result of that testing, I have considerable confidence that pawk can be built, validated, and installed easily on any Unix-family system. Microsoft Windows is not among the test systems; in past years, it had C compilers that could be easily invoked from a terminal window command line, but it appears now (in 2026) that the focus on Windows code development is inside the VS Code graphical user interface whose complexity I am not prepared to battle. Awk's normal installation is accomplished by unpacking a distribution file and running the four-word command make all check install. Command line options allow changing the compiler, compiler options, program naming, and installation locations, making the process highly customizable. No source code modification should ever be required just to get pawk to build on a new system.

A lot of modern software uses the GNU autoconf system to investigate the local computing environment and prepare a customized config.h file, and possibly a customized Makefile. The build process is then ./configure && make …. For familiarity, pawk supplies a configure script, but pawk needs no machine-specific customization, so that script just displays some helpful suggestions about different ways to build, validate, and install the software. Once you know that it is there, you don't need to run it ever again, but if you do, no harm is done.


Repairing awk's major weakness

Awk has enjoyed almost a half century of development and use, and is a critical tool used internally by other software. Nevertheless, it is hugely deficient for processing of numeric data. The next subsections explain why that is, what is needed to fix the problem, and how pawk does so.


Some historical background on computer arithmetic

In awk's early years, there were several different, incompatible, and idiosyncratic floating-point architectures on the computer market. Apart from offering just one numeric floating-point data type, double in the C language, that matched common hardware arithmetic equivalent to about 15 decimal digits, the influential Bell Labs Unix research largely ignored numerical applications. It does get important credit for producing the first Fortran 77 compiler, which brought the rudiments of structured programming to scientific and engineering software, but C remained dominant in Unix software development, and still does.

Unix now permeates most of the world's computing devices, including desktops, laptops, tablets, cellular telephones, and many embedded devices outside the Microsoft world. However, the recent Windows Subsystem for Linux (WSL) has made it trivial to install, manage, and use one or more of about two dozen different Linux distributions on Windows computers. Thus, the Unix environment is now almost ubiquitous.

As a result of the hardware variations of the 1970s and early 1980s, awk's Bell Labs developers chose to supply only a single numeric type in the awk language that handled the need for both integer and floating-point arithmetic. In addition, numbers and numeric strings in awk are trivially converted back and forth. If x is a variable holding a numeric value 1.25, then the awk code s = x "" converts it to a string s, and y = 0 + s converts the string back to a numeric value. Fractional parts can be discarded with int(x). Awk has no type declarations, and for many applications, the distinction between numbers and their string representations is largely transparent.

Because of the job-terminating behavior of older computers in response to floating-point exceptions, nawk tries to catch them itself, issue a possibly helpful message, and terminate, or possibly continue. It does that for division by zero, and also to force zeros to be positive. Modern software should definitely not behave that way, as we discuss in a later section.

When the 1990 ISO C Standard appeared late (it has always been called C89), it offered a new floating-point datatype, float, that was intended to match the hardware precision called single, usually supporting numbers of 6 to 8 decimal digits, depending on the computer word size and floating-point architecture. It remains a mystery of C language development and standardization why the new data type was not called single! C89 also offered a potentially longer type, long double, giving C programmers access, with a 25-year delay, to what Fortran coders had used as REAL*4, REAL*8, and REAL*16, roughly equivalent to 6, 14, and 28 decimal digits.

Alas, C89 also said that C compilers could treat two or three of the floating-point types as equivalent, but implementation dependent, types, whereas Fortran had three distinct types of predictable precision. That serious flaw in the C design made it impossible for most scientific software to be converted from Fortran to C. [There is also another important reason: Fortran's handling of multidimensional array arguments is superior to that of C, and the two languages disagree on array indexing and array storage order. However, that aspect is not relevant for awk.] Even in 2026, 38 years after C89, older BSD systems treat double and long double as equivalent to a 64-bit (8-byte) hardware format, and Apple's C and C++ compilers still do that, severely limiting their usefulness for serious scientific computing, and denying the C programmer access to longer precisions that have been present, and paid for by customers, in Apple's own computers almost since its founding.

Floating-point designs of all computers before 1980 were seriously flawed, and largely incompatible across vendors. That system dependence was the bane of scientific software, which needs to run on many different platforms, and which often survives much longer than the hardware on which it first ran. That software was almost exclusively written in Fortran, and compilers for that language were offered by all major mainframe computer vendors. However, a Fortran program that used the REAL datatype got 32 bits (about 6 digits) on an IBM System/360 computer, 36 bits (8 digits) on a DEC PDP-10, Honeywell/GE 635, and Univac 1108, 60 bits (about 14 digits) on a CDC 6x00 or 7x00 system, and 64 bits (also about 14 digits, but with a wider exponent range) on a Cray vector supercomputer.

On most of those, division by zero was a job-terminating fatal error, although some systems allowed for software traps that could optionally report the error, and sometimes, supply a substitute value, such as the largest floating-point number with a suitable sign, allowing execution to continue.

Those systems had varied rounding practices, with most choosing truncation, because it is simplest to implement in hardware. With the exception of the CDC machines, with their representation called Indefinite, computation of 0 / 0 was also a fatal error. There were many peculiarities across vendors, such as the carefully written code if (x != 0.0) y = w / x that could still terminate execution because of an unexpected divide-by-zero error.

Starting about 1977, a joint effort by dozens of academic, government, and industry floating-point experts, spearheaded and led by a superb wizard in the field, Professor William (Velvel) Kahan at the University of California, Berkeley, set out to prepare a new floating-point design that would offer cross-platform portability, fixed data sizes, user-accessible sticky exception flags, multiple rounding modes, signed zeros, basic operations of add, subtract, multiply, divide, and square root (the default always being correct to the last bit), and representations of 1/0 and 0/0 called Infinity and NaN (Not-a-Number), influenced by the CDC and Cray Infinity and the CDC Indefinite. The goal was to allow nonstop computing. The latter was an extremely important feature, because in the 1960s and 1970s, one hour of mainframe computer time cost as much as several months of a graduate student salary. Unwanted premature termination of a job that had run for hours or days was hugely expensive, and depending on the system, might be nearly impossible to diagnose or debug.

That team effort led to an early draft floating-point design that was used by Intel engineers in Haifa, Israel, to produce in 1980 the first hardware implementation in the Intel 8087 coprocessor chip that recently received the prestigious IEEE Milestone Award. The 8087 offered not only the correctly rounded five basic operations of arithmetic and the floating-point remainder operation, but also several elementary functions, albeit of restricted argument ranges, for six trigonometric functions, six inverse trigonometric functions, six hyperbolic functions, six inverse hyperbolic functions, exponentials (2x - 1, ex - 1, and 10x - 1), and logarithms for bases 2, e, and 10. The availability of the powerful 8087 chip attached to Intel 8088 and 8086 CPUs was a key reason why IBM chose Intel over several competitors for the IBM PC, which became the standard for desktop computing, later for some supercomputers, and currently, also for cloud computing and AI datacenters. The 8087 was a landmark in computer design: it was about twice the size of a normal 8086 CPU, and no other vendor, even IBM, had attempted to put substantial support for floating-point arithmetic on single chip. Even more remarkably, the 8087 was designed by a team of fewer than ten engineers, and needed only about 40_000 transistors. Its elementary functions were compactly implemented after a clever analysis of the then little-known CORDIC algorithm.

The committee design was formalized in the IEEE 754-1985 standard for floating-point arithmetic, and by that time had been adopted by almost all manufacturers of desktop, workstation, and departmental computers. Even DEC, when it evolved its mainstay CPU from the popular 32-bit VAX to the 64-bit Alpha, added support for IEEE 754 arithmetic in the latter. IBM mainframes did so as well in the 1990s, although the older hexadecimal format has largely disappeared from view, with Linux distributions on 390x CPUs now offering high-level language access only to IEEE 754 arithmetic.

The IEEE 754 standard was updated in 2008 and 2019 with refinements and clarifications, and extended to include 32-bit, 64-bit, 80-bit, and 128-bit formats (roughly 7, 15, 19, and 34 decimal digits) for binary floating point arithmetic, and 32-bit, 64-bit, and 128-bit formats for decimal floating-point arithmetic (exactly 7, 16, and 34 digits).

The newer standards require support for a correctly rounded fused multiply-add operation, such that fma(x,y,z) computes an exact double length product x * y to which z is added with only a single rounding error. The FMA was invented at IBM, and was first available in 1990 in the IBM POWER CPU. It was later added to PowerPC and S390x systems.

The FMA doubled floating-point performance, and allowed the assembler to supply separate multiply, add, and subtract pseudoinstructions that simply expand to the special cases of fma(x, y, 0), fma(1, y, z), and fma(1, y, -z).

The FMA operation can be used to find the rounding errors in multiply, divide, and square root, allowing simultaneous computation of a result with an error estimate. That in turn can be used to simulate arithmetic of twice, or even thrice, normal precision. As another example, polynomial evaluation can be reduced to a sequence of FMA instructions, sometimes without intervening memory accesses, producing peak performance of two numeric operations per instruction cycle.

Work is in progress on what is planned to be IEEE 754-2029, but it is not expected to invalidate any existing uses or features of IEEE 754 arithmetic. It will likely require a correctly rounded reciprocal square root, and may also supply correctly rounded w * x + y * z operations that are critical for implementing complex arithmetic, and are key components of many algorithms for computing elementary and special functions.

With my MathCW library and my extended dgcc compilers that I have routinely built at Utah since 2006, a fourth binary format, and three decimal formats, are available to C programmers. My book, The Mathematical-Function Computation Handbook: Programming Using the MathCW Portable Software Library (ISBN 3-319-64109-3, DOI:10.1007/978-3-319-64110-2) describes the design of a portable library that from a single code base supports about a dozen binary and decimal floating-point formats, including a future octuple-word (256 bit) format offering exactly 70 digits in decimal, and about the same in binary. The MathCW library includes all of the mathematical functions defined in the ISO Standards for C and Fortran, and many more, including various Bessel, elliptic integral, psi, theta, and zeta functions. It includes portable complex arithmetic, complex versions of the elementary functions, and support for pair-precision arithmetic, plus much extended input and output conversion functions. All of these become available to C and C++ programs with a two-character addition to their code: simply change the standard header file name from math.h to mathcw.h, and use the dgcc compiler, which adds support for decimal arithmetic, and knows where to find additional header files and libraries.

In Spring 2026, a second C compiler, kefir, recently developed by a single expert programmer in Europe, added support for decimal floating-point arithmetic. That is an encouraging, and healthy, development.

All of that software is available to programmers from several other programming languages. The MathCW book documents interfaces from Ada, C#, C++, Fortran, Java, and Pascal. I have since written free virtual appendices for MathCW library interfaces from COBOL, Go, Modula-2, Oberon, and Rust.

The release in late 2025 of the GNU Algol 68 compiler, ga68, will likely be augmented with a MathCW interface from that powerful, historical, and influential programming language. As of Fall 2026, ga68 still has an implementation restriction that prevents passing writable arrays to functions written in other programming languages, so it is not yet possible to interface Algol 68 code to the MathCW library, or to any of the large number of other libraries in C and Fortran that are needed in serious scientific programming.


Numerical work in awk

Even in its 2023 revision, awk supplies only six mathematical functions: atan2, cos, exp, log, sin, and sqrt. It also provides a function int to discard any fraction, and two random-number functions, rand and srand. By contrast, the C89 and C99 math libraries have around 100 math functions, and each of those is available for up to three binary formats.

With the MathCW library and the dgcc compilers, a fourth (128-bit) binary format is available for m68k and x86_64 CPUs, which natively have only 32-, 64-, and 80-bit floating-point instructions. That library and those compilers also give access to the three IEEE 754 decimal formats, which programming languages and compilers have been far too slow in providing.


Random numbers in awk

Random numbers in computers are needed for cryptography, sampling, simulation, and for algorithms that use randomness to guide further computation. Except in cryptographic algorithms that use large random numbers as unguessable and unpredictable keys for encryption and decryption, random numbers for most other applications are not truly random, because they are generated by a computer algorithm that, from a given starting value, called the seed or initial state, produces a definite sequence of numbers in which successive members, or members separated by a fixed interval, appear to be uncorrelated. They are therefore often termed pseudorandom numbers.

Random sequence reproducibility is a critical requirement: without it, computations cannot be replicated, or verified by others, and debugging of a failed program that uses random numbers may be impossible. When different sequences are needed, that is properly handled via seed initialization, or choice of generators, not by using an unguessable seed, as OpenBSD developers regrettably chose to do. Fortunately, there is a workaround for their design flaw that pawk uses.

I maintain an extensive list of publications on random number generations that is recorded in a bibliography of more than 4360 entries going back to 1900. It is also an area in which almost all of the work prior to 1990 has been shown to be either deficient, inadequate, insufficiently random, unwantedly correlated, easily predictable, or to have sequence lengths that are far too short for modern computational needs.

In recent years, certain models of ARM64, Intel x86_64, IBM PowerPC, and IBM z-Series (s390x) CPUs have added hardware generators that are intended to be true random number generators (TRNGs). They are not user seeded, and their sequences, which are determined by electrical noise and other CPU measurements, are different on every run. Although they are not of interest for pawk, I have developed and tested interfaces to all of them for other work that I do, so it seemed worthwhile to mention them here. Ideally, they can be used in combination with other methods, such as those used to produce the bitstreams from /dev/random and /dev/urandom, when unguessable random numbers are needed for cryptography, or simply for randomization of sequences where reproducibility is never of interest. An example of such a case is the relinking at machine startup of the Unix kernel and Unix libraries, putting modules into random order, such as OpenBSD commendably does to limit several avenues of software attacks.

awk's srand function allows initialization of its internal generator by a single number, but only 31 bits of that number are used. The rand function supplies a floating-point number in [0,1) computed from the generator's random integer sequence.

With many older generators, the seed was the only internal state, and held the last-returned random number. Thus, that number could be used as a seed to resume generation in the same sequence. However, with most modern high-quality generators, the seed is only part of a complicated internal state, and thus, if the last random number returned is used as a new seed, that starts a new sequence, rather than resuming the previous one.

There are several problems with that implementation that are detailed in pawk's documentation that we link to later, and pawk solves all of them in a way that guarantees identical random sequences in pawk on all systems with IEEE 754 arithmetic in all supported numerical formats and both binary and decimal number bases. The new generator is based on a platform-independent international standard, has an enormous period that guarantees that sequences from different seeds do not overlap, and the randomness of its output has been verified with extensive tests.

The original awk srand, when invoked without an argument, uses as a seed the time in seconds since the Unix Epoch (1 January 1970). Modern computers can start thousands of processes in one second, making it hard to generate unique seeds for distinct awk jobs running in parallel. Consequently, pawk instead uses a timer with nanosecond granularity to provide almost certainly distinct seeds for srand.


NaNs and Infinities in pawk

IEEE 754-1985 defined two kinds of NaNs, quiet and signaling, called QNaN and SNaN. The Intel 8087 offered only the first of those, having been designed from a draft of IEEE 754. Regrettably, Intel failed to repair that in later CPU designs, and it was not until AMD extended the x86 instruction set in 2000 to produce what is now called the x86_64 architecture that newer Intel and AMD processors can support both kinds of NaNs. However, optimizing compilers are free to use the older 8087 instructions, as well as the newer ones, so code on systems with Intel processors, which dominated the desktop market for 35 years, cannot assume that SNaNs are available. Later floating-point processors from Intel's competitors all support both QNaNs and SNaNs.

The onetrueawk release with the second book edition decreed that the possibly signed values inf and nan, in any lettercase, represent Infinity and NaN in awk code and data, but longer words beginning with either of those prefixes are interpreted as strings, not as numbers. The language provides no way to recognize QNaN and SNaN in code and data.

In the IEEE 754 binary formats, Infinity and NaN are recognized by their having the largest stored exponent, with a zero significand indicating Infinity, and a nonzero significand, a NaN. One particular bit in the significand distinguishes between QNaN and SNaN. All vendors chose the highest fraction bit, but they differ on whether a 1-bit there means quiet or signaling.

For NaNs, the remaining significand bits are unused, and are available for users to hold a payload that can be used to record an error code, or some other useful indicator. Some statistical software systems use a particular NaN payload to indicate a missing data value, which is critical information when statistics are computed on numeric data.

Regrettably, ISO language standards were slow to add access to many of the features of IEEE 754 arithmetic, and when they did so, they were still vague about how to communicate a NaN payload. The ISO C99 Standard recommends recognition of the word nan, optionally followed by a parenthesized phrase, but declares the interpretation of that phrase to be implementation dependent. It does not indicate how to encode the quiet and signaling aspects. ISO C 2011 did not change that. ISO C 2023 does much better: it specifies named constants for both kinds of NaNs, and families of getpayload and setpayload functions that treat the payload as a floating-point number of the same size, rather than as an unsigned integer. However, for pawk, we focus on the more widely implemented C89 and C99 language definitions.

Hoc and pawk support NaN payloads, and decimal Infinity payloads, with strings that represent integer numbers parseable by the C strtoull function, which returns 64-bit unsigned integers holding values 0 up to 264 - 1. Software can then use qnan("123") or snan("0xcafefeeddeadabbe") to supply distinctive or meaningful payload patterns. There are more than 50 English-language words that can be spelled with just the letters a–f, and the NaN error returns in pawk use payloads like "bad10" to indicate the cause of the error. Separate documentation explains those returns.

Because pawk versions support different floating-point sizes, payloads may need to be truncated: the choice is always to use as many of the low-order bits of a 64-bit payload integer as can fit in the significand. That means that 32-bit floating-point NaNs payloads can hold up to 22 bits, corresponding to 6 decimal digits, or 5 hexadecimal digits. The 64-bit floating-point sizes can hold 15 decimal digits, or 13 hexadecimal ones, and the 128-bit sizes, 33 and 28.

In pawk, payloads can be retrieved as numeric strings with s = payload(x). If the argument x is neither a NaN, nor a decimal Infinity, the returned string is empty, which converts to 0 when used as a number in awk.

The low-level architectural problem with the IEEE 754 binary formats for Infinity and NaNs is that the entire word must be looked at to distinguish them, and that may have to be done for every numerical operation. The later design of the decimal encodings fixed that flaw: the high-order byte alone distinguishes Infinity, QNaN, SNaN, and normal numbers. As a result of that improvement, decimal Infinity can carry a payload too, and pawk supports that with code like x = infty("0xfadeddecaf") and s = payload(x).

The system variations in support of quiet versus signaling NaNs, and getting and setting NaN payloads, means that pawk itself has to provide that support to guarantee uniform cross-platform behavior. Payloads are preserved if the variables holding them are passed as function arguments, or assigned from. However, when NaNs are used in low-level operations, or returned as a function value, payloads may be lost. For example, on many systems, sqrt(qnan("0xfeedface")) returns exactly that QNaN argument as the function value, but on RISC-V systems, numerical operations always force NaN payloads to zeros.


Pawk's extended math function repertoire

Pawk started out in 2002 with simple addition of profiling support to the standard nawk code, but it did nothing to improve the numerics of the awk language.

With the Fall 2026 release of pawk, its standard option-free build still largely offers the same computing environment that nawk has, with the exception of several new run-time options for testing its random number streams, for requesting strict conformance to the nonstop computing model of IEEE 754 floating-point arithmetic, and for recognizing, and producing, C99-style hexadecimal floating-point constants that are needed for bit-exact representation of floating-point numbers. Pawk also adds four timer functions to nawk that are useful for timing, profiling, and simulation of real-time delays in interactive software and intermachine communication.

Pawk's most significant contribution to the awk world is that with a single new build-time option, -DCFUN, it gives access to the entire C89 and C99 math library. That extension includes a score of support functions that have proved valuable over decades of numerical programming. Some come from the hoc language, which grew enormously at Utah from a simple prototype in a 1984 book by two Bell Labs researchers. Hoc is a fine tool that allows interactive access to all of the C math library, and much more, and it supports all of the IEEE 754 binary and decimal formats. Recently, hoc also gives access to the MPFR multiple-precision arithmetic library from hoc's versions for binary arithmetic. The code from hoc was readily adapted for use in pawk, as described in the next section.

The beauty of this new work is that pawk builds on the exact code used in onetrueawk, and documented in the 2023 second edition of its book. Five of the eleven source files of onetrueawk are identical in pawk, and notably, the awk grammar files are not modified in any way. That ensures that pawk behaves just like nawk for virtually all existing awk code, except for unlikely, but possible, name collisions in user code with new names added in pawk. Awk's built-in function names are reserved words in the language, so user code cannot redefine them.

The addition of access to C89 and C99 math functions in pawk was greatly facilitated by the wise decision of the awk architects to use a machine-generated parser controlled by grammar rules (onetrueawk has settled now on the GNU bison tool), and to assign most of the user-visible functions of the awk language a symbolic name via a preprocessor statement. Thus, the header file awk.h contains a macro definition for that name, a function name table in file lex.c holds a sorted list for fast name lookup, and the awk execution engine in file run.c contains a C switch statement that dispatches to a corresponding case, like this:

    #define FSIN 9                         /* in awk.h /
    ⋮
    { "sin", FSIN, BLTIN },                /* in lex.c */
    ⋮
    case FSIN: u = sin(getfval(x)); break; /* in run.c */

Adding a new built-in function in nawk and pawk can take as little as three new lines of code, followed by a complete rebuild that takes less than three seconds on a fast workstation. With somewhat more work, a 25-line test function written in awk can be added to the validation suite, with a one-word addition to the suite's Makefile. Most of those tests are rather similar, so often only 5 to 10 lines of an existing test need to be adapted for each new function. The tests are important, because a simple human typo that wrote sinh instead of sin in the case statement would be syntactically correct, but would not produce the correct function value.

Making pawk work for a different floating-point type takes more effort, but is a practice that I have used for decades, and exploited heavily in the MathCW library:

The new awk*.h header files are very similar, and differ primarily in obvious ways, such as type suffixes on function names, and type selectors in format descriptors. Once the first, awk32.h, had been completed, it was a relatively simple task with the powerful emacs text editor to create the companions for other data types. The C library does, however, have a few function names that have irregular typing, rather than predictable suffixes, so they required special care. In addition, C99 introduced about a dozen feature test names, isxxx(arg), that are defined as macros for fast inline expansion that do type tests inside their definitions, and thus lack type suffixes. However, their expansions work correctly only for three of the seven datatypes that pawk supports, so they need definitions in pawk that invoke type-specific functions, rather than vendor-supplied macros.

The original onetrueawk source code has 11 *.[chy] files, and about 8_200 lines of code (some of which are empty, or have only comments). The new code for pawk has 22 *.[chy] files, and about 14_600 lines of code. That growth of about 6_400 lines is large, but is clean and systematic. [For comparison, the mawk source code is about 20_000 lines, mksawk is about 6_400 lines, and gawk's is about 109_000 lines.] The huge return is that there are now eight different versions of pawk, the first one that behaves like nawk, but with profiling enhancements, and is called pawk, and seven more for all of the currently supported datatypes in C: pawk32, pawk64, pawk80, pawk128, pawkd32, pawkd64, and pawkd128. The seven additions provide access to a much larger function library offering the numeric awk programmer almost everything that can be done with numeric programming in C or C++.

Here is a list of the new functions that are available when pawk is built with the -DCFUN option:

acos, acosh, asin, asinh, atan, atan2, atanh, binom, cbrt, ceil, copysign, cos, cosh, erf, erfc, exp, expm1, exp2, exp2m1, exp10, exp10m1, fabs, fac1, fac2, fac3, fac4, facn, fdim, feclearexcept, fegetround, feraiseexcept, fesetround, fetestexcept, floor, fma, fmax, fmin, fmod, fpclassify, hypot, ilogb, isfinite, isgreater, isgreaterequal, isinf, isless, islessequal, islessgreater, isnan, isnormal, isunordered, j0, j1, jn, ldexp, lgamma, llrint, llround, log, log1p, log2, log21p, log10, log101p, logb, lrint, lround, nearbyint, nextafter, poch, pow, remainder, rint, round, rsqrt, scalbln, scalbn, signbit, sin, sinh, sqrt, tan, tanh, tgamma, trunc, y0, y1, yn.

Almost all of them are defined in ISO C language standards, and documented in Unix manual pages, so we do not describe them further here.

The six ordinary Bessel functions of the first (j0, j1, jn) and second (y0, y1, yn) kinds are not defined in the ISO C Standards, but they were introduced in the POSIX.1:2001 Standard, after more than a decade of availability in BSD Unix distributions. They are widely available in Unix-family systems, and are now accessible from pawk. Portability testing found that in the most recent FreeBSD, NetBSD, and, OpenBSD operating system releases, the long double versions are missing, so for that datatype, pawk just has to use their less precise double versions.

Testing of the extended pawk code on many systems turned up deficiences in some vendor-provided libraries, particularly in the exponential and power functions. They are hard to compute accurately because their error magnification is proportional to their arguments. Consequently, pawk has internal replacement functions that notably improve accuracy.

Importantly, pawk's replacement functions detect and exploit integer powers, so that when powers of the base are computed, either with the awk power operator, or via the extended access to the pow function, or via the exp2, exp2m1, exp10, exp10m1, ldexp, scalbn, and scalbln functions, they are exact, unless they overflow or underflow. For numbers that are not powers of the base, their power computations are still more accurate than they were in the original onetrueawk code, because they are now always computed in the highest available precision.


Pawk and multiple-precision arithmetic

With the -DMPFR compile-time option, which also enables the -DCFUN option, pawk can be built with support for access to the many functions of the MPFR multiple-precision arithmetic library. Here is a list of them:

mpfr_3sum, mpfr_4sum, mpfr_acos, mpfr_acosh, mpfr_acospi, mpfr_agm, mpfr_ai, mpfr_asin, mpfr_asinh, mpfr_asinpi, mpfr_atan, mpfr_atanh, mpfr_atanpi, mpfr_beta, mpfr_binom, mpfr_cbrt, mpfr_ceil, mpfr_compound, mpfr_cos, mpfr_cosh, mpfr_cospi, mpfr_cot, mpfr_coth, mpfr_csc, mpfr_csch, mpfr_digamma, mpfr_dim, mpfr_eint, mpfr_erf, mpfr_erfc, mpfr_exp, mpfr_exp10, mpfr_exp10m1, mpfr_exp2, mpfr_exp2m1, mpfr_expm1, mpfr_fac1, mpfr_fac2, mpfr_fac3, mpfr_fac4, mpfr_facn, mpfr_floor, mpfr_fma, mpfr_fmma, mpfr_gamma, mpfr_gamma_inc, mpfr_get_prec, mpfr_hypot, mpfr_igamma, mpfr_igammac, mpfr_j0, mpfr_j1, mpfr_jn, mpfr_li2, mpfr_lngamma, mpfr_log, mpfr_log10, mpfr_log10p1, mpfr_log1p, mpfr_log2, mpfr_log2p1, mpfr_poch, mpfr_pow, mpfr_powr, mpfr_prod_0, mpfr_prod_1, mpfr_prod_2, mpfr_psi, mpfr_reldiff, mpfr_rint, mpfr_rootn, mpfr_round, mpfr_roundeven, mpfr_rsqrt, mpfr_sec, mpfr_sech, mpfr_set_prec, mpfr_sin, mpfr_sinh, mpfr_sinpi, mpfr_sqr, mpfr_sqrt, mpfr_str_bin, mpfr_str_dec, mpfr_str_hex, mpfr_sum_0, mpfr_sum_1, mpfr_sum_2, mpfr_tan, mpfr_tanh, mpfr_tanpi, mpfr_trunc, mpfr_y0, mpfr_y1, mpfr_yn, mpfr_zeta.

Most of them are normal members of the MPFR library, but others are added to provide a convenient interface with the working precision of pawk. There is no access to low-level MP operations in pawk, just to its high-level functions. Both low-level and high-level MPFR operations are available in hoc. The model in pawk is that the interface functions compute in higher precision, but return a result rounded to working precision. However, the longer result is preserved internally, and can be retrieved as a string in binary, decimal, and hexadecimal formats.

The default precision in the MPFR interface from pawk is 256 bits, roughly 77 decimal digits, but that can be increased to 65_536 bits (about 19_000 digits). The default gives more than twice as many digits as are available in the IEEE 754 128-bit binary format, and that is generally ample for evaluation of accuracy of algorithms implemented in hardware arithmetic.


Manual pages for pawk

The preceding sections of this Web page have left out a lot, including much of the syntax of the awk language, its normal built-in functions, and the many new ones offered by pawk.

There are more details about the features of pawk in its Unix manual pages, conveniently available here in nroff/troff form, PostScript, and PDF. Those files are also included in the bundles at the source code site.

The manual pages include brief documentation of the new functions, a discussion of important features of decimal floating-point arithmetic that must be understood by programmers, and a description of some of the unusual features of IEEE 754 arithmetic that might not be known to most users of computers.


Wrapping up

This document has been written to alert its readers to a significant development: pawk, a compatible version of nawk, based on the same source code, but buildable with full support for all of the floating-point features and library functions available for C and C++ programs, and also for most of what ISO Fortran standards require in that realm, plus support for seven different floating-point types: four for binary arithmetic, and three for decimal arithmetic.

The size of the additions in pawk is significant, and for that reason, I believe that it would be inappropriate to merge them into the onetrueawk source code, which should, and must, remain as the definitive implementation of the awk programming language as it is described in the 2023 second edition of its book.

The development of pawk uncovered several errors in the onetrueawk code, but they are unlikely to be noticed by its users. Updates to onetrueawk to repair them should be innocuous, and should not require OS packaging systems to produce new releases of the nawk executable.

However, I encourage those same OS packagers to build seven versions of pawk with the CFUN, and MPFR, extensions, and the eighth without CFUN support as the simple profiling extension, and include them all in their package systems. Four of the executables need the MathCW library and a dgcc compiler, but those are needed only at the packaging sites, and the MathCW library code can be trivially used with static linking, so no additional header files or libraries are needed in the package system. A pawk installation has one executable for each supported datatype, and a single common Unix manual page file, with no additional configuration file, and no dependencies upon any other libraries beyond the C math library, or the MathCW library, and optionally, the MPFR library. Awk was designed more than four decades ago as a simple and easily learnable language, and pawk continues that tradition. It is just that pawk has made the language applicable to analysis of numeric data from many other programming languages, with the same math library richness that C and C++ have.