    "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.

     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."
    see (`function parseInteger`)
    shared Float? parseFloat(String string) {

        // parse the sign first
        Integer sign;
        String unsignedPart;
        if (string.startsWith("-")) {
            sign = -1;
            unsignedPart = string[1...];
        } else if (string.startsWith("+")) {
            sign = +1;
            unsignedPart = string[1...];
        } else {
            sign = +1;
            unsignedPart = string;
        }
        // split into three main parts
        String wholePart;
        String fractionalPart;
        String? rest;
        if (exists dot = unsignedPart.firstOccurrence('.')) {
            wholePart = unsignedPart[...dot-1];
            String afterWholePart = unsignedPart[dot+1...];
            if (exists mag
                = afterWholePart.firstIndexWhere(Character.letter)) {
                fractionalPart = afterWholePart[...mag-1];
                rest = afterWholePart[mag...];
            }
            else {
                fractionalPart = afterWholePart;
                rest = null;
            }
        }
        else {
            if (exists mag
                = unsignedPart.firstIndexWhere(Character.letter)) {
                wholePart = unsignedPart[...mag-1];
                rest = unsignedPart[mag...];
            }
            else {
                wholePart = unsignedPart;
                rest = null;
            }
            fractionalPart = "0";
        }

        if (!wholePart.every(Character.digit) ||
            !fractionalPart.every(Character.digit)) {
            return null;
        }

        if (exists whole = parseInteger(wholePart),
            exists fractional = parseInteger(fractionalPart)) {
            value shift = fractionalPart.size;
            Integer exponent;
            if (exists rest) {
                if (exists magnitude
                    = parseFloatExponent(rest)) {
                    exponent = magnitude-shift;
                }
                else {
                    return null;
                }
            }
            else {
                exponent = -shift;
            }
            Integer numerator = whole*10^shift + fractional;
            Float signedNumerator
                    = numerator.zero
                          then 0 * sign.float //preserve sign of -0.0
                          else (sign * numerator).float;
            value exponentMagnitude = exponent.magnitude;
            if (exponentMagnitude==0) {
                return signedNumerator;
            }
            else if (exponentMagnitude<maximumIntegerExponent) {
                value scale = 10^exponentMagnitude;
                return exponent<0
                    then signedNumerator / scale
                    else signedNumerator * scale;
            }
            else {
                //scale can't be represented as
                //an integer, resulting in some
                //rounding error
                return signedNumerator * 10.0^exponent;
            }
        }

        return null;
    }

    //TODO: replace with a native implementation
    Integer maximumIntegerExponent
            = smallest(runtime.maxIntegerValue.string.size,
                       runtime.minIntegerValue.string.size-1);

    Integer? parseFloatExponent(String string) {
        switch (string)
        case ("k") {
            return 3;
        }
        case ("M") {
            return 6;
        }
        case ("G") {
            return 9;
        }
        case ("T") {
            return 12;
        }
        case ("P") {
            return 15;
        }
        case ("m") {
            return -3;
        }
        case ("u") {
            return -6;
        }
        case ("n") {
            return -9;
        }
        case ("p") {
            return -12;
        }
        case ("f") {
            return -15;
        }
        else {
            if (string.lowercased.startsWith("e") &&
                string.rest.every(digitOrSign)) {
                return parseInteger(string.rest);
            }
            else {
                return null;
            }
        }
    }

    Boolean(Character) digitOrSign
            = or(Character.digit, "+-".contains);