Saving a string as an integer in a struct. C - c

I am trying to read strings from a file and convert them to integers for storage in a struct. The strtol() function works well but removes any 0s from the start of the tokens. Is there any way I can keep them? The input file is formatted like the example below.
003345.755653
000046.003265
073532.003434
122280.065431
Input file ^^^
struct store{
int *age;
int *ref;
}rec[20];
char *token;
char*ptr;
while (!feof (filea)){
fgets(buffer, sizeof buffer, filea);
token = strtok(buffer, ".");
rec[count].age = malloc(10);
rec[count].age = strtol(token, &ptr, 10);
printf("Age: %i\n", rec[count].age);
token = strtok(NULL, ".");
rec[count].ref = malloc(10);
rec[count].ref = strtol(token, &ptr, 10);
printf("Ref: %i\n\n", rec[count].ref);
count++;
}

Once your string has been converted to an int or any other numeric type, all its leading zeros are gone, because they do not change the value of an integer.
You can add back leading zeros to get your numbers all have the same number of digits, but matching the exact number of leading zeros from a file would require additional storage.
Here is how you can format all your integers to two digits, with zero padding if necessary:
printf("Age: %02i\n", rec[count].age);
Note: Your program has multiple errors. You need to fix them before it starts working properly.
You declare age and ref as pointers, but you use them like scalar variables
You allocate memory to age and ref using malloc, and then you override it with a numeric value
You ignore the new value of ptr after the read. You should use it to see if anything has been read from the file.
The compiler must have issued multiple warnings related to the issues described above. It is a good idea to treat all compiler warnings as errors, because it helps you find simple problems like these.

No, you can't keep the zeros! The computer needs them - those are not growing on trees! No seriously: If you want to "keep" them long you'll just have to append the 0s in front of the printed of file-written strings.
You can use printf("%013.6f\n", a); where %0 means "append zeros" on a field of 13 units where 6 of them are after the decimal point.
#include <stdio.h>
int main()
{
float a = 3345.755653;
float b = 46.003265;
float c = 73532.003434;
float d = 122280.065431;
printf("%013.6f\n", a);
printf("%013.6f\n", b);
printf("%013.6f\n", c);
printf("%013.6f\n", d);
return 0;
}

Related

C - Print ASCII Value for Each Character in a String

