将 float 表达式的结果存储到 C 中的 int 变量中的正确方法是什么?

What is the correct way of storing the result of a float expression into an int variable in C?

让我们考虑以下代码:

#include <stdio.h>
int main()
{
    float dollars;
    int cents;

    dollars = 159.95;
    cents = dollars*100;

    printf("Dollars:%f\tCents:%d\n",dollars,cents);

    return 0;
}

输出将是:

Dollars: 159.949997    Cents:15995

我知道 159.95 没有精确的二进制表示。但是我不确定为什么值 15995 存储在变量 cents 中。

我想知道在这些情况下是否需要以这种方式在表达式中使用 round:

cents = round(dollars*100);

处理这些案例的最佳做法是什么?

请注意,这个使用货币的例子只是一个例子。我想讨论一般情况。是否有执行此类操作的一般最佳实践?

What is the best practice for dealing with those cases?

谈到货币,通常最好*不要使用浮点数。对那些使用整数。以下是关于货币的一些浮动问题:

  • 大多数小数不能精确表示

  • 当数字变得太大时,浮点数会悄悄地失去最后整数位的精度

但仅仅使用整数是不够的。如果你只存储美分的数量,你在计算利息等时可能会遇到问题。相反,您想要存储例如数千美分。如果你愿意的话,一毫。但请注意,这不会自动解决所有问题。

另请记住,存储 millicents 需要非常大的整数。一个 32 位数字可以存储 40 亿微分,也就是 40,000 美元。所以首选 64 位整数。

这里有一个 post 很好地描述了使用浮动货币的问题:https://software.codidact.com/posts/284175

引用上面celtschk的回答link:

For example, say that you've got 128 dollars invested with an interest of 0.6%, but in the first year you only get half of that percentage. So how much do you get in the first year? Well, obviously 0.3% of 128 dollars are 38.4 cents, which then get rounded to 38 cents. But let's do the calculation differently: First, we calculate the interest you'd normally get: 0.6% of 128 dollars are 76.8 cents, which get rounded to 77 cents. And then half of this is 38.5 cents which get rounded to 39 cents. This is one cent more.

To avoid this type of error, intermediate calculations should always be done with a higher precision, and only the end result be converted to integer cents with proper rounding.

* 虽然没有规则是没有例外的。引自 this answer:

To give an example, I once was engaged in a lengthy discussion with a programmer who was insisting on representing cashflows with decimals instead of floating point numbers in a software computing risks. In bookkeeping applications decimals are of course the only sane choice (or integers), but for risk management using a lot of stochastic models and numerical approximations, floating point numbers are the right choice.

正如 Eric Postpischil 在下面的评论中提到的那样:

The “fix” is to understanding mathematics and the representations types use and to design software for the particular situations.

如果我们不是在谈论货币,而是在谈论一般的“将浮点数转换为整数”的情况,那么实际上没有任何最佳实践。这一切都取决于个人情况。使用 round 通常会给出“更正确”的结果。

dollar*100 是一个浮点变量。碰巧乘法的结果恰好是一个整数(由于浮点数不精确,完全不能保证)。

然后,当您将 dollar*100 分配给整数时,小数部分会丢失,但在这种特殊情况下它没有影响,因为没有小数部分。因此,碰巧变量 centsdollars.

更精确

159.95 does not have a precise representation

这是正确的(如您的 printf 电话所示)。然而,在至少 float精度1中执行的dollars * 100操作产生的值是正好15595.0000,如下代码所示:

#include <stdio.h>

int main()
{
    float dollars = 159.95f;
    float p = dollars * 100;
    if (p == 15995.00000) printf("Exact!\n");
    else printf("Not exact: truncation may occur!\n");
    return 0;
}

这里发生这种情况纯属“偶然”。如果您将 159.95 更改为(比如说)159.65,那么您将在 cents (15964).

的值中看到预期的截断

您可以在这里进行更多实验:例如,对于值 159.85,计算值 dollars * 100 的最佳表示比 稍大 精确值 (15985.000977),因此在这种情况下也不会发生截断。


1 100 是一个 int 字面值,在执行乘法运算之前被转换为 float

将浮点输入四舍五入为最接近和最小的货币单位。

如果代码使用 0.01,则

#include <math.h>

//                                 double multiplication  
long long money_in_cents = llround(some_float_value * 100.0);
// int is too narrow, use 64+-bit.