Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert to dollars and cents -- rounding, and with comma separators

Tags:

emacs

elisp

The following code excerpt will add up a column of numbers and yield results that are several decimal points long. Could anyone please give an example of how to convert the results into dollars and cents, rounding to the second decimal -- i.e., 1.555 should round up to 1.56; and 1.554 should round down to 1.55. Also, I'd like to insert comma separators every three digits to the left of the decimal point -- e.g., 1124412.555 should be converted to 1,124,412.56.

(let ((sum 0))
  (while (re-search-forward "[0-9]*\\.?[0-9]+" nil t)
    (setq sum (+ sum (string-to-number (match-string 0)))))
  (insert "\n\nTotal Hours:  " (format "%s" sum ))
  (insert "\n\nTotal Fee for Services Rendered:  " (format "%s" (* 250 sum)))
  (insert "\n\nOne-third of total fee:  " (format "%s" (/ (* 250 sum) 3))))

2.0
0.2
0.1
4.75
4.0
6.5
0.1

Total Hours:  17.650000000000002

Total Fee for Services Rendered:  4412.500000000001

One-third of total fee:  1470.8333333333337

Based upon the answers provided by @Drew and @abo-abo, the following is a revised draft that now appears to be working correctly:

08884.75585
78774.1235
6.545

Total Hours:  87,665.42

Total Fee:  $21,916,356.09

One-Third:  $7,305,452.03

(let ((sum 0))
  (while (re-search-forward "[0-9]*\\.?[0-9]+" nil t)
    (setq sum (+ sum (string-to-number (match-string 0)))))
    (setq total-hours (group-number (number-conversion (format "%s" sum ))))
    (setq services-rendered (group-number (number-conversion (format "%s" (* 250 sum)))))
    (setq one-third (group-number (number-conversion (format "%s" (/ (* 250 sum) 3)))))
  (insert "\n\nTotal Hours:  " total-hours)
  (insert "\n\nTotal Fee:  $" services-rendered)
  (insert "\n\nOne-Third:  $"  one-third) )

;; @abo-abo
(defun number-conversion (str)
  (let ((x (read str)))
    (format "%0.2f" (* 0.01 (round (* 100 x)))) ))

;; http://www.emacswiki.org/emacs/ElispCookbook#toc23
(defun group-number (num &optional size char)
    "Format NUM as string grouped to SIZE with CHAR."
    ;; Based on code for `math-group-float' in calc-ext.el
    (let* ((size (or size 3))
           (char (or char ","))
           (str (if (stringp num)
                    num
                  (number-to-string num)))
           (pt (or (string-match "[^0-9a-zA-Z]" str) (length str))))
      (while (> pt size)
        (setq str (concat (substring str 0 (- pt size))
                          char
                          (substring str (- pt size)))
              pt (- pt size)))
      str))
like image 615
lawlist Avatar asked Nov 20 '13 16:11

lawlist


4 Answers

I believe abo-abo's (* 0.01 (round (* 100 x))) can lead to surprises in corner cases (because 0.01 cannot be represented exactly in the internal floating point representation). A simpler solution for the rounding is:

(format "%0.2f" x)
like image 107
Stefan Avatar answered Oct 25 '22 14:10

Stefan


Use (format "%.2f" YOUR-NUMBER). For example:

(insert "\n\nOne-third of total fee:  " (format "%.2f" (/ (* 250 sum) 3))))

The f says to use decimal-point notation. The .2 says to show two decimal places. See the doc string of format or the Elisp manual, node Formatting Strings.

As far as I know, in Emacs Lisp there is no predefined format specifier that gives you comma-separated digit grouping. In Common Lisp format is much more powerful (a language by itself).

If you really need comma separation then you will need to write code that parses the text (e.g. the string returned from format) and insert commas where needed. Perhaps someone else will bother to do that for you here, but you can probably do it yourself, given the info you have so far. There is nothing predefined that will help magically with this.

But this might help with the commas.

like image 29
Drew Avatar answered Oct 25 '22 14:10

Drew


Here's the code:

(defun num->dollars (str)
  (let ((x (read str)))
    (replace-regexp-in-string
     "\\."
     ","
     (format "%0.2f"
             (* 0.01 (round (* 100 x)))))))

(mapcar
 #'num->dollars
 '("4412.500000000001" "1470.8333333333337" "1.555" "1.554"))

 ; => ("4412,50" "1470,83" "1,56" "1,55") 
like image 38
abo-abo Avatar answered Oct 25 '22 15:10

abo-abo


In general, it's worth considering the built-in calc library when dealing with money values (or other decimal fractions where precision is critical).

calc uses a decimal representation for floating-point numbers (rather than binary), so is not subject to the classical float rounding issues when dealing with decimal values.

It can also take care of grouping digits for display.

See C-hig (calc) RET

like image 1
phils Avatar answered Oct 25 '22 14:10

phils