【问题标题】:I am not sure why my code is producing this arithmetic error?我不确定为什么我的代码会产生这个算术错误?
【发布时间】:2016-03-16 19:51:45
【问题描述】:

大家好,我正在用 c 语言编写一个程序,它会告诉您任何给定金额所需的最少硬币数量。我编写了一个程序,适用于我测试过的所有金额,除了 4.20 美元。

这是我的代码:

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

int main(void)
{
    float f;
    int n, x, y, z, q, s, d, t;

    do {
        printf("How much change do you need?\n");
        f = GetFloat();
    } while(f <= 0);

    {
        n = (f * 100);
    }

    q = (n / 25);
    x = (n % 25);
    y = (x / 10);
    z = (x % 10);
    s = (z / 5);
    d = (z % 5);
    t = (q + y + s + d);

    {
        printf("%d\n" ,t);
    }
}

奇怪的是,当我输入 4.20 时,输出是 22 而不是 18(16 个四分之一和 2 个角钱)。我做了一些调查,发现问题出在我的变量 x 上。当我输入 4.2 时,x 给了我 19 而不是应该的 20。我尝试了其他我认为应该产生相同问题的案例,例如 5.2 和 1.2,但在这些案例中它可以正常工作。这可能是一个四舍五入的问题,但我认为这些相似的值也会发生同样的错误。

有人知道为什么会发生这种情况吗?

PS 我对编码还很陌生,而且我没有得到太多正式的指导,所以如果你看到任何明显的东西,我也欢迎关于更好的缩进和格式的提示。

【问题讨论】:

  • 舍入错误,n大概是419。使用n = round(f*100);
  • float f; 已经错了。如果您需要精确值,切勿使用浮点数!稍加努力,您就会发现这个问题已经在这里问过多次了。
  • 变量名可以(几乎)任意长度。明智地使用它,给它们起描述性的名字!
  • @Olaf:当您需要从用户那里获得float 时,float 没有任何问题(我认为这就是 GetFloat 函数返回的内容)。 OP 通过在早期转换为 int 做正确的事情,他们只是忘记考虑 IEEE 浮点不精确性,并且在转换为 int 美分之前没有四舍五入。
  • 是的,可以使用longDescriptiveName1, longDescriptiveName2, longDescriptiveName3... :)

标签: c floating-point cs50


【解决方案1】:

IEEE 754 floating point 通常有点不精确,并且转换会截断而不是圆整。可能发生的情况是 4.20 * 100 的计算结果为 419.999999999999994(确切的数字无关紧要,关键是,它不是 420),而转换为 int 会去掉小数部分,产生 419。

简单的方法就是这样做:

n = f * 100 + 0.5;

或者您可以使用适当的功能:

n = round(f * 100);

如果数字“几乎”准确,则任何一个都可以,只有当有人通过非整数美分("4.195" 等)时,如果您使用 float货币价值,您已经接受了边际精度问题;如果您想要精确的数字,您可以使用decimal 格式,该格式对十进制值具有固定精度,并且用于财务计算。

【讨论】:

  • 这不是不精确的问题,而是数字基础的问题。用户使用基数 10,但代码使用基数 2,它不具有精确等价性。
  • @ravenspoint 你所描述的是不精确的原因
  • 更好地使用round(f * 100); f * 100 + 0.5; 对于许多负数、f 小于 0.5 和许多 ULP 为 0.5 或 0.25 的 f 失败。
  • @Ctx 了解原因很重要。如果您不知道,您可能会认为从浮点数更改为双精度数会有所帮助。它不会。尽管双精度数更精确,但它们没有以 10 为底的精确等值。
  • @ravenspoint 是的,您在这里明确说明原因很好,它甚至应该附加到上面的答案中。我只是不同意不称其为“不精确”
【解决方案2】:

试试这个:提供高达 2 位的精度。

//float f
double f

f *= 1000;
f = floor(f);  /* optional */
f /= 10;
f = floor(f);  /* optional */
n = f;

【讨论】:

  • @patrick-leppink-shands 这是为了提供 2 位精度。
  • @patrick-leppink-shands,就编码风格而言,使用 cmets 来解释代码的特定部分,对变量使用更具描述性的名称,适当的缩进,使用空行方便眼睛等。
  • 这并不能避免问题
  • 添加此代码如何回答问题的说明将改善您对未来访问者的回答(此回答被标记为低质量)。
  • @Ravi 尝试“看看它如何为您工作”正是应该避免的。标准的存在使得所有实现的行为方式都相同(至少在一定程度上),因此解决方案适用于所有系统,无需反复试验。对于这种情况,真正的解决方案是避免使用floatdouble 数据类型,如果这完全不可能,那么round() 是最好的选择。您的解决方案使用floor(),这完全导致了问题中描述的那种问题。我不打算比必要的更严厉,但我要投反对票。
猜你喜欢
  • 1970-01-01
  • 2015-05-10
  • 2018-04-19
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多