Recently, I started using expressions like the following:
res += (i + n / i) * !(n % i);
Where I assume, that !(n % i) will always have a value of 1 or 0, and thus can be used in calculations directly, instead of writing lengthy if-statements like
if(!(n % i))
res += (i + n / i);
In case you wonder, these lines are taken from a function I wrote to calculate the sum of proper divisors of a number n:
unsigned int sum_of_divisors(unsigned int n)
{
unsigned int res = 1;
unsigned int i;
for(i = 2; i < sqrt(n); ++i)
res += (i + n / i) * !(n % i);
res += i * (i * i == n);
return res;
}
My questions are, is this code guaranteed to behave the way I intended it to behave? What is the approximate performance impact of this (multiplication vs. conditional jump)? would the compiler do that anyway, if appropriate?
EDIT: Please note that I’m not particularly concerned about the performance of the actual code. I’d just like to know, out of pure professional interest, which one of the two would perform better, and why, and also how the compiler would handle each case.
As for the reason why I wrote it that way, it works well with my brain 🙂
It’s hard to describe, but I’ve got a better feeling with multiplying 1 or 0 instead of ternary operators or if statements, at least in some cases.
Thanks,
Andy
Yes, the compiler is guaranteed to behave the way you expect.
No, it will not make your code faster, unless the compiler is really low-quality. The compiler should treat both versions of the code roughly equivalently and choose the way it thinks is best to do the conditional logic.
By the way, in principle, the
!operator is a conditional branch. Some implementations (cpu archs) may have ways to optimize it not to need a real program-counter branch, but the same methods will work for most conditionals.Note that there may be one way your code is “better” from the optimizing standpoint. In writing:
you have given the compiler permission to write to
resin both code paths. In the form:the compiler can only write to
resif the condition is true. Ifresis local and its address has not leaked, the compiler can determine that it’s safe to perform excess writes anyway, but if the address ofresis visible outside the function, the compiler must assume other threads may be able to access it and that code paths that don’t modifyresin the abstract machine must not modify it in the generated code (since they might not hold the lock necessary to modify it safely).