Re: Peculiar number formatting?
"David McClain (as dbm at refined-audiometrics dot com)" <[email protected]> Tue, 19 May 2026 14:29:04 -0700
| Newsgroups | gmane.lisp.lispworks.general |
|---|---|
| Message-ID | <[email protected]> |
No, in retrospect, I see that my confusion arises entirely from having first been exposed to C printf(). In their format specs, the prefix 0, as in %05x (IIRC) means that you want 5 digits, and if necessary, fill the left with zeros. In Lisp, the prefix count specifies a field width, which is entirely different. But it appears to work the same as long as you print positive numbers. So I guess, in all of 30 years of Lisp use, I never tried to print a zero filled negative hex number, until now. Lisp is offering the ancient formatting convention of filling a numeric field with some char of your choice, which was often used in printing dollar amounts on checks. It was an ancient security measure to help prevent people from writing their own prefix digits before cashing in. It is easy to provide my own formatter which gives N digits, regardless of field width. I just always assumed that Lisp worked the way C printf() does. - DM > On May 19, 2026, at 05:15, Pascal Bourguignon (as informatimago at gmail dot com) <[email protected]> wrote: > > > > > >> On 19 May 2026, at 12:07, David McClain (as dbm at refined-audiometrics dot com) <[email protected]> wrote: >> >> While developing some simulation code for 24-bit DSP arithmetic, I ran into what appeared at first to be incorrect number formatting. >> >> (format t “~6,’0x” -1) => 0000-1 >> >> instead of -000001, or even -00001 if you insist on the width control of 6. >> >> Indeed, even this is peculiar to me: >> >> (format t “~6,’0@d” 1) => 0000+1 >> >> I have been around for more than 55 years in programming, and I have never before seen numbers displayed like this. It is almost as though we are working in ancient COBOL banking times where the width control is completely divorced from the numeric display, and you want to be sure to fill the field excess with a printed character to prevent check cashing fraud. “*****1.20" >> >> I did cross check with SBCL and found the same behavior. And there were no examples of negative values being printed in fixed radix format in the HyperSpec. >> >> I wonder what the Lisp world was thinking when they decided to standardize on this kind of format output?? COBOL era? >> > > > Yes they were thinking about the formatting usages of their time. > But they were also forward thinking enough, to provide us with ~/ > and the ability to use our own formatter macros. > > You can write easily your own ~/hex/ formatter: > > #|| > > Write a Common Lisp function named cl-user::hex > to be used in format ~/ taking: > - an optional width parameter > - an optional colon flag, > - an optional at flag, > and displaying the absolute value of the argument as an hexadecimal number. > If the width is specified, then the digits are left-padded with 0s. > > | flags | positive argument | negative argument | > |---------+----------------------------+----------------------| > | no flag | nothing | digits prefixed by - | > | : | digits prefixed by a space | digits prefixed by - | > | : and @ | nothing | nothing | > | @ | digits prefixed by + | digits prefixed by - | > > ||# > > (defun cl-user::hex (stream arg colonp atsignp &optional width) > "Format directive ~[width][flags]/hex/ for hexadecimal output. > WIDTH: optional minimum number of hex digits, left-padded with 0s > No flags: positive → nothing, negative → '-' > Colon: positive → ' ', negative → '-' > At: positive → '+', negative → '-' > Colon+At: positive → nothing, negative → nothing" > (let* ((negative (minusp arg)) > (prefix (cond ((and colonp atsignp) nil) > (colonp (if negative #\- #\Space)) > (atsignp (if negative #\- #\+)) > (t (if negative #\- nil)))) > (hex-string (if width > (format nil "~v,'0X" width (abs arg)) > (format nil "~X" (abs arg))))) > (when prefix (write-char prefix stream)) > (write-string hex-string stream))) > > (progn > (format t "~/hex/~%" 255) ; → FF > (format t "~/hex/~%" -255) ; → -FF > > (format t "~8/hex/~%" 255) ; → 000000FF > (format t "~8/hex/~%" -255) ; → -000000FF > > (format t "~8:/hex/~%" 255) ; → 000000FF (leading space) > (format t "~8:/hex/~%" -255) ; → -000000FF > > (format t "~8@/hex/~%" 255) ; → +000000FF > (format t "~8@/hex/~%" -255) ; → -000000FF > > (format t "~8:@/hex/~%" 255) ; → 000000FF (no sign at all) > (format t "~8:@/hex/~%" -255) ; → 000000FF (no sign at all) > ) > > FF > -FF > 000000FF > -000000FF > 000000FF > -000000FF > +000000FF > -000000FF > 000000FF > 000000FF > nil > > ;; or better, this let you see the alignment problems with signs when > ;; you don't put the sign in the "width" argument: > > (loop for s in '("~3/hex/" "~3:/hex/" "~3@/hex/" "~3:@/hex/") > do (loop for n in '(-42 0 42) > do (format t ">~@?<~8T~:*~4D~%" s n) > finally (terpri))) > >-02A< -42 > >000< 0 > >02A< 42 > > >-02A< -42 > > 000< 0 > > 02A< 42 > > >-02A< -42 > >+000< 0 > >+02A< 42 > > >02A< -42 > >000< 0 > >02A< 42 > > nil > > But the truth of hexadecimal, octal and binary formatting, > is that you don’t use them for negative numbers. When you > have negative integers, which is Common Lisp have infinite > number of digits (1s) before the meaningful digits, what you > do to format them is that you truncate them to the word size: > > (loop for n in '(-42 -1 0 1 42) > do (format t "~4/hex/~%" (logand #xffff n))) > FFD6 > FFFF > 0000 > 0001 > 002A > nil > > This prints the 2-complement representation of negative integers. > > -- > __Pascal Bourguignon__ > [email protected]