Jump to content

String To Number Conversion With Proper Error Detection


Speaker
 Share

Recommended Posts

I have written these functions because IMO the standard AutoIt conversion function called 'Number()' is inadequate, e.g. it just stops conversion when a non-digit character is found. If _any_ kind of badly formed string is found, the function should report an error and abort conversion. Also 'Number() does not distinguish integers and floats which may be important in certain situations, and it cannot convert binary or hexadecimal strings.

My functions are intended for situations when error reporting is important (e.g. manual input or reading values from a text file). If you are absolutely sure that there will be no errors in the number format then you can use 'Number(). But can you ever be absolutely sure? :think:

At the end of this post, in separate code sections, there are to simple test scripts to demostrate how these functions work.

Description of functions:

StringToInt(str, base)

Converts the string 'str' to an integer using the number base 'base' (2, 10 or 16). Returns the converted value. In case of error the return value is zero. Sets '@error' as follows:

0 = no error

-1 = bad argument(s)

-2 = string too long or too short

-3 = string contains non-digit character(s)

StringToFloat(str)

Converts the string 'str' to a floating point number.

Accepts the following formats ('n' represents one or more decimal digits):

n -n .n -.n n. -n. n.n -n.n

nEn nE-n -nEn -nE-n n.nEn -n.nEn n.nE-n -n.nE-n

There must be at most 3 digits in the exponent, and its absolute value must not be larger than 300. The maximum number of characters in 'str' is 22 (sufficient for 15-digit precision in the longest format with full exponent and two minus signs).

Returns the converted value. In case of error returns zero. Sets '@error' as follows:

0 = no error

-1 = bad argument

-2 = string too long or too short

-3 = non-digit character where digit is expected

-4 = formatting error (not a proper floating point number)

Formatting errors (code -4) include:

- neither integer nor fractional part present

- decimal point is after the exponent mark 'E'

- minus sign is in bad position (not the first char in exponent)

- multiple decimal points

- multiple exponent marks 'E'

- Exponent marker 'E' without exponent digits

- too many digits in exponent

- exponent value too big (> 300)

; numconv.au3
;
; String to number conversion with proper error detection and reporting.
;
; AutoIt Version: 3.0
; Language:    English
; Platform:    Win9x/NT
; Author:        Laszlo Menczel (menczel@invitel.hu)
; Date:        4/9/2006
;
; Include this file at the start of your script (use 'include-once').

; Exp10(pow)
; Helper function to calculate power of 10 (for exponent values).
; WARNING: This is NOT a general purpose function, it works only with integer arguments!

Func Exp10($pow)

  Dim $i, $res, $x
  
  $x = Round($pow)

  If $x = 0 Then
    Return 1
  Endif
  
  $res = 1;

  If $x > 0 Then
    For $i = 1 TO $x
      $res = 10.0 * $res
    Next
  Else
    $x = -1 * $x
    For $i = 1 TO $x
      $res = $res / 10.0
    Next
  Endif

  Return $res
  
EndFunc

;-------------------------------------------------------------------------------

; CharToNumber(chr, digits)
; Helper function to get the numeric value of a character interpreted
; as a digit. Valid characters are specified in 'digits'.

Func CharToNumber($chr, $dig)

  Dim $pos, $next, $len

  $len = StringLen($dig)
  $pos = 1
  
  While $pos <= $len
  
    $next = StringMid($dig, $pos, 1)
    If $chr = $next Then
      Return $pos - 1
    Endif
    
    $pos = $pos + 1
  Wend
  
  Return -1
  
EndFunc

;-------------------------------------------------------------------------------

