Newbie Q: Fraction to double

M

mdfoster44

Hi,

I want to read input data for crystal structures.

Often atom positions are given as fractions,
for example one line might read

"Si 1/3 0.250000 1/6"

I would like to store these fractions and print them later with eight
decimals places.

I want to be able to print them as
"Si 0.33333333 0.25000000 0.16666667"

Could someone please advise me how to do this?

I guess it's a very simple question, but I have been scanning the docs
and I've not found a suitable answer yet, except trying not to
"sscanf". Which I have been!

Here's my code:

#include <string.h>
#include <stdio.h>

typedef struct vector {
double x,y,z;
} vector;

int main(void)
{
char line[400];
char label[5];
FILE *input;
vector atom;

if( !(input=fopen("fraction.txt","r")) ){
printf("Cannot open file!\n");
}
while(fgets(line,399,input) != NULL) {
sscanf(line, "%s %lf %lf %lf", &label, &atom.x, &atom.y, &atom.z);
printf("%s %.8lf %.8lf %.8lf\n", label, atom.x, atom.y, atom.z);
}
}

fraction.txt:
Si 1/3 0.250000 1/6

output:
Si 1.00000000 0.00000000 0.00000000


cheers,
Martin
 
K

Ken Human

Hi,

I want to read input data for crystal structures.

Often atom positions are given as fractions,
for example one line might read

"Si 1/3 0.250000 1/6"

I would like to store these fractions and print them later with eight
decimals places.

I want to be able to print them as
"Si 0.33333333 0.25000000 0.16666667"

Could someone please advise me how to do this?

I guess it's a very simple question, but I have been scanning the docs
and I've not found a suitable answer yet, except trying not to
"sscanf". Which I have been!

Here's my code:

#include <string.h>
#include <stdio.h>

add:

typedef struct fraction_ {
int a, b;
} fraction
typedef struct vector {
double x,y,z;
} vector;

int main(void)
{
char line[400];
char label[5];
FILE *input;
vector atom;
add:
fraction a, b;
if( !(input=fopen("fraction.txt","r")) ){
printf("Cannot open file!\n");
}
while(fgets(line,399,input) != NULL) {
sscanf(line, "%s %lf %lf %lf", &label, &atom.x, &atom.y, &atom.z);
change above line to:
sscanf(line, "%s %i/%i %lf %i/%i", &label, &a.a, &a.b, &atom.y, &b.a,
&b.b);
add:
atom.x = double(a.a) / double(a.b);
atom.z = double(b.a) / double(b.b);
printf("%s %.8lf %.8lf %.8lf\n", label, atom.x, atom.y, atom.z);
}
}

fraction.txt:
Si 1/3 0.250000 1/6

output:
Si 1.00000000 0.00000000 0.00000000


cheers,
Martin

HTH
 
M

mdfoster44

Ken said:
Here's my code:

#include <string.h>
#include <stdio.h>

add:

typedef struct fraction_ {
int a, b;
} fraction
typedef struct vector {
double x,y,z;
} vector;

