ccodeblocksmath.h

Creating rational number from float gives imprecise results


I am training my C skills. I was doing a function that receives a float number and was supposed to return a fraction that I did with struct. When I was trying to round down the number, I started to see some weird results. I suspect that is some error in the floor() function.

I use Code::Blocks and I've read that the pow() function gives some error using it, so I thought that it was supposed to be it.

I am reading a lot about how floor() function works, but I couldn't realize how to fix it and prevent from resulting wrong numbers again.

My function is:

Fraction ftof (float f)
/* Transforms a float in a struct type fraction. */
{
    Fraction frac;
    int i, decimalPlaces; 
    float decimalPart, numerator;

    decimalPlaces = 0;
    printf("Number of decimal places: %d\n", decimalPlaces);

    decimalPart = f - floor(f);
    printf("Decimal part: %f\n\n", decimalPart);

    while (decimalPart != 0)
    {
        decimalPlaces++;

        printf("Houses number updated: %d\n", decimalPlaces);

        decimalPart = decimalPart * 10;

        decimalPart = decimalPart - floor(decimalPart);

        printf("Decimal part updated: %f\n", decimalPart);
    }

    numerator = f;
    frac.denominator = 1;

    for (i = 0; i < decimalPlaces; i++)
    {
        numerator = numerator * 10;

        frac.denominator = frac.denominator * 10;
    }

    frac.numerator = (int) floor(numerator);

    writes(frac);
    printf("\n");

    simplification(&frac);

    return frac;
}

Being "writes" and "simplification" another two functions that I wrote that writes formatted fractions and simplifies too, but they are well tested and properly working.

My main is:

int main()
{
    float decimal;

    while (1) {
    printf("Type a decimal number: ");
    scanf("%f", &decimal);
    printf("Typed number: %f\n", decimal);
    writes(ftof(decimal));
    printf("\n\n");
    }
}

With the "printf" that I puted in the middle of the function, it says to me the numbers that it is calculating (I never understanded properly how to debug in CodeBlocks), so when I type 0.25, it will result in 1/4. But, if I type some more difficult number, like 0.74, it will result:

Type a decimal number: 0,74
Typed number: 0,740000
Number of decimal places: 0
Decimal part: 0,740000

Houses number updated: 1
Decimal part updated: 0,400000
Houses number updated: 2
Decimal part updated: 0,000001
Houses number updated: 3
Decimal part updated: 0,000010
Houses number updated: 4
Decimal part updated: 0,000095
Houses number updated: 5
Decimal part updated: 0,000954
Houses number updated: 6
Decimal part updated: 0,009537
Houses number updated: 7
Decimal part updated: 0,095367
Houses number updated: 8
Decimal part updated: 0,953674
Houses number updated: 9
Decimal part updated: 0,536743
Houses number updated: 10
Decimal part updated: 0,367432
Houses number updated: 11
Decimal part updated: 0,674316
Houses number updated: 12
Decimal part updated: 0,743164
Houses number updated: 13
Decimal part updated: 0,431641
Houses number updated: 14
Decimal part updated: 0,316406
Houses number updated: 15
Decimal part updated: 0,164063
Houses number updated: 16
Decimal part updated: 0,640625
Houses number updated: 17
Decimal part updated: 0,406250
Houses number updated: 18
Decimal part updated: 0,062500
Houses number updated: 19
Decimal part updated: 0,625000
Houses number updated: 20
Decimal part updated: 0,250000
Houses number updated: 21
Decimal part updated: 0,500000
Houses number updated: 22
Decimal part updated: 0,000000
0/0
512/311

I mean, when there was just one decimal place, it did 0.4 * 10 = 4, but then 4 - floor(4) = 0.000001.

What can I do?


Solution

  • Floating point types typically use a binary mantissa, which means that numbers that can be represented exactly in base 10 can't be represented exactly in base 2. A number like 0.25 has an exact representation in binary, but 0.4 does not. So you can't get an exact answer.

    The way to solve this is not to read the number as a float but as a string and perform the conversion to a rational number yourself.

    Fraction stof(char *s)
    {
        Fraction f;
        long ipart, fpart;
        char *p, *p2;
        int i;
    
        p = strchr(s, '.');                     // look for the decimal point
        ipart = strtol(s, NULL, 10);            // get the integer part
        if (p) {
            fpart = strtol(p+1, &p2, 10);       // get the fractional part, saving the end pointer
                                                // to count digits in case of leading zeros
        } else {
            fpart = 0;                          // no . found, fractional part is 0
        }
        printf("ipart=%ld, fpart=%ld\n", ipart, fpart);
    
        f.numerator = ipart;                    // start with just the integer part
        f.denominator = 1;
        for (i=0; i<(p2-p-1); i++) {            // loop for each digit in the fractional part
            f.numerator *= 10;                  // scale up the numerator and denominator
            f.denominator *= 10;
        }
        f.numerator += fpart;                   // add in the fractional part
        printf("fraction = %ld / %ld\n", f.numerator, f.denominator);
        return f;
    }