Shop OBEX P1 Docs P2 Docs Learn Events
SPIN language and floats, longs and cast between them! — Parallax Forums

SPIN language and floats, longs and cast between them!

How does SPIN handle mixed types of data?  I am working with a lot of different values, some of them appear to be longs, and some appear to be floats. but if I happen to mix types, I seem to get no answer at all!    The FLOAT keyword says it applies only to CON objects and has no run time action.  I need to test some values, but the software (with no errors!) seems to be ignoring the value.  Anyone knows what is going on?
«1

Comments

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-07-25 00:03
    Spin does not do floating-point math automatically (e.g. x := 1.23 + 4.56 will not do what you want). For that you have to use one of the floating-point objects in the Spin library, such as float32. All floats in Spin are just longs that are manipulated by method calls to the library object (e.g. x := f.fadd(1.23, 4.56)).
    -Phil
  • Heater.Heater. Posts: 21,230
    There is the thing. You can define values as floats in CON sections
    x = 3.14159y = 0.33333
    If you ever use those you will get the floating point representation of those numbers in binary.
    But, Spin does not have floating point arithmetic so
    z : = x + y 
    Ends up being performed as an unsigned 32 bit integer operation and the result is gibberish. 

  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-07-25 02:14
    You have to use a floating point object to do math with floating point values. IMO, "F32" is the best one available. Here's the OBEX link.
    http://obex.parallax.com/object/229
  • Just to finish this one up. What I finally found was that he was using fixed point math (2 or 3 decimal places, depending on the variable!) The funny thing was getting apparently correct results in my IF tests to totally wrong values! Does anyone else find SPIN math flakey? I know that I have one divide that suddenly gives negative answers when memory gets tight!
  • Heater.Heater. Posts: 21,230
    Spin maths is not flakey. Only that business of being able to define floating point format in CON sections confuses people occasionally.
    I know that I have one divide that suddenly gives negative answers when memory gets tight!
    It's best you post a short example of Spin that demonstrates that problem. Else it does not exist.

    There is no way that it fails due to changes in memory space, unless it is actually the stack you have allocated to it that is insufficient perhaps.

    Almost certainly if there were such issues in the way Spin does division it would have been discovered by now. There has been a lot of people using this and analysing the Spin compiler, it's byte codes and it's interpreter over the years.


  • The line of code is :

    Cn1 := (( PackCap - ampSecUsed) * 100) / PackCap 'capacity at time n+1

    So, if PackCap is larger than ampSecUsed, how could this be negative? yet, the displayed result is! When it reaches 60, it jumps to -16...

    No, I don't make this up, I just have to deal with it :-(
  • Heater.Heater. Posts: 21,230
    So we have

    x := ((y - z) * 100) / y

    Certainly this can go negative if (y - z) * 100 oversflows a 32 bit signed number, as used by spin.

    This is not a sufficient example. How are x, y and z defined and what values are assigned to them when this statement is run ?

    Given that you hint at the use of time here, I start to think you have a traditional clock count overflow error.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-08-14 16:56
    Spin math certainly isn't flakey. It's just 32-bit integer math which has its usual limitations.

    As long as you obey the rules for integer math it works fine. Problems occur when you have values larger than 2,147,483,647 since these end up rolling over into the negative values.

    Can you give us the numbers which produce the -16 value?

    If you're running into the rollover issue, there are tricks to be done to get around this.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-08-14 16:55
    when memory gets tight!

    I missed this part of the earlier post. Everything gets flakey when memory is tight. You need to leave enough memory free for cog#0's stack. If the interpreter runs out of stack room, it will start overwriting your code.

    There are lots of ways of freeing up memory. If you post your code I'm sure you could get some suggestions on how to free up some memory.

  • The numbers were 36,000,000 for PackCap, and 21,600,000 for ampSecUsed. Now, what is funny, is that this starts with ampSecUsed at around 18,000,000, and the math is fine then. It is only as it gets smaller that the problem asserts itself!

    Like I said, it makes no sense whatsoever. it just does it...
  • I wish I could, but it is proprietary. I am just charged with modifying it to add some functionality. The guy who originally wrote it did some very strange things, with lots of custom code to do the displays and the database. I am finally able to interpret what he did, but really have no idea WHY he did what he did!
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-08-14 17:06
    You're experiencing rollover issues. Your numbers are too large for the math you're doing. At least in the order you're currently doing the math.
  • Do you think so? We are three orders of magnitude below the top of the possible values, so even multiplying by 100 should give us a power of ten cushion...

    Ok, looked again, and you are right. But, why does the code work fine now, but not when memory is tight?
  • You're really close to the rollover point. You're right, if ampSecUsed stays above 14,525,163, the code should work.

    As I mentioned earlier, when memory is tight, all bets are off. The stack is likely overwriting your code so all sorts of strange things can happen.



  • Heater.Heater. Posts: 21,230
    Charles,

    Hmm... "Charged to...". Sounds to me like you actually have to find that bug in your application rather than passing it off as a Spin problem.

    So, never mind all that "proprietary" business. That does not preclude you from wrapping up the failing code, with variable initialization that causes the failure, into a PUB method that you can post here. You can replace the variable names to "x", "y", "z" but you have already given those away so there is no point.

    I'm still betting on an overflow issue.

    Quite likely the act of creating the requested example of failure will reveal the cause of the problem in your application as you try to recreate it !

    You will be needing to check your stack usage of course.


  • Heater. wrote: »
    Quite likely the act of creating the requested example of failure will reveal the cause of the problem in your application as you try to recreate it !

    I've lost count of how many times this has happened to me.

    The act of isolating the problem code generally reveals where the real problem had been.
  • Heater.Heater. Posts: 21,230
    edited 2015-08-14 19:37
    Yep.

    It's amazing how often one can get fixated on a bug having to be exactly here -> "Some innocent and harmless perfectly functioning line of code". Or in the library it is using, or in the compiler, or....

    9 times out of 10 the problem ends up being some other dumb Smile error in some other place one casually decided was OK.

    This problem smells of such a case.

    We all do it.

    You raise an interesting point. Many times I have given up the business of trying to read and analyse the source code trying to see the bug. Even stepping through code with a debugger or examining log output can be hopeless.

    No, turn it around, let's not try to fix the problem, let's try to make it worse!

    I call it "perturbation debugging". Often effective in finding those seemingly random timing race conditions or memory overuse.


  • The SPIN language has no types and, therefore, has no casts. All runtime expressions are evaluated as 32-bit signed integers.

    The keywords Long, Word and Byte are only used when allocating static storage. Reads from Word- and Byte-sized statically allocated storage are zero-extended to 32 bits. Stores to Word- and Byte-sized statically allocated storage are truncated.

  • Heater.Heater. Posts: 21,230
    edited 2015-08-18 17:04
    Sure the Spin language has types.

    How else would it be possible to declare constants as floating point numbers?

    Most "normal" arithmetic is done with two's complement integers. Except when, as you say, 8 and 16 bit values are coerced into positive 32 bit integers discarding their signedness. Which you might expect them to have because 32 bit values are always signed.

    No wonder people get confused.

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-08-18 17:29
    kstld,

    That's probably the simplest, most concise description of Spin's treatment of numbers I've ever read. It should be engraved in stone.
    heater wrote:
    ... 8 and 16 bit values are coerced into positive 32 bit integers discarding their signedness.
    8- and 16-bit values do not have signedness to discard. They are always positive. For cases where they might be interpreted as being signed, there are the ~ and ~~ prefix operators.

    As to floats being a type, no. They're just 32-bit integers that get treated as floats when using one of the floating-point libraries.

    -Phil

  • Heater.Heater. Posts: 21,230
    Phil,

    If a language parses and accepts numbers written in the source code as floating point then there is a type that it accepts. Similarly for negative numbers. The fact that it proceeds to deal with them in irregular and unexpected ways is not obvious. (I know it's all in the Prop manual if you look hard enough)

    In the following code:
    DAT
    l LONG -1
    w WORD -1
    b BYTE -1
    f LONG -310E3
    
    Compiling it offers no suggestion that negative WORDs and BYTEs don't exist. Or that the real type is a myth. But the types are surely there in the syntax.

    Of course you and I know all that. ksldt is correct in his summary. We have seen many new comers to Spin tripped up by this confusion regarding types over the years though.


  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-08-18 18:58
    Heater,

    Your examples sent me back to the IDE. Surely, the byte and word lines would return a range error, right? Nope! In fact, you can even define them with positive numbers that are out of range. But if you try to define a byte or word with a floating-point value, you get an error.

    -Phil
  • Heater.Heater. Posts: 21,230
    edited 2015-08-18 21:29
    Yep. That is what I was getting at. Types that sort of exist but don't.

    Turns out though BST gives no error for floating point declarations of BYTE and WORD
    DAT
    l    LONG -1
    w    WORD -1
    b    BYTE -1
    f    LONG -42E10
    fb    BYTE -42E10
    fw    WORD -42E10
    

    Openspin does object to floating point values in BYTE and WORD. The error message makes no sense though:
    Untitled1.spin(6:9) : error : Integer not allowed in floating-point expression
    Line:
    fb BYTE -42E10
    

    Then there is this:
    fb1 BYTE -42E2128    'error : Floating-point constant must be within +/- 3.4e+38
    fb2 BYTE -42E128     'error : Integer not allowed in floating-point expression
    
    What? 128 is not bigger than 38 all of a sudden.

    And my favourite piece of Spin type weirdness. What sequence of bytes does this produce:
    s BYTE "hello" + "world"
    
    ?
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-08-18 23:12
    This ain't JavaScript; so, without checking, I'm guessing "h" + "w" == 223?

    -Phil
  • Heater.Heater. Posts: 21,230
    edited 2015-08-19 09:40
    Exactly so. Even JavaScript is not that freaky :)

    I always wondered about this phenomena:
    PUB start
      x := $12345678  'Compiled to BIG endian immediate value
    
    DAT
    x long $12345678  'Compiled to LITTLE endian storage allocation
    
  • ElectrodudeElectrodude Posts: 1,621
    edited 2015-08-19 05:49
    Spin types don't exist past the parser.

    Heater. wrote: »
    Exactly so. Even JavaScript is not that freaky :)
    The fact that
    byte "hello" + "world"
    
    doesn't concatenate the two strings makes perfect sense. Spin has no string concatenation operator, and it certainly isn't '+'. Javascript is freaky for making operators do whatever arbitrary operation the standards committee felt like, while in Spin, operators consistently do the same thing: zero-extend their operands to 32 bits, do the operation integer-wise, and then truncate the result to the storage size. A string in a DAT section is the same as a list of characters. Therefore,
    byte "hello" + "world"
    
    ends up as
    byte "h", "e", "l", "l", "o" + "w", "o", "r", "l", "d
    
    , which ends up as
    byte $68, $65, $6C, $6C, $E6{$6F + $77 truncated}, $6F, $72, $6C, $64
    

    Is there any way to do inline code with BBCode? Similar to `code` notation in Markdown?

    I always wondered about this phenomena:
    PUB start
      x := $12345678  'Compiled to LITTLE endian immediate value
    
    DAT
    x long $12345678  'Compiled to BIG endian storage allocation
    

    According to BST, they're both little endian.
  • Heater.Heater. Posts: 21,230
    edited 2015-08-19 10:01
    Electrodude,

    I have never decided if I like the use of "+" as a string concatenation operator in any language. It makes perfect sense for Spin not to have a string concatenation operator, that would get us into the world of dynamic memory allocation and garbage collection (Except in this case of static memory allocation in DAT sections).

    However what actually happens with "+" and strings here is unexpected and as far as I know never useful (Perhaps for some sneaky code obfuscation contest :) ). Basically it nuts and it would be better to generate a syntax error.

    Yes, Javascript is freaky. Hardly surprising as it was designed and prototyped in 10 days and shipped weeks later! It's creator, Brendan Eich, wanted to fix a lot of that freakyness when Netscape took it to ECMA for standardization, sadly Microsoft barged in to the standards committee and said no. They would have had to make changes to their faithful clone of the original. We can blame MS for the freakyness of JS :)

    Spin operators do consistently do the same thing. Sadly that means LONGs are always signed but WORD and BYTE are unsigned. That is an inconsistency in itself. The programmer has to know and deal with it accordingly.

    We must have different BSTs. That example I posted above compiles to:
    Object DAT Blocks
    |===========================================================================|
    0018(0000) 78 56 34 12 | x long $12345678  'Compiled to LITTLE endian storage allocation
    |===========================================================================|
    |===========================================================================|
    Spin Block start with 0 Parameters and 0 Extra Stack Longs. Method 1
    PUB start
    
    Local Parameter DBASE:0000 - Result
    |===========================================================================|
    2                        x := $12345678  'Compiled to BIG endian immediate value
    Addr : 001C: 3B 12 34 56 78  : Constant 4 Bytes - 12 34 56 78 - $12345678 305419896
    Addr : 0021:          C5 08  : Memory Op Long PBASE + WRITE Address = 0008
    Addr : 0023:             32  : Return
    
    Note the different endianness in code and in DAT. Almost never a problem I guess and I can see why it might be done that way. Still a bit odd.

    Or is BST's display of bytes in the listing backwards somewhere?

    Edit: Seems I labelled BIG and LITTLE backwards. I fixed it.
  • Heater.Heater. Posts: 21,230
    Nope, it's not BST's listing, it's Spin. That code example compiles to the following binary in hex:
    00000000  00 1b b7 00 00 12 10 00  24 00 2c 00 1c 00 30 00  |........$.,...0.|
    00000010  14 00 02 00 0c 00 00 00  78 56 34 12 3b 12 34 56  |........xV4.;.4V|
    00000020  78 c5 08 32                                       |x..2|
    
    We can clearly see the same LONG in different endianness in the last two lines there.
  • AribaAriba Posts: 2,682
    edited 2015-08-19 18:18
    The endianess inside the Bytecode does not matter, what counts is how the value is written to the variable x. And this is always little endian, like in the DAT section.
    An immediate value inside the bytecode can be encoded in several ways to minimize the bytecode size. Try for example:
    x := $8000000
    So you anyway can not access constants in bytecodes, only the Spin interpreter does this. And the interpreter knows how the value is stored.

    It would be fatal if operators like '+' would generate an error inside DAT sections. You really need to be able to do calculations at complie time for data elements.
    If you write "Hello" + "World" then it's your fault, every language has its rules. Just use a comma instead of '+' to concatenate the "strings". After byte follows just a list of comma separated bytes in various formats. The "Hello" is just a simplification for "H","e","l","l","o" and a very handy one.

    You can use signed bytes and words if you need them. Just add a prefix to the variable (~ or ~~). Yes it's different from other languages with types, but for me so much more logical.

    Andy

  • Heater.Heater. Posts: 21,230
    True, it matters not what the Spin interpreter does internally. Only confused me when first looking at the listing trying to see what go on in there.

    The "Hello" + "world" thing only surprised me years ago when I was writing my own PASM assembler. The idea was to support the syntaxes of VAR, CON and DAT blocks. I was busy trying out all kinds of syntax error cases in the Prop Tool to see what happened.

    Certainly "+" and other operators should work in DAT blocks. It's just that "Hello" + "World" thing that perhaps should produce a warning/error. It is rarely (never?) useful to do and generally a mistake like any other syntax error. So why not?





Sign In or Register to comment.