如何计算从2个int值到表示百分比的int值的百分比(为了更准确度,数百个)?
背景/目的:使用没有FPU的处理器,浮点计算需要花费100倍的时间。
int x = 25;
int y = 75;
int resultPercentage; // desire is 250 which would mean 25.0 percent
resultPercentage = (x/(x+y))*1000; // used 1000 instead of 100 for accuracy
printf("Result= ");
printf(resultPercentage);
输出:
结果= 0
当我真正需要的是250.我不能使用任何浮点计算。
正常fpu计算的示例:
int x = 25;
int y = 75;
int resultPercentage; // desire is 250 which would mean 25.0 percent
resultPercentage = (int)( ( ((double)x)/(double(x+y)) ) *1000); //Uses FPU slow
printf("Result= ");
printf(resultPercentage);
输出:
结果= 250
但输出的代价是使用浮点计算。
答案 0 :(得分:4)
resultPercentage = (x/(x+y))*1000;
无法正常工作,因为在(x/(x+y))
出现之前,0
可能是1
或*1000
。代替:
对于x/(x+y)
的舍入无符号整数计算,让a = x
和b = x+y
然后查找a/b
使用:
result = (a + b/2)/b;
对于a/b
使用
result = (100*a + b/2)/b;
对于a/b
使用
result = (1000*a + b/2)/b;
对于a/b
使用
result = (10000*a + b/2)/b;
@ H2CO3井指出了关于吃掉整数范围的担忧,因此乘法需要使用更宽的整数(long
,long long
),可能需要x+y
。
result = (100L*a + b/2)/b;
当然,替换
// printf(resultPercentage);
printf("%d\n", resultPercentage);
答案 1 :(得分:3)
你可以写
(x * 1000 + 5) / (10 * (x + y))
如果您关心正确的舍入,
(x * 100) / (x + y)
如果你不这样做。
你在谈论“百分比”,但我注意到你乘以1000,这导致了数百人。如果这就是你的意思,那么当然你需要将乘法因子分别改为10000和1000.
此外,使用整数会显着降低执行计算的值的有效范围。如果强制中间结果为更长的类型,可以稍微扩大一点,特别是(有符号或无符号)long long
:
(x * 1000ll + 5) / (10ll * (x + y))
应该这样做(由于整数提升)。
答案 2 :(得分:2)
使用长除法的解决方案
现在对于铅笔和纸的答案......不确定这是否比处理器的内置浮点运算更快,但是看起来很有趣(也许可以改进)。这是一个长除法的实现(请记住?) - 原则上它是“无限精确”,有点像BigDecimal数学 - 在实践中它是有限的,因为为字符串分配了有限的空间(你可以改变它一个malloc/free
)。如果您的处理器(代码)空间有问题(以及缺少专用的浮点单元),那么这绝对不是可行的方法。我也假设所有除法(偶数整数)都很慢,只使用乘法,加法和减法。
最终奖金 - 结果以字符串形式显示,以后需要单独进行printf
样式转换。有许多方法可以加快这个速度;现在看看如何只用有限的精度整数来解决这个问题,这很有趣,但是得到一个“非常好”的答案。顺便提一下,根据我的行人定时代码,结果比分频和sprintf例程更快(这是非常令人满意的)。几乎可以肯定,由于转换为一串数字“几乎是免费的”(如果您考虑通常如何进行,它需要大量的除法/模数学......)。
EDIT 以其当前形式,此代码甚至考虑了舍入(计算一个额外的数字,然后进行必要的调整)。一个警告:它只适用于正整数。
玩它并告诉我你是否喜欢它!
#include <stdio.h>
#include <time.h>
void doRound(char *s, int n) {
// round the number string in s
// from the nth digit backwards.
// first digit = #0
int ii;
int N = n;
if(s[n] - '0' < 5) {
s[N] = '\0';
return; // remove last digit
}
while (n-- > 0) {
if(s[n] == '.') {
n--;
}
if(s[n] - '0' < 9) {
s[n]++;
s[N] = '\0';
return;
}
s[n] = '0';
}
if (n == -1) {
for (ii = N-1; ii >=0; ii--) {
s[ii+1] = s[ii];
}
s[0] = '1';
}
else s[N] = '\0';
}
void longDivision(unsigned int a, unsigned int b, char* r, int n) {
// implement b / a using only integer add/subtract/multiply
char temp[20]; // temporary location for result without decimal point
char c = '0'; // current character (digit) of result
int ci = 0; // character index - location in temp where we write c
int t = n + 1; // precision - no more than n digits
int dMult = 0; // scale factor (log10) to be applied at the end
int di = 0;
// first get numbers to correct relative scaling:
while( a > b) {
dMult++;
b *=10;
}
while (10 * a < b) {
dMult --;
a*=10;
}
// now a >= b: find first digit with addition and subtraction only
while( b > a ) {
c++;
b -= a;
}
t--;
temp[ci++] = c; // copy first digit
c = '0';
// now keep going; stop when we hit required number of digits
while( b > 0 && t > 0) {
b *= 10;
t--;
while( b > a ) {
c++;
b -= a;
}
temp[ ci++ ] = c;
c = '0';
}
// copy the result to the output string:
temp[ ci ] = '\0'; // terminate temp string
if (dMult > 0) {
r[ di++ ] = '0';
r[ di++ ] = '.';
while( --dMult > 0 ) {
r[ di++ ] = '0';
}
ci = 0;
while( temp[ ci ] != '\0' ) {
r[ di++ ] = temp[ ci++ ];
}
}
else {
ci = 0;
while( temp[ ci ] != '\0') {
r[ di++ ] = temp[ ci++ ];
if( dMult++ == 0 ) r[ di++ ] = '.';
}
}
r[ di ] = '\0';
// finally, do rounding:
doRound(r, n+1);
}
int main(void) {
int a, b;
time_t startT, endT;
char result[20];
int ii;
a = 123; b = 700;
longDivision(a, b, result, 5);
printf("the result of %d / %d is %s\n", b, a, result);
printf("the actual result with floating point is %.5f\n", (float) b / (float) a );
a = 7; b = 7000;
longDivision(a, b, result, 5);
printf("the result of %d / %d is %s\n", b, a, result);
a = 3000; b = 29999999;
longDivision(a, b, result, 5);
printf("the result of %d / %d is %s\n", b, a, result);
startT = clock();
for(ii = 1; ii < 100000; ii++) longDivision(a, ii, result, 5);
endT = clock();
printf("time taken: %.2f ms\n", (endT - startT) * 1000.0 / CLOCKS_PER_SEC);
// and using floating point:
startT = clock();
for(ii = 1; ii < 100000; ii++) sprintf(result, "%.4f", (float) ii / (float) a);
endT = clock();
printf("with floating point, time taken: %.2f ms\n", (endT - startT) * 1000.0 / CLOCKS_PER_SEC);
return 0;
}
结果(未启用优化):
the result of 700 / 123 is 5.6911
the actual result with floating point is 5.69106
the result of 7000 / 7 is 1000.00
the result of 29999999 / 3000 is 10000.0
time taken: 16.95 ms
with floating point, time taken: 35.97 ms
答案 3 :(得分:0)
为什么不使用
resultPercentage = (x*1000)/(x+y);
答案 4 :(得分:0)
表达式的轻微修改可以解决问题。就像在这种情况下:
resultPercentage =(x * 1000)/(x + y);应该做的。
答案 5 :(得分:0)
试试这个:
int x = 25;
int y = 75;
int resultPercentage; // desire is 250 which would mean 25.0 percent
resultPercentage = (x*1000)/(x+y);
printf("Result= ");
printf(resultPercentage);
答案 6 :(得分:0)
如果你的要求只是找到一个给出两个数字之间关系的值,那么你可以使用1024而不是1000;
与1000的乘法将需要几个周期,但您可以使用
resultRelation =(x <&lt; 10)/(x + y);
乘法只需10个周期。 即使结果也不会有很大的变化1000.在找到百分比后,你可能会用你得到的百分比进行一些阈值处理,只需更改比较值。
假设您正在使用
if(resultPercentage > 500) do something
而不是使用
if(resultRelation > 512) do something
通过这种方式,您可以找到相应的映射值并将其替换为它们。如果你对周期和准确性都非常严格,那就去找一个保存的查找表,它将0-1024转换成0-1000但是它使用了大量的内存