Бесконечный цикл while в C при заданном значении и вычитании до нуля

Я только начал изучать C. Я пишу программу, которая возвращает клиенту сдачу в четвертаках, пятаках, десятицентовиках и пенни. По какой-то причине, когда цикл while достигает 0, он не прерывается.

EDIT: Этот вопрос очень похож на другой вопрос, уже заданный на SO (Is математика с плавающей запятой не работает?). Я бы оставил этот вопрос для тех, кто ищет ответы относительно цикла while, таких как я, кто понятия не имел, что число с плавающей запятой вызывает бесконечный цикл while.

#include <stdio.h>
#include <cs50.h>

int main(void){
    float val;
    int quarters = 0;
    int dimes = 0;
    int nickels = 0;
    int pennies = 0;

    printf("How much change is due?: \n");

    val = GetFloat();

    while (val > 0){

        if (val >= 0.25){
            quarters += 1;
            val -= 0.25;
        }
        else if (val >= 0.1) {
            dimes += 1;
            val -= 0.1;
        }
        else if (val >= 0.05){
            nickels += 1;
            val -= 0.05;
        }
        else if (val >= 0.01){
            pennies += 1;
            val -= 0.01;
        }

        printf("%f \n", val);
    }

    printf("Quarters: %i\n", quarters);
    printf("Dimes: %i\n", dimes);
    printf("Nickels: %i\n", nickels);
    printf("Pennies: %i\n", pennies);

    return 0;
}

Любые предложения о том, как обращаться?


person Ctpelnar1988    schedule 08.09.2016    source источник
comment
Обратите внимание, что компьютерные числа с плавающей запятой не точны, и некоторые числа, на которые вы полагаетесь (например, классическое 0.1), невозможно представить без потерь. Что в итоге печатает?   -  person unwind    schedule 08.09.2016
comment
Возможный дубликат Не работает математика с плавающей запятой?   -  person alain    schedule 08.09.2016
comment
На самом деле я сделал это в конце цикла, чтобы увидеть каждый вывод, и он бесконечно выводит 0.000000. Я обычно тестирую ввод с .41 . Хотя я только что проверил с .50 и .75, и эти числа работают, как и ожидалось.   -  person Ctpelnar1988    schedule 08.09.2016
comment
Распечатайте больше десятичных цифр val - см. stackoverflow.com /вопросы/12761493/   -  person 4386427    schedule 08.09.2016
comment
мои 2 копейки :) while( floorf(val * 100) / 100 › 0) // включаем math.h   -  person Michael Frost Billing    schedule 08.09.2016
comment
при написании литералов float добавляйте f в конец числа, иначе код фактически использует значения double. Примечание: 0 не является значением с плавающей запятой, вместо него используйте 0.0f.   -  person user3629249    schedule 09.09.2016


Ответы (2)


Это связано с тем, что float не может правильно представить все значения на 100%. Для некоторых входных значений ваша программа будет иметь значение valбольше нуля и меньше 0,1. Тогда у вас тупик.

Изменение печати на:

printf("%0.30f \n", val);

выведет:

0.009999995119869709014892578125

в моей системе.

Лучше вместо этого выполнять вычисления в int. Что-то вроде:

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

int main(void){
    float val;
    int intval;
    int quarters = 0;
    int dimes = 0;
    int nickels = 0;
    int pennies = 0;

    printf("How much change is due?: \n");

    val = GetFloat();

    intval = roundf(100 * val);  // Multiply by 100 and convert to int

    // Use intval instead of val for the remaining code
    while (intval > 0){

        if (intval >= 25){     // note: 0.25 --> 100 * 0.25 --> 25 
            quarters += 1;     // similar for all other code below 
            intval -= 25;
        }
        else if (intval >= 10) {
            dimes += 1;
            intval -= 10;
        }
        else if (intval >= 5){
            nickels += 1;
            intval -= 5;
        }
        else if (intval >= 1){
            pennies += 1;
            intval -= 1;
        }

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

    printf("Quarters: %i\n", quarters);
    printf("Dimes: %i\n", dimes);
    printf("Nickels: %i\n", nickels);
    printf("Pennies: %i\n", pennies);

    return 0;
}
person 4386427    schedule 08.09.2016
comment
не будет ли intval = 100 * val + 0.5 лучше? - person alain; 08.09.2016
comment
... потому что если GetFloat() возвращает 0,00999... это не сработает. - person alain; 08.09.2016

Проверив отладчиком, вы увидите, что значения с плавающей запятой неточны. В этом примере. после назначения val = (float)0.41 вы можете увидеть в gdb:

(gdb) p val
$2 = 0.409999996

Бесконечный цикл возникает из-за того, что в конце val больше 0, но меньше 0,01.

самое простое решение - сломать в таком случае. Конечно, вы можете потерять копейку:

while (val >= 0.01){
    ...
person eyalm    schedule 08.09.2016
comment
это не сработает, потому что 0,00999 также следует рассматривать как 0,01. - person alain; 08.09.2016
comment
Вот почему я написал Конечно, вы можете потерять ни копейки - person eyalm; 08.09.2016