Shop OBEX P1 Docs P2 Docs Learn Events
embeded "case" question and Variable check — Parallax Forums

embeded "case" question and Variable check

LtechLtech Posts: 366
edited 2017-01-09 21:07 in Propeller 1
What is the best way to control the integrity of some manual imputed variables ?
Try to get ValidHex 1 for pased check, and $F if not

need to check the "ID, Tslot , STx, TxPos" variables
PUB ControlVars

  ValidHex := $F

  case ID
     0..200 : case Tslot
                    0..2 : case Stx
                              1..10 : case TxPos
                                            0..9 : ValidHex := 1
                                            other: ValidHex := $F


This one do not work
PUB ControlVars

  case ID
    0..200 : case Tslot
                   0..2 : case Stx
                             1..10 : case TxPos
                                          0..9  : ValidHex := 1
                                          other: ValidHex := $F

                              other: ValidHex := $F
                     other: ValidHex := $F
      other: ValidHex := $F


Thank-you

Comments

  • I'd use the first one, and get rid of the other condition. You don't need it, since it defaults to $f anyway.

    -Phil
  • Heater.Heater. Posts: 21,230
    I have no idea what is going on there.

    Only I'm sure nesting a case within a case within a case is the road to confusion and madness.

  • LtechLtech Posts: 366
    edited 2017-01-09 22:09
    @ Heater,

    I need to control the manual input of variables.
    Each of them have to be inside strict limits.
    We use them for time multiplexing so no fault are tolerable
    We store the variables in the eprom, and notice we can have bad recall when program to ram while testing. (I know why)
    We want to make inputs the most fool prove we can ...

    In stead of doing "if " I find the "case" a compact way of quick check limits
    If al "case" are passed, we know all variables are in the range

    So is this a correct way do do this ?
    Somebody a other system to do this trick ?
  • The only real reason to use a CASE statement is if there are several different individual values, or discontinuous sets of values.

    For example, if you were checking to see if a character is a vowel or a consonant, a case statement would be ideal, because the set of inputs is not continuous. On the other hand, checking to see if a character is a number is simpler with an IF statement because the range of things being checked is a continuous, single range.

    In your example, I'd suggest using this instead:
      if (ID => 0 AND ID =< 200) AND (TSlot => 0 AND TSlot =< 2) AND (Stx => 1 AND Stx =< 10) AND (TxPos => 0 AND TxPos =< 9)
        ValidHex := 1
      else
        ValidHex := $F
    

    In an optimizing compiler this will short-circuit at the first failed test, though I think Spin evaluates everything in an expression, so it might be more efficient to use nested IF statements, like this:
      ValidHex := $F
      if ID => 0 AND ID =< 200
        if TSlot => 0 AND TSlot =< 2
          if Stx => 1 AND Stx =< 10
            if TxPos => 0 AND TxPos =< 9
              ValidHex := 1
    
  • Heater.Heater. Posts: 21,230
    edited 2017-01-09 22:38
    I'm all for validating program inputs.

    Given a program with inputs A, B, C I might start with something like:
    IF A is not valid
        EXIT
    ELSE IF B is not valid
       EXIT
    ELSE IF C is not valid
        EXIT
    ELSE
        do stuff with A, B, C
    
    In a sloppy pseudo code.


    Of course things get more complex if the validity of A or B or C is not independent of each other. Perhaps certain combinations of them all are valid but other combinations are not.

    Either way, when I find myself nesting "case" or "if" or whatever too much I start to think there must be a better way to express what it is I actually want to do.

  • Ltech, I'm sure I'm missing something but I use CASE a lot because it is so much more compact and readable than "If Then'. I'm going to post a couple examples of how I use CASE
    PUB Inputs  'adc.average(channel, n)      
      repeat   
        temp1 := adc.in(0) >> 3
        Case  temp1 
          0..30     : F_B := 10   'back   
          31..91    : F_B := 9 
          92..154   : F_B := 8 
          155..216  : F_B := 7 
          217..230  : F_B := 6          
          231..310  : F_B := 0    'midpoint
          311..350  : F_B := 1  
          351..400  : F_B := 2          
          401..460  : F_B := 3
          461..510  : F_B := 4        
          511       : F_B := 5    'forward
    
    A range of values for a joystick
    PRI NewStepLS(n)  | coil
      coil:= n // 4
      case coil
        0:
          outa[11..8]:=%1000
        1:
          outa[11..8]:=%0100   
        2:
          outa[11..8]:=%0010   
        3:
          outa[11..8]:=%0001
    

    Output for a stepper motor.

  • Heater.Heater. Posts: 21,230
    To be clear. I have no issue with "case" as such.

    Only that when you find yourself nesting case within case within case, that might be a clue that ones program needs a bit of thinking about before it becomes an unreadable mess.
  • I thought the OP's use of case was clear, concise, and efficient. I liked it! Maybe it would look less confusing if the indents were only two spaces instead of fourteen? :)

    -Phil
  • Heater.Heater. Posts: 21,230
    Indenting aside, I guess we have to disagree.

    I like a "case " to check for particular cases. Like 1, 2, 3.

    What we have here is a check over ranges of values. And then nested....

    It does not express what the intention is.

    Actually, thinking about it, I don't like "case" at all. It's as if the "structured programming" gurus wanted to ban GOTO because it makes a spaghetti mess out of your code. But then they had to put in "case" to do what you needed GOTO for in the first place! :)




  • JasonDorieJasonDorie Posts: 1,930
    edited 2017-01-10 00:54
    The reason case exists in many languages is that it allows for better optimization. You're telling the compiler to make a decision based on discrete values of a single variable of a known type.

    If your cases are 0 based and sequential, the code becomes a jump table. Some compilers even allow a non-zero base, and subtract the offset at runtime. If your cases are discrete and non-contiguous, they can be sorted, and the compiler can generate a binary search / decision tree to efficiently decide on the target code.

    If the user creates a sequence of if/else statements, the generated code is entirely dependent on the order of those statements, so it's up to the creator to make it efficient.

    In the case of Spin, with ranged case statements, I'm pretty sure the under-the-hood implementation is equivalent to a sequence of if/else statements, which makes me die a little inside.
  • Heater.Heater. Posts: 21,230
    Yep, totally with you there.

    So, in a high level language, should one design the the thing to micro-optimize low level machine features. Or design the the thing to make it easier for humans to express what they mean and share that with other humans?

    For example, the C language seems to have a ton of things that make it easier for compilers. In favor of encouraging the writing of clear code for humans to read.

    The "case" is one such example.










  • I think "case" accomplishes both, particularly when used well.
    switch( userChar )
    {
    case 'L':
    case 'l':
        turnLeft();
        break;
    
    case 'R':
    case 'r':
        turnRight();
        break;
    
    default:
        Message( "Invalid command" );
    }
    

    To me that's more readable than:
    if( userChar == 'L' || userChar == 'L' ) turnLeft();
    else if( userChar == 'R' || userChar == 'r' ) turnRight();
    else Message( "Invalid command" );
    

    It also lends itself well to doing things like this:
    switch( LowerCase(userChar) )
    {
     . . .
    }
    

    ...whereas the if-based version would need to re-do the LowerCase() call for each if/else, or use a temporary variable.

    Ultimately it's just another tool in the toolbox. Part of what makes an good programmer is knowing what the various tools are good for and using them effectively.
  • So it is just a way of human readability.
    I am with Phil, "case" is more readable for me regard complex "if then".
    But Jason way is nice too.

    In my part of the program case will do it, but for more complex nested brunch I use "if then"
    The layout here make it not nice tabulating, so I make it viewing nicer...

    PUB ControlVars
    
    ValidHex := $F
    
      case ID
          0..200 : case Tslot                                                 '# ID    0 to 200 check
                        0..2 : case Stx                                       '# Tslot 0 to 2 check
                                    1..10 : case TxPos                        '# Stx   1 to 10 check
                                                 0..9 : ValidHex := 1         '# TxPos 0 to 9 check
                                                
    

    PUB ControlVars
    
     If (ID => 0 AND ID =< 200) AND (TSlot => 0 AND TSlot =< 2) AND (Stx => 1 AND Stx =< 10) AND (TxPos => 0 AND TxPos =< 9)
        ValidHex := 1
      else
        ValidHex := $F
    
    


    Thank-you
  • Just a question: is it even possible for your values to be negative? If not, the => 0 comparisons are superfluous and can be deleted. In that case the if/then construct would be better.

    -Phil
  • GenetixGenetix Posts: 1,742
    edited 2017-01-10 22:03
    Ltech,

    Instead of checking for everything at once it would make more sense to check each item at a time so it can be flagged as bad.
    One of the most frustrating things that a program can do is just tell the user there was an error and not what the error was.

    If something is bad then you can tell the user that is was bad and to enter it again.
    Also, remember that Spin has TRUE and FALSE which are a lot more meaningful in this situation.
  • ErNaErNa Posts: 1,742
    In the case of case it makes sense to first test the condition, that is most unlikely met.
  • Phil, does Spin support unsigned values? I thought it didn't, meaning that the negative checks would be necessary.
  • JasonDorie wrote:
    Phil, does Spin support unsigned values? I thought it didn't, meaning that the negative checks would be necessary.
    No, it doesn't. However if the OP is reading one- or two-digit hex values from an input device, he would never see a negative number.

    -Phil
  • Phil brings up a good point!

    If the program is getting Hex values then why not check them in Hex.
  • The hex name is coming from the past (>10 years), using hex encoding wheels.
    In fact they are dec values.

    @Genetics

    Of course we check on every entry, but we notice (ex on new ram program loads) some time the variables, taken from eprom on startup, are wrong.
    We know why, we do not expect it on field use, but it can happens.
    My vision to handle this is checking if the values are in specs when read the eprom variables.
    If we have a buffer overrun, at least we do not eventually lose al the modules in the time multiplex, by random jamming of that module.
    The module go to standby, give a alarm, and we can make a manual connection

    Project description:

    This is used on complex antennas tracking, with a lot of realtime moving vehicles. (motorbikes, helicopters, plane, ground station)
    The time multiplex is locked on 1pps gps, sending all dada on one 15khz uhf frequency.
    We need it to build the first tracking of the antennas.
    Once we have a basic track, we use the up and down freq to embed the tracking data.
    On lose of those freq, we go back on the uhf tracking.

    PS the "case" option only compile on BTS
  • I always strive to write easy-to-read code (unless there's a critical speed issue where that's difficult), and I find too many nested levels can become unwieldy. Sometimes I simplify like this:
    pub process_shot | x
    
      if (HitDelay)                                                 ' hit delay enabled?
        if (htimer.millis < 0)                                      ' if timer running
          return                                                    '  abort
        else
          htimer.set(-hitdelayms)                                   ' restart hit delay timer
    
      if (buffer[0] & $80)                                          ' validate shooter
        return
    
      x := buffer[1] & 1                                            ' validate field                                          
      if (x <> FieldID)
        return
    
      if (FFire == NO)                                              ' ff disabled?
        x := buffer[1] >> 5                                         '  extract team
        if (x == TeamID)                                            '  abort if match
          return
         
      x := (buffer[1] >> 1) & %1111                                 ' extract damage
      x := damage_points(x)                                         ' convert to points
    
      take_damage(x, buffer[0])                                     ' deal with damage
    
    Instead of deep nesting, I use return to abort the process. This doesn't work for all cases, of course, but I find it helpful from time-to-time.
Sign In or Register to comment.