0

I'm coding in C - I've got some speed-critical calculations I'm trying to make on a microcontroller, and I want to find the ratio of the numbers without using floating point variables.

I have a byte between 0 and 255, and I want to find a percent of it.. For example - if I want to find 75% of 'value', I'm using this code.

float x = value * 0.75;

where 'value' is a number between 0 and 255.

Is there some clever bit math I can perform to do this calculation? Is there a way to scale the values up and calculate only using integer divides?

CarlosTheJackal
  • 220
  • 1
  • 14
  • 2
    First of all *measure* that this is really a bottleneck! Some modern microcontrollers have floating-point units today. And the compiler might be able to optimize it into the operations you want. Measure and check the generated code before you continue. – Some programmer dude Feb 20 '18 at 18:08
  • 4
    Does `int pct = (value * 3) / 4` not work? – Bob Jarvis - Слава Україні Feb 20 '18 at 18:09
  • What kinds of percentages are you talking here? Any arbitrary integer percentage? Some subset (like increments of 5%)? The math isn't particularly clever, you just have to decide exactly what you need. You do, however, need to be cautious. For example, you can do an arbitrary percentage by multiplying by the percentage number first (*e.g.*, multiply by integer 75 in the case of 75%), then integer divide by 100. But you have to handle possible integer overflow. If your value is 0 to 255 and your word size in the micro is 16 bits, that shouldn't be a problem. – lurker Feb 20 '18 at 18:11
  • The percentage scaler is coming from a value between 0 and 255 too.. – CarlosTheJackal Feb 20 '18 at 18:13
  • 2
    What do you mean by "percentage scaler"? You mean 255 represents 100%? Or is 256 actually 100% and 255 is just shy of 100%? That would make it even easier. Multiply by the scaler value, then divide by 256, which is an 8 bit shift to the right. If 255 is 100%, then multiply by the scaler value and integer divide by 255. – lurker Feb 20 '18 at 18:15
  • 1
    And for a 0 to 100 you can have a lookup table that's not so bad. – Iharob Al Asimi Feb 20 '18 at 18:15
  • At a minimum, avoid `double` math. `float x = value * 0.75;` converts the `value` to a `double`, then multiplies by `0.75`. The product is then converted to a `float`. `float x = value * 0.75f;` (f added) is less work. Yet I think all you need is `unsigned x = value * 3u/4;` or `unsigned x = value * percent / 100u;` – chux - Reinstate Monica Feb 20 '18 at 18:40
  • "Is there some clever bit math I can perform to do this calculation?" --> Post your compilable code[MCVE] that does the job and you will certainly receive good advice on how to improve. Right now, this is too general for an optimal answer. Your call. – chux - Reinstate Monica Feb 20 '18 at 18:46

1 Answers1

1

Thanks everybody! I've got it..

my scaling value is a number between 0 and 256 - so:

percent = (value * scaler) >> 8
CarlosTheJackal
  • 220
  • 1
  • 14
  • 1
    Hint: since you said `value` is a byte value, typecast it up to a larger data type first. Otherwise, the multiply will likely overflow a byte-sized variable and your results will be wrong. Don't trust the compiler to do this automatically. – bta Feb 20 '18 at 18:32
  • 1
    @bta `value * scaler` is done using at least `int` wide math. C specifies _integer promotions_ as part of the `*`. – chux - Reinstate Monica Feb 20 '18 at 18:32
  • @chux - In general you're correct, but you have to be careful with microcontrollers. I've been bitten by this more than once, especially on a 16-bit micro. Default promotion is to regular `int`, but you need to manually specify `unsigned int` or `uint16_t` to avoid `255 * 255` from overflowing. – bta Feb 20 '18 at 18:39
  • I also work with micros. It is more productive to use a more compliant C compiler. Micros' compilers often do has such unavoidable troubles though, I hope you filed a bug report with its maker. – chux - Reinstate Monica Feb 20 '18 at 18:44
  • @chux: the standard only requires that `int` be able to support the range -32767..32767, so having a 16 bit int is perfectly in accordance with the standard... – Chris Dodd Feb 20 '18 at 19:28
  • @ChrisDodd My comment was to bta initial post: "In general you're correct, but you have to be careful with microcontrollers. I've been bitten by this more than once" which didn't detail 16-bit unsigned vs signed OF. That 1 extra bit may help in select cases, yet often even more bits are needed Code can use `1u * value * scaler` to use _unsigned_ math without casting. If code is concerned about 16-overflow, code can use `UINT32_C(1) * value * scaler`. A singular trouble with casting is that it may also truncate the width employed. Multiplying by 1 of the minimal desired type is more gently. – chux - Reinstate Monica Feb 20 '18 at 19:50