Numeric promotions - where/what exactly is the C++ theory?

Reading through a C++ course, I'm trying to figure out how numeric promotions work in an expressions containing numeric values (literals in this case).
This is what this course says:

promotions are conducted according to the following set of rules (the rules are applied in the order below until all the data used in a particular expression has the same type):

1. data of type char or short int will be converted to type int;
2. data of type float undergoes a conversion to type double;
3. if there’s any value of type double in the expression, the other data will be converted to a double;
4. if there’s any value of type long int in the expression, the other data will be converted to long int;


OK, so I did this test:

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
int main(){
	int Int;
	Int = 1 / 2 * 2 / 1. * 2. / 4 * 4;
	cout << Int;     
	return 0;
}


According to the above theory, I should get 2.0 (after all values are converted to doubles) which converted to int would be 2.

But reality says otherwise: using g++-4.9.1 I get 0.

Now is the above theory wrong or what?
I tried looking up this topic on cppreference.com (which seems to be the preferred reference around here), but all I found is a one-page megatext which does not even mention this order of numeric promotions I quoted at the beginning. Just a bunch of useless chinese megaobfuscation which doesn't tell me anything meaningful.
Last edited on
closed account (SECMoG1T)
the theory is correct, the reason you get the result is because of something
called "implicit conversion" a statically declared integer variable can only hold an integer value, assigning a double to it then cause the value to be implicitly converted to an int losing some info.

https://www.quora.com/What-happens-when-you-assign-fractional-number-to-an-integer-variable-in-c-program
Last edited on
1/2 is zero. Multiply through to get zero. This isn't theory, it is simply integer math.

change the first one to 2. and you get the expected result. Because Int is an int, you get 2 instead of 2.0.

Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1 / 2 * 2 / 1. * 2. / 4 * 4
 =
(((((1 / 2) * 2) / 1.0) * 2.0) / 4) * 4 // Multiplication and division are left-associative.
 =
((((0 * 2) / 1.0) * 2.0) / 4) * 4 // 1/2 => 0
 =
(((0 / 1.0) * 2.0) / 4) * 4 // 0*2 => 0
 =
(((0.0 / 1.0) * 2.0) / 4) * 4 // int => double
 =
((0.0 * 2.0) / 4) * 4 // 0.0/1.0 => 0.0
 =
(0.0 / 4) * 4 // 0.0*2.0 => 0.0
 =
(0.0 / 4.0) * 4 // int => double
 =
0.0 * 4 // 0.0/4.0 => 0.0
 =
0.0 * 4.0 // int => double
 = 
0.0 // 0.0/4.0 => 0.0 
@jonnin
Yes, I understand that integer division of 1/2 yields zero.
The problem is 1 and 2 and 2 and 4 AND ALL THE REST of the expression

1 / 2 * 2 / 1. * 2. / 4 * 4

were supposed to be converted to double before computing the expression, if the theory I quoted is correct.

So according to the 4 steps I quoted in my first message, I thought ALL literals in this expression were supposed to be CONVERTED AT ONCE to double BEFORE ANY OPERAND WOULD ACT.

But, from what @Peter87 says, I suspect the problem is, those 4 steps only apply to the OPERANDS OF ONE SINGLE OPERATOR at a time, IN THEIR ASSOCIATIVITY ORDER (in this case left-to-right).

So 1 / 2 is taken as a separate expression through the 4 steps, then the result (0, int) is taken into the next expression 0 * 2 through the same 4 steps again, etc.

Well, then the theory I quoted is not quite accurate.
It fails to specify that "expression" is supposed to have ONLY ONE operand.

FYI this aspect would never be noticed if for instance this expression had a * operator instead of the / operator.

So, did I get this right - this time?
@Peter87, is this what you meant?
The operand of an expression may be a subexpression.
To evaluate an expression, each of its subexpressions are evaluated.

For example, as a part of the evaluation of e1 + e2, the subexpressions e1 and e2 are evaluated.

To evaluate 2/5 + 7.3, the subexpression 2/5 (involving integer division) is evaluated;
the result of its value computation is the integer zero.
@JLBorges

Yes, ok, I understand that, but if you replace the + with a *, all operators will have the same precedence and it wasn't clear to me how the promotion will work in a case like that.
If you look at my example and replace all int literals with doubles at once before evaluating anything...

What I'm talking about is whether those 4 promotion steps apply:

- ONCE to ALL OPERANDS in the whole expression at once BEFORE operating anything,

or they apply

- MULTIPLE TIMES for ONE operator at a time, in the associativity order of the operators in the expression.

Choosing one or the other gives different results.

And I guess the second option is the one implemented by the compiler.
I just don't find this clearly specified anywhere, not even the 4 promotion steps... if I had not read this course, I would have never even known about the 4 steps.
Last edited on
cristian c wrote:
And I guess the second option is the one implemented by the compiler.
I just don't find this clearly specified anywhere,

yes, it is one operator at a time. The text you quote made an error by using the word 'expression' where it was supposed to say 'operator' (it also confusingly combined promotions with arithmetic conversions)

cristian c wrote:
if there’s any value of type double in the expression, the other data will be converted to a double;

The actual rule in the C++ language specification is http://eel.is/c++draft/expr#11.3 (which describes "usual arithmetic conversions", not "promotions")
standard wrote:
Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. [...] Otherwise, if either operand is double, the other shall be converted to double.

Then if you look at the individual operator definition, http://eel.is/c++draft/expr#mul-2
standard wrote:
The operands of * and / shall have arithmetic or unscoped enumeration type; [...] The usual arithmetic conversions are performed on the operands

You can also find that on cppreference.com (which is more precise about the "many") here: http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions
cppreference wrote:
For the binary operators (except shifts), if the promoted operands have different types, additional set of implicit conversions is applied, known as usual arithmetic conversions [...] Otherwise, if either operand is double, the other operand is converted to double

and likewise in the actual operator desc http://en.cppreference.com/w/cpp/language/operator_arithmetic#Multiplicative_operators

in any case, this is about an operator, not about an expression.
Last edited on
> What I'm talking about is whether those 4 promotion steps apply

The rules (usual arithmetic conversions) are a bit more involved than what those four steps specify.
For details, see: http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions


> ONCE to ALL OPERANDS in the whole expression at once BEFORE operating anything

No.


> MULTIPLE TIMES for ONE operator at a time

Yes; this is done (usual arithmetic conversions are performed) (individually) for the evaluation of each subexpression.


> In the associativity order of the operators in the expression.

The order in which the subexpressions are evaluated is governed by the sequenced-before rules of the language.
See: http://en.cppreference.com/w/cpp/language/eval_order

Here is an example. With:
1
2
3
4
int i = -2 ;
unsigned int u = 3 ;
float f = 4.0 ;
double d = 5.0 ;

evaluate i / u * f + d / i => due to precedence and associativity, parsed as ( (i/u) * f ) + (d/i)
1. evaluate i/u => apply usual arithmetic conversions => (unsigned int)i / u => result is unsigned int, say r1
2. evaluate r1 * f => apply usual arithmetic conversions => float(r1) * f => the result is float, say r2
3. evaluate d/i => apply usual arithmetic conversions => d / double(i) => the result is double, say r3
4. evaluate r2 + r3 => apply usual arithmetic conversions => double(r2) + r3 => to yield the result (double) of the whole expression

However, there the subexpression in step 3 may be evaluated before the subexpression
in step 2, or vice versa, or their evaluations may interleave. The two evaluations are unsequenced.
Last edited on
OK, then it's all clear.

Thank you everybody for your replies !
Topic archived. No new replies allowed.