I'm new to C and I'm trying to write a program that prints the ASCII value for every letter in a name that the user enters. I attempted to store the letters in an array and try to print each ASCII value and letter of the name separately but, for some reason, it only prints the value of the first letter.
For example, if I write "Anna" it just prints 65 and not the values for the other letters in the name. I think it has something to do with my sizeof(name)/sizeof(char) part of the for loop, because when I print it separately, it only prints out 1.
I can't figure out how to fix it:
#include <stdio.h>
int main(){
int e;
char name[] = "";
printf("Enter a name : \n");
scanf("%c",&name);
for(int i = 0; i < (sizeof(name)/sizeof(char)); i++){
e = name[i];
printf("The ASCII value of the letter %c is : %d \n",name[i],e);
}
int n = (sizeof(name)/sizeof(char));
printf("%d", n);
}
Here's a corrected, annotated version:
#include <stdio.h>
#include <string.h>
int main() {
int e;
char name[100] = ""; // Allow for up to 100 characters
printf("Enter a name : \n");
// scanf("%c", &name); // %c reads a single character
scanf("%99s", name); // Use %s to read a string! %99s to limit input size!
// for (int i = 0; i < (sizeof(name) / sizeof(char)); i++) { // sizeof(name) / sizeof(char) is a fixed value!
size_t len = strlen(name); // Use this library function to get string length
for (size_t i = 0; i < len; i++) { // Saves calculating each time!
e = name[i];
printf("The ASCII value of the letter %c is : %d \n", name[i], e);
}
printf("\n Name length = %zu\n", strlen(name)); // Given length!
int n = (sizeof(name) / sizeof(char)); // As noted above, this will be ...
printf("%d", n); // ... a fixed value (100, as it stands).
return 0; // ALWAYS return an integer from main!
}
But also read the comments given in your question!
This is a rather long answer, feel free to skip to the end for the code example.
First of all, by initialising a char array with unspecified length, you are making that array have length 1 (it only contains the empty string). The key issue here is that arrays in C are fixed size, so name will not grow larger.
Second, the format specifier %c causes scanf to only ever read one byte. This means that even if you had made a larger array, you would only be reading one byte to it anyway.
The parameter you're giving to scanf is erroneous, but accidentally works - you're passing a pointer to an array when it expects a pointer to char. It works because the pointer to the array points at the first element of the array. Luckily this is an easy fix, an array of a type can be passed to a function expecting a pointer to that type - it is said to "decay" to a pointer. So you could just pass name instead.
As a result of these two actions, you now have a situation where name is of length 1, and you have read exactly one byte into it. The next issue is sizeof(name)/sizeof(char) - this will always equal 1 in your program. sizeof char is defined to always equal 1, so using it as a divisor causes no effect, and we already know sizeof name is equal to 1. This means your for loop will only ever read one byte from the array. For the exact same reason n is equal to 1. This is not erroneous per se, it's just probably not what you expected.
The solution to this can be done in a couple of ways, but I'll show one. First of all, you don't want to initialize name as you do, because it always creates an array of size 1. Instead you want to manually specify a larger size for the array, for instance 100 bytes (of which the last one will be dedicated to the terminating null byte).
char name[100];
/* You might want to zero out the array too by eg. using memset. It's not
necessary in this case, but arrays are allowed to contain anything unless
and until you replace their contents.
Parameters are target, byte to fill it with, and amount of bytes to fill */
memset(name, 0, sizeof(name));
Second, you don't necessarily want to use scanf at all if you're reading just a byte string from standard input instead of a more complex formatted string. You could eg. use fgets to read an entire line from standard input, though that also includes the newline character, which we'll have to strip.
/* The parameters are target to write to, bytes to write, and file to read from.
fgets writes a null terminator automatically after the string, so we will
read at most sizeof(name) - 1 bytes.
*/
fgets(name, sizeof(name), stdin);
Now you've read the name to memory. But the size of name the array hasn't changed, so if you used the rest of the code as is you would get a lot of messages saying The ASCII value of the letter is : 0. To get the meaningful length of the string, we'll use strlen.
NOTE: strlen is generally unsafe to use on arbitrary strings that might not be properly null-terminated as it will keep reading until it finds a zero byte, but we only get a portable bounds-checked version strnlen_s in C11. In this case we also know that the string is null-terminated, because fgets deals with that.
/* size_t is a large, unsigned integer type big enough to contain the
theoretical maximum size of an object, so size functions often return
size_t.
strlen counts the amount of bytes before the first null (0) byte */
size_t n = strlen(name);
Now that we have the length of the string, we can check if the last byte is the newline character, and remove it if so.
/* Assuming every line ends with a newline, we can simply zero out the last
byte if it's '\n' */
if (name[n - 1] == '\n') {
name[n - 1] = '\0';
/* The string is now 1 byte shorter, because we removed the newline.
We don't need to calculate strlen again, we can just do it manually. */
--n;
}
The loop looks quite similar, as it was mostly fine to begin with. Mostly, we want to avoid issues that can arise from comparing a signed int and an unsigned size_t, so we'll also make i be type size_t.
for (size_t i = 0; i < n; i++) {
int e = name[i];
printf("The ASCII value of the letter %c is : %d \n", name[i], e);
}
Putting it all together, we get
#include <stdio.h>
#include <string.h>
int main() {
char name[100];
memset(name, 0, sizeof(name));
printf("Enter a name : \n");
fgets(name, sizeof(name), stdin);
size_t n = strlen(name);
if (n > 0 && name[n - 1] == '\n') {
name[n - 1] = '\0';
--n;
}
for (size_t i = 0; i < n; i++){
int e = name[i];
printf("The ASCII value of the letter %c is : %d \n", name[i], e);
}
/* To correctly print a size_t, use %zu */
printf("%zu\n", n);
/* In C99 main implicitly returns 0 if you don't add a return value
yourself, but it's a good habit to remember to return from functions. */
return 0;
}
Which should work pretty much as expected.
Additional notes:
This code should be valid C99, but I believe it's not valid C89. If you need to write to the older standard, there are several things you need to do differently. Fortunately, your compiler should warn you about those issues if you tell it which standard you want to use. C99 is probably the default these days, but older code still exists.
It's a bit inflexible to be reading strings into fixed-size buffers like this, so in a real situation you might want to have a way of dynamically increasing the size of the buffer as necessary. This will probably require you to use C's manual memory management functionality like malloc and realloc, which aren't particularly difficult but take greater care to avoid issues like memory leaks.
It's not guaranteed the strings you're reading are in any specific encoding, and C strings aren't really ideal for handling text that isn't encoded in a single-byte encoding. There is support for "wide character strings" but probably more often you'll be handling char strings containing UTF-8 where a single codepoint might be multiple bytes, and might not even represent an individual letter as such. In a more general-purpose program, you should keep this in mind.
If we need write a code to get ASCII values of all elements in a string, then we need to use "%d" instead of "%c". By doing this %d takes the corresponding ascii value of the following character.
If we need to only print the ascii value of each character in the string. Then this code will work:
#include <stdio.h>
char str[100];
int x;
int main(){
scanf("%s",str);
for(x=0;str[x]!='\0';x++){
printf("%d\n",str[x]);
}
}
To store all corresponding ASCII value of character in a new variable, we need to declare an integer variable and assign it to character. By this way the integer variable stores ascii value of character. The code is:
#include <stdio.h>
char str[100];
int x,ascii;
int main(){
scanf("%s",str);
for(x=0;str[x]!='\0';x++){
ascii=str[x];
printf("%d\n",ascii);
}
}
I hope this answer helped you.....😊