Func StringToInt($s, $base)

  Dim $pos, $len, $maxlen, $val, $tmp
  Dim $fact, $mult, $sum, $sign, $digits, $start

  If $s = "" Then
    SetError(-1)
    Return 0
  Endif

  If $base <> 2 AND $base <> 10 AND $base <> 16 Then
    SetError(-1)
    Return 0
  Endif

  $start = 1
  $sign = 1
  
  Select

    Case $base = 2
      $mult = 2
      $maxlen = 32
      $digits = "01"
            
    Case $base = 10
      $mult = 10
      $maxlen = 10
      $digits = "0123456789"      

      $tmp = StringMid($s, 1, 1)
      If $tmp = "-" Then
        $maxlen = $maxlen + 1
        $start = 2
        $sign = -1
      Endif
      
    Case $base = 16
      $mult = 16
      $maxlen = 8
      $digits = "0123456789ABCDEF"

  EndSelect

  $pos = StringLen($s)
  If $pos > $maxlen OR ($pos - $start) < 1 Then
    SetError(-2)
    Return 0
  Endif
  
  $fact = 1
  $sum = 0

  While $pos >= $start
  
    $tmp = StringMid($s, $pos, 1)
    $val = CharToNumber($tmp, $digits)

    If $val = -1 Then
      SetError(-3)
      Return 0
    Endif
    
    $sum = $sum + $fact * $val
    $fact = $fact * $mult
    $pos = $pos - 1

  Wend

  $sum = $sum * $sign
      
  SetError(0)
  Return $sum
  
EndFunc

;-------------------------------------------------------------------------------

Func StringToFloat($s)

  Dim $tmp, $val, $factor, $len, $pos, $start, $digits
  Dim $int_present, $frac_present, $exp_present
  Dim $dotpos, $fracval, $intval, $sign
  Dim $exppos, $expbeg, $expval, $expsign

 ;--------- check argument

  If $s = "" Then
    SetError(-1)
    Return 0.0
  Endif
  
  $len = StringLen($s)

  If $len < 1 OR $len > 22 Then
    SetError(-2)
    Return 0.0
  Endif

 ;----------- initialize variables

  $sum = 0.0
  $sign = 1.0
  $expsign = 1.0
  $expval = 0.0
  $fracval = 0.0
  $intval = 0.0
  
  $int_present = 0
  $frac_present = 0
  $exp_present = 0

  $start = 1
  $digits = "0123456789"      
  
 ;----------- scan string & collect info

  $tmp = StringMid($s, 1, 1)       ; check for minus sign

  If $tmp = "-" Then
    If $len < 2 Then               ; no digits
      SetError(-2)
      Return 0.0
    Else  
      $sign = -1.0
      $start = 2
    Endif
  Endif
  
  $dotpos = 0
  $exppos = $len + 1
  $pos = $start

  While $pos <= $len

    $tmp = StringMid($s, $pos, 1)

    Select
      Case $tmp = "."
        If $dotpos > 0 Then      ; multiple decimal points
          SetError(-4)
          Return 0.0
        Else
          $dotpos = $pos
        Endif

      Case $tmp = "e"              ; multiple exponent markers
        If $exppos < $len + 1 Then
          SetError(-4)
          Return 0.0
        Else
          $exppos = $pos
        Endif

    EndSelect
     
    $pos = $pos + 1
     
  Wend
           
 ;----------- check positions & set flags

  If $dotpos > $exppos Then
    SetError(-4)
    Return 0.0
  Endif 

  If $exppos < $len + 1 Then

    $expbeg = $exppos

    If $expbeg = $len Then       ; no exponent spec
      SetError(-4)
      Return 0.0
    Endif
    
    $tmp = StringMid($s, $expbeg + 1, 1)

    If $tmp = "-" Then           ; check for minus sign
      $expbeg = $expbeg + 1
      If $expbeg = $len Then       ; no digits
        SetError(-4)
        Return 0.0
      Else
        If ($len - $expbeg) > 3 Then ; too many digits
          SetError(-4)
          Return 0.0
        Else
          $expsign = -1.0
        Endif
      Endif
    Endif

    $exp_present = 1

  Endif

  If $dotpos > 0 Then              ; decimal point present
    If ($exppos - $dotpos) > 1 Then
      $frac_present = 1
    Endif
    If ($dotpos - $start) > 0 Then
      $int_present = 1
    Endif
  Else                            ; only integer part if any
    If ($exppos - $start) > 0 Then
      $int_present = 1
    Endif
  Endif
  
  If $int_present = 0 AND $frac_present = 0 Then 
    SetError(-4)
    Return 0.0
  Endif

 ;----------- convert the exponent if present

  If $exp_present > 0 Then

    $pos = $len
    $factor = 1.0
    $expval = 0.0
    
    While $pos > $expbeg

      $tmp = StringMid($s, $pos, 1)
      $val = CharToNumber($tmp, $digits)

      If $val < 0 Then
        SetError(-3)
        Return 0.0
      Else
        $expval = $expval + $val * $factor
        $factor = 10.0 * $factor
      Endif
      
      $pos = $pos - 1

    Wend

    If $expval > 300 Then
      SetError(-4)
      Return 0.0
    Endif
  
  Endif

 ;----------- convert fractional part if present

  If $frac_present > 0 Then

    $pos = $dotpos + 1
    $factor = 0.1
    
    While $pos < $exppos
    
      $tmp = StringMid($s, $pos, 1)
      $val = CharToNumber($tmp, $digits)

      If $val < 0 Then
        SetError(-3)
        Return 0.0
      Else
        $fracval = $fracval + $val * $factor
        $factor = $factor / 10.0
      Endif
    
      $pos = $pos + 1

    Wend
    
  Endif

 ;------------ convert integer part if present

  If $int_present > 0 Then
  
    If $dotpos > 0 Then
      $pos = $dotpos - 1
    Else
      $pos = $exppos - 1
    Endif
    
    $factor = 1.0
    
    While $pos >= $start
    
      $tmp = StringMid($s, $pos, 1)
      $val = CharToNumber($tmp, $digits)

      If $val < 0 Then
        SetError(-3)
        Return 0.0
      Else
        $intval = $intval + $val * $factor
        $factor = $factor * 10.0
      Endif
    
      $pos = $pos - 1

    Wend

  Endif

 ;------------ calculate & return the final value

  $val = $sign * ($intval + $fracval) * Exp10($expsign * $expval) 
  SetError(0)
  Return $val
  
