0

I want to find the sum of all the digits entered by a user after the decimal point in c.

Eg. 12.36 must return 9
15.06 must return 6
9.0 must return 0

My approach

#include <stdio.h>
int main()
{
    double num,numfloat;
    int digitf,numint,sumf=0;
    scanf("%lf",&num);
    numint=num;
    numfloat=num-numint;
    while(numfloat!=0)
    {
        digitf=(numfloat*10);
        numfloat=numfloat*10-digitf;
        sumf=sumf+digitf;
    }
    printf("Sum float %d",sumf);
return 0;
}

The sum comes out a lot more than the expected.

KKS
  • 3
  • 2
  • Do you really require input to be `double`? Can't it be a character string? – kiner_shah Jan 21 '22 at 09:32
  • Yes the input has to be a number only – KKS Jan 21 '22 at 09:34
  • 3
    Side note: Floating point numbers may not be exact, so the values you think are present after the decimal point may not be accurate. – Tim Biegeleisen Jan 21 '22 at 09:35
  • 1
    Do not transform to float but work with character string. Using float the conversion will loose the number of decimals entered by user because of internal float representation. – Ptit Xav Jan 21 '22 at 09:40
  • 1
    You should run your code with a debugger and check step by step the results of your operations – Odysseus Jan 21 '22 at 09:44
  • About "The sum comes out a lot more than the expected.", I'm sorry, but that *is* expected: https://godbolt.org/z/rbszzrKfY – Bob__ Jan 21 '22 at 09:56
  • 2
    It's worse than @TimBiegeleisen said: it's not that they "may" not be exact, or that the values you think are present "may" not be accurate: they **will** not be! As a `double`, the number 12.36 *does not exist!* It's actually a binary fraction equivalent to 12.3599999999999994316. Which pretty much explains why you're getting sums bigger than you expect. – Steve Summit Jan 21 '22 at 23:10
  • 2
    Who gave you this exercise? It's meaningless and impossible. – Steve Summit Jan 21 '22 at 23:11
  • 1
    For some more explanation of why this problem makes no sense, SO's canonical explanations are at the question, [Is floating point math broken?](https://stackoverflow.com/questions/588004) – Steve Summit Jan 22 '22 at 00:33

3 Answers3

1

