| [13390] | 1 | ;; decimal.lisp
|
|---|
| 2 |
|
|---|
| 3 | #|
|
|---|
| 4 | The MIT license.
|
|---|
| 5 |
|
|---|
| 6 | Copyright (c) 2010 Paul L. Krueger
|
|---|
| 7 |
|
|---|
| 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|---|
| 9 | and associated documentation files (the "Software"), to deal in the Software without restriction,
|
|---|
| 10 | including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|---|
| 11 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|---|
| 12 | furnished to do so, subject to the following conditions:
|
|---|
| 13 |
|
|---|
| 14 | The above copyright notice and this permission notice shall be included in all copies or substantial
|
|---|
| 15 | portions of the Software.
|
|---|
| 16 |
|
|---|
| 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|---|
| 18 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|---|
| 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|---|
| 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|---|
| 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|---|
| 22 |
|
|---|
| 23 | |#
|
|---|
| 24 |
|
|---|
| [13646] | 25 | (eval-when (:compile-toplevel :load-toplevel :execute)
|
|---|
| 26 | (require :ns-string-utils))
|
|---|
| [13390] | 27 |
|
|---|
| 28 | (defpackage :interface-utilities
|
|---|
| 29 | (:nicknames :iu)
|
|---|
| 30 | (:export lisp-to-ns-decimal lisp-from-ns-decimal))
|
|---|
| 31 |
|
|---|
| 32 | (in-package :iu)
|
|---|
| 33 |
|
|---|
| 34 | ;; This contains a set of utility functions for converting back and forth between
|
|---|
| 35 | ;; NSDecimalNumber objects and lisp integers.
|
|---|
| 36 | ;; Lisp does a good job representing large integers, so in general we can do things
|
|---|
| 37 | ;; like manipulate dollar amounts just by using integers to represent the total
|
|---|
| 38 | ;; cents value and doing something like:
|
|---|
| 39 | ;; (floor lisp-value 100)
|
|---|
| 40 | ;; to retrieve dollars and cents values.
|
|---|
| 41 |
|
|---|
| 42 | ;; Using float values for dollars and cents is problematic. Rounding errors will get
|
|---|
| 43 | ;; you anytime you convert to or from float-values and input or display strings. Apple
|
|---|
| 44 | ;; has created a special class NSDecimalNumber to represent long decimal numbers. They
|
|---|
| 45 | ;; defined an array of arithmetic functions to manipulate instance of this class. While
|
|---|
| 46 | ;; we could probably just use those functions in lisp and manipulate NSDecimalNumber
|
|---|
| 47 | ;; objects directly, it seems preferable to convert back and forth to lisp integers and
|
|---|
| 48 | ;; use normal arithmetic operations when in Lisp. Of course it will be up to the Lisp
|
|---|
| 49 | ;; programmer to understand how many decimal digits are being represented whereas this
|
|---|
| 50 | ;; value is represented within NSDecimalNumberObjects.
|
|---|
| 51 |
|
|---|
| 52 | (defun lisp-to-ns-decimal (int-val &key (decimals 2))
|
|---|
| 53 | ;; construct an NSDecimalNumber object with the given int-val and number of decimals
|
|---|
| 54 | ;; For example if you have a dollar amount 123.45 represented by the fixnum 12345
|
|---|
| 55 | ;; you would call (make-ns-decimal 12345 :decimals 2) to get a corresponding
|
|---|
| 56 | ;; NSDecimalNumber object. This object is the responsibility of the caller and a
|
|---|
| 57 | ;; call to #/release must be made when the caller is done with it.
|
|---|
| 58 | (unless (typep int-val 'fixnum)
|
|---|
| 59 | (error "Input must be a fixnum"))
|
|---|
| 60 | (#/decimalNumberWithMantissa:exponent:isNegative:
|
|---|
| 61 | ns:ns-decimal-number
|
|---|
| 62 | (abs int-val)
|
|---|
| 63 | (- decimals)
|
|---|
| 64 | (if (minusp int-val) #$YES #$NO)))
|
|---|
| 65 |
|
|---|
| 66 | (defun lisp-from-ns-decimal (ns-dec-obj &key (decimals 2))
|
|---|
| 67 | ;; This function returns a fixnum that corresponds to the NSDecimalNumber
|
|---|
| 68 | ;; or NSNumber that is passed in as the first argument.
|
|---|
| 69 | ;; The result will be scaled and rounded to represent the desired
|
|---|
| 70 | ;; number of decimal digits as specified by the :decimals keyword argument.
|
|---|
| 71 | ;; For example, if an NSNumber is passed in which is something like 123.45678
|
|---|
| 72 | ;; and you ask for 2 decimal digits, the returned value will be the integer 12346.
|
|---|
| 73 | (let* ((loc (#/currentLocale ns:ns-locale))
|
|---|
| 74 | (lisp-str (ccl::lisp-string-from-nsstring
|
|---|
| 75 | (#/descriptionWithLocale: ns-dec-obj loc)))
|
|---|
| 76 | (str-len-1 (1- (length lisp-str)))
|
|---|
| 77 | (dec-pos (or (position #\. lisp-str) str-len-1))
|
|---|
| 78 | (dec-digits (- str-len-1 dec-pos))
|
|---|
| 79 | (dec-diff (- decimals dec-digits))
|
|---|
| 80 | (mantissa-str (delete #\. lisp-str)))
|
|---|
| 81 | (cond ((zerop dec-diff)
|
|---|
| 82 | (read-from-string mantissa-str))
|
|---|
| 83 | ((plusp dec-diff)
|
|---|
| 84 | (read-from-string (concatenate 'string
|
|---|
| 85 | mantissa-str
|
|---|
| 86 | (make-string dec-diff :initial-element #\0))))
|
|---|
| 87 | (t ;; minusp dec-diff
|
|---|
| 88 | (let ((first-dropped (+ (length mantissa-str) dec-diff)))
|
|---|
| 89 | (+ (if (> (char-code (elt mantissa-str first-dropped)) (char-code #\4)) 1 0)
|
|---|
| 90 | (read-from-string (subseq mantissa-str 0 first-dropped))))))))
|
|---|
| 91 |
|
|---|
| 92 |
|
|---|