Re: So...what if I wanted to do something like atof?
- Posted by Derek Parnell <derekp at SOLACE.COM.AU> Dec 13, 2000
- 527 views
Hi Jonas, >Anyway, I have been working on a project using Euphoria and Win32Lib. I >have an edit control where I want the user to enter a price. I want to >convert and store that sequence as a floating point. In C++ I would have >probably used atof() to do that conversion. What is the functional >equivalent in Euphoria? Of course when you use getText() to retrieve whatever the user entered, you get a Euphoria sequence returned. This can be thought of a "string" field. But what you need in your program is to treat the user data as a monetary amount. Both integer and floating point numbers can be stored in a Euphoria atom. So the problem is "how to convert a sequence to an atom". Euphoria provides a very simplistic function, value(), which can do this for you. However, it has limitations that make it unusable in real-world applications. For example, it stops conversion when it finds a non-digit but still reports success. So if the user enters "12o.99" it will successfully return the value of 12 rather than generate an error. A number of people on the list have already created another version of value(), including myself, which handles a larger range of possible user input. Below is some code of mine that you can use as you see fit. ----------------------- constant TRUE = (1=1), FALSE = (0=1) integer vBase, vPeriod, vComma, vMoney sequence vLegalChars vBase = 10 vPeriod = '.' vComma = ',' vMoney = '$' vLegalChars = "0123456789abcdef-+.," ---------------------------------------------------------------------------- --- -- Sets the default number base to be used when converting strings to numbers. global function setnumberbase(integer pNewBase) integer lOldBase lOldBase = vBase if pNewBase > 1 and pNewBase <= 16 then vBase = pNewBase end if return lOldBase end function ---------------------------------------------------------------------------- --- -- Sets the default punctuation characters. -- If any parameter is zero, then the corresponding punctuation symbol is not changed. -- All three punctuation symbols must be different from each other. -- Returns a sequence of three elements containing the current punctuation chars. -- 1) The 'decimal place' character. Initially '.' -- 2) The digit separator character. Initially ',' -- 3) The money symbol. Initially '$' global function setnumberpunct(integer pPeriod, integer pComma, integer pMoney) sequence lOldValues lOldValues = {vPeriod, vComma, vMoney} if pPeriod >= 0 and pPeriod <= 255 then vPeriod = pPeriod end if if pComma >= 0 and pComma <= 255 then vComma = pComma end if if pMoney >= 0 and pMoney <= 255 then vMoney = pMoney end if if (vPeriod = vComma) or (vPeriod = vMoney) or (vComma = vMoney) then -- Duplicate chars used so revert. vPeriod = lOldValues[1] vComma = lOldValues[2] vMoney = lOldValues[3] else vLegalChars = "0123456789abcdef-+" & vPeriod & vComma end if return lOldValues end function ---------------------------------------------------------------------------- --- -- Converts a text sequence to a number (atom). -- Leading and trailing spaces are ignored. -- If the text is enclosed in parenthesis, a negative number is returned. -- If the number begins with ... -- [Money Symbol] : base is assumed as 10 -- # : base is assumed as 16 (Hexadecimal) -- 0x : base is assumed as 16 (Hexadecimal) -- 0b : base is assumed as 2 (Binary) -- 0d : base is assumed as 16 (Decimal) -- 0O : base is assumed as 8 (Octal) -- x' : base is assumed as 16 (Hexadecimal) and final char is dropped -- b' : base is assumed as 2 (Binary) and final is char dropped -- d' : base is assumed as 16 (Decimal) and final char is dropped -- O' : base is assumed as 8 (Octal) and final char is dropped -- -- The text can have a single '-' or '+', either before or after the number. -- Base 10 numbers can have digit separators. eg. 12,345.678,9 -- -- The function returns a 3-element sequence. -- 1) 0 ==> The input had some invalid format. Either a subsequence, -- a non-integer atom, -- or a character other than 0-9 a-f "+-", decimal or separator symbol -- or money symbol not in front of the number -- or a digit not in the current number base. -- 1 ==> No invalid elements were found. -- 2) The converted number (or as much as could be converted) -- 3) The index into the input were conversion stopped. Use this as an -- error marker if a zero was returned in the first element. -- -- Examples: -- sequence rc -- rc = seqtonumber(" $2,150.95") -- ? rc -- {1,2150.95,11} -- -- rc = seqtonumber("1234-") -- ? rc -- {1,-1234,6} -- -- rc = seqtonumber("0xff73") -- ? rc -- {1,65395,7} -- -- rc = seqtonumber("b'001101'") -- ? rc -- {1,13,9} (The final ' is ignored) -- -- rc = seqtonumber("12 Cats") -- ? rc -- {0,12,3} -- -- rc = seqtonumber("12.3.3 Cats") -- ? rc -- {0,12.3,5} -- ---- Valid "number" modifier prefixes. constant kNumStarts = { {"0X", 256, 16, 0}, {"0O", 256, 8, 0}, {"0B", 11, 2, 0}, {"0D", 13, 10, 0}, {"X'", 256, 16, 1}, {"D'", 256, 10, 1}, {"O'", 256, 8, 1}, {"B'", 256, 2, 1} } -------------------------------------------- global function seqtonumber(sequence pData) -------------------------------------------- atom lResult integer lDotFound, lSign, lPos, lOk, lChar, lValue atom lRHS, lLHS, lRHSdepth, lFullValue integer lBase, lUsingRHS, lCommas integer lStart, lEnd, lConvStarted, lTrailingSign sequence lTemp lResult = 0 lDotFound = FALSE lSign = 0 lUsingRHS = 0 lConvStarted = FALSE lTrailingSign = FALSE lRHS = 0 lRHSdepth = 1 lLHS = 0 lBase = vBase -- Disregard trailing and leading blanks lStart = length(pData) + 1 for i = 1 to length(pData) do if pData[i] != ' ' then lStart = i exit end if end for lEnd = 0 for i = length(pData) to 1 by -1 do if pData[i] != ' ' then lEnd = i exit end if end for -- Look for parenthesized numbers. if equal('(', pData[lStart]) and equal(')', pData[lEnd]) then lStart += 1 lEnd -= 1 lSign = -1 end if -- Examine for special start codes. if lEnd - lStart >= 0 then if equal(vMoney, pData[lStart]) then -- Cater for spaces between the $ and first digit. for i = lStart+1 to lEnd do if pData[i] != ' ' then lStart = i exit end if end for lBase = 10 elsif equal('#', pData[lStart]) then lBase = 16 lStart += 1 elsif lStart != length(pData) then lTemp = upper(pData[lStart .. lStart + 1]) for i = 1 to length(kNumStarts) do if equal(lTemp, kNumStarts[i][1]) then if lBase < kNumStarts[i][2] then lBase = kNumStarts[i][3] lStart += length(kNumStarts[i][1]) lEnd -= kNumStarts[i][4] exit end if end if end for end if end if -- Only base-10 can have commas if lBase != 10 then lCommas = FALSE else lCommas = TRUE end if -- Start parsing the string. lPos = lStart lOk = TRUE while TRUE do -- No more characters? if lPos > lEnd then exit end if -- Check for embedded sequences if sequence(pData[lPos]) then lOk = FALSE exit end if -- Check for non-integer elements. if not integer(pData[lPos]) then lOk = FALSE exit end if -- Pluck out the next char to examine. lChar = lower(pData[lPos]) -- Is it legal char.? lValue = find(lChar, vLegalChars) - 1 if lValue < 0 then lOk = FALSE exit end if -- Values below the base are useable digits. if lValue < lBase then -- This prevents embedded signs. if lTrailingSign = TRUE then lOk = FALSE exit end if -- Signal that conversion is underway. lConvStarted = TRUE -- Am I doing the Left or Right side of the "decimal" symbol? if lUsingRHS then -- Right side. -- Accume that value so far. lRHS = (lRHS * lBase) + lValue -- Calculate the RHS divisor lRHSdepth *= lBase else -- Left side. -- Accume the value so far. lLHS = (lLHS * lBase) + lValue end if else -- Start checking for special symbols. -- A negative sign? if lChar = '-' then -- If I haven't found one yet then mark this a -ve number -- and if conversion had started, its a trailing sign. if lSign = 0 then lSign = -1 lTrailingSign = lConvStarted else -- Duplicate sign symbol detected. lOk = FALSE exit end if -- A positive sign? elsif lChar = '+' then -- If I haven't found one yet then mark this a +ve number -- and if conversion had started, its a trailing sign. if lSign = 0 then lSign = 1 lTrailingSign = lConvStarted else -- Duplicate sign symbol detected. lOk = FALSE exit end if -- A decimal position symbol? elsif lChar = vPeriod then -- If I haven't found one yet then mark that I now have -- and start processing the right hand side. if lDotFound = FALSE then lDotFound = TRUE lUsingRHS = TRUE else -- A duplicate decimal symbol found. lOk = FALSE exit end if -- A digit separator? elsif lChar = vComma then -- If this base is not allowed digit separators, flag an error. if lCommas = FALSE then lOk = FALSE exit end if else -- Must be a non-legal character for current number base. lOk = FALSE exit end if end if -- Bump to next input character. lPos += 1 end while -- If no sign symbols used, assume a positive number. if lSign = 0 then lSign = 1 end if -- Calculate the actual value represented by the string. lFullValue = lSign *(lLHS + (lRHS / lRHSdepth)) return {lOk, lFullValue, lPos} end function ---------------------------------- cheers, Derek Parnell