"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);