计算百分比时出现意外结果 - 即使考虑整数除法规则
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)
转换。
我试图用百分比表示电池电压。我的电池电量是一个(全局)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)
转换。