    import java.lang {
        JVMMath=Math
    }

    "The string decimal representation of the given
     [[floating point number|float]]. If the given number is
     [[negative|Float.negative]], the string representation will
     begin with `-`. The [[whole part|Float.wholePart]] and
     [[fractional parts|Float.fractionalPart]] of the number are
     separated by a `.` decimal point. Digits consist of decimal
     digits `0` to `9`.

     The number of decimal places following the decimal point is
     controlled by the parameters [[minDecimalPlaces]] and
     [[maxDecimalPlaces]], which default to `1` and `9`
     respectively, so that by default the string representation
     always contains a decimal point, and never contains more
     than nine decimal places. The decimal representation is
     rounded so that the number of decimal places never
     exceeds the specified maximum.

     For example:

     - `formatFloat(1234.1234)` is `\"1234.1234\"`
     - `formatFloat(0.1234)` is `\"0.1234\"`
     - `formatFloat(1234.0)` is `\"1234.0\"`
     - `formatFloat(1234.0,0)` is `\"1234\"`
     - `formatFloat(1234.1234,6)` is `\"1234.123400\"`
     - `formatFloat(1234.1234,0,2)` is `\"1234.12\"`
     - `formatFloat(1234.123456,0,5)` is `\"1234.12346\"`
     - `formatFloat(0.0001,2,2)` is `\"0.00\"`
     - `formatFloat(0.0001,0,2)` is `\"0\"`

     Finally:

     - `formatFloat(-0.0)` is `\"0.0\"`,
     - `formatFloat(0.0/0)` is `\"NaN\"`,
     - `formatFloat(1.0/0)` is `\"Infinity\"`, and
     - `formatFloat(-1.0/0)` is `\"-Infinity\".`

     This function never produces a representation involving
     scientific notation."
    tagged("Numbers")
    see (`function formatInteger`,
         `function parseFloat`)
    since("1.2.0")
    shared String formatFloat(
            "The floating point value to format."
            Float float,
            "The minimum number of allowed decimal places.

             If `minDecimalPlaces<=0`, the result may have no
             decimal point."
            variable Integer minDecimalPlaces=1,
            "The maximum number of allowed decimal places.

             If `maxDecimalPlaces<=0`, the result always has no
             decimal point."
            variable Integer maxDecimalPlaces=9,
            "The character to use as the decimal separator.

             `decimalSeparator` may not be '-' or a digit as
             defined by the Unicode general category *Nd*."
            Character decimalSeparator = '.',
            "If not `null`, `thousandsSeparator` will be used to
             separate each group of three digits, starting
             immediately to the left of the decimal separator.

             `thousandsSeparator` may not be equal to the
             decimalSeparator and may not be '-' or a digit as
             defined by the Unicode general category *Nd*."
            Character? thousandsSeparator = null) {

        if (exists thousandsSeparator) {
            "thousandsSeparator may not be '-' or a numeric digit."
            assert (!thousandsSeparator.digit
                    && !thousandsSeparator == '-');

            "The same character may not be used for both
             thousandsSeparator and decimalSeparator."
            assert (thousandsSeparator != decimalSeparator);
        }

        "The decimalSeparator may not be '-' or a numeric digit."
        assert (!decimalSeparator.digit
                && !decimalSeparator == '-');

        // let's not be rude and throw
        if (maxDecimalPlaces < 0) {
            maxDecimalPlaces = 0;
        }
        if (minDecimalPlaces < 0) {
            minDecimalPlaces = 0;
        }
        if (maxDecimalPlaces < minDecimalPlaces) {
            maxDecimalPlaces = minDecimalPlaces;
        }

        // handle 0, undefined, and infinities
        if (float == 0) {
            if (minDecimalPlaces > 0) {
                return "0``decimalSeparator````"0".repeat(minDecimalPlaces)``";
            }
            return "0";
        }
        else if (float.undefined || float.infinite) {
            return float.string;
        }

        variable value wholeDigitNumber = 0;
        value thousands = thousandsSeparator?.string else "";

        value result = StringBuilder();
        value magnitude = float.magnitude;
        value decimalMoveRight = smallest {
            // Don't include more fractional digits than
            // necessary. See rounding (halfEven) below.
            maxDecimalPlaces;
            14 - exponent(magnitude);
        };

        "The float, but with all meaningful digits shifted to the
         first ~15 positions of the whole part"
        value normalized = scaleByPowerOfTen(magnitude, decimalMoveRight);

        "The usable digits: [[normalized]] as an [[Integer]] after rounding"
        variable value integer = halfEven(normalized).integer;

        "The number of digits of [[integer]] that are to the right of
         the decimal point in [[float]]. May be negative."
        variable value fractionalPartDigits = decimalMoveRight;

        "Have any digits to the right of the '.' been emitted?"
        variable value emittedFractional = false;

        if (minDecimalPlaces > fractionalPartDigits) {
            // we have fewer fractional digits than we need
            if (fractionalPartDigits > 0) {
                result.append("0".repeat(
                    minDecimalPlaces - fractionalPartDigits));
            } else {
                result.append("0".repeat(minDecimalPlaces));
            }
            emittedFractional = true;
        }
        while (fractionalPartDigits > maxDecimalPlaces) {
            // we have more fractional digits than we need
            integer /= 10;
            fractionalPartDigits--;
        }
        while (fractionalPartDigits > 0) {
            // emit fractional part of 'integer'
            value digit = integer % 10;
            integer /= 10;
            if (digit != 0 || emittedFractional
                    || fractionalPartDigits <= minDecimalPlaces) {
                result.appendCharacter('0'.neighbour(digit));
                emittedFractional = true;
            }
            fractionalPartDigits--;
        }
        if (emittedFractional) {
            result.appendCharacter(decimalSeparator);
        }
        if (integer == 0) {
            result.appendCharacter('0');
        }
        else {
            while (fractionalPartDigits++ < 0) {
                // we have fewer whole part digits than we need
                if (3.divides(wholeDigitNumber++)
                        && wholeDigitNumber != 1) {
                    result.append(thousands);
                }
                result.appendCharacter('0');
            }
            while (integer != 0) {
                // emit whole part
                if (3.divides(wholeDigitNumber++)
                        && wholeDigitNumber != 1) {
                    result.append(thousands);
                }
                value digit = integer % 10;
                integer /= 10;
                result.appendCharacter('0'.neighbour(digit));
            }
        }
        if (float < 0.0) {
            result.appendCharacter('-');
        }
        return(result.string.reversed);
    }

    Integer exponent(variable Float f)
        =>  let (l10 = log10(f.magnitude))
            // now, compute the floor
            if (l10.fractionalPart == 0.0 || l10 > 0.0)
            then l10.wholePart.integer
            else l10.wholePart.integer - 1;

    Float scaleByPowerOfTen(Float float, variable Integer power) {
        function doScale(Float float, Integer power) {
            value scale =
                    let (magnitude = power.magnitude)
                    if (magnitude <= 15)
                        then (10^magnitude).nearestFloat     //fast
                        else 10.0.powerOfInteger(magnitude); //slow
            return if (power < 0)
                    then float / scale
                    else float * scale;
        }
        // don't attempt to create a float larger than 1.0e308.
        variable value result = float;
        while (power > 0) {
            value amount = smallest(308, power);
            result = doScale(result, amount);
            power -= amount;
        }
        while (power < 0) {
            value amount = largest(-308, power);
            result = doScale(result, amount);
            power -= amount;
        }
        return result;
    }

    Float twoFiftyTwo = (2^52).float;

    Float halfEven(Float num) {
        if (num.infinite ||
                num.undefined ||
                num.fractionalPart == 0.0) {
            return num;
        }

        variable value result = num.magnitude;
        if (result >= twoFiftyTwo) {
            return num;
        }

        // else, round
        result = (twoFiftyTwo + result) - twoFiftyTwo;
        return result * num.sign.float;
    }

    native
    Float log10(Float num);

    native("jvm")
    Float log10(Float num)
        =>  JVMMath.log10(num);

    native("js")
    Float log10(Float num) {
        dynamic {
            Float n = Math.log(num);
            Float d = Math.\iLN10;
            return n / d;
        }
    }