3

As far as I know, C uses lazy calculation for logical expressions, e. g. in expression

f(x) && g(x)

g(x) will not be called if f(x) is false.

But what about arithmetic expressions like

f(x)*g(x)

Does g(x) will be called if f(x) is zero?

5 Answers5

3

Yes, arithmetic operations are eager, not lazy.

So in f(x)*g(x) both f and g are always called (pedantically the compiler is transforming that into some A-normal form and could even avoid some calls if that is not observable), but there is no guarantee about the order of calling f before or after g. And evaluating x*1/x or y*1/x is undefined behavior when x is 0.

This is not true in Haskell AFAIU

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Standard-conforming implementations can do whatever it wishes on `1/x` when `x` is 0 (including ending the universe). I've heard that on some PowerPC compiler with some optimizations it gives -1 without runtime error (but I am not sure). – Basile Starynkevitch Oct 06 '13 at 07:48
  • Interesting I was unaware of this..Thanks! – Grijesh Chauhan Oct 06 '13 at 07:52
  • Inside the C computing model, `g` is always called. However, in optimized code in an actual machine, if `g` has no side effects, there is no guarantee that `g` is actually executed. The program results (as defined by the C standard) would be indistinguishable, but observation of either execution time or instruction execution could reveal no instructions are executed for `g`, in theory. – Eric Postpischil Oct 06 '13 at 09:41
2

Yes, g(x) will still be called.

Generally, it would be a quite slow to conditionally elide the evaluation of the right-hand side just because the left-hand side is zero. Perhaps not in the case where the right-hand side is an expensive function call, but the compiler wouldn't presume to know that.

Dolda2000
  • 25,216
  • 4
  • 51
  • 92
2

It's called "Short Circuit" instead of lazy. And, at least as far as the standard cares, yes -- i.e., it doesn't specify short-circuit evaluation for *.

