[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-core-devel
Subject:    Re: KLocale::formatNumber
From:       Klaus Niederkrueger <kniederk () math ! uni-koeln ! de>
Date:       2005-06-14 23:17:25
Message-ID: Pine.GSO.4.44.0506150112170.7508-200000 () Thales
[Download RAW message or body]

Please review the code and tell me whether you think I can apply it to
kdecore or not? Other suggestions?

What does it do:

It basically adds a new "formatNumber"-method in klocale, which supports
arbitrarily large numbers (as QString) and one can switch off rounding.

What is it good for? In "kcalc" there was an implementation of such a
routine, but I thought let's make it available to everyone, and it is nice
to have a formatNumber that only rounds optionally.


Thanks

	Klaus

["out.txt" (TEXT/PLAIN)]

Index: klocale.h
===================================================================
--- klocale.h	(revision 424104)
+++ klocale.h	(working copy)
@@ -389,14 +389,41 @@
    * e.g. given 123456.78F, return "123,456.78" (for some European country).
    * If precision isn't specified, 2 is used.
    *
+   * This function is a wrapper that is provided for convenience.
+   *
    * @param num The number to convert
    * @param precision Number of fractional digits used.
    *
    * @return The number as a localized string
+   * @see formatNumber(const QString, bool, int)
    */
   QString formatNumber(double num, int precision = -1) const;
 
   /**
+   * @deprecated
+   *
+   * KDE 4.0: merge with formatNumber(const QString int)
+   *
+   * calls formatNumber(numStr, 2)
+   */
+  QString formatNumber(const QString &numStr) const KDE_DEPRECATED;
+
+  /**
+   * Given a string representing a number, converts that to a numeric
+   * string containing the localized numeric equivalent.
+   *
+   * e.g. given 123456.78F, return "123,456.78" (for some European country).
+   *
+   * @param numStr The number to convert
+   * @param round Round  fractional digits.
+   * @param precision Number of fractional digits used.
+   *
+   * @return The number as a localized string
+   * @since 3.5
+   */
+  QString formatNumber(const QString &numStr, bool round, int precision) const;
+
+  /**
    * Given an integer, converts that to a numeric string containing
    * the localized numeric equivalent.
    *
@@ -1118,12 +1145,6 @@
 
   /**
    * @deprecated
-   * use formatNumber(double)
-   */
-  QString formatNumber(const QString &numStr) const KDE_DEPRECATED;
-
-  /**
-   * @deprecated
    * Use languageList()
    *
    * @return String containing language codes separated by colons
Index: tests/klocaletest.cpp
===================================================================
--- tests/klocaletest.cpp	(revision 424104)
+++ tests/klocaletest.cpp	(working copy)
@@ -114,6 +114,17 @@
   formatted = KGlobal::locale()->formatNumber( 70.245 ); \
check("formatNumber(70.245)",formatted,"70.25"); /*rounded*/  formatted = \
KGlobal::locale()->formatNumber(1234567.89123456789,8); \
check("formatNumber(1234567.89123456789,8)",formatted,"1,234,567.89123457");  
+  formatted = KGlobal::locale()->formatNumber("70"); \
check("formatNumber(\"70\")",formatted,"70.00"); +  formatted = \
KGlobal::locale()->formatNumber("70", true, 2); check("formatNumber(\"70\", true, \
2)",formatted,"70.00"); +  formatted = KGlobal::locale()->formatNumber("70", true, \
0); check("formatNumber(\"70\", true, 0)",formatted,"70"); +  formatted = \
KGlobal::locale()->formatNumber("-70.2", true, 2); check("formatNumber(\"-70.2\", \
true, 2)",formatted,"-70.20"); +  formatted = \
KGlobal::locale()->formatNumber("+70.24", true, 2); check("formatNumber(\"+70.24\", \
true, 2)",formatted,"70.24"); +  formatted = \
KGlobal::locale()->formatNumber("70.245", true, 2); check("formatNumber(\"70.245\", \
true, 2)",formatted,"70.25"); /*rounded*/ +  formatted = \
KGlobal::locale()->formatNumber("99.996", true, 2); check("formatNumber(\"99.996\", \
true, 2)",formatted,"100.00"); /*rounded*/ +  formatted = \
KGlobal::locale()->formatNumber("12345678901234567.89123456789", false, 0); \
check("formatNumber(\"12345678901234567.89123456789\", false, \
0)",formatted,"12,345,678,901,234,567.89123456789"); +
+
+
   double num;
   bool ok;
   num = KGlobal::locale()->readNumber( "12,1", &ok ); \
                check("readNumber(12,1)",ok?"yes":"no","no");
Index: klocale.cpp
===================================================================
--- klocale.cpp	(revision 424104)
+++ klocale.cpp	(working copy)
@@ -1171,20 +1171,8 @@
 
 QString KLocale::formatNumber(double num, int precision) const
 {
-  bool neg = num < 0;
   if (precision == -1) precision = 2;
-  QString res = QString::number(neg?-num:num, 'f', precision);
-  int pos = res.find('.');
-  if (pos == -1) pos = res.length();
-  else res.replace(pos, 1, decimalSymbol());
-
-  while (0 < (pos -= 3))
-    res.insert(pos, thousandsSeparator()); // thousand sep
-
-  // How can we know where we should put the sign?
-  res.prepend(neg?negativeSign():positiveSign());
-
-  return res;
+  return formatNumber(QString::number(num, 'f', precision), false, 0);
 }
 
 QString KLocale::formatLong(long num) const
@@ -1194,9 +1182,143 @@
 
 QString KLocale::formatNumber(const QString &numStr) const
 {
-  return formatNumber(numStr.toDouble());
+  return formatNumber(numStr, true, 2);
 }
 
+// increase the digit at 'position' by one
+static void _inc_by_one(QString &str, int position)
+{
+  for (int i = position; i >= 0; i--)
+    {
+      char last_char = str[i].latin1();
+      switch(last_char)
+	{
+	case '0':
+	  str[i] = '1';
+	  break;
+	case '1':
+	  str[i] = '2';
+	  break;
+	case '2':
+	  str[i] = '3';
+	  break;
+	case '3':
+	  str[i] = '4';
+	  break;
+	case '4':
+	  str[i] = '5';
+	  break;
+	case '5':
+	  str[i] = '6';
+	  break;
+	case '6':
+	  str[i] = '7';
+	  break;
+	case '7':
+	  str[i] = '8';
+	  break;
+	case '8':
+	  str[i] = '9';
+	  break;
+	case '9':
+	  str[i] = '0';
+	  if (i == 0) str.prepend('1');
+	  continue;
+	case '.':
+	  continue;
+	}
+      break;
+    }
+}
+
+static void _round(QString &str, int precision)
+{
+  // Cut off if more digits in fractional part than 'precision'
+  int decimalSymbolPos = str.find('.');
+
+  if (precision == 0  && decimalSymbolPos == -1) return;
+      
+  // add dot if missing (and needed)
+  if (decimalSymbolPos == -1  &&  precision > 0)
+    {
+      str.append('.');
+      decimalSymbolPos = str.length() - 1;
+    }
+
+  // fill up with more than enough zeroes (in case number too short)
+  str.append(QString().fill('0', precision));
+
+  // Now decide whether to round up or down
+  char last_char = str[decimalSymbolPos + precision + 1].latin1();
+  switch (last_char)
+    {
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+      // nothing to do, rounding down
+      break;
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      _inc_by_one(str, decimalSymbolPos + precision);
+      break;
+    default:
+      break;
+    }
+
+  decimalSymbolPos = str.find('.');
+  str.truncate(decimalSymbolPos + precision + 1);
+  
+  // if precision == 0 delete also '.'
+  if (precision == 0) str = str.section('.', 0, 0);
+}
+
+
+QString KLocale::formatNumber(const QString &numStr, bool round,
+			      int precision) const
+{
+  QString tmpString = numStr;
+  if ((round  && precision < 0)  ||
+      ! QRegExp("^[+-]?\\d+(\\.\\d+)*(e[+-]?\\d+)?$").exactMatch(tmpString))
+    return numStr;
+
+  
+  // Skip the sign (for now)
+  if (tmpString[0] == '+')  tmpString.remove(0, 1);
+  bool neg = (tmpString[0] == '-');
+  if (neg) tmpString.remove(0, 1);
+
+  // Split off exponential part (including 'e'-symbol)
+  QString mantString = tmpString.section('e', 0, 0,
+					 QString::SectionCaseInsensitiveSeps);
+  QString expString = tmpString.section('e', 1, 1,
+					QString::SectionCaseInsensitiveSeps |
+					QString::SectionIncludeLeadingSep);
+
+  if (round) _round(mantString, precision);
+ 
+  // Replace dot with locale decimal separator
+  mantString.replace(QChar('.'), decimalSymbol());
+  
+  // Insert the thousand separators
+  int pos = mantString.find(decimalSymbol());
+  if (pos == -1) pos = mantString.length();
+  pos --;
+
+  for (int index=1; pos>0; pos--, index++)
+    if (index%3 == 0)
+      mantString.insert(pos, thousandsSeparator());
+
+  // How can we know where we should put the sign?
+  mantString.prepend(neg?negativeSign():positiveSign());
+  
+  return mantString +  expString;
+}
+
 QString KLocale::formatDate(const QDate &pDate, bool shortFormat) const
 {
   const QString rst = shortFormat?dateFormatShort():dateFormat();



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic