0

I had the issue that

voltage = voltage*2/3; and voltage *= 2/3;

gave different results. The variable is uint16_t and is running on an 8bit AVR microcontroller First statement gave the correct result, second statement always returned 0.

Some friends of mine told me that unary operators should not be used in general which made me think since I also use things like PORTC &= ~(1 << csBit);. For compiling, I use avr-gcc if that might give you an idea.

Thanks in advance for your help

edit#1:

OK, I understood that = is not an unary operator. Also the underlying difference is that '''=''' is starting from the right and ''''*, /''' is starting from left.

I guess for uints, both statements are not correct and I would have to write voltage = (uint16_t)((float)voltage*(float)2/3)

and thanks @Lundin for pointing out how to correctly react to replies

jkbs1337
  • 53
  • 6
  • 2
    `voltage *= 2/3;` is equivalent to `voltage = voltage * (2/3)`. The `*=` is not a unary operator, it's a binary operator belonging to the _compound assignment_ group of operators. – Lundin Dec 09 '21 at 10:16
  • 2
    Btw `1 << ...` is almost always a bug on 8 and 16 bit microcontrollers, because you might end up shifting data into the sign bit of the `int` type created by `1`. Never do bitwise arithmetic on signed operands, replace `1` with `1u`. – Lundin Dec 09 '21 at 10:18
  • The term for assignment operators like `*=` or `+=` is **compound assignment**, as opposed to the **direct assignment** operator `=`. – ndim Dec 19 '21 at 12:15

3 Answers3

6

voltage = voltage*2/3 multiplies voltage by 2, divides by 3, and stores the result in voltage.

voltage *= 2/3 divides 2 by 3, multiplies the result by voltage and stores the result of that in voltage. Integer division truncates, so 2/3 produces zero.

None of those are unary operators.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
2

You’re being bitten by a combination of differing order of operations and integer arithmetic.

Arithmetic operators are left-associative, so

voltage = voltage * 2 / 3;

is parsed as

voltage = (voltage * 2) / 3;

you’re dividing the result of voltage * 2 by 3, whereas

voltage *= 2 / 3;

is equivalent to

voltage = voltage * (2 / 3);

you’re multiplying voltage by the result of 2/3, which is 0.

The problem isn’t so much the *=, the problem is that

(a * b) / c != a * (b / c)
John Bode
  • 119,563
  • 19
  • 122
  • 198
1

the difference is that in voltage = voltage * 2 / 3, voltage is multiplied by 2 and the result divided by 3:

 voltage = 5
 5 * 2 = 10
 10 / 3 = 3

while in voltage * = 2 / 3, since you are using uint16_t, and therefore the decimals are truncated it is first performed 2/3 and the result multiplied by voltage:

 votage = 5
 2 / 3 = 0
 voltage = 5 * 0 = 0

to avoid this you should, for example make the calculation run in floating point before it is assigned to voltage, for example by adding ".0" somewhere:

 voltage = voltage * 2.0 / 3 = 3
 voltage *= 2.0 / 3 = 3
  • 2
    Using floating point and division on a 8 bit AVR is very bad advise, since it doesn't have a FPU and will produce extremely bad machine code out of your examples. Simply stick to the version of the code which is correct. If higher accuracy is needed, that should be solved with fixed point arithmetic. – Lundin Dec 09 '21 at 10:22
  • The specific example `voltage *= 2.0 / 3 = 3` is incorrect - it will generate a more exact non-integer result (3.333...). Not sure what OP wanted; probably not this. But the idea is good anyway (for someone else than OP). – anatolyg Dec 09 '21 at 10:24
  • ah ok. then simply the form voltage *= 2/3; it cannot be used. –  Dec 09 '21 at 10:28
  • 1
    @anatolyg: it is not correct. once the result is assigned to voltage the decimals will be truncated (auto cast) –  Dec 09 '21 at 10:31
  • 1
    @jkbs1337: voltage = (uint16_t)((float)voltage*(float)2/3); those recasts are not needed, the form should suffice: voltage = voltage * 2.0 / 3; (at least it is for gcc). –  Dec 09 '21 at 11:02