vendredi 27 mars 2015

sprintf() precision .16 bug



This is my first thread/question here, so hello all :-)


I have to do a project for university so I chose to code a calculator/equation parser.


Whole Program (without (syntax-)error handling): click here


I have encountered a bug that occurs only when I set the precision in a sprintf()-call to 16. Everything works fine with any precision above or below and also 16 works fine in debug mode, too.


Expressions that cause my program to crash:


12/(-9)+3 , but 12/(-9) works fine


(1)+(2)+(3) and similar things




So here's what the code does:


-it looks for the "upper" parantheses pair in the user entered string


-> j: position of the first char inside the parantheses


-> i: position of the first char after the parantheses


-calculate the inside of the parantheses recursively with the same function until there is no more pair of parantheses


-another function is called to cast this calculation into double


-return value of function is recursive call of concatenation of string[0 to j-2]##result_str[j to i-2]##string[i to end]




Here's the code (important part I described above is all the way down at the end):



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#define LENGTH(x) (sizeof(x) / sizeof(x[0]))

int hasPar (char *); //Überprüft, ob Klammern im String sind und gibt Position der ersten Klammer zurück
char* subStr (char* , char* , int , int ); //Funktion, die Substring zurückgibt
double calcStr(char *); //Wertet Terme ohne Klammern aus
double calc (char *); //Rekursive Berechnung des Terms


int main(/*int argc, char *argv[]*/) {
char *s/*, c*/;
double ergebnis;

do {
puts("Taschenrechner. Ignoriert alles, au\xe1""er 0-9,.,+,-,*,/,^,(,)");

fflush(stdin);
s= calloc(100,1);
scanf("%99[^\n]", s); //[^\n] bedeutet, dass alle Zeichen außer Zeilenumbruch eingelesen werden sollen

printf("Erkannter Ausdruck:\n%s\n", s);

ergebnis= calc(s);
printf("Berechnetes Ergebnis:\n%f\n", ergebnis);


puts("Erfolg!");;
free(s);
fflush(stdin);
//if (getc(stdin) == 'c') {break;}
} while (0);

return 0;
}




int hasPar (char *s) {
for (unsigned int k=0; k<strlen(s); k++) { //k verwendet, da mit i Interferenzen mit calc() aufgetreten sind
if ( s[k] == '(' ) {
return k;
}
}

return (-1);
}



char* subStr (char* dest, char* src, int offset, int len) {
int input_len = strlen (src);

if ( offset+len > input_len ) { //Wenn Substring größer sein sollte als Usprungsstring oder Substring Null Zeichen enthalten soll
return NULL;
} else if (len <= 0) {
dest[0]= '\0';
}

strncpy(dest, src + offset, len); //len Zeichen werden aus s ab offset in t kopiert
dest[len]= '\0';
return dest;
}



