Home > Blockchain >  How can i round a fraction properly to two places after the decimal?
How can i round a fraction properly to two places after the decimal?

Time:11-28

I've written a simple program, which a user types an amount of money, and the computer returns some bills to change. When I type 99.99, the computer cannot handle properly with 0.01 fraction. roundf() and floorf() doesn't work. Can anyone know how to fix this? Thank you in advance. Note: I'm learning how pointers work now, that's why I use pointers here.

#include <stdio.h>
#include <math.h>
#define COINS_TYPES 14

void read_num(float* cash);
void analyze_and_result(float* arr, float* arr_val, float* cash);

int main(){
    float cash;
    float coins[COINS_TYPES] = { 0 };
    float coins_val[COINS_TYPES] = { 200,100,50,20,10,5,2,1,0.5,0.2,0.1,0.05,0.02,0.01 };

    for (;;) {
        read_num(&cash);
        analyze_and_result(coins, coins_val, &cash);
        break;
    }
    return 0;
}

void read_num(float* cash) {
    printf("Type amount of cash to exchange for coins\n");
    scanf("%f", cash);
    *cash = ceilf(*cash * 100) / 100; //seems that this line doesn't work
}

void analyze_and_result(float* arr, float* arr_val, float* cash) {
    for (int i = 0;i < COINS_TYPES;i  ) {
        int flag = *cash / arr_val[i];
        if (flag) {
            arr[i] = flag;
            *cash -= arr_val[i] * flag;
            printf("Coins quantity %0.2f : %.f\n", arr_val[i], arr[i]);
        }
    }
}

Result

CodePudding user response:

As able, use integers to encode the lowest unit of currency.

Instead of float cash;, use long long cash;

After reading in as a double, convert to an integer.

//scanf("%f", cash);
double cash_d;
scanf("%lf", &cash_d);
*cash = roundll(cash_d * 100.0);

Use integer math to then manipulate money.

To print money, covert back to double.

printf("Coins quantity %0.2f\n", (double) arr_val[i] / 100.0);

CodePudding user response:

The main problem is that the multiplication and rounding works fine unless there's an overflow (the result of ceilf(*cash * 100) has no fractional bits), but the division by 100 causes precision loss (e.g. maybe 1234/100 = 1.233999999).

The reason is that some fractions are infinite (e.g. 1/3 = 0.333333.... in decimal and would need an infinite number of digits to store without precision loss), and in different bases different fractions become infinite. Specifically; 1/10 or 0.1 is a repeating fraction in binary (it becomes 0.00110011001100110011...b), and therefore all powers of 1/10 are also infinite fractions in binary (1/100, 1/1000, 1/10000, ...).

The correct way to fix the problem/s is to parse user input properly. If the user enters a string like "Bork!" then they should get an error message saying that it's not a number, if the user enters a string like "9999999999999999999999999999999999999" they should be told the number is too large, and if the user enters a string like "0.001" they should be told that the factions of 1 cent aren't valid; and in each of those cases the user should be asked to retry, so that the rest of the program doesn't have to deal with invalid input.

Once you start doing parsing properly (and after you realize that scanf() is not fit for any purpose) you'll realize that the easiest way to parse your user's input properly is to write code to convert (with lots of sanity checks) the input string directly into an integer number of cents yourself.

For a crude (untested) example:

int convertStringToUnsignedCents(char *string) {
    int result = 0;
    int decimalPointsFound = 0;
    int fractionalDigitsCounter = 0;

    while( (*string != 0) && isspace(*string) ) {
        string  ;
    }
    if(*string == 0) {
        printf("ERROR: Empty string.\n");
        return -1;
    }

    while(*string != 0) {
        if(*string == '.') {
            if(decimalPointsFound > 0) {
                printf("ERROR: Too many decimal points.\n");
                return -1;
            }
            decimalPointsFound  ;
        } else if( (*string < '0') || (*string > '9') ) {
            printf("ERROR: Bad character.\n");
            return -1;
        } else {
            if(decimalPointsFound > 0) {
                fractionalDigitsCounter  ;
                if(fractionalDigitsCounter > 2) {
                    printf("ERROR: Fractions of 1 cent aren't valid.\n");
                    return -1;
                }
            }
            if(result > INT_MAX/10) {
                printf("ERROR: Number too large.\n");
                return -1;
            }
            result = result * 10;
            if(result > INT_MAX - (*string - '0') ) {
                printf("ERROR: Number too large.\n");
                return -1;
            }
            result = result   (*string - '0');
        }
        string  ;
    }
    return result;
}

Note that this is only a beginning - you can have more/better error messages (e.g. "ERROR: Unknown characters after valid number"), and be more accepting of valid input (e.g. if the user enters the string "$1,234.56" you could quietly ignore the $ and the thousands separator).

CodePudding user response:

Use integer numbers for it:

#include <stdio.h>
#include <stdlib.h>

int nominals[] = {20000,10000,5000,2000,1000,500,200,100,50,20,10,5,2,1};

void getNominals(double money, int *result)
{
    unsigned ncents = money * 100.0;
    int *nm = nominals;
    while(*nm && ncents)
    {
        *result   = ncents / *nm;
        ncents %= *nm  ;
    }
}

int main(void)
{
    int result[sizeof(nominals) / sizeof(nominals[0])] = {0};

    getNominals(9.99, result);

    for(size_t index = 0; nominals[index]; index  )
    {
        printf("%.2f = %d\n", (double)nominals[index]/100, result[index]);
    }
}

https://godbolt.org/z/Y9nYca7G7

  • Related