Re: So...what if I wanted to do something like atof?

new topic     » goto parent     » topic index » view thread      » older message » newer message

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

new topic     » goto parent     » topic index » view thread      » older message » newer message

Search



Quick Links

User menu

Not signed in.

Misc Menu