【问题标题】:How can i round a fraction properly to two places after the decimal?如何将分数正确四舍五入到小数点后两位?
【发布时间】:2022-01-05 04:42:53
【问题描述】:

我写了一个简单的程序,用户输入一笔钱,然后计算机返回一些账单来找零。当我键入 99.99 时,计算机无法正确处理 0.01 分数。 roundf() 和 floorf() 不起作用。谁能知道如何解决这个问题?先感谢您。 注意:我现在正在学习指针是如何工作的,这就是我在这里使用指针的原因。

#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

【问题讨论】:

  • 更改您的程序,以便在读取输入后立即将其转换为整数美分,并在剩余的计算中仅使用整数运算。如果做得好,浮点算术可以用于此,但这样做需要您还没有学过的知识。只需使用整数算术。最后,在输出数字时,您可以使用浮点将美分转换为美元,方法是打印value/100.%.2f。对于适度的金额,这不会有明显的舍入误差。
  • 我将浮动现金分成两个整数部分,现在一切正常,谢谢
  • “我将浮动现金分成两个整数部分,现在一切正常” --> 我怀疑它是否能正常工作。发布该代码以获取详细信息。

标签: c floating-point type-conversion


【解决方案1】:

使用整数:

#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

【讨论】:

    【解决方案2】:

    主要问题是乘法和舍入工作正常,除非有溢出(ceilf(*cash * 100) 的结果没有小数位),但除以 100 会导致精度损失(例如,可能 1234/100 = 1.233999999)。

    原因是某些分数是无限的(例如 1/3 = 0.333333.... 在十进制中,需要无限位数来存储而不会损失精度),并且在不同的基数下,不同的分数会变成无限的。具体来说; 1/10 或 0.1 是二进制的重复分数(它变成 0.00110011001100110011...b),因此 1/10 的所有幂也是二进制的无限分数(1/100、1/1000、1/10000、.. .).

    解决问题的正确方法是正确解析用户输入。如果用户输入像“Bork!”这样的字符串,那么他们应该收到一条错误消息,指出它不是数字,如果用户输入像“9999999999999999999999999999999999999”这样的字符串,他们应该被告知数字太大,如果用户输入像“0.001”这样的字符串,他们应该被告知 1 美分的派系无效;并且在每种情况下,都应要求用户重试,以便程序的其余部分不必处理无效输入。

    一旦您开始正确解析(并且在您意识到 scanf() 不适合任何目的之后),您将意识到正确解析用户输入的最简单方法是编写代码进行转换(有很多理智检查)将输入字符串直接转换成整数美分。

    对于一个粗略的(未经测试的)示例:

    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;
    }
    

    请注意,这只是一个开始 - 您可以有更多/更好的错误消息(例如“错误:有效数字后的未知字符”),并且更容易接受有效输入(例如,如果用户输入字符串“@987654327 @" 你可以悄悄地忽略$ 和千位分隔符)。

    【讨论】:

      【解决方案3】:

      尽可能使用整数来编码货币的最低单位。

      long long cash;代替float cash;

      作为double读入后,转换为整数。

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

      使用整数数学来操纵金钱。

      要印钱,请转回double

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多