double calcStr (char *s) {
char *t, *t_first;
int len_s= strlen(s), len_first=0;
t= calloc(len_s, sizeof(char)); //Kopie von s zum Arbeiten erstellen, Schritt 1
strcpy(t,s); //Kopie von s zum Arbeiten erstellen, Schritt 2

//ACHTUNG: REIHENFOLGE WICHTIG FÜR KORREKTE ANWENDUNG VON RECHENREGELN

if(t[0] != '+') { //+ als unärer Operator
t_first= strtok(t,"+"); //String auf + prüfen, Nach Ausführung von strtok: t_first: String bis exklusiv +
len_first=strlen(t_first); //Länge des Ergebnisstrings berechnen zum Vergleich mit Länge des Ursprungsstrings
if (len_first != len_s) { //Wenn Länge gleich, dann ist hier auch der Inhalt gleich, also kein Plus enthalten
switch ( t_first[len_first-1] ) { //Wenn + unär, also vor dem + ein anderes OpSym
case '+':
t[len_first-1]= '\0';
return calcStr(t) + (calcStr(s+len_first));
case '-':
t[len_first-1]= '\0';
return calcStr(t) - (calcStr(s+len_first));
case '*':
t[len_first-1]= '\0';
return calcStr(t) * (calcStr(s+len_first));
case '/':
t[len_first-1]= '\0';
return calcStr(t) / (calcStr(s+len_first));
case '^':
t[len_first-1]= '\0';
return pow(calcStr(t), (calcStr(s+len_first)));
default:
return calcStr(t_first) + (calcStr(s+len_first+1)); //Rekursives Aufrufen der Strings links und rechts des Operationszeichens
}
}
}

strcpy(t,s); //da t bei Überprüfung auf + verändert wurde, Wiederherstellung der Arbeitskopie aus Ursprungsstring
if(t[0] != '-') { //- als unärer Operator
t_first= strtok(t,"-"); //analog oben
len_first=strlen(t_first);
if (len_first != len_s) {
switch ( t_first[len_first-1] ) {
case '+':
t[len_first-1]= '\0';
return calcStr(t) + (calcStr(s+len_first));
case '-':
t[len_first-1]= '\0';
return calcStr(t) - (calcStr(s+len_first));
case '*':
t[len_first-1]= '\0';
return calcStr(t) * (calcStr(s+len_first));
case '/':
t[len_first-1]= '\0';
return calcStr(t) / (calcStr(s+len_first));
case '^':
t[len_first-1]= '\0';
return pow(calcStr(t), (calcStr(s+len_first)));
default:
return calcStr(t_first) + (calcStr(s+len_first+1)); //Rekursives Aufrufen der Strings links und rechts des Operationszeichens
}
}
}

strcpy(t,s);
t_first= strtok(t,"*");
len_first=strlen(t_first);
if (len_first != len_s) {
return calcStr(t_first) * (calcStr(s+len_first+1));
}

strcpy(t,s); //analog
t_first= strtok(t,"/");
len_first=strlen(t_first);
if (len_first != len_s) {
return calcStr(t_first) * (1 / calcStr(s+len_first+1));
}

strcpy(t,s);
t_first= strtok(t,"^");
len_first=strlen(t_first);
if (len_first != len_s) {
if (t_first[len_first-1]=='e') {
if (t_first[0]=='-') {
return (-1)*exp(calcStr(s+len_first+1));
} else if (t_first[0]=='+') {
return exp(calcStr(s+len_first+1));
} else {
return pow(calcStr(t_first), (calcStr(s+len_first+1)));
}
}
}

return atof(s); //String ist bei keinem Operationszeichen zerfallen => String ist Zahl ; atof castet string zu double (aus stdlib.h)

}


double calc (char *s) {
double result_d=0.;
char *t, *result_str;
int check=1, i=hasPar(s), j=0;
if ( i == (-1) ) {
return calcStr(s);
} else {
j= ++i; //j=++i ist Position des Chars nach der ersten öffnenden Klammer
while (check > 0) {
if (s[i] == '(') {
check++;
} else if (s[i] == ')') {
check--;
}
i++;
} //Bestimmen der Länge der "obersten" Klammer, i ist Position des ersten Zeichens nach der Klammer

t= calloc (strlen(s), sizeof(char)); //string to store substring in
result_str= calloc (strlen(s), sizeof(char)); //string to store result of parantheses-calculation in
result_d= calc (subStr (result_str, s, j, i-j-1)); //call the function we're already in
sprintf (result_str, "%-.16f", result_d); //cast result back to string, this is where I think the crash is caused
return calc (strcat (subStr (t, s, 0, j-1), strcat (result_str, s+i))); //recursive call of concatenated string described as above
}
}


-subStr(char *dest, char *src, int offset, int len) is just a function that uses strncpy() with error handling calc() is the function we're in


-s is the *char given by the user


-result_d is calculated result of substring inside the parantheses (double-variable)


-result_str is the *char where the typecast of result_d is stored




I hope I didn't forget anything. If more code or info is needed just comment. I can also supply the *.exe to try out.


And remember: Everything works fine with sprintf (result_str, "%.17f", result_d); So I guess it can't be array out of bound (I think)


Thanks to all of you in advance!


P.S.: if anyone has an idea of how to avoid the re-casting of a double to string, please say so.




Aucun commentaire:

Enregistrer un commentaire