How to convert a string value to numerical value?

I have tried this code to separate my Str[] string into 2 string, but my problem is "I want to separate John(name) as string and 100(marks) as integer",How can I do it, any suggestion?
#include <stdio.h>
#include <string.h>
void main()
{
char Str[] = "John,100";
int i, j, xchange;
char name[50];
char marks[10];
j = 0; xchange = 0;
for(i=0; Str[i]!='\0'; i++){
if(Str[i]!=',' && xchange!=-1){
name[i] = Str[i];
}else{
xchange = -1;
}
if(xchange==-1){
marks[j++] = Str[i+1];
}
}
printf("Student name is %s\n", name);
printf("Student marks is %s", marks);
}
How to separate "John,100" into 2 strings?
There are three common approaches:
Use strtok() to split the string into individual tokens. This will modify the original string, but is quite simple to implement:
int main(void)
{
char line[] = "John,100;passed";
char *name, *score, *status;
/* Detach the initial part of the line,
up to the first comma, and set name
to point to that part. */
name = strtok(line, ",");
/* Detach the next part of the line,
up to the next comma or semicolon,
setting score to point to that part. */
score = strtok(NULL, ",;");
/* Detach the final part of the line,
setting status to point to it. */
status = strtok(NULL, "");
Note that if you change char line[] = "John,100"; then status will be NULL, but the code is otherwise safe to run.
So, in practice, if you required all three fields to exist in line, it would be sufficient to ensure the last one was not NULL:
if (!status) {
fprintf(stderr, "line[] did not have three fields!\n");
return EXIT_FAILURE;
}
Use sscanf() to convert the string. For example,
char line[] = "John,100";
char name[20];
int score;
if (sscanf(line, "%19[^,],%d", name, &score) != 2) {
fprintf(stderr, "Cannot parse line[] correctly.\n");
return EXIT_FAILURE;
}
Here, the 19 refers to the number of chars in name (one is always reserved for the end-of-string nul char, '\0'), and [^,] is a string conversion, consuming everything except a comma. %d converts an int. The return value is the number of successful conversions.
This approach does not modify the original string, and it allows you to try a number of different parsing patterns; as long as you try them the most complex one first, you can allow multiple input formats with very little added code. I do this regularly when taking 2D or 3D vectors as inputs.
The downside is that sscanf() (all functions in the scanf family) ignores overflow. For example, on 32-bit architectures, the largest int is 2147483647, but scanf functions will happily convert e.g. 9999999999 to 1410065407 (or some other value!) without returning an error. You can only assume the numerical inputs are sane and within the limits; you cannot verify.
Use helper functions to tokenise and/or parse the string.
Typically, the helper functions are something like
char *parse_string(char *source, char **to);
char *parse_long(char *source, long *to);
where source is a pointer to the next character in the string to be parsed, and to is a pointer to where the parsed value will be stored; or
char *next_string(char **source);
long next_long(char **source);
where source is a pointer to a pointer to the next character in the string to be parsed, and the return value is the value of the extracted token.
These tend to be longer than above, and if written by me, then quite paranoid about the inputs they accept. (I want my programs to complain if their input cannot be reliably parsed, rather than silently produce garbage.)
If the data is some variant of CSV (comma-separated values) read from a file, then the proper approach is a different one: instead of reading the file line by line, you read the file token by token.
The only "trick" is to remember the separator character that ended the token (you can use ungetc() for this), and use a different function to (read and ignore the rest of the tokens in the current record, and) consume the newline separator.

Convert entire array of characters into integer in C

I'm reading input from a file and trying to create a numerical value from the strings I take in.
I tried simply using the atoi but that doesn't work on characters.
Then I tried using a forloop over my array of characters but then I got error because some characters are actually integers.
Then I tried using ifstatement to check if the characters themselves are integers and just add it to my "sum" manually.
But so far all I get is errors and errors, I'm not sure where my logic is wrong.
In C an array is simply a pointer right? So to access the value at a certain index I use *arr[num] right?
This is my code
char newlineC;
char input[14];
while(fscanf(fp,"%s%c",input, &newlineC)!=EOF){
int val = 0;
int x;
for(x=0; x<14; x++){
if(isdigit(*input[x])){
val = val + input[x];
}else{
int p = atoi(input[x]);
val = val + p;
}
}
I've tried the strol function... didn't work either. I've been at this for so long I feel dumb that I am stumped on something that seems so simple. Any help is appreciated.
You are passing the wrong types all over the place.
char input[14];
this declares an char array of dimension 14. input[i] is the ith char in
the array, it has type char. It's not a pointer, you cannot dereference it,
that's why *input[x] fails. In fact the compiler should have given you an
error there, this error:
invalid type argument of unary ‘*’ (have ‘int’)
The same problem with atoi. It expects a pointer to char that points to a
string. input[x] is single char, you cannot pass to atoi. Again the
compiler should have warned you.
fscanf(fp,"%s%c",input, &newlineC)
This is very clumsy. If the input is larger than 13 characters, you will
overflow the buffer. A better way would be:
fscanf(fp, "%13s%c", input, &newline);
Or even better
int val;
fscanf(fp, "%d", &val);
Another error: if you know that input[x] is a digit, then the integer that the
digit represent is input[x] - '0'. So this should be the calculation:
val = val + input[x] - '0';
Overall I would use fgets and strtol:
while(fgets(input, sizeof input, fp))
{
long int val;
char *tmp;
val = strtol(line, &tmp, 0);
if(*tmp == 0 || *tmp == '\n')
printf("An integer was read: %ld\n", val);
else
printf("More than an integer was read: '%s'\n", line);
}
If you are only converting the chars [1..0] to an integer value, all you have to do is
int main(void) {
char input[14];
scanf("%s", input);
if (isdigit(input[0])) {
int num = atoi(input);
printf("%d\n", num);
}
else {
printf("INPUT ERROR\n");
}
}
Are you wanting to process alphabet characters as well and turn them into some integer value?
Arrays in C are based on pointers, but that's not all they are. Arrays in C is just a bunch of those data types in a line in memory. That way you can just access the pointer of the lead variable, than hop down that list in order to get the next iteration of the array.
isdigit(*input[14])
This line will cause issues. Look at what input itself is. input is the pointer to your first element in that array. input is essentially saying char* input = &array[0]; So lets say you dereference that input variable without that 14, you would get the first element. So we can say that *input = array[0]; Do you see the issue here? You basically dereferenced it twice. If you had just done insdigit(input[14]) that would work a bit better.
But onto the bigger issue here. You're taking a char array, that contains only chars, and you're trying to convert them into numbers. Remember that char and int are two different data types. Go ahead and check out this table: https://upload.wikimedia.org/wikipedia/commons/d/dd/ASCII-Table.svg
Recall that chars are basically just numbers that correspond to that ASCII value. For example your computer doesn't read a letter as a D, it reads it as 68 (or the binary format of 68). For numbers it's the same concept, even if it seems like it's just a number and you should be able to add it to val, you'd first have to subtract 48 or use the atoi function on digits.
So what can you do here? I can't say for sure without knowing exactly what you're trying to do as I don't know your specific needs, but just realize that you can already convert char into ints very easily. I believe you can just add a char to an int, although I may be mistaken (I do know there's a very easy way to add a char's value though, maybe you have to cast it first?) However recall that if you want the digits to count for face value, you'd have to subtract 48 from them first.
If you want to use atoi you can, however honestly I don't see the need here, since you're already converting regular chars to numbers here. It'd be sufficient to check to see if the char value is between 48 and 58 (or whatever the actual numbers are) and if they are then you could subtract that.
Hope this helped!

C Data Types reading different input from stdin and printing altered data

Dusting off my C cobwebs here using a site called HackerRank... the challenge here is to read 3 different inputs from stdin and then print out altered data.
Input
The first line contains an integer
The second line contains a double
The third line contains a string / sentence
Output
integer input + variable i
double input + variable d
variable s + string input
Seemed pretty straight forward, I'd use scanf for the integer and double then fgets for the string since scanf would terminate after the first space.
My problem is, doesn't seem like fgets is filling the buffer, but I'm unsure whether or not it could be the sites compiler or just my lack of knowledge.
int i = 4;
double d = 4.0;
char s[] = "HackerRank ";
// Declare second integer, double, and String variables.
int singleNum;
double doubleNum;
char buffer[256];
char outputString[300];
// Read and save an integer, double, and String to your variables.
scanf("%d", &singleNum);
scanf("%lf", &doubleNum);
fgets(buffer, 256, stdin);
// Print the sum of both integer variables on a new line.
singleNum += i;
printf("%d\n", singleNum);
// Print the sum of the double variables on a new line.
doubleNum += d;
printf("%.1f\n", doubleNum);
// Concatenate and print the String variables on a new line
strcat(outputString, s);
strcat(outputString, buffer);
printf("%s", outputString);
// The 's' variable above should be printed first.
However, when I do this, buffer is always empty. If I were to use scanf I would at least get the first word front the string input.
Not super concerned about memory usage here, just trying to complete the problem to work within fixed parameters.
So, my question is - am I doing something wrong here?
My Output:
Input (stdin)
12
4.0
is the best place to learn and practice coding!
Your Output (stdout)
16
8.0
HackerRank
Expected Output
16
8.0
HackerRank is the best place to learn and practice coding!
Compiler Message
Wrong Answer
However, when I do this, buffer is always empty. If I were to use scanf I would at least get the first word front the string input.
The problem is that white space ('\n' entered at the end of scanning double number ) into the buffer is getting consumed
instead consume white space using scanf(" "); before scanning in buffer
scanf(" ");
fgets(buffer, 256, stdin);
Is there a way to include the newline character in the scanf statement so I don't need an extra one?
yes you can further simplify above two statements into :
scanf(" %255[^\n]",buffer); //consumes and scans into buffer
or you could also :
scanf("%lf\n", &doubleNum); //consume at the end
fgets(buffer, 256, stdin); //scan into buffer
One visible problem here is that the outputString is declared, but not initialized:
char outputString[300];
I assume, that it is declared in block scope, so it contains trash values, whatever is on the stack. This may confuse strcat, which expects it to be NUL terminated:
strcat(outputString, s);
The fix would be add following line before the strcat call:
outputString[0] = '\0';
I have solved the Hackerrank C dataType First day challenge problem without using strcat function. Below is the solution to this problem.
int main() {
int i = 4;
double d = 4.0;
char s[] = "HackerRank ";
// Declare second integer, double, and String variables.`enter code here`
int i1=0;
double d1=0.0;
char name[100];
// Read and save an integer, double, and String to your variables.
scanf("%d",&i1);
scanf("%lf",&d1);
getchar(); // Used this function to remove the '\n'from double.
scanf("%[^\n]s",name);
// Print the sum of both integer variables on a new line.
printf("%d\n",i+i1);
// Print the sum of the double variables on a new line.
printf("%0.1lf\n",d+d1);
// Concatenate and print the String variables on a new line
// The 's' variable above should be printed first.
printf("%s%s\n",s,name);
return 0;
}

C language reading columnated text file

First of all let me ask for your forgiveness if this is too trivial, I am not a C developer, usually I program in Fortran.
I am in need to read some columnated text files. The problem I have is that some columns can have blank space (non filled value) or not fully filed field.
Let me use a short example of the problem. Lets say I have a generator program like:
#include <stdio.h>
#include <stdlib.h>
int main(){
printf("xxxx%4d%4.2f\n",99,3.14);
}
When I execute this program I get:
$ ./t1
xxxx 993.14
If I get it into a text file and try to read using (e.g.) sscanf with the code:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *fmt = "%*4c%4d%4f";
char *line = "xxxx 993.14";
int ival;
float fval;
sscanf(line,fmt,&ival,&fval);
printf(">>>>%d|%f\n",ival,fval);
}
The result is:
$ ./t2
>>>>993|0.140000
What is the problem here? The sscanf seems to think that all space is meaningless and should be discarded. So the "%4c" does what it is meant to be, it counts 4 characters without discarding any blank space and discards everything due to "". Next the %4d start skipping all blank spaces and start count the 4 characters of the field upon finding the first valid character for the conversion. So the value, meant to be 99 becomes 993, and the 3.14 becomes 0.14.
In Fortran the reading code would be:
program t3
implicit none
integer :: ival
real :: fval
character(len=30) :: fmt="(4x,i4,f4.0)"
character(len=30) :: line="xxxx 993.14"
read(line,fmt) ival, fval
write(*,"('>>>>',i4,'|',f4.2)") ival,fval
end program t3
and the result would be:
$ ./t3
>>>> 99|3.14
That is, the format specification states the field width and nothing is discarding in conversion, except if instructed to by the "nX" specification.
Some final remarks to help the helpers:
The format to be read is an international standard and there is no
way to change it.
The number of existing files is to big to think of intervention or
format change.
It is not a CSV or similar format.
The code has to be in C for integration in a free software package.
Sorry to be too long, trying to state the problem as completely as possible.
The question is: Is there a way to tell sscanf to not skip the blank spaces? If not, is there a simple way to do it in C or it will be necessary write an specialized parser for each record type?
Thank you in advance.
When reading fixed-length fields with sscanf, it is best to parse the values as character strings (which you could do a number of ways), and then perform independent conversion of each of the fields. This allows you to handle conversion/error detection on a per-field basis. For example, you could use a format string of:
char *fmt = "%*4s%2[^0-9]%s";
which would read/discard the 4 leading characters, then read 2-chars as your integer, followed by the remainder of line (or up until the next whitespace) as a string containing your float value.
To handle the storage and parsing of line as fixed length fields, you could use temporary character arrays to hold each of the strings and then use sscanf to fill them much as you have attempted to do with the integer and float directly. e.g.:
char istr[8] = {0};
char fstr[16] = {0};
...
sscanf (line,fmt,istr,fstr);
(note: you could use minimum storage of istr[3] and fstr[7] in this given case, adjust the storage length as required, but providing space for the nul-terminating character)
You can then use strtol and strtof to provide conversion with error checking on each value. For example:
errno = 0;
if ((ival = (int)strtol (istr, NULL, 10)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* underflow/overflow checks omitted */
and
errno = 0;
if ((fval = strtof (fstr, NULL)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* nan and inf checks omitted */
Putting all the pieces together in you example, you could use something like:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
char *fmt = "%*4s%2[^0-9]%s";
char *line = "xxxx 993.14";
char istr[8] = {0};
char fstr[16] = {0};
int ival;
float fval;
sscanf (line,fmt,istr,fstr);
errno = 0;
if ((ival = (int)strtol (istr, NULL, 10)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* underflow/overflow checks omitted */
errno = 0;
if ((fval = strtof (fstr, NULL)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* nan and inf checks omitted */
printf(">>>>%d|%6.2f\n",ival,fval);
return 0;
}
Example/Output
$ >>>>0|993.14
*scanf() is not designed to handle fixed column width with non-intervening white-space.
With sscanf(), to not skip spaces, code must use "%c", "%n", "%[]" as all other specifiers skip leading white-space and those skipped characters do not contribute to a width limit.
To scan the printed line, which in now in buffer, take advantage that the only use of '\n' is at the end of the line.
char str_int[5];
char str_float[5];
int n = 0;
sscanf(buffer, "%*4c%4[^\n]%4[^\n]%n", str_int, str_float, &n);
if (n != 12 || buffer[n] != '\n') Fail();
// Now convert str_int, str_float as needed.
Another way to use sscanf() would be to parse buffer as
int ival;
float fval;
if (strlen(buffer) != 13) Fail();
if (sscanf(&buffer[8], "%f", &fval) != 1) Fail();
buffer[8] = '\0';
if (sscanf(&buffer[4], "%d", &ival) != 1) Fail();
Note: The 4s in the below do not specified the output width as 4 characters. 4 is the minimum width to print.
printf("xxxx%4d%4.2f\n",ival, fval);
Code could use the following to detect problems.
if (13 != printf("xxxx%4d%4.2f\n",ival, fval)) Fail();
Watch out for
printf("xxxx%4d%4.2f\n",123, 9.995000001f); // "xxxx 12310.00\n"
First off, I dunno. There might be some way to wrangle sscanf to recognize the whitespace towards your integer count. But I just don't think scanf was made for this sort of format in mind. The tool's trying to be smart of helpful and it's biting you in the ass.
But if it's columnated data and you know the position of the various fields, there's a really easy work around. Just extract the field you want.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
char line[] = "xxxx 893.14";
char tmp[100];
int thatDamnNumber;
float myfloatykins;
//Get that field
memcpy(tmp, line+4, 4);
sscanf(tmp, "%d", &thatDamnNumber);
//Kill that field so it doesn't goober-up the float
memset(line+4, ' ', 4);
sscanf(line, "%*4c%f", &myfloatykins);
printf("%d %f\n", thatDamnNumber, myfloatykins);
return 0;
}
If there is a lot of this, you could make some generalized functions: integerExtract(int positionStart, int sizeInCharacters), floatExtract(), etc.
If each element is of fixed width you don't really need scanf(), try this
char copy[5];
const char *line = "xxxx 993.14";
int ival;
float fval;
copy[0] = line[4];
copy[1] = line[5];
copy[2] = line[6];
copy[3] = line[7];
copy[4] = '\0'; // nul terminate for `atoi' to work
ival = atoi(copy);
fval = atof(&line[8]);
fprintf(stdout, "%d -- %f\n", ival, fval);
If you want (probably should) you can use strtol() instead of atoi() and strtof() instead of atof() to check for malformed data.
Both these functions take a parameter to store the unconverted/invalid characters, you can check the passed pointer in order to verify that there was a problem with conversion.
Or if you really want scanf() do the same, capture the integer + whitespaces to a char array and then convert it to int later, like this
char integer[5];
const char *line = "xxxx 993.14";
int ival;
float fval;
if (sscanf(line, "%*4c%4[0-9 ]%f", integer, &fval) != 2)
return -1;
ival = atoi(integer);
fprintf(stdout, "%d -- %f\n", ival, fval);
The format "%*4c%4[0-9 ]%f" will
Skip the first four characters including white spaces.
Scan the next four characters if they consist only of digits or white spaces.
Scan the rest of the input string searching for a matching float value.
I am posting what I think is a final conclusion from the answers I have got so far and from other sources.
What is a very trivial task in Fortran is not a so trivial task in other languages. I guess — not sure — that the same task could be as easy as in Fortran in other languages. I think that Cobol, Pascal, PL/I and others from the time of punched card probably could be trivial.
I think that most languages nowadays are more comfortable with different data structure and inherited its I/O structure from C. I think that Java, Python, Perl(?) and others could serve as examples.
From what I saw in this thread there are two main problems to read / convert fixed column length text data with C.
The first problem is that, as Philip said in his answer: “The tool’s trying to be smart of helpful and it’s biting you in the ass.” Quite right! The point is that it seems that C text I/O thinks that “white space” is something like a NULL character and should be thrown away, completely disregarding any information of the start of field. The only exception to that seems to be the %nc that get exactly n chars, even blanks.
The second problem is that the conversion “tag” (how is that called?) %nf will keep converting while it finds a valid character, even if you say stop at the 4th character.
If we join those two problems with a field completely filled with white space, depending on the conversion tool used, it throws an error or keeps going madly looking for something meaningful.
At the end of the day, it seems that the only way is to extract the field length to another memory area, dynamically allocated or not (we can have an area for each column length), and try to parse this separate area, taking into account the possibility of a full white space area to cache the error.

Resources