EndFunc

; Script to test StringToInt()

  #include <GUIConstants.au3>
  #include "numconv.au3"

  Opt("ExpandVarStrings", 1)
  Opt("RunErrorsFatal", 0)

  $tmp = InputBox("Number base", "2, 10 or 16:")
  $x = Number($tmp)
  
  If $x <> 2 AND $x <> 10 AND $x <> 16 Then
    $str = StringFormat("bad number base %s", $tmp)
    MsgBox(0, "Error", $str)
    Exit
  Endif

  $tmp = InputBox("Input", "String to convert:")
  $val = StringToInt($tmp, $x)
  
  If @error = 0 Then
    $tmp = StringFormat("value = %d", $val)
    MsgBox(0, "Result", $tmp)
  Else
    $tmp = StringFormat("error %d", @error)
    MsgBox(0, "Result", $tmp)
  Endif  

  Exit

; Script to test StringToFloat()

  #include <GUIConstants.au3>
  #include "numconv.au3"

  Opt("ExpandVarStrings", 1)
  Opt("RunErrorsFatal", 0)

  $tmp = InputBox("Input", "String to convert:")
  $val = StringToFloat($tmp)
  
  If @error = 0 Then
    $tmp = StringFormat("value = %f", $val)
    MsgBox(0, "Result", $tmp)
  Else
    $tmp = StringFormat("error %d", @error)
    MsgBox(0, "Result", $tmp)
  Endif  

  Exit
Link to comment
Share on other sites

hi,

I would line to ask you it you pay attention that the decimal separator can be different than the dot "." in other countries ?

Here it's the coma "," for example.. i don't know if it can be modified easily ?

Cya

Link to comment
Share on other sites

nice function.

@Jango

If you look through the script you could easily change the '.' to a ',' or add in a stringreplace that changes the ',' to a '.'

My Programs:AInstall - Create a standalone installer for your programUnit Converter - Converts Length, Area, Volume, Weight, Temperature and Pressure to different unitsBinary Clock - Hours, minutes and seconds have 10 columns each to display timeAutoIt Editor - Code Editor with Syntax Highlighting.Laserix Editor & Player - Create, Edit and Play Laserix LevelsLyric Syncer - Create and use Synchronised Lyrics.Connect 4 - 2 Player Connect 4 Game (Local or Online!, Formatted Chat!!)MD5, SHA-1, SHA-256, Tiger and Whirlpool Hash Finder - Dictionary and Brute Force FindCool Text Client - Create Rendered ImageMy UDF's:GUI Enhance - Enhance your GUIs visually.IDEA File Encryption - Encrypt and decrypt files easily! File Rename - Rename files easilyRC4 Text Encryption - Encrypt text using the RC4 AlgorithmPrime Number - Check if a number is primeString Remove - remove lots of strings at onceProgress Bar - made easySound UDF - Play, Pause, Resume, Seek and Stop.
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...