Shop OBEX P1 Docs P2 Docs Learn Events
trap? ++x // y versus x++ // y — Parallax Forums

trap? ++x // y versus x++ // y

Tracy AllenTracy Allen Posts: 6,664
edited 2015-02-14 19:10 in Propeller 1
It's a Spin peculiarity I've run afoul of more than once. You might expect the two forms
x := x++ // 10
and
x := ++x // 10
to give the same result. That is, a sequence that cycles from zero to 9 and repeats. However, only the second form works as expected. The first form with x++ simply sticks at the starting value of x.
Is there a logic behind that difference?
[size=1]CON
  _clkmode = xtal1 + pll8x                '
  _xinfreq = 5_000_000

OBJ
  fds : "fullDuplexSerial"

PUB PlusplusSlashslashTester | x1,x2
  fds.Start(31,30,0,9600)
  waitcnt(clkfreq/10+cnt)
  fds.str(string(13,"top",13))
  repeat
    waitcnt(clkfreq/10+cnt)
    x1 := x1++ // 10
    x2 := ++x2 // 10
    fds.tx(13)
    fds.dec(x1)
    fds.tx(32)
    fds.dec(x2)
[/size]

Comments

  • Dave HeinDave Hein Posts: 6,347
    edited 2015-02-13 15:39
    x := x++ will give you the original value of x. This is true in Spin, C and any other language that uses the post-increment operator. What would you expect with "y := x++"? y will have the initial value of x, and will be incremented by 1. "x := x++" is the same, except you're storing the original value back into x.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-02-13 15:39
    Apparently, 1 is added to X once the expression is evaluated, since it's a post-increment, but before the assignment is made.

    -Phil
  • localrogerlocalroger Posts: 3,451
    edited 2015-02-13 16:59
    Phil is exactly right. Expression evaluators always run to completion before the result of the expression is used, in this case the assignment written. x++ increments x at moment x is read in the process of evaluating the expression -- before the remainder calculation (which can't be done until x has been read either) and before the assignment, which overwrites the change.

    ++x works because the increment is applied to the result used for following work, including the div mod and the assignment.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-02-13 18:47
    Okay, there's definitely some weirdness going on with post incrementing in Spin. Take the following program, for example:
    CON
    
        _clkmode  = xtal1 + pll16x
        _xinfreq  = 5_000_000
    
    OBJ
    
        pst : "Parallax Serial Terminal"
        
    PUB start | x, y
    
        pst.Start(115200)
        x := 1
        x := x++
        pst.dec(x)
    
    As expected, it outputs 1.

    But look at this program:
    CON
    
        _clkmode  = xtal1 + pll16x
        _xinfreq  = 5_000_000
    
    OBJ
    
        pst : "Parallax Serial Terminal"
        
    PUB start | x, y
    
        pst.Start(115200)
        x := 1
        x := x++ + x++
        pst.dec(x)
    
    I would have expected the output to be 2, but it's 3! In other words, one of the post-increments executes before the assignment takes place -- unlike the case in the first example.

    Here's another example:
    CON
    
        _clkmode  = xtal1 + pll16x
        _xinfreq  = 5_000_000
    
    OBJ
    
        pst : "Parallax Serial Terminal"
        
    PUB start | x, y
    
        pst.Start(115200)
        x := 1
        x := x + x++
        pst.dec(x)
    
    As expected, the output is 2. But in this program,
    CON
    
        _clkmode  = xtal1 + pll16x
        _xinfreq  = 5_000_000
    
    OBJ
    
        pst : "Parallax Serial Terminal"
        
    PUB start | x, y
    
        pst.Start(115200)
        x := 1
        x := x++ + x
        pst.dec(x)
    

    the output is 3! (I guess addition is no longer commutative?)

    It appears that the rule is this: post-increments are deferred only until the incremented variable is encountered again in the expression.

    -Phil
  • David BetzDavid Betz Posts: 14,516
    edited 2015-02-13 19:04
    I think what you are seeing makes perfect sense. The Spin compiler generates code to evaluate the right hand size of the expression (x++) first. The result is 1 with the side effect that x is incremented and now has the value 2. However, the next thing that the compiler does is store the value of the right hand side of the expression into the variable on the left hand side. That writes the result 1 into x overwriting the 2 that was there after the increment. C would do the same thing.
        x := 1
        x := x++
    
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-02-13 21:43
    David,

    True, but that doesn't address the issue of how long the increment(s) should be deferred (i.e. until the end of the expression or until the next time the variable is encountered in the expression).

    -Phil
  • tonyp12tonyp12 Posts: 1,951
    edited 2015-02-13 22:31
    > how long the increment(s) should be deferred
    Does not look like it's deferred.
    As soon it's pushed on to the stack the VAR location that holds the value is incremented.
    If the that same VAR location is used again, it will have the new value already.
  • TorTor Posts: 2,010
    edited 2015-02-13 22:40
    Honestly, to mix a post- or pre-incrementing variable with itself in an expression.. that's never occured to me and I can't see the point of doing it. So x := ++x doesn't make much sense. Use the feature where it's clear what it means: Incremented then read, or read then incremented. There's no ambiguity in the C expression 'if (+xx > 0)' or 'if (x++ > 0)'. Why whould anyone try a trick like 'if (+xx > (x++ -x))'? Because that's how these artificial cases look to me.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-02-13 23:10
    I don't disagree that the cases posited are artificial -- even pathological. Nor would I advocate their use in normal programming. But that does not diminish the value of exploring pathological cases when they can provide insight into the language's logical underpinnings.

    -Phil
  • Heater.Heater. Posts: 21,230
    edited 2015-02-13 23:18
    Personally I think such side effect causing things as pre and post increment should be banned from all languages. Considered harmful, like GOTO.

    A statement like:

    y = a++ + b++ + c++

    Is actually changing 4 variables with a single assignment. Crazy nuts! There are no "logical underpinnings" to this, only perhaps some arbitrary rules of evaluation decided by the language designer.

    Confusion over which order what happens has led to many a bug. Or a least wasted a lot of time with confusion. As this thread nicely demonstrates:)

    These pre and post increment operators are a hangover from the days when compilers could not optimize as much as they do now. A way for the programmer to tell the compiler to use INC and DEC instructions rather than generating a sequence of code to fetch a small number and add it.

    Generally modern compilers can optimize away the less confusing but slightly more verbose way of expressing what you want to do. For example the following two loops both take 10 seconds +/- a random millisecond or two depending how the OS feels when it runs it:
    while (1) {
        y = a + b + c;
        a += 1;
        b += 1;
        c += 1;
    
        if (y > 10000000000) {
           break;
       }
    }
    
    while (1) {
        y = a++ + b++ + c++;
    
        if (y > 10000000000) {
           break;
        }
    }
    
    How such code get compiled in Spin and what the comparative run times there are I have no idea.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-02-13 23:32
    Sill, though, I would not mind seeing something like this:
    ++x //= 10

    Pre-incrementing/decrementing should still retain the LHS's L-value so the assignment can take place. The Spin compiler doesn't like it, though.

    -Phil
  • Heater.Heater. Posts: 21,230
    edited 2015-02-13 23:45
    I'm very glad to see that such heinous crimes are prevented :)
  • Dave HeinDave Hein Posts: 6,347
    edited 2015-02-14 04:58
    BTW, if you compile "x = x++ % 10;" under gcc with -Wall you will get the following warning:
    warning: operation on ‘x’ may be undefined
    
    Code like this should be avoided in C because the optimizer may do things in a different order than left to right. With the Spin compiler there is no optimization, and you are guaranteed that it will do things from left to right.
  • Heater.Heater. Posts: 21,230
    edited 2015-02-14 07:21
    That's a really odd warning from GCC. Why do they say "may be undefined" as opposed to "is undefined"?

    Don't they know?

    Surely the C language specification spells out undefined behaviour, things that are implementation dependent.

    Or does GCC actually specify the behaviour, like it's many other extensions to C, but it's just warning that other compilers may do things differently.

    I don't think that the amount of optimization should have any bearing on this. Either the behaviour is defined and the optimizer makes no difference to the outcome. Or the behaviour is undefined in which case it may do.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-02-14 08:49
    How such code get compiled in Spin and what the comparative run times there are I have no idea.


    One of my favorite things about the Propeller is using it to time itself.

    Fastest (time = 1)
    repeat while (y < 100_000)
        y := a++ + b++ + c++
    


    Slower (time = ~1.2)
    repeat while (y < 100_000)
        y := a + b + c
        a++
        b++
        c++
    


    Slowest (time = ~1.5)
    repeat while (y < 100_000)
        y := a + b + c
        a += 1
        b += 1
        c += 1
    
  • Heater.Heater. Posts: 21,230
    edited 2015-02-14 09:32
    JonnyMac,

    Interesting results. As we expected I guess except verbose versions are not as bad as I might have guessed.

    I was not being lazy there. As it happens I have totally run out of Props and PropPlugs here.

    I did try C versions of my examples. First attempt resulted in the compiler optimizing the entire loop away and compiling down to a printf of the final result !

    Modern optimizers are wonderful things.
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2015-02-14 09:50
    Thanks for the discussion, to clear it up and to fly the caution flag.

    I'm thinking of it now in terms of the result variable, in relation to the variable X that is under consideration at each step in evaluation of the expression. Normal operators act on the result only, but the assignment operators, they always act on the result, and they act back on the variable itself either before or after doing their bit with the result. I understand that happens internally using some not-so-fancy footwork with the expression stack.

    The pathological cases make sense in that context.

    I'm pretty careful with making tricky clever expressions using the assignment operators. Or, too, using subsidiary assignments within expressions. There is always that feeling of potential misunderstanding. Always needs to be verified. But as Jon pointed out, it can be significantly faster than the straight step by step.

    I've run into this trap too, in this example thinking to do something at 10 seconds and again at 60 seconds.
    if tocks++ // 10  == 0
      ' do something per 10 seconds, okay
        if tocks // 60  == 0     ' [color=red]but never happens, because tocks mod 10 = 1[/color]
           ' do something per minute
    
    That of course works okay with ++ tocks in the first statement, or the tocks++ assignment transferred to the last statement.
  • TorTor Posts: 2,010
    edited 2015-02-14 18:56
    Heater. wrote: »
    Personally I think such side effect causing things as pre and post increment should be banned from all languages. Considered harmful, like GOTO.

    A statement like:

    y = a++ + b++ + c++

    Is actually changing 4 variables with a single assignment. Crazy nuts! There are no "logical underpinnings" to this, only perhaps some arbitrary rules of evaluation decided by the language designer.
    Oh, I actually consider those language features useful.. put there by the language designer to trap those unworthy of being on your project! Just check what kind of code the programmers write, and don't bring them on your team if you see something like that! :)

    -Tor
  • Dave HeinDave Hein Posts: 6,347
    edited 2015-02-14 19:10
    Heater. wrote: »
    Surely the C language specification spells out undefined behaviour, things that are implementation dependent.
    It seems like "x = x++ % 10;" should be safe in C, but there are certain things like "y = function(x++, x);" that are ambiguous. There is no guarantee that the compiler will compute the left parameter before computing the right one. The Spin compiler will do the left one before the right one, though there is no language specification that defines this.
Sign In or Register to comment.