C How to calculate a percentage(perthousands) with

2019-06-24 01:17发布

How do you calculate a percentage from 2 int values into a int value that represents a percentage(perthousands for more accuracy)?

Background/purpose: using a processor that doesn't have a FPU, floating point computations take 100's of times longer.

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

output:

Result= 0

When really what I need is 250. and I can't use ANY Floating point computation.

Example of normal fpu computation:

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

output:

Result= 250

But the output came at the cost of using floating point computations.

7条回答
男人必须洒脱
2楼-- · 2019-06-24 01:57

resultPercentage = (x/(x+y))*1000; does not work as (x/(x+y)) is likely 0 or 1 before the multiplcation *1000 occurs. Instead:

For a rounded unsigned integer calculation of x/(x+y), let a = x and b = x+y then to find a/b use:

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

For a rounded unsigned integer percent % calculation of a/b use

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

For a rounded unsigned integer permil ‰ calculation of a/b use

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

For a rounded unsigned integer permyriad ‱ calculation of a/b use

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

@H2CO3 wells points out the concerns about eating up the integer range so using wider integers (long, long long) are needed for the multiplication and maybe x+y.

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

Of course, replace

// printf(resultPercentage);
printf("%d\n", resultPercentage);
查看更多
一夜七次
3楼-- · 2019-06-24 01:58

A slight modification of expression can do the trick. Like in this case :

resultPercentage = (x*1000)/(x+y); should do the job.

查看更多
仙女界的扛把子
4楼-- · 2019-06-24 02:02

try this:

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);
查看更多
我想做一个坏孩纸
5楼-- · 2019-06-24 02:03

You could just write

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

if you care about correct rounding, and

(x * 100) / (x + y)

if you don't.


You are talking about "percentages", but I noticed that you are multiplying by 1000, which results in perthousands. If that's what you meant, then of course you'll need to change the multiplication factors to 10000 and 1000, respectively.

Furthermore, using integers significantly reduces the valid range of values to perform the computation on. This can be a bit widened if you force the intermediate results to be of a longer type, notably (signed or unsigned) long long:

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

should do the trick (due to integer promotion).

查看更多
不美不萌又怎样
6楼-- · 2019-06-24 02:06

A solution using long division

And now for the pencil-and-paper answer… Not sure if this will be faster than your processor's built in floating point arithmetic, but it's a fun thing to look at (and maybe improve upon). This is an implementation of long division (remember that?) - in principle it is "infinitely precise", a bit like BigDecimal math - in practice it is limited because a finite amount of space is allocated for the string (you could change that with a malloc/free). If you have trouble with (code) space on your processor (as well as the lack of a dedicated floating point unit) then this is definitely not the way to go; also I am assuming that all division (even integer) would be slow, and using only multiplication, addition and subtraction.

Final bonus - the result comes out as a string, saving the need for a separate printf style conversion later. There are many ways conceivable for speeding this up; for now it's just fun to see how you can solve this problem with only limited precision integers, yet get a "very good" answer. Incidentally, according to my pedestrian timing code, the result is faster than a divide-and-sprintf routine (which was pretty gratifying). Almost certainly due to the fact that the conversion to a string of digits is happening "almost for free" (if you think about how that is normally done, it takes lots of division/modulo math…).

EDIT in its current form, this code even takes account of rounding (computing one additional digit, and then making the necessary adjustments). One caveat: it only works for positive integers.

Play with it and tell me if you like it!

#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 (without optimization turned on):

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
查看更多
爷的心禁止访问
7楼-- · 2019-06-24 02:20

Why dont you use

resultPercentage = (x*1000)/(x+y);
查看更多
登录 后发表回答