Hallo Martin,

On Tue, Jul 14, 2009 at 08:19:30AM +0200, to...@tuxteam.de wrote:
> Ich muss Micha recht geben. Ich erwarte hier keine wilden Operationen,
> die solch eine Zahl rechtfertigen würden (im Wesentlichen geteilt durch
> Hundert bei der Dezimal -> Intern Wandlung, und das müsste eine
> Bruchlibrary wie libgmp verlustfrei können: 36154/100, ggf. gekürzt). Es
> sieht also aus, als wäre die arme Zahl durch eine float-Mangel gezogen
> worden.

Korrekt, und zwar von AqBanking selbst in der Funktion
AB_Value_fromString(), die den übergebenen String bisher zunächst in
eine Gleitkommazahl (der Typ mpf_t aus der Bibliothek GMP ist eine
Gleitkommazahl mit beliebiger Präzision!) umwandelt, um erst dann eine
rationale Zahl daraus zu machen.

Ich habe gerade einen Patch geschrieben, der eine Zahl, die
so normalerweise in Strings übergeben wird, korrekt in ein mpq_t
(rationale Zahl aus libGMP) umwandelt (im Anhang die Datei
proper_rational_number_AB_Value.patch). Dazu habe ich ein kleines
Testprogramm geschrieben, dass mit dem folgenden Befehl in eine
ausführbare Datei übersetzt werden kann:
gcc $(pkg-config --cflags --libs aqbanking) -o ab_value_test ab_value_test.c
Anhand dieses Beispiel-Programms kann man gut nachvollziehen, wie mit
dem ungepatchten AqBanking aus der Zahl 361,54 intern die unsägliche
Zahl 28644149875407128613569879804477/79228162514264337593543950336
wird. Mit dem Patch wird die Zahl hingegen als einfache rationale Zahl
36154/100 gespeichert.

Zusätzlich behebt der Patch einen kleinen, sehr selten auftretendes
Speicherleck: free(tmpString) wurde unter bestimmten Umständen nicht
aufgerufen.

Zu guter letzt habe ich die ganzen API-Funktionen, die irgendwie mit
Gleitkommazahlen agieren als veraltet (deprecated) markiert, damit man
beim Übersetzen mal sieht, wo überall dieser Mist verwendet wird und
damit man evtl. irgendwann mal gänzlich auf die Gleitkommazahlen
verzichten kann. Auch die Funktionen für Multiplikation und Division von
AB_VALUEs habe ich als veraltet markiert, da sie zum einen nicht
wirklich sinnvoll sind (Was ist denn 10€ * 10€? Korrekt wäre 100€²...)
und zum anderen nach meinen Recherchen nicht benutzt werden.

Ich würde mich freuen, wenn der Patch ins SVN kommt.

Schöne Grüße
  Micha
