C How to calculate a percentage(perthousands) without floating point precision

11,274

Solution 1

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

Solution 2

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

Solution 3

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

Solution 4

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

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

Solution 5

Why dont you use

resultPercentage = (x*1000)/(x+y);
Share:
11,274
Ashitakalax
Author by

Ashitakalax

University of Utah graduate offering a strong academic background in computer engineering combined with excellent internship experience and three years in industry. Architected a new software infrastructure utilizing the latest programming practices held to a high coding standard. Currently leading an R&amp;D team of developers in creating a new line of fitness equipment.

Updated on June 18, 2022

Comments

  • Ashitakalax
    Ashitakalax almost 2 years

    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.