    import java.lang {
        JDouble=Double
    }
    import ceylon.language {
        ParseFloatState {
            start, afterPlusMinus, digitsBeforeDecimal,
            afterJustDecimal, afterDecimal, digitsAfterDecimal,
            afterE, exponentDigits, afterEPlusMinus,
            afterSuffix, invalid
        }
    }

    class ParseFloatState of
            start | afterPlusMinus | digitsBeforeDecimal |
            afterJustDecimal | afterDecimal |
            digitsAfterDecimal | afterE | exponentDigits |
            afterEPlusMinus | afterSuffix | invalid {

        shared new start {}
        shared new afterPlusMinus {}
        shared new digitsBeforeDecimal {}
        shared new afterJustDecimal {}
        shared new afterDecimal {}
        shared new digitsAfterDecimal {}
        shared new afterE {}
        shared new exponentDigits {}
        shared new afterEPlusMinus {}
        shared new afterSuffix {}
        shared new invalid {}
    }

    "The [[Float]] value of the given
     [[string representation|string]] of a decimal floating
     point number, or `null` if the string does not represent a
     decimal floating point number.

     If the given string representation contains more digits
     than can be represented by a `Float`, then the least
     significant digits are ignored.

     The syntax accepted by this method is the same as the
     syntax for a `Float` literal in the Ceylon language
     except that it may optionally begin with a sign
     character (`+` or `-`) and may not contain grouping
     underscore characters. That is, an optional sign character,
     followed by a string of decimal digits, followed by an
     optional decimal point and string of decimal digits,
     followed by an optional decimal exponent, for example
     `e+10` or `E-5`, or SI magnitude, `k`, `M`, `G`, `T`, `P`,
     `m`, `u`, `n`, `p`, or `f`.

         Float: Sign? Digits ('.' Digits)? (Magnitude|Exponent)
         Sign: '+' | '-'
         Magnitude: 'k' | 'M' | 'G' | 'T' | 'P' | 'm' | 'u' | 'n' | 'p' | 'f'
         Exponent: ('e'|'E') Sign? Digits
         Digits: ('0'..'9')+"
    see (`function formatFloat`,
         `function parseInteger`)
    tagged("Numbers", "Basic types")
    shared Float? parseFloat(String string) {
        // ("-"|"+")?
        // (Digit* "." Digit+) | (Digit+ "."?)
        // (("E"|"e") ("+"|"-")? Digit+) | suffix

        variable value state = start;
        variable value size = 0;
        variable Integer? suffixExponent = null;

        for (c in string) {
            size++;
            state = switch (state)
            case (start)
                if (c == '+' || c == '-')
                    then afterPlusMinus
                else if ('0' <= c <= '9')
                    then digitsBeforeDecimal
                else if (c == '.')
                    then afterJustDecimal
                else invalid
            case (afterPlusMinus)
                if ('0' <= c <= '9')
                    then digitsBeforeDecimal
                else if (c == '.')
                    then afterJustDecimal
                else invalid
            case (digitsBeforeDecimal)
                if ('0' <= c <= '9')
                    then digitsBeforeDecimal
                else if (c == '.')
                    then afterDecimal
                else if (c == 'e' || c == 'E')
                    then afterE
                else if (c in "PTGMkmunpf")
                    then afterSuffix
                else invalid
            case (afterJustDecimal)
                if ('0' <= c <= '9')
                    then digitsAfterDecimal
                else invalid
            case (digitsAfterDecimal |
                  afterDecimal)
                if ('0' <= c <= '9')
                    then digitsAfterDecimal
                else if (c == 'e' || c == 'E')
                    then afterE
                else if (c in "PTGMkmunpf")
                    then afterSuffix
                else invalid
            case (afterE)
                if ('0' <= c <= '9')
                    then exponentDigits
                else if (c == '+' || c == '-')
                    then afterEPlusMinus
                else invalid
            case (exponentDigits |
                  afterEPlusMinus)
                if ('0' <= c <= '9')
                    then exponentDigits
                else invalid
            case (afterSuffix)
                invalid
            case (invalid)
                invalid;

            if (state == afterSuffix) {
                suffixExponent = parseSuffix(c);
            }

            if (state == invalid) {
                return null;
            }
        }

        if (!state in [digitsBeforeDecimal,
                afterDecimal, digitsAfterDecimal,
                exponentDigits, afterSuffix]) {
            return null;
        }

        if (exists exponent = suffixExponent) {
            // Ceylon style magnitude suffix
            return nativeParseFloat(
                "``string[...size-2]``\
                 E``exponent``");
        }
        else {
            // may or may not have exponent
            return nativeParseFloat(string);
        }
    }

    Integer parseSuffix(Character suffix) {
        switch (suffix)
        case ('P') {
            return 15;
        }
        case ('T') {
            return 12;
        }
        case ('G') {
            return 9;
        }
        case ('M') {
            return 6;
        }
        case ('k') {
            return 3;
        }
        case ('m') {
            return -3;
        }
        case ('u') {
            return -6;
        }
        case ('n') {
            return -9;
        }
        case ('p') {
            return -12;
        }
        case ('f') {
            return -15;
        }
        else {
            assert (false);
        }
    }

    native
    Float? nativeParseFloat(String string);

    native("jvm")
    Float? nativeParseFloat(String string)
        =>  JDouble.parseDouble(string);

    native("js")
    Float? nativeParseFloat(String string) {
        Float result;
        dynamic {
            result = nativeJSParseFloat(string);
        }
        if (result == 0.0 && string.occursAt(0, '-')) {
            return -0.0;
        }
        return result;
    }