Index: src/libs/aqbanking/types/value.c
===================================================================
--- src/libs/aqbanking/types/value.c	(Revision 1719)
+++ src/libs/aqbanking/types/value.c	(Arbeitskopie)
@@ -90,7 +90,7 @@
 AB_VALUE *AB_Value_fromString(const char *s) {
   AB_VALUE *v;
   const char *currency=NULL;
-  int rv;
+  int conversion_succeeded = 1;	// assume conversion will succeed
   char *tmpString=NULL;
   char *p;
   char *t;
@@ -128,26 +128,29 @@
   if (t)
     *t='.';
 
-  if (strchr(p, '.')) {
-    mpf_t v1;
+  if (t=strchr(p, '.')) {
+    // remove comma and calculate denominator
+    unsigned long denominator = 1;
+    char *next;
+    do {
+      next=t+1;
+      *t=*next;
+      if (*next != 0)
+        denominator *= 10;
+      t++;
+    } while (*next);
 
-    mpf_init(v1);
-    if (mpf_set_str(v1, p, 10)) {
-      DBG_ERROR(AQBANKING_LOGDOMAIN, "[%s] is not a valid value", s);
-      AB_Value_free(v);
-#ifdef HAVE_SETLOCALE
-      setlocale(LC_NUMERIC, currentLocale);
-      free(currentLocale);
-#endif
-      return NULL;
+    // set denominator to the calculated value
+    mpz_set_ui(mpq_denref(v->value), denominator);
+
+    // set numerator to the resulting integer string without comma
+    if (mpz_set_str(mpq_numref(v->value), p, 10) == -1) {
+      conversion_succeeded = 0;
     }
-    mpq_set_f(v->value, v1);
-    mpf_clear(v1);
-    rv=1;
   }
   else {
     /*DBG_ERROR(0, "Scanning this value: %s\n", p);*/
-    rv=gmp_sscanf(p, "%Qu", v->value);
+    conversion_succeeded = (gmp_sscanf(p, "%Qu", v->value) == 1);
   }
 
 #ifdef HAVE_SETLOCALE
@@ -162,15 +165,12 @@
   /* temporary string no longer needed */
   free(tmpString);
 
-  if (rv!=1) {
+  if (!conversion_succeeded) {
     DBG_ERROR(AQBANKING_LOGDOMAIN, "[%s] is not a valid value", s);
     AB_Value_free(v);
     return NULL;
   }
 
-  /* canonicalize */
-  mpq_canonicalize(v->value);
-
   if (isNeg)
     mpq_neg(v->value, v->value);
 
Index: src/libs/aqbanking/types/value.h
===================================================================
--- src/libs/aqbanking/types/value.h	(Revision 1719)
+++ src/libs/aqbanking/types/value.h	(Arbeitskopie)
@@ -65,7 +65,7 @@
 						   int prec,
 						   int withCurrency);
 
-AQBANKING_API AB_VALUE *AB_Value_fromDouble(double i);
+AQBANKING_API AQBANKING_DEPRECATED AB_VALUE *AB_Value_fromDouble(double i);
 
 
 /** Create a value from the given GWEN_DB. */
@@ -75,14 +75,14 @@
 AQBANKING_API int AB_Value_toDb(const AB_VALUE *v, GWEN_DB_NODE *db);
 
 /** Write the given value into the given GWEN_DB (uses float instead of rational). */
-AQBANKING_API int AB_Value_toDbFloat(const AB_VALUE *v, GWEN_DB_NODE *db);
+AQBANKING_API AQBANKING_DEPRECATED int AB_Value_toDbFloat(const AB_VALUE *v, GWEN_DB_NODE *db);
 
 /**
  * This function returns the value as a double.
  * You should not feed another AB_VALUE from this double, because the
  * conversion from an AB_VALUE to a double might be lossy!
  */
-AQBANKING_API double AB_Value_GetValueAsDouble(const AB_VALUE *v);
+AQBANKING_API AQBANKING_DEPRECATED double AB_Value_GetValueAsDouble(const AB_VALUE *v);
 
 
 /**
@@ -91,7 +91,7 @@
  * the conversion from AB_VALUE to double to AB_VALUE might change the
  * real value.
  */
-AQBANKING_API void AB_Value_SetValueFromDouble(AB_VALUE *v, double i);
+AQBANKING_API AQBANKING_DEPRECATED void AB_Value_SetValueFromDouble(AB_VALUE *v, double i);
 
 /**
  * Write the value (without the currency) in nominator/denominator
@@ -111,8 +111,8 @@
 
 AQBANKING_API int AB_Value_AddValue(AB_VALUE *v1, const AB_VALUE *v2);
 AQBANKING_API int AB_Value_SubValue(AB_VALUE *v1, const AB_VALUE *v2);
-AQBANKING_API int AB_Value_MultValue(AB_VALUE *v1, const AB_VALUE *v2);
-AQBANKING_API int AB_Value_DivValue(AB_VALUE *v1, const AB_VALUE *v2);
+AQBANKING_API AQBANKING_DEPRECATED int AB_Value_MultValue(AB_VALUE *v1, const AB_VALUE *v2);
+AQBANKING_API AQBANKING_DEPRECATED int AB_Value_DivValue(AB_VALUE *v1, const AB_VALUE *v2);
 
 AQBANKING_API int AB_Value_Negate(AB_VALUE *v);
 
#include <gwenhywfar/buffer.h>
#include <aqbanking/banking.h>

char *input = "361,54";

int
main(int argc, char *argv[])
{
	AB_VALUE *value;
	GWEN_BUFFER *buf;
	
	if (argc > 1) input = argv[1];
	value = AB_Value_fromString(input);
	buf = GWEN_Buffer_new(NULL, 300, 0, 0);
	AB_Value_toString(value, buf);
	printf("AqBanking stores %s internally as rational number %s\n",
		input, GWEN_Buffer_GetStart(buf));
	GWEN_Buffer_free(buf);
	AB_Value_free(value);
}

Attachment: signature.asc
Description: Digital signature

------------------------------------------------------------------------------
Enter the BlackBerry Developer Challenge  
This is your chance to win up to $100,000 in prizes! For a limited time, 
vendors submitting new applications to BlackBerry App World(TM) will have
the opportunity to enter the BlackBerry Developer Challenge. See full prize  
details at: http://p.sf.net/sfu/Challenge
_______________________________________________
Aqbanking-devel mailing list
Aqbanking-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/aqbanking-devel

Reply via email to