计算百分比时出现意外结果 - 即使考虑整数除法规则

Unexpected result when calculating a percentage - even when factoring in integer division rules

我试图用百分比表示电池电压。我的电池电量是一个(全局)uint16,单位为 mV。我有一个 16 位 CPU。这是我的代码:

static uint8 convertBattery(void){
    uint16 const fullBattery = 3000; /* 3V = 3000mV */
    uint8 charge;
    charge = ((battery*100)/fullBattery);
    return charge;
}

如您所见,我通过先将分子乘以 100 来考虑整数除法舍入。

对于 battery = 2756,我的费用值计算为 04,这出乎意料。我在这个相当琐碎的任务上花了很长时间,但没有取得任何进展。谁能指出问题出在哪里?

谢谢。

battery*100 的中间结果对于 uint16 来说太大了,所以会溢出。

诊断

您期望的值大概是 91。

问题似乎是您的编译器使用的是 16 位 int 值。

您应该确定您正在使用的平台并包括有关异常情况的信息,例如 16 位 int 类型。我们默认使用 32 位或 64 位系统是合理的——它们是迄今为止最常见的 SO 问题环境。

您有 battery = 2756。如果将其乘以 100,它会以 32 位精度给出 275600,但在 16 位精度下,它会给出 13546,当除以 3000 时,得到 4,观察到的答案。

下面是用模拟 16 位计算的 64 位编译器编译的代码:

#include <stdio.h>

int main(void)
{
    short battery = 2756;
    short fullbattery = 3000;
    short r1 = battery * 100;
    printf("r1 = battery * 100 = %d\n", r1);
    short r2 = r1 / fullbattery;
    printf("r2 = (battery * 100) / fullbattery = %d\n", r2);
    int r3 = (battery * 100) / fullbattery;
    printf("r3 = (battery * 100) / fullbattery = %d\n", r3);
    return 0;
}

它的输出是:

r1 = battery * 100 = 13456
r2 = (battery * 100) / fullbattery = 4
r3 = (battery * 100) / fullbattery = 91

为了进行 16 位计算,我不得不将中间结果强制转换为 16 位变量。是的,还有其他更详细的代码编写方法,使用 uint16_t 等。是的,对 r1 的赋值溢出是严格未定义的行为,但我的编译器(GCC 5.1.0 on Mac OS X 10.10.3) 似乎对未定义的行为给出了理智的解释。这就说明了这一点。

合理的修复

您应该可以在您的计算机上使用 long:

来解决这个问题
static uint8 convertBattery(void){
    uint16 const fullBattery = 3000; /* 3V = 3000mV */
    uint8 charge = (battery * 100L) / fullBattery;
    return charge;
}

100L 中的 L 使其成为 long 值,在符合标准的 C 编译器中必须至少为 32 位,因此乘法必须给出 long 值,除数在除法之前会被转换为 long,结果将是 long(并且值为 91),您可以将其分配给 charge 安全。

或者,如果您认为这太微妙,您可以在计算中使用一个或多个显式 (long) 转换。