Search code examples
cprintingcasting

Why is the printf statement for number of pennies printing off when casting the double variable to an int?


I have a trivial program that I wrote to calculate the number of quarters, dimes, nickels, and pennies in an amount entered in (less than $1.00).

When I enter in specific amounts (.57 as an example) the number of pennies is printed incorrectly. In testing, the math on the doubles to calculate the pennies is correct and prints out correctly but when casting it to an int, it prints out incorrectly and I can't figure out exactly why.

I absolutely understand that there can be round off errors with decimal division etc. but in the case of an input of .57 it is doing the division part properly (0.02 / 0.01) = 2.00000 but the casting of that amount stored in the variable is failing. I've tried it by coding constant double amounts of 0.02 / 0.01 and then casting to an int and that seems to work properly so I'm struggling to figure out why it's failing when I work with the variable instead.

Example

As an example, when i enter it .57 for the change amount, the math on the doubles to calculate the pennies prints out amount_left as 2.000000 but when i then cast the variable to an int and assign that to the number of pennies variable like this:

number_of_pennies = (int)amount_left;

the number of pennies variable prints out Pennies: 1 instead of the expected 2.

Here is the full program code.

Full Code

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

int main() {
    char line[100], character_eater[100];
    int number_read_status, number_of_quarters, number_of_dimes,
        number_of_nickels, number_of_pennies;
    double amount_entered_in, amount_left;

    printf("Enter in an amount less than $1.00 to be converted to quarters, dimes, nickels, and pennies in format '.##': ");
    fgets(line, sizeof(line), stdin);
    number_read_status = sscanf(line, "%lf", &amount_entered_in);

    /* store all non-numeric characters entered in for input validation */
    sscanf(line, "%s", &character_eater);

    /* input validation */
    while(number_read_status != 1 || amount_entered_in > .99) {
        printf("\nA valid amount was not entered in. The amount must be less than $1.00 in format '.##'. Please try again.\n");
        printf("Enter in an amount less than $1.00 to be converted to quarters, dimes, nickels, and pennies: ");
        fgets(line, sizeof(line), stdin);
        number_read_status = sscanf(line, "%lf", &amount_entered_in);
        sscanf(line, "%s", &character_eater);
    }

    /* calculate change in quarters, dimes, nickels, and pennies */
    number_of_quarters = (amount_entered_in / .25);
    amount_left = amount_entered_in - number_of_quarters * .25;
    number_of_dimes = (amount_left / .10);
    amount_left -= number_of_dimes * .10;
    number_of_nickels = (amount_left / .05);
    amount_left = (amount_left - number_of_nickels * .05);
    amount_left = (amount_left / .01);
    printf("Amount left: %lf", amount_left); /* this prints out 2.000000 */
    number_of_pennies = (int)amount_left;

    /* print out change in quarters, dimes, nickels, and pennies */
    printf("\nThe number of quarters, dimes, nickels, and pennies equivalent to %lf are as follows:\n", amount_entered_in);
    printf("\tQuarters: %d\n", number_of_quarters);
    printf("\tDimes: %d\n", number_of_dimes);
    printf("\tNickels: %d\n", number_of_nickels);
    printf("\tPennies: %d\n", number_of_pennies); /* prints out wrong sometimes -- .57 for example */
    return(0);
}

Solution

  • OP's code is suffering from truncation issues that convert a floating point number like x.9999 to an int x rather than int x + 1.

    Instead, after reading in the dollar amount of money as a double, round and convert to an integer type base unit of currency like cents.

    number_read_status = sscanf(line, "%lf", &amount_entered_in);
    if (number_read_status != 1) {
      Handle_Bad_input();
    }
    long long amount_cents = llround(amount_entered_in * 100.0);
    

    Perform the rest of code using integer type objects like amount_cents, rather than floating point math.


    Pedantically I would use long long amount_cents = llroundl(amount_entered_in * 100.0L); for potentially slightly better rounding in edge cases.


    Tip: rather than debug floating point issues with printf("Amount left: %lf", amount_left);, use %g with sufficient precision to see issues.

    printf("Amount left: %.*g", DBL_DECIMAL_DIG, amount_left);
    

    ... or if comfortable with hex output, use %a

    printf("Amount left: %a", amount_left);
    

    C2X plans to offer an optional decimal floating point, which is an elegant solution for the near future.