Shop OBEX P1 Docs P2 Docs Learn Events
StrToFloat() — Parallax Forums

StrToFloat()

cgraceycgracey Posts: 14,151
edited 2024-02-21 10:33 in Propeller 2

I made a StrToFloat method using the built-in Spin2 floating-point operators.

It accepts numbers in all formats and converts them to single-precision IEEE-754 floats:

123
123.456
0.00000000000000000000000012345
1.0
1.0e9
1.0e+9
1.0e-9
1e12
9e+45
1_000_000_000

So, no decimal point is needed. "E" is allowed for power-of-ten exponents. Underscores are allowed for grouping in the mantissa, but not in the exponent. It also checks for errors and ABORTS with messages.

'
' Get floating-point value - source_ptr must point to first digit
'
PRI get_float() : fp | n, dflag, nflag, exp

  repeat                                'MAC mantissa digits
    fp := fp *. 10.0 +. fp_dig[source_chr() & $0F]
    repeat while source_test("_")       'skip any "_" chrs
    if dflag                            'hold magnitude after decimal point
      n++
    if source_test(".")                 'check for decimal point, one only
      if dflag~~
        abort(@"Extra decimal point")
  while source_digit_next()             'another digit?

  if source_test_uppercase("E")         'check for exponent
    if source_test("-")                 'if "-", set negative flag
      nflag~~
    else                                'else, skip any "+"
      source_test("+")
    if not source_digit_next()          'make sure exponent digit
      abort(@"Expected exponent digit")
    repeat                              'MAC exponent digits
      exp := exp * 10 + source_chr() & $0F
    while source_digit_next()
    exp <#= 99                          'limit exponent, still under/overflows
    n -= (nflag ? -exp : exp)

  repeat while n                        'adjust magnitude
    exp := n #> -12 <# 12
    fp := fp /. fp_exp[exp]
    n -= exp

  if NaN(fp)                            'check overflow
    abort(@"Floating-point overflow")


DAT

fpstr   byte    "1.175494e+9",0         'test string

fp_dig  long    0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0

        long                                1e-12, 1e-11, 1e-10, 1e-09
        long    1e-08, 1e-07, 1e-06, 1e-05, 1e-04, 1e-03, 1e-02, 1e-01
fp_exp  long    1e+00, 1e+01, 1e+02, 1e+03, 1e+04, 1e+05, 1e+06, 1e+07
        long    1e+08, 1e+09, 1e+10, 1e+11, 1e+12

