summaryrefslogtreecommitdiff
path: root/module/ice-9
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2017-02-13 00:07:40 +0100
committerAndy Wingo <wingo@pobox.com>2017-03-01 21:16:25 +0100
commit4c7d1a64fa970c42f233af84dda49180a9836321 (patch)
treedf74f996e90e627f01e5c19835035cee85b729e5 /module/ice-9
parent585bf8387109a0c956b15452df6c56024a5de271 (diff)
i18n: Fix corner cases for monetary and number string conversions.
Fixes <http://bugs.gnu.org/24990>. Reported by Martin Michel <dev@famic.de>. * module/ice-9/i18n.scm (integer->string, number-decimal-string): New procedures. (monetary-amount->locale-string): Use them instead of 'number->string' followed by 'string-split'. (number->locale-string): Likewise. * test-suite/tests/i18n.test ("number->locale-string")["fraction"]: Add second argument to 'number->locale-string'. ["fraction, 1 digit"]: Round up. ["fraction, 10 digits", "trailing zeros", "negative integer"]: New tests. * test-suite/tests/i18n.test ("format ~h"): Pass the number of decimals for ~h. ("monetary-amount->locale-string")["French"]: Always expect two decimals after the comma. ["one cent", "very little money"]: New tests. * test-suite/tests/format.test ("~h localized number")["1234.5"]: Specify the number of decimals explicitly. ["padding"]: Expect zero decimals. ["padchar"]: Ask for one decimal. ["decimals", "locale"]: Adjust rounding.
Diffstat (limited to 'module/ice-9')
-rw-r--r--module/ice-9/i18n.scm57
1 files changed, 43 insertions, 14 deletions
diff --git a/module/ice-9/i18n.scm b/module/ice-9/i18n.scm
index 1326a2a02..2363ba350 100644
--- a/module/ice-9/i18n.scm
+++ b/module/ice-9/i18n.scm
@@ -246,6 +246,36 @@
'unspecified 'unspecified)
+(define (integer->string number)
+ "Return a string representing NUMBER, an integer, written in base 10."
+ (define (digit->char digit)
+ (integer->char (+ digit (char->integer #\0))))
+
+ (if (zero? number)
+ "0"
+ (let loop ((number number)
+ (digits '()))
+ (if (zero? number)
+ (list->string digits)
+ (loop (quotient number 10)
+ (cons (digit->char (modulo number 10))
+ digits))))))
+
+(define (number-decimal-string number digit-count)
+ "Return a string representing the decimal part of NUMBER, with exactly
+DIGIT-COUNT digits"
+ (if (integer? number)
+ (make-string digit-count #\0)
+
+ ;; XXX: This is brute-force and could be improved by following one
+ ;; of the "Printing Floating-Point Numbers Quickly and Accurately"
+ ;; papers.
+ (let ((number (* (expt 10 digit-count)
+ (- number (floor number)))))
+ (string-pad (integer->string (round (inexact->exact number)))
+ digit-count
+ #\0))))
+
(define (%number-integer-part int grouping separator)
;; Process INT (a string denoting a number's integer part) and return a new
;; string with digit grouping and separators according to GROUPING (a list,
@@ -336,12 +366,11 @@ locale is used."
(substring dec 0 fraction-digits)
dec)))))
- (external-repr (number->string (if (>= amount 0) amount (- amount))))
- (int+dec (string-split external-repr #\.))
- (int (car int+dec))
- (dec (decimal-part (if (null? (cdr int+dec))
- ""
- (cadr int+dec))))
+ (int (integer->string (inexact->exact
+ (floor (abs amount)))))
+ (dec (decimal-part
+ (number-decimal-string (abs amount)
+ fraction-digits)))
(grouping (locale-monetary-digit-grouping locale))
(separator (locale-monetary-thousands-separator locale)))
@@ -388,14 +417,14 @@ number of fractional digits to be displayed."
(substring dec 0 fraction-digits)
dec))))))
- (let* ((external-repr (number->string (if (>= number 0)
- number
- (- number))))
- (int+dec (string-split external-repr #\.))
- (int (car int+dec))
- (dec (decimal-part (if (null? (cdr int+dec))
- ""
- (cadr int+dec))))
+ (let* ((int (integer->string (inexact->exact
+ (floor (abs number)))))
+ (dec (decimal-part
+ (number-decimal-string (abs number)
+ (if (integer?
+ fraction-digits)
+ fraction-digits
+ 0))))
(grouping (locale-digit-grouping locale))
(separator (locale-thousands-separator locale)))