Common Lisp Package: DECIMALS

README:

Decimals

A decimal number parser and formatting package for Common Lisp

Author and license

Author: Teemu Likonen <<tlikonen@iki.fi>>

License: Public domain

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Introduction

This package introduces three public functions and one macro. See their own documentation for complete description. Here's a short introduction.

Function: parse-decimal-number (string &amp;key ...)

Examine string for a decimal number and return it as a rational number.

Examples

DECIMALS> (parse-decimal-number "12,34" :decimal-separator #\,)  
617/50  

Keyword arguments and their default values

:decimal-separator  #\.  
:positive-sign      #\+  
:negative-sign      #\-  
:start              0  
:end                nil  

Function: format-decimal-number (number &amp;key ...)

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

Examples

DECIMALS> (format-decimal-number 20000/3 :round-magnitude -3  
                                 :decimal-separator ","  
                                 :integer-group-separator " "  
                                 :integer-minimum-width 7)  
"  6 666,667"  
("" "6 666" "," "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  

Keyword arguments and their default values

: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            #\space  
:fractional-group-separator  nil  
:fractional-group-digits     3  
:fractional-minimum-width    0  
:fractional-pad-char         #\space  
:show-trailing-zeros         nil  
:positive-sign               nil  
:zero-sign                   nil  
:negative-sign               #\-  

Function: round-half-away-from-zero (number &amp;optional (divisor 1))

A rounding function similar to cl:round except that when number is exactly half-way between two integers round always away from zero. format-decimal-number uses this rounding function by default.

Macro: define-decimal-formatter (name &amp;body keyword-arguments)

Define a custom decimal number formatter function suitable for the ~/ directive of cl:format.

FUNCTION

Public

FORMAT-DECIMAL-NUMBER (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 a list of formatted strings using the same rules but it separates the parts of the number. 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. NUMBER must be of type real. Formatting rules are specified with keyword arguments, as described below. The default value is in parentheses. ROUND-MAGNITUDE (0) This is the order of magnitude used for rounding. The value must be an integer and it is interpreted as a power of 10. SHOW-TRAILING-ZEROS (nil) If the value is non-nil print all trailing zeros in fractional part. Example: (format-decimal-number 1/5 :round-magnitude -3 :show-trailing-zeros nil) => "0.2" (format-decimal-number 1/5 :round-magnitude -3 :show-trailing-zeros t) => "0.200" ROUNDER (#'round-half-away-from-zero) The value must be a function (or a symbol naming a function). It is used to round the number to the specified round magnitude. The function must work like CL:TRUNCATE, CL:FLOOR, CL:CEILING and CL:ROUND, that is, take two arguments, a number and a divisor, and return the quotient as the first value. This package introduces another rounding function, ROUND-HALF-AWAY-FROM-ZERO, which is used by default. See its documentation for more information. DECIMAL-SEPARATOR (#\.) If the value is non-nil the PRINC output of the value will be added between integer and fractional parts. Probably the most useful types are character and string. INTEGER-GROUP-SEPARATOR (nil) FRACTIONAL-GROUP-SEPARATOR (nil) If the value is non-nil the digits in integer or fractional parts are put in groups. The PRINC output of the value will be added between digit groups. INTEGER-GROUP-DIGITS (3) FRACTIONAL-GROUP-DIGITS (3) The value is a positive integer defining the number of digits in groups. INTEGER-MINIMUM-WIDTH (0) FRACTIONAL-MINIMUM-WIDTH (0) Format integer or fractional part using minimum of this amount of characters, possibly using some padding characters (see below). POSITIVE-SIGN, NEGATIVE-SIGN or ZERO-SIGN (see below) is included when calculating the width of the integer part. Similarly DECIMAL-SEPARATOR is included when calculating the width of the fractional part. INTEGER-PAD-CHAR (#\Space) FRACTIONAL-PAD-CHAR (#\Space) The value is the padding character which is used to fill INTEGER-MINIMUM-WIDTH or FRACTIONAL-MINIMUM-WIDTH. POSITIVE-SIGN (nil) NEGATIVE-SIGN (#\-) ZERO-SIGN (nil) If values are non-nil these are used as the leading sign for positive, negative and zero numbers. The PRINC output of the value is used. Examples ======== 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

PARSE-DECIMAL-NUMBER (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 these rules are not met a DECIMAL-PARSE-ERROR condition is signaled. 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

ROUND-HALF-AWAY-FROM-ZERO (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

Private

Undocumented

DECIMAL-ROUND-SPLIT (NUMBER &KEY (ROUND-MAGNITUDE 0) (ROUNDER #'ROUND-HALF-AWAY-FROM-ZERO) (POSITIVE-SIGN +) (NEGATIVE-SIGN -) (ZERO-SIGN NIL))

DIVIDE-INTO-GROUPS (STRING &KEY (SEPARATOR ) (FROM-END NIL) (GROUP-DIGITS 3))

NUMBER-STRING-TO-FRACTIONAL (STRING)

NUMBER-STRING-TO-INTEGER (STRING)

STRING-ALIGN (STRING WIDTH &KEY (SIDE LEFT) (CHAR ))

MACRO

Public

DEFINE-DECIMAL-FORMATTER (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. Example: (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 "

CONDITION

Public

DECIMAL-PARSE-ERROR

Function PARSE-DECIMAL-NUMBER signals this condition when it couldn't parse a decimal number from string.