Home

Awesome

Decimals

A decimal number parser and formatting package for Common Lisp

Introduction

This Common Lisp package offers functions for parsing and formatting decimal numbers. Package's main interface are functions parse-decimal-number and format-decimal-number. The former is for parsing strings for decimal numbers and the latter for pretty-printing them as strings. See section The Programming Interface for the full documentation of the public programming interface. Here are some examples.

Parsing

DECIMALS> (parse-decimal-number "0.24")
6/25


DECIMALS> (parse-decimal-number "−12,345"
                                :decimal-separator #\,
                                :negative-sign #\−)
-2469/200

Formatting

DECIMALS> (format-decimal-number -100/6 :round-magnitude -3)
"-16.667"
("-" "16" "." "667")


DECIMALS> (loop for e from -5 upto 5
                do (print (format-decimal-number
                           (expt 10 e) :round-magnitude -5
                           :decimal-separator ","
                           :integer-minimum-width 7
                           :integer-group-separator " "
                           :fractional-minimum-width 7
                           :fractional-group-separator " ")))

"      0,000 01"
"      0,000 1 "
"      0,001   "
"      0,01    "
"      0,1     "
"      1       "
"     10       "
"    100       "
"  1 000       "
" 10 000       "
"100 000       "
NIL


DECIMALS> (loop for m from -3 upto 3
                do (print (format-decimal-number
                           2000/3 :round-magnitude m
                           :integer-minimum-width 4
                           :fractional-minimum-width 4)))

" 666.667"
" 666.67 "
" 666.7  "
" 667    "
" 670    "
" 700    "
"1000    "
NIL

License and Source Code

Author: Teemu Likonen <tlikonen@iki.fi>

OpenPGP key: 6965F03973F0D4CA22B9410F0F2CAE0E07608462

License: Creative Commons CC0 (public domain dedication)

The source code repository: https://github.com/tlikonen/cl-decimals

The Programming Interface

Condition: decimal-parse-error

Function parse-decimal-number signals this condition when it couldn't parse a decimal number from string. Function decimal-parse-error-string can be used to read the input string from the condition object.

Function: format-decimal-number

The lambda list:

 (number &key (round-magnitude 0)
  (rounder #'round-half-away-from-zero) (decimal-separator #\.)
  (integer-group-separator nil) (integer-group-digits 3)
  (integer-minimum-width 0) (integer-pad-char #\ )
  (fractional-group-separator nil) (fractional-group-digits 3)
  (fractional-minimum-width 0) (fractional-pad-char #\ )
  (show-trailing-zeros nil) (positive-sign nil) (negative-sign #\-)
  (zero-sign nil))

Apply specified decimal number formatting rules to number and return a formatted string.

The second return value is (almost) the same formatted string divided into four strings. It's a list of four strings: sign, integer part, decimal separator and fractional part. Formatting arguments integer-minimum-width and fractional-minimum-width do not apply to the second return value. Everything else does.

Number must be of type real. This function uses rational types internally. If the given number is a float it is first turned into rational by calling cl:rational.

Formatting rules are specified with keyword arguments, as described below. The default value is in parentheses.

Function: parse-decimal-number

The lambda list:

 (string &key (decimal-separator #\.) (positive-sign #\+)
         (negative-sign #\-) (start 0) (end nil))

Examine string (or its substring from start to end) for a decimal number. Assume that the decimal number is exact and return it as a rational number.

Rules for parsing: First all leading and trailing #\Space characters are stripped. The resulting string may start with a positive-sign or a negative-sign character. The latter causes this function to assume a negative number. The following characters in the string must include one or more digit characters and it may include one decimal-separator character which separates integer and fractional parts. All other characters are illegal.

If the parsing rules are not met a decimal-parse-error condition is signaled. Function decimal-parse-error-string can be used to read the string from the condition object.

Examples:

(parse-decimal-number "0.2")  => 1/5
(parse-decimal-number ".2")   => 1/5
(parse-decimal-number "+3.")  => 3
(parse-decimal-number " -7 ") => -7

(parse-decimal-number "−12,345"
                      :decimal-separator #\,
                      :negative-sign #\−)
=> -2469/200

Function: round-half-away-from-zero

The lambda list:

 (number &optional (divisor 1))

Divide number by divisor and round the result to the nearest integer. If the result is half-way between two integers round away from zero. Two values are returned: quotient and remainder.

This is similar to cl:round function except that cl:round rounds to an even integer when number is exactly between two integers. Examples:

(round-half-away-from-zero 3/2) => 2, -1/2
(round 3/2)                     => 2, -1/2

(round-half-away-from-zero 5/2) => 3, -1/2
(round 5/2)                     => 2, 1/2

Macro: define-decimal-formatter

The lambda list:

 (name &body keyword-arguments)

Define a decimal number formatter function to use with the ~/ directive of cl:format. The valid format is this:

(define-decimal-formatter name
  (:keyword form)
  ...)

Name is the symbol that names the function. Keyword must be a valid keyword argument for the format-decimal-number function (see its documentation for more information). Form is evaluated and the value is used with the keyword argument. Macro's side effect is that global function name is defined. It can be used with the ~/ directive of cl:format function.

Examples:

(define-decimal-formatter my-formatter
  (:round-magnitude -6)
  (:decimal-separator ",")
  (:integer-group-separator " ")
  (:integer-minimum-width 4)
  (:fractional-group-separator " ")
  (:fractional-minimum-width 10)
  (:show-trailing-zeros t))
=> MY-FORMATTER

(format nil "~/my-formatter/" 10/6)
=> "   1,666 667  "

(format nil "~/my-formatter/" 100/8)
=> "  12,500 000  "

The ~/ directive function call can optionally take up to three arguments to override the defaults:

~round-magnitude,integer-minimum-width,fractional-minimum-width/FUNCTION/

For example:

(format nil "~-2,3,4/my-formatter/" 10/6)
=> "  1,67 "