diff options
author | Ludovic Courtès <ludo@gnu.org> | 2017-02-13 00:07:40 +0100 |
---|---|---|
committer | Andy Wingo <wingo@pobox.com> | 2017-03-01 21:16:25 +0100 |
commit | 4c7d1a64fa970c42f233af84dda49180a9836321 (patch) | |
tree | df74f996e90e627f01e5c19835035cee85b729e5 /module/ice-9 | |
parent | 585bf8387109a0c956b15452df6c56024a5de271 (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.scm | 57 |
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))) |