C++中的精确百分比

8

给定两个数字,其中 A <= B,例如 A = 9B = 10,我正在尝试计算 A 相对于 B 的较小百分比。 我需要将百分比作为 int。例如,如果结果是 10.00%,则int应该是 1000。

这是我的代码:

int A = 9;
int B = 10;

int percentage = (((1 - (double)A/B) / 0.01)) * 100;

我的代码返回了999而不是1000。与使用 double 相关的一些精度丢失了。

在我的情况下,有没有方法可以避免失去精度?


尝试将其存储在浮点变量中并转换为整数。对我有效。浮点百分比= (((1-(double)A/B)/ 0.01))* 100; cout <<(int)percentage; 显示1000。 - Vivek V K
@VivekVK 使用浮点数可能对某些值有效,但并不能解决根本问题。 - Mohit Jain
@Vivek V K:即使百分比是“double”,它也可以工作...不太确定为什么... - DragonSlayer
你的计算似乎有点过于复杂了。使用更简单的 ((B-A) / (double)B) * 100.0 有什么问题吗?另外请注意,浮点格式中无法精确表示0.01,因此会在你所编写的计算中引入一些误差。 - Dale Hagglund
4个回答

10

看起来你正在寻找的公式是

int result = 10000 - (A*10000+B/2)/B;

这个想法是使用整数进行所有计算,并延迟除法。

在执行除法之前,将分母的一半加上以进行四舍五入(否则,在除法中会得到截断,因此由于100%-x而导致上舍入)

例如,对于A = 9和B = 11,百分比为18.18181818 ...,四舍五入为18.18,没有四舍五入的计算会给出1819而不是预期的结果1818。

请注意,计算都以整数完成,因此在大的AB值的情况下存在溢出风险。例如,如果int是32位,则在计算A*10000时,A最大可以达到约200000,否则可能会发生溢出。

在公式中使用A*10000LL而不是A*10000将交换一些速度以提高极限值。


之前你发布了 10000 - A * 10000 / B,虽然它能工作,但会进行一些四舍五入。你能解释一下为什么要加上 +B/2 吗? - DragonSlayer
这个解决方案在不擅长浮点运算的CPU上比OP更快(且精度完美)。 - Mohit Jain
3
请记住,这仅适用于较小的A值(使用A * 10000 + B/2时,大值可能会溢出)。 - M.M
@MattMcNabb:添加了关于溢出风险的注释。 - 6502

2

浮点数可能存在精度损失。你可以像@6502所说的那样使用定点数,或者在结果中添加一个偏差来获得预期的答案。

建议采取以下措施:

assert(B != 0);
int percentage = ((A<0) == (B<0) ? 0.5 : -0.5) + (((1 - (double)A/B) / 0.01)) * 100;

由于精度损失的原因,(((1 - (double)A/B) / 0.01)) * 100 的结果可能会比预期略微小或大。如果你加上额外的 0.5,则可以保证结果略微大于预期。现在,当将这个值转换为整数时,你会得到预期的答案。(向下取整或向上取整,具体取决于方程式结果的小数部分是否大于或小于 0.5)

谢谢,您的解决方案看起来不错! - DragonSlayer
此链接提供了有关浮点数行为的一些信息。 - Mohit Jain
注意,这需要修改才能用于负数。 - M.M

0

我尝试过

float floatpercent = (((1 - (double)A/B) / 0.01)) * 100;
int percentage = (int) floatpercent;
cout<< percentage;

显示1000

我怀疑自动转换为int时的精度损失是您代码的根本问题。


0

[我在对原问题的评论中提到了这一点,但我认为我应该将其发布为答案。]

核心问题是,您正在使用的表达形式在表示10的简单分数时放大了无法避免的浮点精度损失。

您的表达式(现在去除强制类型转换,使用标准优先级也避免了一些括号)

((1 - A/B) / 0.01) * 100

这是一种相当复杂的表示方式,虽然在代数上是正确的。不幸的是,浮点数只能精确地表示像1/2、1/4、1/8等数字,它们的倍数以及这些数字的和。特别地,9/10、1/10或1/100都没有精确的表示。

上述表达式两次引入了这些误差:首先是在计算A/B时,然后是在除以0.01时。这两个不精确的值然后被除以,进一步放大了固有误差。

最直接的写法(同样不需要强制转换)是:

((B-A) / B) * 10000

这将产生正确的答案,而且相对于原始代码来说更易读。我建议使用完全正确的C语言形式:

((B - A) / (double)B) * 10000

我已经测试过这个,它的可靠性很高。正如其他人所指出的那样,通常最好使用双精度浮点数而不是单精度浮点数,因为它们的额外精度使它们不太容易(但并非免疫)遇到这种困难。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接