int main(void)
{
char line[400];
char label[5];
FILE *input;
vector atom;
add:
fraction a, b;
if( !(input=fopen("fraction.txt","r")) ){
printf("Cannot open file!\n");
}
while(fgets(line,399,input) != NULL) {
sscanf(line, "%s %lf %lf %lf", &label, &atom.x, &atom.y,
&atom.z);
change above line to:
sscanf(line, "%s %i/%i %lf %i/%i", &label, &a.a, &a.b, &atom.y, &b.a,
&b.b);

Yes, but I'm going to be scanning more than one line and I don't know
if
the next row will contain fractions. For example,

Si 1/3 0.250000 1/6
Si 0.123456 0.500000 0.654321

On the next line, I won't be able to use your sscanf command.
Maybe I should scan in the line as strings and look for "/" in the
columns.(?)

add:
atom.x = double(a.a) / double(a.b);
atom.z = double(b.a) / double(b.b);

HTH
It does, but I didn't mention the extent of the problem.

Thanks,
Martin.
 
K

Ken Human

[...]
Yes, but I'm going to be scanning more than one line and I don't know
if
the next row will contain fractions. For example,

Si 1/3 0.250000 1/6
Si 0.123456 0.500000 0.654321

On the next line, I won't be able to use your sscanf command.
Maybe I should scan in the line as strings and look for "/" in the
columns.(?)

How about this:

int main(void)
{
char line[400];
char label[5];
char buf1[32], buf2[32];
char *ptr;
FILE *input;
vector atom;
fraction a, b;

if( !(input=fopen("fraction.txt","r")) ){
printf("Cannot open file!\n");
}
while(fgets(line,399,input) != NULL) {
sscanf(line, "%s %s %lf %s", &label, &buf1, &atom.y, &buf2);
if(ptr = strchr(buf1, '/')) {
*ptr = '\0';
ptr++;
atom.x = (double)atoi(buf1) / (double)atoi(ptr);
} else atom.x = atof(buf1);
if(ptr = strchr(buf2, '/')) {
*ptr = '\0';
ptr++;
atom.z = (double)atoi(buf2) / (double)atoi(ptr);
} else atom.z = atof(buf2);

printf("%s %.8lf %.8lf %.8lf\n", label, atom.x, atom.y, atom.z);
}
return 0;
}
 
M

mdfoster44

Ken said:
[...]

How about this:

int main(void)
{
char line[400];
char label[5];
char buf1[32], buf2[32];
char *ptr;
FILE *input;
vector atom;
fraction a, b;

if( !(input=fopen("fraction.txt","r")) ){
printf("Cannot open file!\n");
}
while(fgets(line,399,input) != NULL) {
sscanf(line, "%s %s %lf %s", &label, &buf1, &atom.y, &buf2);
if(ptr = strchr(buf1, '/')) {
*ptr = '\0';
ptr++;
atom.x = (double)atoi(buf1) / (double)atoi(ptr);
} else atom.x = atof(buf1);
if(ptr = strchr(buf2, '/')) {
*ptr = '\0';
ptr++;
atom.z = (double)atoi(buf2) / (double)atoi(ptr);
} else atom.z = atof(buf2);

printf("%s %.8lf %.8lf %.8lf\n", label, atom.x, atom.y, atom.z);
}
return 0;
}
Nice! This helps a lot!
Thanks,
Martin.
 
C

Chris Torek

Yes.

How about this:

int main(void)
{
char line[400];
char label[5];
char buf1[32], buf2[32];
char *ptr;
FILE *input;
vector atom;
fraction a, b;

if( !(input=fopen("fraction.txt","r")) ){
printf("Cannot open file!\n");

You need some more code here, because if input is NULL, it is
unwise to go on to pass it to fgets().
}
while(fgets(line,399,input) != NULL) {
sscanf(line, "%s %s %lf %s", &label, &buf1, &atom.y, &buf2);

The magic constant in the fgets() call (399) is not so great, and
is one smaller than allowed. Neither of these is an error.

The argument to sscanf(), however, are wrong. They probably *work*
but they are wrong in two important ways:

- The %s directives have no maximum field width, so they can scan
as many characters as there are in the input line (i.e., up to
398). The label, buf1, and buf2 ararys have size 5, 32, and 32
respectively, not 398+1 (unlike fgets(), the field width in a
scanf directive is the number of characters to read, to which
one must add 1 to account for the terminating '\0').

- The %s directives need a value of type "char *", pointing to
the first of the however-many characters there are in the
buffer. The call passes values of type "char (*)[5]",
"char (*)[32]", and "char (*)[32]" respectively. Either
write &label[0], &buf1[0], etc., or just write label, buf1,
etc., using The Rule about pointers and arrays in C
(see <http://web.torek.net/torek/c/pa.html>).

Finally, ignoring the result of sscanf() is almost always a serious
mistake. In this case, if the input line is malformed, the contents
of the various variables may be garbage.

Writing correct yet self-adjusting sscanf() specifications here is
fairly painful, so you could resort to:

result = sscanf(line, "%4s%31s%lf%31s", label, buf1, &atom.y, buf2);
/* and make sure that result==4, doing something sensible if not */
if(ptr = strchr(buf1, '/')) {
*ptr = '\0';
ptr++;
atom.x = (double)atoi(buf1) / (double)atoi(ptr);
} else atom.x = atof(buf1);

If you want to be belt-and-suspenders-y about this, use strtol()
and strtod() instead of atoi() and atof(), and check that the things
separated by the slash are in fact numbers. (Or: write a real
parser for the input lines, replacing the entire sscanf and company,
and perhaps even the fgets(). This input file format is just on
the edge of the point where a Serious Parser might be wise.)
 
C

CBFalconer

I want to read input data for crystal structures.

Often atom positions are given as fractions,
for example one line might read

"Si 1/3 0.250000 1/6"

I would like to store these fractions and print them later with eight
decimals places.

I want to be able to print them as
"Si 0.33333333 0.25000000 0.16666667"

Could someone please advise me how to do this?

You will have to input a line and then parse the content. You
should write a routine that will parse one value from a string, and
return a pointer to the terminating character. If that character
is a '/' you have a fraction, and should be able to parse the
following integer. If it is a '.' you have a real, and should be
able to back up and parse the whole thing. Once you decide between
reals and fractions you should check for suitable terminating
characters for the whole value.

It's not too hard, just take it one step at a time. Make sure you
signal error for anything that doesn't fit. Or you can hire me to
do it for you.
 
K

Ken Human

Lawrence said:
On Fri, 06 May 2005 17:05:00 -0500, Ken Human wrote:

...




%i and %d do different things in scanf() formats. Unless you specifically
want numbers with leading 0's to be treated as octal (and 0x or 0X treated
as hexadecimal but that's less important), then stick to using %d.

Thanks for the correction, I wasn't aware of the difference before.
As noted in other responses &label should be just label.




This is not valid C syntax, a cast is of the form:

atom.x = (double)a.a / (double)a.b;
atom.z = (double)b.a / (double)b.b;

You only need one of the casts on each line but there's no harm in having
both.

Again, thanks. I was compiling in C++ mode, my mistake.
 
L

Lawrence Kirby

On Fri, 06 May 2005 17:05:00 -0500, Ken Human wrote:

....
change above line to:
sscanf(line, "%s %i/%i %lf %i/%i", &label, &a.a, &a.b, &atom.y, &b.a,
&b.b);

%i and %d do different things in scanf() formats. Unless you specifically
want numbers with leading 0's to be treated as octal (and 0x or 0X treated
as hexadecimal but that's less important), then stick to using %d.

As noted in other responses &label should be just label.
add:
atom.x = double(a.a) / double(a.b);
atom.z = double(b.a) / double(b.b);

This is not valid C syntax, a cast is of the form:

atom.x = (double)a.a / (double)a.b;
atom.z = (double)b.a / (double)b.b;

You only need one of the casts on each line but there's no harm in having
both.

The normal printf() format specifier for double is %f - remember you can't
pass a value as a float in a variable argument list, it will get promoted
automaticaly to double. C99 does also support %lf but C90 doesn't, so
stick with %f because it works with all recognised definitions of C. Yes,
this is inconsistent with scanf().

Lawrence
 
M

mdfoster44

Chris said:
Yes.

How about this:

int main(void)
{
char line[400];
char label[5];
char buf1[32], buf2[32];
char *ptr;
FILE *input;
vector atom;
fraction a, b;

if( !(input=fopen("fraction.txt","r")) ){
printf("Cannot open file!\n");

You need some more code here, because if input is NULL, it is
unwise to go on to pass it to fgets().

I use this quite a bit. Why is this unwise?

I need to find out a bit more about "Serious" parsers.
Do you have any good pointers to free resources on the web about these?
}
while(fgets(line,399,input) != NULL) {
sscanf(line, "%s %s %lf %s", &label, &buf1, &atom.y,
&buf2);

The magic constant in the fgets() call (399) is not so great, and
is one smaller than allowed. Neither of these is an error.

The argument to sscanf(), however, are wrong. They probably *work*
but they are wrong in two important ways:

- The %s directives have no maximum field width, so they can scan
as many characters as there are in the input line (i.e., up to
398). The label, buf1, and buf2 ararys have size 5, 32, and 32
respectively, not 398+1 (unlike fgets(), the field width in a
scanf directive is the number of characters to read, to which
one must add 1 to account for the terminating '\0').

- The %s directives need a value of type "char *", pointing to
the first of the however-many characters there are in the
buffer. The call passes values of type "char (*)[5]",
"char (*)[32]", and "char (*)[32]" respectively. Either
write &label[0], &buf1[0], etc., or just write label, buf1,
etc., using The Rule about pointers and arrays in C
(see <http://web.torek.net/torek/c/pa.html>).

Finally, ignoring the result of sscanf() is almost always a serious
mistake. In this case, if the input line is malformed, the contents
of the various variables may be garbage.

Writing correct yet self-adjusting sscanf() specifications here is
fairly painful, so you could resort to:

result = sscanf(line, "%4s%31s%lf%31s", label, buf1, &atom.y, buf2);
/* and make sure that result==4, doing something sensible if not */
if(ptr = strchr(buf1, '/')) {
*ptr = '\0';
ptr++;
atom.x = (double)atoi(buf1) / (double)atoi(ptr);
} else atom.x = atof(buf1);

If you want to be belt-and-suspenders-y about this, use strtol()
and strtod() instead of atoi() and atof(), and check that the things
separated by the slash are in fact numbers. (Or: write a real
parser for the input lines, replacing the entire sscanf and company,
and perhaps even the fgets(). This input file format is just on
the edge of the point where a Serious Parser might be wise.)

I've incorporated the changes you suggest above.
Thank you very much for your help.

Martin
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to
spammers.
 
C

Chris Torek

if( !(input=fopen("fraction.txt","r")) ){
I use this quite a bit. Why is this unwise?

What is the effect of the executable line of code below?

char *p;
char buf[SOME_SIZE];

p = fgets(buf, sizeof buf, NULL);

(On most of my systems, the effect is "segmentation fault - core
dumped" or similar. This is because those systems have some memory
protection. On systems without memory protection it may behave
even worse.)
I need to find out a bit more about "Serious" parsers.
Do you have any good pointers to free resources on the web about these?

Try google searches for lexing, parsing, "regular expressions",
and so on. But this is a large topic, and you might be better
served by taking a compiler-construction course, or at least working
through the "Dragon Book" ("Compilers: Principles, Techniques, and
Tools", by Aho, Sethi, and Ullman; ISBN 0201100886; see
<http://www.amazon.com/exec/obidos/tg/detail/-/0201100886/104-4230346-9605538?v=glance>).
(It is possible to use compiler-construction tools like lex and yacc
without understanding the theory of finite state automata, but it
is a lot easier *with* that understanding.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top