Try, (The reason why in this example, I haven't joined the loops is that: I want this to be usable on other situations where they use an alternate method of validating input, see the SECOND EXAMPLE for both loops joined):

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

int main() {
        // read from stdin
        char num[512] = { };
        fgets(num, sizeof(num), stdin);

        num[strcspn(num, "\n")] = '\0';

        // verify if number is valid
        int decimal_point_found = 0;

        for (int i = 0; i < strlen(num); i++) {
                // check if digit
                if (!isdigit(num[i])) {
                        // be safe from multiple decimal points
                        if (num[i] == '.' && decimal_point_found == 0) {
                                decimal_point_found = 1;
                                continue;
                        }
                        printf("ERROR: enter a valid number\n");
                        return 1;
                }
        }

        int total = 0;
        // add all the decimal points
        for (int i = 0, decimal_point_found = 0; i < strlen(num); i++) {
                if (decimal_point_found == 1) {
                        total += num[i] - '0'; // - '0' converts char to int
                }
                if (num[i] == '.') {
                        decimal_point_found = 1;
                }
        }

        // show total
        printf("%d\n", total);

}

In the above, I have read char instead of reading float. I have read using fgets() which is safer than scanf().

Handling char makes it so much easier to calculate such things. As we know the number of digits, etc.

With both loops joined:

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

int main() {
        char num[512] = { };
        fgets(num, 512, stdin);

        num[strcspn(num, "\n")] = '\0';

        // verify if number is valid
        int decimal_point_found = 0;

        int total = 0;

        for (int i = 0; i < strlen(num); i++) {
                if (!isdigit(num[i])) {
                        if (num[i] == '.' && decimal_point_found == 0) {
                                decimal_point_found = 1;
                                continue;
                        }
                        printf("ERROR: enter a valid number\n");
                        break;
                }
                if (decimal_point_found == 1) {
                        total += num[i] - '0';
                }

        }

        printf("%d\n", total);
}

Example person
  • 3,198
  • 3
  • 18
  • 45
  • You could easily join both loops. – chtz Jan 21 '22 at 10:06
  • You could improve this answer by explaining why the original code in question doesn't work (i.e. conversion between binary and decimal systems, which result in slightly different values because of finite precision of the types), which I think is the core of the problem. – user694733 Jan 21 '22 at 10:12
  • @user694733, sorry to say, but I do not know anything about that topic :( I can only help with what I know. You can surely edit that in yourself if you know about it – Example person Jan 21 '22 at 10:17
0

You mentioned in a comment that the input has to be a number.

This first point to mention is that when coding, we are not manipulating such abstract things as numbers, but imperfection representations of numbers. Think to the famous painting "This is not a pipe". Same here, "This is not a number".

float, double and char* are all or can be all representations of numbers. Depending on the context, one representation can be more suitable than others. Here, using a char* is the best solution, has no internal conversion error occurs.

Now, let us assume that the input format double is imposed by your professor. Why is your code not working? Mainly because internally, the representation of the numbers is generally imperfect. A small error can lead to a large error when converting a float to an integer.

For example, int i = 0.999999 will give i = 0.

The solution is to account for the internal error representation, by introducing a margin, e.g. eps = 1.0e-14, when performing the float-to-integer conversion, or when testing if a number is equal to 0.

A difficulty is that the internal error is multiplied by 10 when the number is multiplied by 10. So the value of epshas to be updated accordingly.

Moreover, we have to take into accout that the mantissa provides a relative accurracy only, not an absolute one.
Therefore, the eps value must be increased when the number is large.

0.123456789 --> 45
19.1 -> 1
12.45e-36  -> 12
12345.973 -> 19
0.83 -> 11
#include <stdio.h>
int main() {
    double num, numfloat;
    int digitf, numint, sumf = 0;
    double eps = 1.0e-14;        // to deal with representation inaccuracy of numbers
    if (scanf("%lf", &num) != 1) return 1;
    printf("number in memory: %.20g\n", num);
    if (num < 0.0) num = -num;
    numint = (int) (num + eps);
    numfloat = num - numint;
    int deal_with_low_number = numint == 0;
    while (numint) {        // the mantissa only proposes a relative accurracy ...
        eps *= 10;
        numint /= 10;
    }
    while (numfloat > eps || deal_with_low_number) {
        numfloat *= 10;
        digitf = (int) (numfloat + eps);
        numfloat -= digitf;
        sumf = sumf + digitf;
        if (digitf != 0) deal_with_low_number = 0;
        if (!deal_with_low_number) eps *= 10;
    }
    printf("Sum float %d\n", sumf);
    return 0;
}
Damien
  • 4,809
  • 4
  • 15
  • 20
  • For input of “.33333333333333333” (17 3s, total of digits 51), this prints “Sum float 214”. Or for “.123456789” (total 45), it prints “Sum float 274”, and nine digits is well within the precision for which `double` arithmetic has plenty of room to correct for rounding errors. These errors persist even if `eps` is set to `1e-15`. – Eric Postpischil Jan 21 '22 at 13:38
  • @EricPostpischil So the `eps` value must be increased. I know that reading a string is more accurate, as indicated in the post. I just assume that the professor wants OP to understand the effect of inaccuracy in number representation. – Damien Jan 21 '22 at 13:44
  • @EricPostpischil oh I see. The inaccuracy is multiplied by 10 at each iteration. Therefore `eps` must be also multiplied by 10, with a much lower initial value. I cannot test now, travelling with no PC. I will test and correct, not now unfortunately. – Damien Jan 21 '22 at 14:30
  • @EricPostpischil I corrected the code. It works for .123456789. However, clearly, it cannot work when the requested precision is higher than the internal precision. – Damien Jan 22 '22 at 09:35
  • For input of “19.1”, the current code prints “Sum float 3”. – Eric Postpischil Jan 22 '22 at 21:08
  • @EricPostpischil New corrections added... I wonder if there is finally a not too bad solution... Thanks for your help anyway. – Damien Jan 23 '22 at 16:00
  • For input of “.83”, the current code prints “Sum float 127”. – Eric Postpischil Jan 25 '22 at 00:46
  • If you move the code into a routine called `Routine` that takes a `double` and returns an `int` with the sum, here is a basic test program: `int main(void) { for (int p = 1; p <= 1000000; p *= 10) { printf("p = %d.\n", p); for (int i = 1; i < 10000; ++i) { int t = i - i/p*p; int ye = 0; for (; 0 < t; t /= 10) ye += t % 10; double x = i / (double) p; int yo = Routine(x); if (ye != yo) { printf("For %g, expected %d but observed %d.\n", x, ye, yo); printf("\t%.99g\n", x); exit(EXIT_FAILURE); } } } }` – Eric Postpischil Jan 25 '22 at 00:53
  • @EricPostpischil Effectively, `eps = 1.0e-15` was too optimistic. With `1.0e-14`, all your tests pass. It seems OK also with `5e-15`, which is roughly equal to `2^(-50) * 10 / 2`. – Damien Jan 25 '22 at 09:41
0

Original code fails as unless the fraction if exactly an integer/power-of-2, the input number, as decimal text, does not convert exactly to the same double. Instead num is the closet double possible. Yet that closest double may have many more digits when manipulated.

Further OP's numfloat=numfloat*10-digitf; injects repeated rounding errors.

Instead code needs to compensate for that rounding in some fashion.

the input has to be a number

Hmm, better to read in as a string, yet we can code a tolerable solution if we know the length of input by using "%n" to record the length of user input.

width below is the number of non-white-space characters in input. If we assume things like 1) sign only when negative, 2) no exponential 3) not infinity nor NAN, 4) no more than 15 significant digits 5) no more than 15 fraction digits --> then width will almost always*1 gives us what is needed to process num.

#include<stdio.h>
#include<math.h>

int sumFraction(void) {
  int sum = 0;
  double num;
  int start, end;
  if (scanf(" %n%lf%n", &start, &num, &end) != 1) {
    return -1;
  }
  if (num == 0) {
    return 0;
  }
  int width = end - start;
  if (num < 0) {
    num = -num;
    width--;
  }
  int p10 = (int) log10(num);
  width--;  // Decrement for the '.'
  if (width > 15) {
    printf("Too many leading digits\n");
    return -1;
  }
  width -= (p10 + 1);

  // Only care about fractional part
  double ipart;
  num = modf(num, &ipart);
  if (num == 0) {
    return 0;
  }

  // assert(width >= 0);
  num *= pow(10, width);
  long long ival  = llround(num);  // Form an integer

  while (width > 0) {
    width--;
    sum += ival % 10;
    ival /= 10;
  }
  return sum;
}

int main() {
  printf(" %d\n", sumFraction());
  printf(" %d\n", sumFraction());
  printf(" %d\n", sumFraction());
}

*1 IMO, code is not robust as the given limitation of not reading in as a string is not real for the real world. So here is a non-real solution.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256