-1

I'm new to C language, it is required for my degree to complete one course on C programming. Hope the title makes sense... I had a hard time trying to convey what I meant. I'll elaborate here.

The problem I am facing right now is double type data being able to slip through the program undetected when it should have been rejected. This happens when a number very very close to the boundaries I set is provided.

In this assignment, in order to get an HD, my code has to have identical output to the lecturer's. The solutions are able to detect any number larger than the boundaries, no matter how close.

Basically, the program will ask the user to provide a GPA which is from 0 to 4. I tried the following inputs: 4.01 rejected 4.001 rejected ... 4.000001 rejected But at 4.0000001, it is accepted. This should not happen at all. I am baffled.

Here's my code:

double GPA=-1; /*Flags are being initialised*/
int GPAflag=1;
while(GPAflag){
    char str[50];
    char *ptr;
    int isnum=1,n=0,i,point=0;
    printf("Enter GPA>");
    fflush(stdin);
    gets(str); 
    n = strlen(str); 
    if(n==0){ /*string length is 0? There was no input, thus invalid*/
        printf("Invalid GPA. ");
        isnum=0;
    }else{
        for(i=0;i<n;i++) /*Validates numerical inputs, if not numerical, prompts user with error*/
        if(!(str[i]>='0' && str[i]<='9')){
            if(str[i]!='.'){
                printf("Invalid GPA. ");
                isnum=0;
                break;
            }else{
                point++;
            }
        }
    }
    if(isnum){ /*If the input is a number, it may still be invalid*/
        GPA=strtod(str, &ptr); /*The string is converted to a double*/
        if(GPA>=0&&GPA<=4&&point<=1) /*If GPA is between 0 and 4, and the above is also satisfied (point=1), then the flag is 0 and thus a valid input*/
            GPAflag=0;
        else
            printf("Invalid GPA. "); /*If not, it is still invalid*/
    }
}

I personally don't think this will be a problem but I would really like to know why it happens, and if possible, a way to fix it.

I can simply make a FOR loop: IF leading number is 4 and there's a '.' -> Reject But this seems like hard coding to me, I think it's rather a workaround than an actual solution. Or I can change the IF statement from "<=4" to "<4.0000000000000000000000...1". Which in my opinion should lose marks... (if I was the marker I will take marks off for that)

Also, I am totally aware that the function "gets" is primitive and causes many problems (overflow... etc.). I have tried using fgets but it's a pain to implement... replacing the '\n' with '\0' blah blah... we haven't even learnt gets, let alone fgets. Is there a better way to implement fgets? Maybe have a function to replace the '\n'? I've tried that and it refused to work.

Any help and tips are greatly appreciated!