A compiler might be able to do short-circuit evaluation if it can be certain g() has no side effects, but only under the as-if rule (i.e., it can do so only by finding that there's no externally observable difference, not because the standard gives it any direct permission to do so).

Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
1

In case of logical operators && and || order of evaluation bound to take place from left to right and short circuiting takes place. There is a sequence point between evaluation of the left and right operands of the && (logical AND), || (logical OR) (as part of short-circuit evaluation). For example, in the expression *p++ != 0 && *q++ != 0, all side effects of the sub-expression *p++ != 0 are completed before any attempt to access q, but not in case of arithmetic operators .

haccks
  • 104,019
  • 25
  • 176
  • 264
  • 2
    Should add that logical operator introduce [sequence point, and use short circuit](http://stackoverflow.com/questions/628526/is-short-circuiting-boolean-operators-mandated-in-c-c-and-evaluation-order), as side notes. – Grijesh Chauhan Oct 06 '13 at 07:41
  • 1
    @jerry's answer: "It's called "short circuit" instead of lazy" – Grijesh Chauhan Oct 06 '13 at 07:45
  • @Grijesh Chauhan: It's don't matter how it's called. –  Oct 06 '13 at 07:47
  • 2
    @D.Ross Read Jerry's answer. – Grijesh Chauhan Oct 06 '13 at 07:49
  • 2
    @D.Ross Um, it does. If you go to the shop and ask for a loaf of bread, would you like to receive a bottle of milk instead? –  Oct 06 '13 at 07:49
  • @H2CO3: "Short circuit" is just a synonim for general term "laziness" thet used in the C standard. –  Oct 06 '13 at 08:11
  • @D.Ross Neither the string "lazy" nor the word "laziness" are found in the copy of the C99 standard I have. The standard doesn't use either of these words. Instead, it explicitly describes the behavior. Both lazy evaluation and short-circuiting are "general" (non-standard) but widely accepted terms **which refer to different things.** Refer [Wikipedia: lazy evaluation](http://en.wikipedia.org/wiki/Lazy_evaluation) and [Wikipedia: short-circuit evaluation](http://en.wikipedia.org/wiki/Short-circuit_evaluation) if you still don't believe me. –  Oct 06 '13 at 08:14
  • @H2CO3: There is no word "lazy" in the C standard because the C standard uses "short circuit" instead. "Laziness" is generally used term for evaluation of an expression until it needed. So, short cicruit **is** a lazy evaluation. Anyway, it's doesn't matter how you name it, because I explain what I want in my question. –  Oct 06 '13 at 08:19
  • @D.Ross "short circuit" isn't in the Standard either. Simply no. It isn't used by the standard. At all. Never. Absolutely not. –  Oct 06 '13 at 08:20
1

While that optimization would be possible, there are a few arguments against it:

  • You might pay more for the optimization than you get back from it: Unlike with logical operators, the optimization is likely to be beneficial in only a small percentage of all cases with arithmetic operators, but at the same time requires an additional check for 0 for every operation.

    Because boolean truth values only have two possible values, there is a theoretical 50 % chance (1 ÷ 2) with short-circuiting boolean expressions that the second operand will not have to be evaluated. (This assumes uniform distribution, which is perhaps not realistic, but bear with me.) That is, you are likely to profit from the optimization in a relatively large percentage of cases.

    Contrast this with integral numbers, where 0 is only one out of millions of possible values. The probability that the first operand is 0 is much lower: 1 ÷ 232 (for 32-bit integers, again assuming uniform distribution). Even if 0 were in fact somewhat more probable to occur than that (i.e. with a non-uniform distribution), it's still unlikely that we're dealing with the same order of magnitude as with truth values.

    Floating point math further aggravates that issue. Here you need to deal with the possibility of rounding errors and denormalization. The probability that some calculation yields exactly 0 is likely to be even lower than with integral numbers.

    Therefore the optimization is relatively unlikely to result in the remaining operand not being evaluated. But it will result in an added check for zero, 100 % of the time!

  • If you want evaluation rules to remain reasonably consistent, you would have to redefine short-circuit evaluation order of && and ||: Division has one important corner case, namely division by 0: Even if the first operand is 0, the quotient is not necessarily 0. Divison by 0 is to be treated as an error (except perhaps in IEEE floating-point math); therefore, you always have to evaluate the second operand in order to determine whether the calculation is valid.

    There is one alternative optimization for /: division by 1. In that case, you wouldn't have to divide at all, but simply return the first operand. / would therefore be better optimised by starting with the second operand (divisor).

    Now, unless you want &&, ||, and * to start evaluation with the first operand, but / to start with the second (which might seem unintuitive), you would have to generally re-define short-circuiting behavior such that the second operand always gets evaluated first, which would be a departure from the status quo.

    This is not per se a problem, but might break a lot of existing code if the C language were thus changed.

  • The optimization might break "compatibility" with C++ code where operators can be overloaded. Would the optimizations still apply to overloaded * and / operators? Or would there have to be two different forms of these operators, one short-circuiting, and one with eager evaluation?

    Again, this is not a deficiency inherent in short-circuit arithmetic operators, but an issue that would arise if such short-circuiting were introduced into the C (and C++) language as a breaking change.

Community
  • 1
  • 1
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • +1 this is a great anwser, however I've got something to say about the "intuitiveness" part: I think the analogous optimization for division would be not to evaluate the **second** operand if the first one is zero. –  Oct 06 '13 at 07:52
  • 1
    @H2CO3: Thanks for your input. You would be absolutely correct, if it wasn't for division by 0. See my edited answer. – stakx - no longer contributing Oct 06 '13 at 08:01
  • 2
    The fact that Boolean objects have two values does not mean they are equally likely. – Eric Postpischil Oct 06 '13 at 09:43
  • Obviously, 10**–20 would not qualify as zero in a multiplication, unless the compiler could further determine that the result of the multiplication would actually be zero (due to floating-point underflow) or would not affect later results (due to addition to a value known to be, for example, sufficiently larger that adding the small product produces not change). These, and overloading the `*` operator, are different questions and are not very relevant to this question. – Eric Postpischil Oct 06 '13 at 09:47
  • The digressions and misstatements in this answer detract from it. – Eric Postpischil Oct 06 '13 at 10:23
  • @EricPostpischil: I've restructured my answer. Is that any better? – stakx - no longer contributing Oct 07 '13 at 08:02