mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-30 03:40:34 +02:00
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.
This commit is contained in:
parent
21602109f0
commit
4aead68cdb
3 changed files with 88 additions and 30 deletions
|
@ -246,6 +246,36 @@
|
||||||
'unspecified 'unspecified)
|
'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)
|
(define (%number-integer-part int grouping separator)
|
||||||
;; Process INT (a string denoting a number's integer part) and return a new
|
;; 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,
|
;; string with digit grouping and separators according to GROUPING (a list,
|
||||||
|
@ -336,12 +366,11 @@ locale is used."
|
||||||
(substring dec 0 fraction-digits)
|
(substring dec 0 fraction-digits)
|
||||||
dec)))))
|
dec)))))
|
||||||
|
|
||||||
(external-repr (number->string (if (>= amount 0) amount (- amount))))
|
(int (integer->string (inexact->exact
|
||||||
(int+dec (string-split external-repr #\.))
|
(floor (abs amount)))))
|
||||||
(int (car int+dec))
|
(dec (decimal-part
|
||||||
(dec (decimal-part (if (null? (cdr int+dec))
|
(number-decimal-string (abs amount)
|
||||||
""
|
fraction-digits)))
|
||||||
(cadr int+dec))))
|
|
||||||
(grouping (locale-monetary-digit-grouping locale))
|
(grouping (locale-monetary-digit-grouping locale))
|
||||||
(separator (locale-monetary-thousands-separator locale)))
|
(separator (locale-monetary-thousands-separator locale)))
|
||||||
|
|
||||||
|
@ -388,14 +417,14 @@ number of fractional digits to be displayed."
|
||||||
(substring dec 0 fraction-digits)
|
(substring dec 0 fraction-digits)
|
||||||
dec))))))
|
dec))))))
|
||||||
|
|
||||||
(let* ((external-repr (number->string (if (>= number 0)
|
(let* ((int (integer->string (inexact->exact
|
||||||
number
|
(floor (abs number)))))
|
||||||
(- number))))
|
(dec (decimal-part
|
||||||
(int+dec (string-split external-repr #\.))
|
(number-decimal-string (abs number)
|
||||||
(int (car int+dec))
|
(if (integer?
|
||||||
(dec (decimal-part (if (null? (cdr int+dec))
|
fraction-digits)
|
||||||
""
|
fraction-digits
|
||||||
(cadr int+dec))))
|
0))))
|
||||||
(grouping (locale-digit-grouping locale))
|
(grouping (locale-digit-grouping locale))
|
||||||
(separator (locale-thousands-separator locale)))
|
(separator (locale-thousands-separator locale)))
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
;;;; Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de> --- June 2001
|
;;;; Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de> --- June 2001
|
||||||
;;;;
|
;;;;
|
||||||
;;;; Copyright (C) 2001, 2003, 2004, 2006, 2010, 2011, 2012,
|
;;;; Copyright (C) 2001, 2003, 2004, 2006, 2010, 2011, 2012,
|
||||||
;;;; 2014 Free Software Foundation, Inc.
|
;;;; 2014, 2017 Free Software Foundation, Inc.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; This library is free software; you can redistribute it and/or
|
;;;; This library is free software; you can redistribute it and/or
|
||||||
;;;; modify it under the terms of the GNU Lesser General Public
|
;;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
@ -124,22 +124,22 @@
|
||||||
(with-test-prefix "~h localized number"
|
(with-test-prefix "~h localized number"
|
||||||
|
|
||||||
(pass-if "1234.5"
|
(pass-if "1234.5"
|
||||||
(string=? (format #f "~h" 1234.5) "1234.5"))
|
(string=? (format #f "~,1h" 1234.5) "1234.5"))
|
||||||
|
|
||||||
(pass-if "padding"
|
(pass-if "padding"
|
||||||
(string=? (format #f "~6h" 123.2) " 123.2"))
|
(string=? (format #f "~6h" 123.2) " 123"))
|
||||||
|
|
||||||
(pass-if "padchar"
|
(pass-if "padchar"
|
||||||
(string=? (format #f "~8,,'*h" 123.2) "***123.2"))
|
(string=? (format #f "~8,1,'*h" 123.2) "***123.2"))
|
||||||
|
|
||||||
(pass-if "decimals"
|
(pass-if "decimals"
|
||||||
(string=? (format #f "~,2h" 123.4567)
|
(string=? (format #f "~,2h" 123.4567)
|
||||||
"123.45"))
|
"123.46"))
|
||||||
|
|
||||||
(pass-if "locale"
|
(pass-if "locale"
|
||||||
(string=? (format #f "~,3:h, ~a" 1234.5678
|
(string=? (format #f "~,3:h, ~a" 1234.5678
|
||||||
%global-locale "approximately")
|
%global-locale "approximately")
|
||||||
"1234.567, approximately")))
|
"1234.568, approximately")))
|
||||||
|
|
||||||
;;;
|
;;;
|
||||||
;;; ~{
|
;;; ~{
|
||||||
|
|
|
@ -506,12 +506,20 @@
|
||||||
|
|
||||||
(pass-if-equal "fraction"
|
(pass-if-equal "fraction"
|
||||||
"1234.567"
|
"1234.567"
|
||||||
(number->locale-string 1234.567))
|
(number->locale-string 1234.567 3))
|
||||||
|
|
||||||
(pass-if-equal "fraction, 1 digit"
|
(pass-if-equal "fraction, 1 digit"
|
||||||
"1234.5"
|
"1234.6"
|
||||||
(number->locale-string 1234.567 1))
|
(number->locale-string 1234.567 1))
|
||||||
|
|
||||||
|
(pass-if-equal "fraction, 10 digits"
|
||||||
|
"0.0000300000"
|
||||||
|
(number->locale-string .00003 10))
|
||||||
|
|
||||||
|
(pass-if-equal "trailing zeros"
|
||||||
|
"-10.00000"
|
||||||
|
(number->locale-string -10.0 5))
|
||||||
|
|
||||||
(pass-if-equal "positive inexact zero, 1 digit"
|
(pass-if-equal "positive inexact zero, 1 digit"
|
||||||
"0.0"
|
"0.0"
|
||||||
(number->locale-string .0 1)))
|
(number->locale-string .0 1)))
|
||||||
|
@ -525,15 +533,22 @@
|
||||||
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
(number->locale-string 123456 #t fr)))))
|
(number->locale-string 123456 #t fr)))))
|
||||||
|
|
||||||
|
(pass-if-equal "negative integer"
|
||||||
|
"-1 234 567"
|
||||||
|
(under-french-locale-or-unresolved
|
||||||
|
(lambda ()
|
||||||
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
|
(number->locale-string -1234567 #t fr)))))
|
||||||
|
|
||||||
(pass-if-equal "fraction"
|
(pass-if-equal "fraction"
|
||||||
"1 234,567"
|
"1 234,567"
|
||||||
(under-french-locale-or-unresolved
|
(under-french-locale-or-unresolved
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
(number->locale-string 1234.567 #t fr)))))
|
(number->locale-string 1234.567 3 fr)))))
|
||||||
|
|
||||||
(pass-if-equal "fraction, 1 digit"
|
(pass-if-equal "fraction, 1 digit"
|
||||||
"1 234,5"
|
"1 234,6"
|
||||||
(under-french-locale-or-unresolved
|
(under-french-locale-or-unresolved
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
|
@ -553,7 +568,7 @@
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(if (null? (locale-digit-grouping %french-locale))
|
(if (null? (locale-digit-grouping %french-locale))
|
||||||
(throw 'unresolved)
|
(throw 'unresolved)
|
||||||
(format #f "~:h" 12345.6789 %french-locale))))))
|
(format #f "~,4:h" 12345.6789 %french-locale))))))
|
||||||
|
|
||||||
(with-test-prefix "English"
|
(with-test-prefix "English"
|
||||||
|
|
||||||
|
@ -563,7 +578,7 @@
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(if (null? (locale-digit-grouping %american-english-locale))
|
(if (null? (locale-digit-grouping %american-english-locale))
|
||||||
(throw 'unresolved)
|
(throw 'unresolved)
|
||||||
(format #f "~:h" 12345.6789
|
(format #f "~,4:h" 12345.6789
|
||||||
%american-english-locale)))))))
|
%american-english-locale)))))))
|
||||||
|
|
||||||
(with-test-prefix "monetary-amount->locale-string"
|
(with-test-prefix "monetary-amount->locale-string"
|
||||||
|
@ -571,22 +586,36 @@
|
||||||
(with-test-prefix "French"
|
(with-test-prefix "French"
|
||||||
|
|
||||||
(pass-if-equal "integer"
|
(pass-if-equal "integer"
|
||||||
"123 456 +EUR"
|
"123 456,00 +EUR"
|
||||||
(under-french-locale-or-unresolved
|
(under-french-locale-or-unresolved
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
(monetary-amount->locale-string 123456 #f fr)))))
|
(monetary-amount->locale-string 123456 #f fr)))))
|
||||||
|
|
||||||
(pass-if-equal "fraction"
|
(pass-if-equal "fraction"
|
||||||
"1 234,56 EUR "
|
"1 234,57 EUR "
|
||||||
(under-french-locale-or-unresolved
|
(under-french-locale-or-unresolved
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
(monetary-amount->locale-string 1234.567 #t fr)))))
|
(monetary-amount->locale-string 1234.567 #t fr)))))
|
||||||
|
|
||||||
(pass-if-equal "positive inexact zero"
|
(pass-if-equal "positive inexact zero"
|
||||||
"0,0 +EUR"
|
"0,00 +EUR"
|
||||||
(under-french-locale-or-unresolved
|
(under-french-locale-or-unresolved
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
(monetary-amount->locale-string 0. #f fr)))))))
|
(monetary-amount->locale-string 0. #f fr)))))
|
||||||
|
|
||||||
|
(pass-if-equal "one cent"
|
||||||
|
"0,01 EUR "
|
||||||
|
(under-french-locale-or-unresolved
|
||||||
|
(lambda ()
|
||||||
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
|
(monetary-amount->locale-string .01 #t fr)))))
|
||||||
|
|
||||||
|
(pass-if-equal "very little money"
|
||||||
|
"0,00 EUR "
|
||||||
|
(under-french-locale-or-unresolved
|
||||||
|
(lambda ()
|
||||||
|
(let ((fr (make-locale LC_ALL %french-locale-name)))
|
||||||
|
(monetary-amount->locale-string .00003 #t fr)))))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue