From: Robert Monfera
Subject: Formatting: separating commas for non-decimals
Date: 
Message-ID: <382DB704.CDE07BCE@fisec.com>
Hello,

I needed to format a number such that there are separating commas at
every third digit (dollars), and two decimals for the cents:

3,352,194.82

I haven't found any directive for this one, so I glued it together with
the help of FLOOR.  I looked at the CLHS and CLtL2 - I feel I must have
overlooked something?

Thanks,
Robert

From: David B. Lamkins
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <dlamkins-1411991409560001@ip216-26-47-127.dsl.du.teleport.com>
In article <·················@fisec.com>, ·······@fisec.com wrote:

>Hello,
>
>I needed to format a number such that there are separating commas at
>every third digit (dollars), and two decimals for the cents:
>
>3,352,194.82
>
>I haven't found any directive for this one, so I glued it together with
>the help of FLOOR.  I looked at the CLHS and CLtL2 - I feel I must have
>overlooked something?

I couldn't find anything, either.

I ended up with this:

? (multiple-value-bind (int frac)
                       (floor 12345678.90)
    (format nil "~:D~2,0$" int frac))
"12,345,678.90"
From: Russell Senior
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <86k8nkmp03.fsf@coulee.tdb.com>
>>>>> "David" == David B Lamkins <········@psg.com> writes:

>> I haven't found any directive for this one, so I glued it together
>> with the help of FLOOR.  I looked at the CLHS and CLtL2 - I feel I
>> must have overlooked something?

David> I couldn't find anything, either.

David> I ended up with this:

David> ? (multiple-value-bind (int frac) (floor 12345678.90) (format
David> nil "~:D~2,0$" int frac)) "12,345,678.90"

That doesn't work on negative numbers.  How about:

(defun print-money (x)
  (multiple-value-bind (dollars cents) (truncate x)
    (format nil "~:D~2,0$" dollars (abs cents))))