Comments

  • Added to OBEX : https://obex.parallax.com/obex/strtofloat/

    Once you finish the intense video driver work Chip, we'll get you setup with an OBEX account and have these objects moved to your name.

  • JonnyMacJonnyMac Posts: 9,102
    edited 2024-02-21 15:02

    @cgracey : Consider making a source pointer argument so that the method can be used with any string.

    pri get_float(p_src) : fp | n, dflag, nflag, exp
    
  • cgraceycgracey Posts: 14,151

    @VonSzarvas said:
    Added to OBEX : https://obex.parallax.com/obex/strtofloat/

    Once you finish the intense video driver work Chip, we'll get you setup with an OBEX account and have these objects moved to your name.

    Sounds good.

  • RaymanRayman Posts: 14,632

    I'm not sure I appreciated that Spin2 supports floating point much more than Spin1 until just now...
    String to float could be very useful.

    Have to figure out how to use floats in Spin2 first...
    I see this in the docs:
    x *. y

    Does this mean multiplying signed integer with float? Is this possible to do?
    Can I multiply two floats?
    Can I convert integer to float?

    Some things to figure out...

  • cgraceycgracey Posts: 14,151
    edited 2024-02-22 03:13

    @Rayman said:
    I'm not sure I appreciated that Spin2 supports floating point much more than Spin1 until just now...
    String to float could be very useful.

    Have to figure out how to use floats in Spin2 first...
    I see this in the docs:
    x *. y

    Does this mean multiplying signed integer with float? Is this possible to do?
    Can I multiply two floats?
    Can I convert integer to float?

    Some things to figure out...

    Because Spin2 is type-agnostic at runtime, you must use special floating-point operators to do floating-point operations. And be sure to do them on LONGs that can hold 32 bits:

     +.
     -. (subtract / negate)
     *.
     /.
     <.
     <=.
     ==.
     "<>."
     ">=."
     ">."
    

    So, just use these operators and you're doing floating-point.

  • cgraceycgracey Posts: 14,151

    Here is a new version that works on a string you point it to. It is self-contained and doesn't call any other methods.

    ' https://baseconvert.com/ieee-754-floating-point
    
    
    pub go() | error
    
      if error := \doit()                   'call with abort trap, show any error
        debug(zstr(error))
    
    
    PRI doit() | fp
    
      fp := get_float(@fp_str)              'call and show results
    
      debug(fdec(fp), uhex(fp)) 
    
    '
    ' Get floating-point value - ptr must point to first digit
    '
    PRI get_float(ptr) : fp | n, dflag, nflag, exp
    
      repeat while byte[ptr] == " "                 'skip any spaces
        ptr++
    
      ifnot lookdown(byte[ptr]: "0".."9")           'make sure digit
        abort(@"Expected digit")
    
      repeat                                        'MAC mantissa digits
        fp := fp *. 10.0 +. fp_dig[byte[ptr++] & $0F]
        repeat while byte[ptr] == "_"               'skip any "_" chrs
          ptr++
        if dflag                                    'hold magnitude after decimal point
          n++ 
        if byte[ptr] == "."                         'check for single decimal point
          ptr++
          if dflag~~
            abort(@"Extra decimal point")
      while lookdown(byte[ptr]: "0".."9")           'another digit?
    
      if lookdown(byte[ptr]: "E", "e")              'check for exponent
        ptr++
        if byte[ptr] == "-"                         'if "-", set negative flag
          ptr++
          nflag~~
        else                                        'else, skip any "+"
          if byte[ptr] == "+"
            ptr++
        if not lookdown(byte[ptr]: "0".."9")        'make sure exponent digit
          abort(@"Expected exponent digit")
        repeat                                      'MAC exponent digits
          exp := exp * 10 + byte[ptr++] & $0F
        while lookdown(byte[ptr]: "0".."9")
        exp <#= 99                                  'limit exponent, still under/overflows
        n -= (nflag ? -exp : exp)
    
      repeat while n                                'adjust magnitude
        exp := n #> -12 <# 12
        fp := fp /. fp_exp[exp]
        n -= exp
    
      if NaN(fp)                                    'check overflow
        abort(@"Floating-point overflow")
    
    
    DAT
    
    fp_str  byte    "0.00000000001e-9",0                    'test string
    
    fp_dig  long    0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
    
            long                                1e-12, 1e-11, 1e-10, 1e-09
            long    1e-08, 1e-07, 1e-06, 1e-05, 1e-04, 1e-03, 1e-02, 1e-01
    fp_exp  long    1e+00, 1e+01, 1e+02, 1e+03, 1e+04, 1e+05, 1e+06, 1e+07
            long    1e+08, 1e+09, 1e+10, 1e+11, 1e+12
    
  • cgraceycgracey Posts: 14,151

    @Rayman said:
    I'm not sure I appreciated that Spin2 supports floating point much more than Spin1 until just now...
    String to float could be very useful.

    Have to figure out how to use floats in Spin2 first...
    I see this in the docs:
    x *. y

    Does this mean multiplying signed integer with float? Is this possible to do?
    Can I multiply two floats?
    Can I convert integer to float?

    Some things to figure out...

    FLOAT(integer) : float
    ROUND(float) : integer
    TRUNC(float) : integer

    The floating-point operators work on floats, only. Use FLOAT/ROUND/TRUNC to go between integers and floats.

  • @cgracey said:
    Here is a new version that works on a string you point it to. It is self-contained and doesn't call any other methods.

    ' https://baseconvert.com/ieee-754-floating-point
    
    
    pub go() | error
    
      if error := \doit()                   'call with abort trap, show any error
        debug(zstr(error))
    
    
    PRI doit() | fp
    
      fp := get_float(@fp_str)              'call and show results
    
      debug(fdec(fp), uhex(fp)) 
    
    '
    ' Get floating-point value - ptr must point to first digit
    '
    PRI get_float(ptr) : fp | n, dflag, nflag, exp
    
      repeat while byte[ptr] == " "                 'skip any spaces
        ptr++
    
      ifnot lookdown(byte[ptr]: "0".."9")           'make sure digit
        abort(@"Expected digit")
    
      repeat                                        'MAC mantissa digits
        fp := fp *. 10.0 +. fp_dig[byte[ptr++] & $0F]
        repeat while byte[ptr] == "_"               'skip any "_" chrs
          ptr++
        if dflag                                    'hold magnitude after decimal point
          n++ 
        if byte[ptr] == "."                         'check for single decimal point
          ptr++
          if dflag~~
            abort(@"Extra decimal point")
      while lookdown(byte[ptr]: "0".."9")           'another digit?
    
      if lookdown(byte[ptr]: "E", "e")              'check for exponent
        ptr++
        if byte[ptr] == "-"                         'if "-", set negative flag
          ptr++
          nflag~~
        else                                        'else, skip any "+"
          if byte[ptr] == "+"
            ptr++
        if not lookdown(byte[ptr]: "0".."9")        'make sure exponent digit
          abort(@"Expected exponent digit")
        repeat                                      'MAC exponent digits
          exp := exp * 10 + byte[ptr++] & $0F
        while lookdown(byte[ptr]: "0".."9")
        exp <#= 99                                  'limit exponent, still under/overflows
        n -= (nflag ? -exp : exp)
    
      repeat while n                                'adjust magnitude
        exp := n #> -12 <# 12
        fp := fp /. fp_exp[exp]
        n -= exp
    
      if NaN(fp)                                    'check overflow
        abort(@"Floating-point overflow")
    
    
    DAT
    
    fp_str  byte    "0.00000000001e-9",0                    'test string
    
    fp_dig  long    0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
    
            long                                1e-12, 1e-11, 1e-10, 1e-09
            long    1e-08, 1e-07, 1e-06, 1e-05, 1e-04, 1e-03, 1e-02, 1e-01
    fp_exp  long    1e+00, 1e+01, 1e+02, 1e+03, 1e+04, 1e+05, 1e+06, 1e+07
            long    1e+08, 1e+09, 1e+10, 1e+11, 1e+12
    

    @cgracey

    It appears your routine won't work with negative floating values (i.e. "-1.02"), it complains that it is expecting a digit as the first string character. It works OK with positive and negative exponents, but not with negative values.

  • cgraceycgracey Posts: 14,151
    edited 2024-02-22 11:09

    @"Francis Bauer" said:

    @cgracey said:
    Here is a new version that works on a string you point it to. It is self-contained and doesn't call any other methods.

    ' https://baseconvert.com/ieee-754-floating-point
    
    
    pub go() | error
    
      if error := \doit()                   'call with abort trap, show any error
        debug(zstr(error))
    
    
    PRI doit() | fp
    
      fp := get_float(@fp_str)              'call and show results
    
      debug(fdec(fp), uhex(fp)) 
    
    '
    ' Get floating-point value - ptr must point to first digit
    '
    PRI get_float(ptr) : fp | n, dflag, nflag, exp
    
      repeat while byte[ptr] == " "                 'skip any spaces
        ptr++
    
      ifnot lookdown(byte[ptr]: "0".."9")           'make sure digit
        abort(@"Expected digit")
    
      repeat                                        'MAC mantissa digits
        fp := fp *. 10.0 +. fp_dig[byte[ptr++] & $0F]
        repeat while byte[ptr] == "_"               'skip any "_" chrs
          ptr++
        if dflag                                    'hold magnitude after decimal point
          n++ 
        if byte[ptr] == "."                         'check for single decimal point
          ptr++
          if dflag~~
            abort(@"Extra decimal point")
      while lookdown(byte[ptr]: "0".."9")           'another digit?
    
      if lookdown(byte[ptr]: "E", "e")              'check for exponent
        ptr++
        if byte[ptr] == "-"                         'if "-", set negative flag
          ptr++
          nflag~~
        else                                        'else, skip any "+"
          if byte[ptr] == "+"
            ptr++
        if not lookdown(byte[ptr]: "0".."9")        'make sure exponent digit
          abort(@"Expected exponent digit")
        repeat                                      'MAC exponent digits
          exp := exp * 10 + byte[ptr++] & $0F
        while lookdown(byte[ptr]: "0".."9")
        exp <#= 99                                  'limit exponent, still under/overflows
        n -= (nflag ? -exp : exp)
    
      repeat while n                                'adjust magnitude
        exp := n #> -12 <# 12
        fp := fp /. fp_exp[exp]
        n -= exp
    
      if NaN(fp)                                    'check overflow
        abort(@"Floating-point overflow")
    
    
    DAT
    
    fp_str  byte    "0.00000000001e-9",0                    'test string
    
    fp_dig  long    0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
    
            long                                1e-12, 1e-11, 1e-10, 1e-09
            long    1e-08, 1e-07, 1e-06, 1e-05, 1e-04, 1e-03, 1e-02, 1e-01
    fp_exp  long    1e+00, 1e+01, 1e+02, 1e+03, 1e+04, 1e+05, 1e+06, 1e+07
            long    1e+08, 1e+09, 1e+10, 1e+11, 1e+12
    

    @cgracey

    It appears your routine won't work with negative floating values (i.e. "-1.02"), it complains that it is expecting a digit as the first string character. It works OK with positive and negative exponents, but not with negative values.

    Thanks for noticing this. I just added that...


    UPDATED

  • @cgracey

    Thanks Chip, it works for negative values now.

  • VonSzarvasVonSzarvas Posts: 3,450
    edited 2024-02-22 09:22

    StrToFloat OBEX updated with the negative fix : https://obex.parallax.com/obex/strtofloat/

  • cgraceycgracey Posts: 14,151

    @VonSzarvas said:
    StrToFloat OBEX updated with the negative fix : https://obex.parallax.com/obex/strtofloat/

    Thanks, Michael.

    Could you please put the latest file I posted in place of the older one in there? I posted a new file 3 posts up which handles regular strings. It needs to be renamed from StrToFloat2.spin2 to StrToFloat.spin2. That is the one people should be using. Thanks a lot.

  • JonnyMacJonnyMac Posts: 9,102

    Since the string could come from outside the P2, you might consider stripping all leading whitespace characters:

      repeat while lookdown(byte[ptr] : 32, 9..13)
        ptr++
    
  • RaymanRayman Posts: 14,632
    edited 2024-02-22 23:55

    Is there an inverse function? FloatToStr()?

    BTW: Seeing how the floating point ops work now. At first, looked like the "." was operating on y and not the operator.
    When I first looked at it, was not seeing the spaces before and after the operator. A lot clearer now.
    x *. y

    Also, I see the Float() and Round() methods that make using all this viable..

  • JonnyMacJonnyMac Posts: 9,102
    edited 2024-02-23 15:08

    I made a small variation of Chip's object that uses a second return value for the error code instead of using the abort mechanism. For testing, there is a method in the object that converts the error code to the strings Chip used. The test program demonstrates this -- though I don't know how to make the object return the NaN error.

Sign In or Register to comment.