Karl Gjertsen
  • 4,690
  • 8
  • 41
  • 64
  • 1
    `warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]` – LPs May 20 '16 at 15:26
  • A floating point number only has so much precision... if you enter a number close enough to 4, it gets stored as exactly 4. Not much you can do about that except to reject it while it's character data, before converting it (if it's important to be that picky). – Dmitri May 20 '16 at 15:28
  • @LPs: Actually `gets` is not even part of the standard. Did you compile as C99? – too honest for this site May 20 '16 at 15:39
  • @olaf yes -std=c99 – LPs May 20 '16 at 15:43
  • @LPs: Maybe you better use standard C then :-) – too honest for this site May 20 '16 at 15:47
  • " it's a pain to implement... replacing the '\n' with '\0' blah blah... " --> `str[strcspn(str, "\n")] = 0;` after `fgets(str, sizeof str, stdin)` is not too painful. – chux - Reinstate Monica May 20 '16 at 16:09
  • Curious: what text or who suggested using `fflush(stdin);`? – chux - Reinstate Monica May 20 '16 at 16:10
  • @olaf I did it because of I'm lazy and it prints that warning on terminal I can just copy and paste it :) – LPs May 20 '16 at 16:16
  • 1
    OP's code, as written, should reject input `"4.0000001"`. This is _not_ a limited precision issue of C as a `double` has at least 10 decimal digits of precision. so either OP has posted something other that a true cope of code/input or something else is causing the issue. – chux - Reinstate Monica May 20 '16 at 16:44
  • Post a complete true code and input used that we can compile and run that recreates the issue. - not just a code snippet. – chux - Reinstate Monica May 20 '16 at 16:46
  • I suspect the problem is that `GPA ` is a `float` in true code and not `double` as posted here. – chux - Reinstate Monica May 20 '16 at 16:52
  • 1
    normally double is IEEE-754 double precision which has at least 15 digits of precision – phuclv May 20 '16 at 16:52
  • Hi, thanks for the replies! 1. Yep, I am aware that gets is bad, but for this task it seems like the easiest method. I asked the tutors and they said it's fine at this stage for use to use gets... we were using scanf previously which is very weak too. As for the fflush(stdin), it clears the buffer before reading so that after one entry, the gets() won't grab something that it's not supposed to. And we're using GCC to compile. – Hypergeometry May 21 '16 at 02:47
  • @chux I had it declared as float to begin with! Maybe that's the problem! I'll go have a look. My friend told me the same thing just a while ago. – Hypergeometry May 21 '16 at 02:48
  • struct student{char studentname[MAX_NAME_SIZE];struct birthday sdate; >>>>>float<<<<< GPA;}; Ahhhh! Just found out that despite me changing the type in the function, I still have GPA declared as a float in the structure! I'll change it to see what happens. – Hypergeometry May 21 '16 at 02:49
  • Now 4. -15 zeros- 1 is allowed to pass rather than 4. -6 zeros- 1. So I guess it's the limitations of the datatype after all. Am I correct? The teacher's code won't even let 4. -30 zeros- 1 pass but I think he's probably using a different method to verify the input. I doubt he will go to such an extreme and test something this obscure though. Thanks for the help! – Hypergeometry May 21 '16 at 02:59
  • In future please post the actual code that causes the problem, not some stuff you made up with vague resemblance to the actual code – M.M May 21 '16 at 04:51
  • @M.M The thing is I didn't know what caused the problem to start with, until someone mentioned that my declarations might be the cause. I thought the problem lied within the function so I posted it. The entire code is 300+ lines long, not going to expect anyone to read it, and also some classmates might find this thread and plagiarise my code! I do not want that happening. What "made up" part were you referring to? I didn't post anything that was made up... – Hypergeometry May 21 '16 at 11:16

2 Answers2

2

OP's true code was using float GPA=-1; and so GPA=strtod("4.0000001", &ptr); resulted in a GPA value of exactly 4.0 due to the greater limited precision of float versus a double.

By using double GPA;, as in the posted code, GPA=strtod("4.0000001", ... and GPA=strtod("4.000000001", ... will return a value just greater than 4. Even GPA=strtod("4.00000000000001", ... is likely to return a value a bit larger than 4.

At some point the limited precision of double will render GPA=strtod("4.0 many digits 001", ... as 4.0. A typical solution to limit the number of digits to something like 15 or less.

(Could taunt the user if more that 15 digits are used.)

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • A solution could be to parse the string before calling `strtod` and look for `4.` followed by anything except all-zeroes – M.M May 21 '16 at 04:51
  • @M.M I quote "I can simply make a FOR loop: IF leading number is 4 and there's a '.' -> Reject But this seems like hard coding to me, I think it's rather a workaround than an actual solution." I have actually used this method and unsurprisingly it works. But as I have said, this seemed like hard coding to me, forcing my code to act exactly the same as the teacher's. I don't think he used the same method. – Hypergeometry May 21 '16 at 11:20
0

The problem is solved, and looking back, it was due to a mistake:

  1. In the structure, I have declared GPA as a float. I later changed it in my functions but missed out the most important part. As a result, the GPA was still stored as a float. Hope this conclusion is correct.
  2. The limitations of the datatype causes round-off errors when an incredibly "close to allowed" value is provided.

Lesson learnt: When changing something, always make sure everything is consistent.

I appreciate all the help! Thanks a lot!