计算百分比时出现意外结果 - 即使在分解整数除法规则时也是如此

时间:2015-06-15 13:50:18

标签: c percentage integer-overflow integer-division

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

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

正如你所看到的,我已经通过将分子乘以100来减少整数除法。

使用battery = 2756我的费用值计算为04,这是意料之外的。我在这项相当微不足道的工作上花了很多年的时间,并没有取得任何进展。任何人都可以建议问题出在哪里?

感谢。

2 个答案:

答案 0 :(得分:5)

Diagnosis

The value you expect is, presumably, 91.

The problem appears to be that your compiler is using 16-bit int values.

You should identify the platform on which you're working and include information about unusual situations such as 16-bit int types. It is reasonable for us to assume 32-bit or 64-bit systems by default — they are by far the most common environments for questions on SO.

You have battery = 2756. If that is multiplied by 100, it gives you 275600 in 32-bit precision, but in 16-bit precision, it gives 13546, which, when divided by 3000, yields 4, the observed answer.

Here's code compiled with a 64-bit compiler that simulates the 16-bit calculation:

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

The output from it is:

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

To get a 16-bit calculation, I had to force the intermediate results into 16-bit variables. Yes, there are other more elaborate ways of writing the code, using uint16_t etc. And yes, the overflow in the assignment to r1 is strictly undefined behaviour, but my compiler (GCC 5.1.0 on Mac OS X 10.10.3) appears to be giving a sane interpretation to the undefined behaviour. This serves to demonstrate the point.

Plausible fix

You should be able to work around it on your machine by using long:

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

The L in 100L makes it a long value, which must be at least 32-bits in a conforming C compiler, so the multiplication must give a long value, and the divisor will be converted to long before the division, and the result will be a long (and the value will be 91), and you can assign that to charge safely.

Alternatively, you could use one or more explicit (long) casts in the calculation if you think this is too subtle.

答案 1 :(得分:0)

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