C如何计算没有浮点精度的百分比(千分之一)

vvppvyoh  于 2023-05-28  发布在  其他
关注(0)|答案(7)|浏览(148)

如何将2个int值的百分比计算为表示百分比的int值(perthousands以获得更高的精度)?
背景/目的:使用没有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
但是输出是以使用浮点计算为代价的。

wljmcqd8

wljmcqd81#

resultPercentage = (x/(x+y))*1000;不工作,因为在乘法*1000发生之前,(x/(x+y))可能是01。相反:
对于x/(x+y)的四舍五入无符号整数计算,让a = xb = x+y然后找到a/b用途:

result = (a + b/2)/b;

对于舍入的无符号整数percent %a/b的计算使用

result = (100*a + b/2)/b;

对于舍入的无符号整数permil ‰a/b的计算使用

result = (1000*a + b/2)/b;

对于舍入的无符号整数permyriad ‱a/b的计算使用

result = (10000*a + b/2)/b;

关于吃掉整数范围的担忧:使用更宽的整数数学(unsigned long long)进行乘法运算,可能还有x+y

result = (100ULL*a + b/2)/b;

对于 signeda, b,它更复杂,因为b/2需要匹配a/b的符号。

if (b > 0) {
  if (a >= 0) { 
    result = (a + b/2)/b;
  } else {
    result = (a - b/2)/b;
  }
} else {
  if (a >= 0) { 
    result = (a - b/2)/b;
  } else {
    result = (a + b/2)/b;
  }
}

可以对一个衬垫进行编码:

result = (a + (((a < 0)==(b < 0)) ? b/2 : b/-2))/b;

b/-2-b/2更好地防止b == INT_MIN的UB。或者使用-(b/2)
当然,替换

// printf(resultPercentage);
printf("%d\n", resultPercentage);
// or for unsigned
printf("%u\n", resultPercentage);
vaj7vani

vaj7vani2#

你可以直接写

(x * 1000 + 5) / (10 * (x + y))

如果您关心正确的舍入,并且

(x * 100) / (x + y)

如果你不这么做的话
你说的是“百分比”,但我注意到你乘以1000,结果是千分之一。如果这就是你的意思,那么当然你需要将乘法因子分别更改为10000和1000。
此外,使用整数会显著减少要执行计算的值的有效范围。如果您强制中间结果为更长的类型,特别是(有符号或无符号)long long,则可以稍微扩大:

(x * 1000ll + 5) / (10ll * (x + y))

应该做的伎俩(由于整数提升)。

nbnkbykc

nbnkbykc3#

使用长除法的解决方案

现在来看看纸笔的答案不确定这是否会比你的处理器内置的浮点运算更快,但这是一个有趣的事情(也许可以改进)。这是一个长除法的实现(还记得吗?)-原则上它是“无限精确的”,有点像BigDecimal数学-实际上它是有限的,因为为字符串分配了有限的空间(您可以使用malloc/free来更改)。如果你在处理器上的(代码)空间上有问题(以及缺乏专用的浮点单元),那么这绝对不是要走的路;我还假设 all 除法(偶数)会很慢,并且只使用乘法,加法和减法。
最后的好处-结果以字符串的形式输出,省去了以后单独进行printf样式转换的需要。有许多可以想象的方法来加速这一点;现在,看看你如何用有限精度的整数来解决这个问题,还能得到一个“非常好”的答案,这很有趣。顺便说一句,根据我的行人计时代码,结果比divide-and-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
bqjvbblv

bqjvbblv4#

你为什么不用

resultPercentage = (x*1000)/(x+y);
8hhllhi2

8hhllhi25#

稍微改变一下表情就能达到目的。就像这个例子:
result =(x*1000)/(x+y);应该能胜任

sshcrbum

sshcrbum6#

试试这个:

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);
j5fpnvbx

j5fpnvbx7#

如果你的要求只是找到一个值,它给出了两个数字之间的关系,那么不用1000,你可以用1024
与1000相乘需要几个周期,但您可以使用
result =(x<<10)/(x+y);
乘法只需要10个周期。即使结果与1000也不会有很大的变化。找到百分比后,您可能会使用所获得的百分比进行一些阈值处理,只需更改比较值即可。
假设您正在使用

if(resultPercentage > 500) do something

代替使用

if(resultRelation > 512) do something

通过这种方式,您可以找到对应的Map值并将其替换。如果你是非常严格的周期和准确性,然后去保存查找表转换为0-1024到0-1000,但它使用了大量的RAM

相关问题