Problem with *= operator

Hi,

I'm getting in to C++ using the book by Stephen Prata: C++ Primer Plus. As part of explaining functions, listing 7.4 illustrates an odds calculator, which calculates a binomial coeffcient. The code is inserted below (slightly modified to typecast the long double to long for screen printing).

The problem I have is the statement: odds = odds * ntotal / i;

When I use this form, the function returns the correct answer, e.g. binomial(36,7) = 8,347,680. However, if I use the compound assignment:

odds *= ntotal / i,

the function returns binomial(36,7) = 6,462,720, which is wrong.

If these expressions are equivalent, how come they yield different answers?

Thanks for enlightening me.

Br,
redak

------------------------------------------------

// lotto.cpp

#include <iostream>

long double probability(unsigned, unsigned);

int main()
{
using namespace std;
double total, choices;
cout << "Enter the total number of choices on the game card and\n"
"the number of picks allowed:\n";

while ((cin >> total >> choices) && choices <= total)
{
cout << "You have one chance in ";
cout << (long) probability(total, choices); // compute the odds
cout << " of winning.\n";
cout << "Next two number (q to quit): ";
}
cout << "Bye!\n";
return 0;
}

long double probability(unsigned ntotal, unsigned ndraw)
{
long double odds = 1.0;

for (int i = 1; i <= ndraw; i++, ntotal--)
{
odds = odds * ntotal / i;
}

return odds;
}
Last edited on
Those two expressions are not equivalent.
1
2
3
4
5
odds = odds * ntotal / i;
// however
odds *= ntotal / i;
odds = odds * (ntotal / i);
// note the parentheses 
1
2
3
4
for (double i = 1; i <= ndraw; i++, ntotal--)
{
    odds *=( ntotal / i);
}

I try to cast the i to double , then it works well,and prints 8347680. It suggests that when you calculate (ntotal/i),if i is an integer, the precision would lost.
Last edited on
I try to cast the i to double , then it works well,and prints 8347680. It suggests that when you calculate (ntotal/i),if i is an integer, the precision would lost.


This appears to be the right answer. Apparently, the compiler promotos the unsigned ntotal and i in the expression odds = odds * ntotal / i, while it is not clever enough to promote them in the compound assignment, so it performs integer division and the fractions are lost.

Thanks!
redak
it's not about compiler being "clever", it's about compiler obeying the language requirements. If the arguments to some operator are mismathed, they are promoted so that they match, and the return type of the operator also matches what the arguments were promoted to. The compiler has no freedom in this case.
@Cubbi,

you are wrong. It depends only on the decision adopted by the C/C++ Standard Committee. In fact, as in operator op= the left operand also takes part as an operand for the operator op it could be adopted that the left operand also is promoted. And in this case compilers were more clever and give the same result for two operators

a op= b;

and

a = a op b;
vlad wrote:
It depends only on the decision adopted by the C/C++ Standard Committee

Correct, and a compiler that does the opposite has a bug (in case of such trivial expression evaluation, a major bug). As for a op= b vs. a = a op b, the compiler does in fact treat them the same (as it should). That's not where the difference was: see @Zhuge's answer.
Last edited on
What does mean "as it should"? As I pointed out it is one of the possibilities and has its drawback which makes two statements

a op= b; and a = a op b; non-equivalent though it is more naturally to do them equivalent.
The phrase about more clever compilers is ironical and can be interpretated that the C/C++ committee could be more clever.
Last edited on
@vlad What do you call "one of the possibilities"? Are you suggesting that a "clever" compiler should not follow operator precedence rules whenever they don't feel "natural" (to who?)
I suggest that a op= b; will be equivalent to a = a op b;
The order of the operator precedence can be preserved, but the left ooperand shall take part in the operation twice.
For example consider for based on range. You write

for ( const auto &x : v ) { /* some code */ }

but you know that it changed to compound construction that uses begin( v ) and end( v ). So the code of a op= b could be converted by compilers to a = a op b;
vlad wrote:
I suggest that a op= b; will be equivalent to a = a op b;

It is already exactly as you say.
As we see from the example

odds = odds * ntotal / i;

does not equivalent to

odds *= ntotal / i,
Yes, now go back to where I mention operator precedence and think what "b" stands for in you posts.
I already said about the precedence. There is no any problem with the precedence if accoording to the updated Standard compilers shall comvert internally the operation a op= b; to a = a op b;
I already said about the precedence. There is no any problem with the precedence if accoording to the updated Standard compilers shall comvert internally the operation a op= b; to a = a op b;


They do.

a op= b; is the same as a = a op b.

x *= (y / z);
is
x = x * (y / z);

Just like you say. b = (y/z).
@firedraco

No they are not equivalent because the results are different. Look the original code example.
a *= b is equivalent to a = a * (b) (¿happy?)
However, IIRC the compiler may take the order that it wants when evaluates a*b/c
The problem is that there is no promotion to the type of the left operand. So we can get different results.

P.S. By the way I would like to ask question What is the meaning of name Lope, Lopes, or Lopez, or even Lopa (as in Italian)?
There is promotion.
First it calculates the integer division, then promotes that integer to a double for the multiplication.
Because of the parenthesis, the order is fixed, and that's what should happen.

PS: wikipedia says wolf, as in lupus ¿why?

O'k.

I do not think that Lopes means wolf. And moreover Lopa da Vega could not be named as wolf. Compare PeneLopa.

I think that Lopa is something as beauitiful.
Topic archived. No new replies allowed.