-- 
Russell Senior         ``The two chiefs turned to each other.        
·······@teleport.com     Bellison uncorked a flood of horrible       
                         profanity, which, translated meant, `This is
                         extremely unusual.' ''                      
From: David B. Lamkins
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <dlamkins-1511992243180001@ip216-26-47-127.dsl.du.teleport.com>
In article <··············@coulee.tdb.com>, Russell Senior
<·······@teleport.com> wrote:

>>>>>> "David" == David B Lamkins <········@psg.com> writes:
>
>>> I haven't found any directive for this one, so I glued it together
>>> with the help of FLOOR.  I looked at the CLHS and CLtL2 - I feel I
>>> must have overlooked something?
>
>David> I couldn't find anything, either.
>
>David> I ended up with this:
>
>David> ? (multiple-value-bind (int frac) (floor 12345678.90) (format
>David> nil "~:D~2,0$" int frac)) "12,345,678.90"
>
>That doesn't work on negative numbers.  How about:
>
>(defun print-money (x)
>  (multiple-value-bind (dollars cents) (truncate x)
>    (format nil "~:D~2,0$" dollars (abs cents))))

Yes, of course.  Thanks for the correction.
From: Marco Antoniotti
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <lw3du8ayus.fsf@parades.rm.cnr.it>
Russell Senior <·······@teleport.com> writes:

> >>>>> "David" == David B Lamkins <········@psg.com> writes:
> 
> >> I haven't found any directive for this one, so I glued it together
> >> with the help of FLOOR.  I looked at the CLHS and CLtL2 - I feel I
> >> must have overlooked something?
> 
> David> I couldn't find anything, either.
> 
> David> I ended up with this:
> 
> David> ? (multiple-value-bind (int frac) (floor 12345678.90) (format
> David> nil "~:D~2,0$" int frac)) "12,345,678.90"
> 
> That doesn't work on negative numbers.  How about:
> 
> (defun print-money (x)
>   (multiple-value-bind (dollars cents) (truncate x)
>     (format nil "~:D~2,0$" dollars (abs cents))))

I know CL is an ANSI standard, but in Italy you would write

	21.975.308.442

(at the exchange rate of 1.780 ITL per USD).  I.e. you'd use points
instead of commas. :)

So, this is an issue of "internationalization".  Of course, imposing
the MKS system to the Anglo-Saxon world is a matter of
"rationalization" :)  Unless you want to end up like in the "Babylon 5"
universe. :)

Cheers
	

-- 
Marco Antoniotti ===========================================
PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
tel. +39 - 06 68 10 03 17, fax. +39 - 06 68 80 79 26
http://www.parades.rm.cnr.it/~marcoxa
From: Robert Monfera
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <3830CEBF.977E610E@fisec.com>
Marco Antoniotti wrote:
...
> I know CL is an ANSI standard, but in Italy you would write
> 
>         21.975.308.442

Generally, continental Europe uses decimal commas and separating points,
I think:

         21.975.308.442,92  (OK, this was Euro rather than Lira :-)

> So, this is an issue of "internationalization".  Of course, imposing
> the MKS system to the Anglo-Saxon world is a matter of
> "rationalization" :)

I'm wondering what ISO has to say about it.  Erik seems to imply they
are going with the Old Continent method.  It may be irrelevant, as
Americans still measure liquids in pints, tablespoons and cubic inches,
and their paper size is Letter rather than A4.

This comma versus point issue is not too confusing, as there is usually
enough context to figure out the number.  What is definitely worse is
the ambiguity of digits between the Continental and the Anglo-Saxon
world.  If you write a European number 1 in the States, people will
swear that's a seven.  To avoid it, I use European 7's and Anglo 1's: 

 ------     |
     /      |
  --/--     |
   /        |
  /         | 
  

Robert
From: Erik Naggum
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <3151655297035748@naggum.no>
* Marco Antoniotti <·······@parades.rm.cnr.it>
| I know CL is an ANSI standard, but in Italy you would write
| 
| 	21.975.308.442
| 
| (at the exchange rate of 1.780 ITL per USD).  I.e. you'd use points
| instead of commas. :)

(defun print-money (lire)
  (format nil "ITL ~,,'.:D" lire))

(print-money 21975308442)
=> "ITL 21.975.308.442"

| So, this is an issue of "internationalization".

  using the period (espesially when mislabeled "full stop") as the grouping
  delimiter and comma as the fraction mark is REALLY STUPID.  as early as
  in second grade, when I was first exposed to the other use of comma, I
  raised my hand and asked the completely befuddled teacher why we used the
  comma both for decimal point and a list separator when that clearly made
  it easy to get things wrong.  I recall that she got angry when I insisted
  it was dumb, and I can probably trace my years of arduous involvement in
  standards committees to these traumatic early childhood events.  the
  really sad thing is that ISO buys into this comma crap, too.

#:Erik
-- 
  Attention Microsoft Shoppers!  MS Monopoly Money 6.0 are now worthless.
From: Erik Naggum
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <3151640547310053@naggum.no>
* Robert Monfera <·······@fisec.com>
| I needed to format a number such that there are separating commas at
| every third digit (dollars), and two decimals for the cents:

(in-package :cl-user)

(declaim (declaration :author :date :copyright))
(declaim (:author "Erik Naggum")
	 (:date "1997-08-12")
	 (:copyright "Copyright 1997 by Erik Naggum.
Any use is permitted provided that this copyright notice is retained and
that all changes are duly identified."))

(defun dollars (stream amount colon-p atsign-p
		&optional (width 0) (padchar #\Space) (commachar #\,))
  "Print an amount of dollars for humans to read from FORMAT.

The full form is ~WIDTH,PADCHAR,··········@/DOLLARS/, where WIDTH is the
minimum width of the field (default 0), PADCHAR is the character used to
pad to the width on the left end, and COMMACHAR is the separator between
each group of three digits.

The @ modifier controls printing of the sign of positive amounts.  If
omitted, a positive value prints without a sign.  Otherwise, a positive
amount has an explicit + sign.

The : modifier controls the position of the sign and the padding.  If
omitted, the dollar sign is printed in the leftmost position, then any
intervening pad characters, then the signed value.  Otherwise, the sign
occupies the leftmost position, and the dollar sign follows any intervening
pad characters."
  (let* ((digits 
	  (multiple-value-bind (dollars cents) (floor (abs amount) 1)
	    (format nil "~,,V:D.~2,'0D" commachar dollars (round cents 0.01))))
	 (sign (if (minusp amount) #\- (if atsign-p #\+ nil)))
	 (padding (max 0 (- width 1 (if sign 1 0) (length digits))))
	 (pre (if colon-p sign #\$))
	 (post (if colon-p #\$ sign)))
    (when pre (write-char pre stream))
    (dotimes (i padding) (write-char padchar stream))
    (when post (write-char post stream))
    (write-string digits stream)))


#:Erik
-- 
  Attention Microsoft Shoppers!  MS Monopoly Money 6.0 are now worthless.
From: Sam Steingold
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <u903zsdqs.fsf@ksp.com>
>>>> In message <················@naggum.no>
>>>> On the subject of "Re: Formatting: separating commas for non-decimals"
>>>> Sent on 15 Nov 1999 07:42:27 +0000
>>>> Honorable Erik Naggum <····@naggum.no> writes:
 >> * Robert Monfera <·······@fisec.com>
 >> | I needed to format a number such that there are separating commas at
 >> | every third digit (dollars), and two decimals for the cents:
 >> 
 >> (in-package :cl-user)
 >> 
 >> (declaim (declaration :author :date :copyright))
 >> (declaim (:author "Erik Naggum")
 >> 	 (:date "1997-08-12")
 >> 	 (:copyright "Copyright 1997 by Erik Naggum.
 >> Any use is permitted provided that this copyright notice is retained and
 >> that all changes are duly identified."))
 >> 
 >> (defun dollars (stream amount colon-p atsign-p
 >> 		&optional (width 0) (padchar #\Space) (commachar #\,))
 >>   "Print an amount of dollars for humans to read from FORMAT.
 >> 
 >> The full form is ~WIDTH,PADCHAR,··········@/DOLLARS/, where WIDTH is the
 >> minimum width of the field (default 0), PADCHAR is the character used to
 >> pad to the width on the left end, and COMMACHAR is the separator between
 >> each group of three digits.
 >> 
 >> The @ modifier controls printing of the sign of positive amounts.  If
 >> omitted, a positive value prints without a sign.  Otherwise, a positive
 >> amount has an explicit + sign.
 >> 
 >> The : modifier controls the position of the sign and the padding.  If
 >> omitted, the dollar sign is printed in the leftmost position, then any
 >> intervening pad characters, then the signed value.  Otherwise, the sign
 >> occupies the leftmost position, and the dollar sign follows any intervening
 >> pad characters."
 >>   (let* ((digits 
 >> 	  (multiple-value-bind (dollars cents) (floor (abs amount) 1)
 >> 	    (format nil "~,,V:D.~2,'0D" commachar dollars (round cents 0.01))))
 >> 	 (sign (if (minusp amount) #\- (if atsign-p #\+ nil)))
 >> 	 (padding (max 0 (- width 1 (if sign 1 0) (length digits))))
 >> 	 (pre (if colon-p sign #\$))
 >> 	 (post (if colon-p #\$ sign)))
 >>     (when pre (write-char pre stream))
 >>     (dotimes (i padding) (write-char padchar stream))
 >>     (when post (write-char post stream))
 >>     (write-string digits stream)))

This is broken:

> (format t "~/dollars/" 12345678.999)
$12,345,678.100

The correct version is (GPL2):

(defun comma (stream num colon-p atsign-p
              &optional (dd 0 ddp) (di 0) (padchar #\Space) (commachar #\,))
  "Print the number, commified.
Can be used in `format' as ~cents,dollars,padchar,commachar,dollchar/comma/,
where `cents' and `dollars' are the widths of the corresponding fields,
padchar is the character used to pad to the width on the left end, and
commachar is the separator between each group of three digits, dollarchar
is the currency character.
The @ modifier controls printing of the sign of positive amounts.  If
omitted, a positive value prints without a sign.  Otherwise, a positive
amount has an explicit + sign.
The : modifier controls the presence of the dollar sign: if given,
the dollar sign is printed right before the first digit, if not, no
dollar sign is printed."
  (declare (stream stream) (fixnum dd di) (number num)
           (character padchar commachar))
  (multiple-value-bind (inum dnum) (truncate num)
    (declare (integer inum))
    (let ((dnum (abs (dfloat dnum))))
      (declare (double-float dnum))
      (unless ddp (setq dd (1- (length (format nil "~f" dnum)))))
      (when (< (- 1 dnum) (expt 1/10 dd)) ; rounding at formatting
        (incf inum (signum inum))
        (setq dnum 0.0d0))
      (let ((str (format nil "~,,v:d~v,vf" commachar inum (1+ dd) dd dnum))
            (sig (if (and atsign-p (plusp num)) #\+)))
        (declare (simple-string str))
        (format stream "~v,,,···@[~c~]·@[~c~]~a"
                (max 0 (- (+ 1 dd di) (length str)
                          (if sig 1 0) (if colon-p 1 0)))
                padchar "" (if colon-p #\$) sig str)))))


-- 
Sam Steingold (http://www.podval.org/~sds/)
Micros**t is not the answer.  Micros**t is a question, and the answer is Linux,
(http://www.linux.org) the choice of the GNU (http://www.gnu.org) generation.
will write code that writes code that writes code for food
From: Gareth McCaughan
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <86903zfbon.fsf@g.local>
Sam Steingold wrote:

[SNIP: Erik's code]
> This is broken:
> 
>> (format t "~/dollars/" 12345678.999)
> $12,345,678.100
> 
> The correct version is (GPL2):
[SNIP: Sam's code]

This is also broken, because (1) it refers to the nonexistent
function DFLOAT, and (2) the documentation says it takes a dollar-character
argument but it doesn't. Oh, and also because (3) it's GPLed, which
makes it unusable by people writing non-RMSFree software.

It would be simpler to amend Erik's code a little: before
	    (format nil "~,,V:D.~2,'0D" commachar dollars (round cents 0.01))))
add
            (when (>= 100 (setf cents (round cents 0.01)))
              (incf dollars)
              (setf cents 0))
	    (format nil "~,,V:D.~2,'0D" commachar dollars cents)))

On the other hand, Sam's code does a couple of things Erik's doesn't.

-- 
Gareth McCaughan  ················@pobox.com
sig under construction
From: Sam Steingold
Subject: Re: Formatting: separating commas for non-decimals
Date: 
Message-ID: <uso26pgdj.fsf@ksp.com>
>>>> In message <··············@g.local>
>>>> On the subject of "Re: Formatting: separating commas for non-decimals"
>>>> Sent on 16 Nov 1999 01:49:12 +0000
>>>> Honorable Gareth McCaughan <················@pobox.com> writes:
 >> 
 >> This is also broken, because (1) it refers to the nonexistent
 >> function DFLOAT, and (2) the documentation says it takes a

I am sorry.

(defmacro dfloat (num)
  "Coerce to double float."
  `(float ,num 1.0d0))

this is from http://www.podval.org/~sds/data/cllib.zip:base.lsp,
just as the code I posted was taken verbatim from
 http://www.podval.org/~sds/data/cllib.zip:print.lsp

 >> dollar-character argument but it doesn't. Oh, and also because (3)

huh? oh, oops! sorry! :-)
forgot the implement :-)
the following should work.

(defun comma (stream num colon-p atsign-p &optional (dd 0 ddp) (di 0)
              (padchar #\Space) (commachar #\,) (dollarchar #\$))
  "Print the number, commified.
Can be used in `format' as ~cents,dollars,padchar,commachar,dollchar/comma/,
where `cents' and `dollars' are the widths of the corresponding fields,
padchar is the character used to pad to the width on the left end, and
commachar is the separator between each group of three digits, dollarchar
is the currency character.
The @ modifier controls printing of the sign of positive amounts.  If
omitted, a positive value prints without a sign.  Otherwise, a positive
amount has an explicit + sign.
The : modifier controls the presence of the dollar sign: if given,
the dollar sign is printed right before the first digit, if not, no
dollar sign is printed."
  (declare (stream stream) (fixnum dd di) (number num)
           (character padchar commachar dollarchar))
  (multiple-value-bind (inum dnum) (truncate num)
    (declare (integer inum))
    (let ((dnum (abs (float dnum 1.0d0))))
      (declare (double-float dnum))
      (unless ddp (setq dd (1- (length (format nil "~f" dnum)))))
      (when (< (- 1 dnum) (expt 1/10 dd)) ; rounding at formatting
        (incf inum (signum inum))
        (setq dnum 0.0d0))
      (let ((str (format nil "~,,v:d~v,vf" commachar inum (1+ dd) dd dnum))
            (sig (if (and atsign-p (plusp num)) #\+)))
        (declare (simple-string str))
        (format stream "~v,,,···@[~c~]·@[~c~]~a"
                (max 0 (- (+ 1 dd di) (length str)
                          (if sig 1 0) (if colon-p 1 0)))
                padchar "" (if colon-p dollarchar) sig str)))))


 >> it's GPLed, which makes it unusable by people writing non-RMSFree
 >> software.

like who? :-)
I say in http://www.podval.org/~sds/software.html, that I am willing to
consider changing the license to LGPL for those part which are needed by
those who cannot abide by the GPL.

 >> On the other hand, Sam's code does a couple of things Erik's doesn't.

thanks.

-- 
Sam Steingold (http://www.podval.org/~sds/)
Micros**t is not the answer.  Micros**t is a question, and the answer is Linux,
(http://www.linux.org) the choice of the GNU (http://www.gnu.org) generation.
MS DOS: Keyboard not found. Press F1 to continue.