"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 Float.format`)
since("1.2.0")
deprecated("Use [[Float.format]]")
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
value emitZeros = if (fractionalPartDigits > 0)
then minDecimalPlaces - fractionalPartDigits
else minDecimalPlaces;
result.append("0".repeat(emitZeros));
emittedFractional = emitZeros > 0;
}
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.containsAny('1'..'9')) {
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) {
import java.lang {
Math
}
return Math.log10(num);
}
native("js")
Float log10(Float num) {
dynamic {
Float n = Math.log(num);
Float d = Math.\iLN10;
return n / d;
}
}