// Copyright (c) Jython Developers
package org.python.core.stringlib;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import org.python.core.stringlib.InternalFormat.Spec;
/**
* A class that provides the implementation of floating-point formatting. In a limited way, it acts
* like a StringBuilder to which text and one or more numbers may be appended, formatted according
* to the format specifier supplied at construction. These are ephemeral objects that are not, on
* their own, thread safe.
*/
public class FloatFormatter extends InternalFormat.Formatter {
/** The rounding mode dominant in the formatter. */
static final RoundingMode ROUND_PY = RoundingMode.HALF_EVEN;
/** Limit the size of results. */
// No-one needs more than log(Double.MAX_VALUE) - log2(Double.MIN_VALUE) = 1383 digits.
static final int MAX_PRECISION = 1400;
/** If it contains no decimal point, this length is zero, and 1 otherwise. */
private int lenPoint;
/** The length of the fractional part, right of the decimal point. */
private int lenFraction;
/** The length of the exponent marker ("e"), "inf" or "nan", or zero if there isn't one. */
private int lenMarker;
/** The length of the exponent sign and digits or zero if there isn't one. */
private int lenExponent;
/** if ≥0, minimum digits to follow decimal point (where consulted) */
private int minFracDigits;
/**
* Construct the formatter from a client-supplied buffer, to which the result will be appended,
* and a specification. Sets {@link #mark} to the end of the buffer.
*
* @param result destination buffer
* @param spec parsed conversion specification
*/
public FloatFormatter(StringBuilder result, Spec spec) {
super(result, spec);
if (spec.alternate) {
// Alternate form means do not trim the zero fractional digits.
minFracDigits = -1;
} else if (spec.type == 'r' || spec.type == Spec.NONE) {
// These formats by default show at least one fractional digit.
minFracDigits = 1;
} else {
/*
* Every other format (if it does not ignore the setting) will by default trim off all
* the trailing zero fractional digits.
*/
minFracDigits = 0;
}
}
/**
* Construct the formatter from a specification, allocating a buffer internally for the result.
*
* @param spec parsed conversion specification
*/
public FloatFormatter(Spec spec) {
this(new StringBuilder(size(spec)), spec);
}
/**
* Recommend a buffer size for a given specification, assuming one float is converted. This will
* be a "right" answer for e and g-format, and for f-format with values up to 9,999,999.
*
* @param spec parsed conversion specification
*/
public static int size(Spec spec) {
// Rule of thumb used here (no right answer):
// in e format each float occupies: (p-1) + len("+1.e+300") = p+7;
// in f format each float occupies: p + len("1,000,000.%") = p+11;
// or an explicit (minimum) width may be given, with one overshoot possible.
return Math.max(spec.width + 1, spec.getPrecision(6) + 11);
}
/**
* Override the default truncation behaviour for the specification originally supplied. Some
* formats remove trailing zero digits, trimming to zero or one. Set member
* minFracDigits, to modify this behaviour.
*
* @param minFracDigits if <0 prevent truncation; if ≥0 the minimum number of fractional
* digits; when this is zero, and all fractional digits are zero, the decimal point
* will also be removed.
*/
public void setMinFracDigits(int minFracDigits) {
this.minFracDigits = minFracDigits;
}
@Override
protected void reset() {
// Clear the variables describing the latest number in result.
super.reset();
lenPoint = lenFraction = lenMarker = lenExponent = 0;
}
@Override
protected int[] sectionLengths() {
return new int[] {lenSign, lenWhole, lenPoint, lenFraction, lenMarker, lenExponent};
}
/*
* Re-implement the text appends so they return the right type.
*/
@Override
public FloatFormatter append(char c) {
super.append(c);
return this;
}
@Override
public FloatFormatter append(CharSequence csq) {
super.append(csq);
return this;
}
@Override
public FloatFormatter append(CharSequence csq, int start, int end) //
throws IndexOutOfBoundsException {
super.append(csq, start, end);
return this;
}
/**
* Format a floating-point number according to the specification represented by this
* FloatFormatter.
*
* @param value to convert
* @return this object
*/
public FloatFormatter format(double value) {
return format(value, null);
}
/**
* Format a floating-point number according to the specification represented by this
* FloatFormatter. The conversion type, precision, and flags for grouping or
* percentage are dealt with here. At the point this is used, we know the {@link #spec} is one
* of the floating-point types. This entry point allows explicit control of the prefix of
* positive numbers, overriding defaults for the format type.
*
* @param value to convert
* @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
* @return this object
*/
@SuppressWarnings("fallthrough")
public FloatFormatter format(double value, String positivePrefix) {
// Puts all instance variables back to their starting defaults, and start = result.length().
setStart();
// Precision defaults to 6 (or 12 for none-format)
int precision = spec.getPrecision(Spec.specified(spec.type) ? 6 : 12);
// Guard against excessive result precision
// XXX Possibly better raised before result is allocated/sized.
if (precision > MAX_PRECISION) {
throw precisionTooLarge("float");
}
/*
* By default, the prefix of a positive number is "", but the format specifier may override
* it, and the built-in type complex needs to override the format.
*/
char sign = spec.sign;
if (positivePrefix == null && Spec.specified(sign) && sign != '-') {
positivePrefix = Character.toString(sign);
}
// Different process for each format type, ignoring case for now.
switch (Character.toLowerCase(spec.type)) {
case 'e':
// Exponential case: 1.23e-45
format_e(value, positivePrefix, precision);
break;
case 'f':
// Fixed case: 123.45
format_f(value, positivePrefix, precision);
break;
case 'n':
// Locale-sensitive version of g-format should be here. (Désolé de vous decevoir.)
// XXX Set a variable here to signal localisation in/after groupDigits?
case 'g':
// General format: fixed or exponential according to value.
format_g(value, positivePrefix, precision, 0);
break;
case Spec.NONE:
// None format like g-format but goes exponential at precision-1
format_g(value, positivePrefix, precision, -1);
break;
case 'r':
// For float.__repr__, very special case, breaks all the rules.
format_r(value, positivePrefix);
break;
case '%':
// Multiplies by 100 and displays in f-format, followed by a percent sign.
format_f(100. * value, positivePrefix, precision);
result.append('%');
break;
default:
// Should never get here, since this was checked in PyFloat.
throw unknownFormat(spec.type, "float");
}
// If the format type is an upper-case letter, convert the result to upper case.
if (Character.isUpperCase(spec.type)) {
uppercase();
}
// If required to, group the whole-part digits.
if (spec.grouping) {
groupDigits(3, ',');
}
return this;
}
/**
* Convert just the letters in the representation of the current number (in {@link #result}) to
* upper case. (That's the exponent marker or the "inf" or "nan".)
*/
@Override
protected void uppercase() {
int letters = indexOfMarker();
int end = letters + lenMarker;
for (int i = letters; i < end; i++) {
char c = result.charAt(i);
result.setCharAt(i, Character.toUpperCase(c));
}
}
/**
* Common code to deal with the sign, and the special cases "0", "-0", "nan, "inf", or "-inf".
* If the method returns false, we have started a non-zero number and the sign is
* already in {@link #result}. The client need then only encode abs(value). If the method
* returns true, and {@link #lenMarker}==0, the value was "0" or "-0": the caller
* may have to zero-extend this, and/or add an exponent, to match the requested format. If the
* method returns true, and {@link #lenMarker}>0, the method has placed "nan, "inf"
* in the {@link #result} buffer (preceded by a sign if necessary).
*
* @param value to convert
* @return true if the value was one of "0", "-0", "nan, "inf", or "-inf".
* @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
*/
private boolean signAndSpecialNumber(double value, String positivePrefix) {
// This is easiest via the raw bits
long bits = Double.doubleToRawLongBits(value);
// NaN is always positive
if (Double.isNaN(value)) {
bits &= ~SIGN_MASK;
}
if ((bits & SIGN_MASK) != 0) {
// Negative: encode a minus sign and strip it off bits
result.append('-');
lenSign = 1;
bits &= ~SIGN_MASK;
} else if (positivePrefix != null) {
// Positive, and a prefix is required. Note CPython 2.7 produces "+nan", " nan".
result.append(positivePrefix);
lenSign = positivePrefix.length();
}
if (bits == 0L) {
// All zero means it's zero. (It may have been negative, producing -0.)
result.append('0');
lenWhole = 1;
return true;
} else if ((bits & EXP_MASK) == EXP_MASK) {
// This is characteristic of NaN or Infinity.
result.append(((bits & ~EXP_MASK) == 0L) ? "inf" : "nan");
lenMarker = 3;
return true;
} else {
return false;
}
}
private static final long SIGN_MASK = 0x8000000000000000L;
private static final long EXP_MASK = 0x7ff0000000000000L;
/**
* The e-format helper function of {@link #format(double, String)} that uses Java's
* {@link BigDecimal} to provide conversion and rounding. The converted number is appended to
* the {@link #result} buffer, and {@link #start} will be set to the index of its first
* character.
*
* @param value to convert
* @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
* @param precision precision (maximum number of fractional digits)
*/
private void format_e(double value, String positivePrefix, int precision) {
// Exponent (default value is for 0.0 and -0.0)
int exp = 0;
if (!signAndSpecialNumber(value, positivePrefix)) {
// Convert abs(value) to decimal with p+1 digits of accuracy.
MathContext mc = new MathContext(precision + 1, ROUND_PY);
BigDecimal vv = new BigDecimal(Math.abs(value), mc);
// Take explicit control in order to get exponential notation out of BigDecimal.
String digits = vv.unscaledValue().toString();
int digitCount = digits.length();
result.append(digits.charAt(0));
lenWhole = 1;
if (digitCount > 1) {
// There is a fractional part
result.append('.').append(digits.substring(1));
lenPoint = 1;
lenFraction = digitCount - 1;
}
exp = lenFraction - vv.scale();
}
// If the result is not already complete, add point and zeros as necessary, and exponent.
if (lenMarker == 0) {
ensurePointAndTrailingZeros(precision);
appendExponent(exp);
}
}
/**
* The f-format inner helper function of {@link #format(double, String)} that uses Java's
* {@link BigDecimal} to provide conversion and rounding. The converted number is appended to
* the {@link #result} buffer, and {@link #start} will be set to the index of its first
* character.
*
* @param value to convert
* @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
* @param precision precision (maximum number of fractional digits)
*/
private void format_f(double value, String positivePrefix, int precision) {
if (!signAndSpecialNumber(value, positivePrefix)) {
// Convert value to decimal exactly. (This can be very long.)
BigDecimal vLong = new BigDecimal(Math.abs(value));
// Truncate to the defined number of places to the right of the decimal point).
BigDecimal vv = vLong.setScale(precision, ROUND_PY);
// When converted to text, the number of fractional digits is exactly the scale we set.
String raw = vv.toPlainString();
result.append(raw);
if ((lenFraction = vv.scale()) > 0) {
// There is a decimal point and some digits following
lenWhole = result.length() - (start + lenSign + (lenPoint = 1) + lenFraction);
} else {
// There are no fractional digits and so no decimal point
lenWhole = result.length() - (start + lenSign);
}
}
// Finally, ensure we have all the fractional digits we should.
if (lenMarker == 0) {
ensurePointAndTrailingZeros(precision);
}
}
/**
* Append a decimal point and trailing fractional zeros if necessary for 'e' and 'f' format.
* This should not be called if the result is not numeric ("inf" for example). This method deals
* with the following complexities: on return there will be at least the number of fractional
* digits specified in the argument n, and at least {@link #minFracDigits};
* further, if minFracDigits<0, signifying the "alternate mode" of certain
* formats, the method will ensure there is a decimal point, even if there are no fractional
* digits to follow.
*
* @param n smallest number of fractional digits on return
*/
private void ensurePointAndTrailingZeros(int n) {
// Set n to the number of fractional digits we should have.
if (n < minFracDigits) {
n = minFracDigits;
}
// Do we have a decimal point already?
if (lenPoint == 0) {
// No decimal point: add one if there will be any fractional digits or
if (n > 0 || minFracDigits < 0) {
// First need to add a decimal point.
result.append('.');
lenPoint = 1;
}
}
// Do we have enough fractional digits already?
int f = lenFraction;
if (n > f) {
// Make up the required number of zeros.
for (; f < n; f++) {
result.append('0');
}
lenFraction = f;
}
}
/**
* Implementation of the variants of g-format, that uses Java's {@link BigDecimal} to provide
* conversion and rounding. These variants are g-format proper, alternate g-format (available
* for "%#g" formatting), n-format (as g but subsequently "internationalised"), and none-format
* (type code Spec.NONE).
*
* None-format is the basis of float.__str__.
*
* According to the Python documentation for g-format, the precise rules are as follows: suppose
* that the result formatted with presentation type 'e' and precision p-1
* would have exponent exp. Then if -4 <= exp < p, the number is formatted with
* presentation type 'f' and precision p-1-exp. Otherwise, the number is
* formatted with presentation type 'e' and precision p-1. In both cases
* insignificant trailing zeros are removed from the significand, and the decimal point is also
* removed if there are no remaining digits following it.
*
* The Python documentation says none-format is the same as g-format, but the observed behaviour
* differs from this, in that f-format is only used if -4 <= exp < p-1 (i.e. one
* less), and at least one digit to the right of the decimal point is preserved in the f-format
* (but not the e-format). That behaviour is controlled through the following arguments, with
* these recommended values:
*
*
* Recommended values for formatting arguments
*
* type
* precision
* minFracDigits
* expThresholdAdj
* expThreshold
*
*
* g
* p
* 0
* 0
* p
*
*
* #g
* p
* -
* 0
* p
*
*
* \0
* p
* 1
* -1
* p-1
*
*
* __str__
* 12
* 1
* -1
* 11
*
*
*
* @param value to convert
* @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
* @param precision total number of significant digits (precision 0 behaves as 1)
* @param expThresholdAdj +precision = the exponent at which to resume using
* exponential notation
*/
private void format_g(double value, String positivePrefix, int precision, int expThresholdAdj) {
// Precision 0 behaves as 1
precision = Math.max(1, precision);
// Use exponential notation if exponent would be bigger thatn:
int expThreshold = precision + expThresholdAdj;
if (signAndSpecialNumber(value, positivePrefix)) {
// Finish formatting if zero result. (This is a no-op for nan or inf.)
zeroHelper(precision, expThreshold);
} else {
// Convert abs(value) to decimal with p digits of accuracy.
MathContext mc = new MathContext(precision, ROUND_PY);
BigDecimal vv = new BigDecimal(Math.abs(value), mc);
// This gives us the digits we need for either fixed or exponential format.
String pointlessDigits = vv.unscaledValue().toString();
// If we were to complete this as e-format, the exponent would be:
int exp = pointlessDigits.length() - vv.scale() - 1;
if (-4 <= exp && exp < expThreshold) {
// Finish the job as f-format with variable-precision p-(exp+1).
appendFixed(pointlessDigits, exp, precision);
} else {
// Finish the job as e-format.
appendExponential(pointlessDigits, exp);
}
}
}
/**
* Implementation of r-format (float.__repr__) that uses Java's
* {@link Double#toString(double)} to provide conversion and rounding. That method gives us
* almost what we need, but not quite (sometimes it yields 18 digits): here we always round to
* 17 significant digits. Much of the formatting after conversion is shared with
* {@link #format_g(double, String, int, int, int)}. minFracDigits is consulted
* since while float.__repr__ truncates to one digit, within
* complex.__repr__ we truncate fully.
*
* @param value to convert
* @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
*/
private void format_r(double value, String positivePrefix) {
// Characteristics of repr (precision = 17 and go exponential at 16).
int precision = 17;
int expThreshold = precision - 1;
if (signAndSpecialNumber(value, positivePrefix)) {
// Finish formatting if zero result. (This is a no-op for nan or inf.)
zeroHelper(precision, expThreshold);
} else {
// Generate digit sequence (with no decimal point) with custom rounding.
StringBuilder pointlessBuffer = new StringBuilder(20);
int exp = reprDigits(Math.abs(value), precision, pointlessBuffer);
if (-4 <= exp && exp < expThreshold) {
// Finish the job as f-format with variable-precision p-(exp+1).
appendFixed(pointlessBuffer, exp, precision);
} else {
// Finish the job as e-format.
appendExponential(pointlessBuffer, exp);
}
}
}
/**
* Common code for g-format, none-format and r-format called when the conversion yields "inf",
* "nan" or zero. The method completes formatting of the zero, with the appropriate number of
* decimal places or (in particular circumstances) exponential; notation.
*
* @param precision of conversion (number of significant digits).
* @param expThreshold if zero, causes choice of exponential notation for zero.
*/
private void zeroHelper(int precision, int expThreshold) {
if (lenMarker == 0) {
// May be 0 or -0 so we still need to ...
if (minFracDigits < 0) {
// In "alternate format", we won't economise trailing zeros.
appendPointAndTrailingZeros(precision - 1);
} else if (lenFraction < minFracDigits) {
// Otherwise, it should be at least the stated minimum length.
appendTrailingZeros(minFracDigits);
}
// And just occasionally (in none-format) we go exponential even when exp = 0...
if (0 >= expThreshold) {
appendExponent(0);
}
}
}
/**
* Common code for g-format, none-format and r-format used when the exponent is such that a
* fixed-point presentation is chosen. Normally the method removes trailing digits so as to
* shorten the presentation without loss of significance. This method respects the minimum
* number of fractional digits (digits after the decimal point), in member
* minFracDigits, which is 0 for g-format and 1 for none-format and r-format. When
* minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros
* generated in the conversion are not removed. This supports "%#g" format.
*
* @param digits from converting the value at a given precision.
* @param exp would be the exponent (in e-format), used to position the decimal point.
* @param precision of conversion (number of significant digits).
*/
private void appendFixed(CharSequence digits, int exp, int precision) {
// Check for "alternate format", where we won't economise trailing zeros.
boolean noTruncate = (minFracDigits < 0);
int digitCount = digits.length();
if (exp < 0) {
// For a negative exponent, we must insert leading zeros 0.000 ...
result.append("0.");
lenWhole = lenPoint = 1;
for (int i = -1; i > exp; --i) {
result.append('0');
}
// Then the generated digits (always enough to satisfy no-truncate mode).
result.append(digits);
lenFraction = digitCount - exp - 1;
} else {
// For a non-negative exponent, it's a question of placing the decimal point.
int w = exp + 1;
if (w < digitCount) {
// There are w whole-part digits
result.append(digits.subSequence(0, w));
lenWhole = w;
result.append('.').append(digits.subSequence(w, digitCount));
lenPoint = 1;
lenFraction = digitCount - w;
} else {
// All the digits are whole-part digits.
result.append(digits);
// Just occasionally (in r-format) we need more digits than the precision.
while (digitCount < w) {
result.append('0');
digitCount += 1;
}
lenWhole = digitCount;
}
if (noTruncate) {
// Extend the fraction as BigDecimal will have economised on zeros.
appendPointAndTrailingZeros(precision - digitCount);
}
}
// Finally, ensure we have all and only the fractional digits we should.
if (!noTruncate) {
if (lenFraction < minFracDigits) {
// Otherwise, it should be at least the stated minimum length.
appendTrailingZeros(minFracDigits);
} else {
// And no more
removeTrailingZeros(minFracDigits);
}
}
}
/**
* Common code for g-format, none-format and r-format used when the exponent is such that an
* exponential presentation is chosen. Normally the method removes trailing digits so as to
* shorten the presentation without loss of significance. Although no minimum number of
* fractional digits is enforced in the exponential presentation, when
* minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros
* generated in the conversion are not removed. This supports "%#g" format.
*
* @param digits from converting the value at a given precision.
* @param exp would be the exponent (in e-format), used to position the decimal point.
*/
private void appendExponential(CharSequence digits, int exp) {
// The whole-part is the first digit.
result.append(digits.charAt(0));
lenWhole = 1;
// And the rest of the digits form the fractional part
int digitCount = digits.length();
result.append('.').append(digits.subSequence(1, digitCount));
lenPoint = 1;
lenFraction = digitCount - 1;
// In no-truncate mode, the fraction is full precision. Otherwise trim it.
if (minFracDigits >= 0) {
// Note positive minFracDigits only applies to fixed formats.
removeTrailingZeros(0);
}
// Finally, append the exponent as e+nn.
appendExponent(exp);
}
/**
* Convert a double to digits and an exponent for use in float.__repr__ (or
* r-format). This method takes advantage of (or assumes) a close correspondence between
* {@link Double#toString(double)} and Python float.__repr__. The correspondence
* appears to be exact, insofar as the Java method produces the minimal non-zero digit string.
* It mostly chooses the same number of digits (and the same digits) as the CPython repr, but in
* a few cases Double.toString produces more digits. This method truncates to the
* number maxDigits, which in practice is always 17.
*
* @param value to convert
* @param maxDigits maximum number of digits to return in buf.
* @param buf for digits of result (recommend size be 20)
* @return the exponent
*/
private static int reprDigits(double value, int maxDigits, StringBuilder buf) {
// Most of the work is done by Double.
String s = Double.toString(value);
// Variables for scanning the string
int p = 0, end = s.length(), first = 0, point = end, exp;
char c = 0;
boolean allZero = true;
// Scan whole part and fractional part digits
while (p < end) {
c = s.charAt(p++);
if (Character.isDigit(c)) {
if (allZero) {
if (c != '0') {
// This is the first non-zero digit.
buf.append(c);
allZero = false;
// p is one *after* the first non-zero digit.
first = p;
}
// Only seen zeros so far: do nothing.
} else {
// We've started, so every digit counts.
buf.append(c);
}
} else if (c == '.') {
// We remember this location (one *after* '.') to calculate the exponent later.
point = p;
} else {
// Something after the mantissa. (c=='E' we hope.)
break;
}
}
// Possibly followed by an exponent. p has already advanced past the 'E'.
if (p < end && c == 'E') {
// If there is an exponent, the mantissa must be in standard form: m.mmmm
assert point == first + 1;
exp = Integer.parseInt(s.substring(p));
} else {
// Exponent is based on relationship of decimal point and first non-zero digit.
exp = point - first - 1;
// But that's only correct when the point is to the right (or absent).
if (exp < 0) {
// The point is to the left of the first digit
exp += 1; // = -(first-point)
}
}
/*
* XXX This still does not round in all the cases it could. I think Java stops generating
* digits when the residual is <= ulp/2. This is to neglect the possibility that the extra
* ulp/2 (before it becomes a different double) could take us to a rounder numeral. To fix
* this, we could express ulp/2 as digits in the same scale as those in the buffer, and
* consider adding them. But Java's behaviour here is probably a manifestation of bug
* JDK-4511638.
*/
// Sometimes the result is more digits than we want for repr.
if (buf.length() > maxDigits) {
// Chop the trailing digits, remembering the most significant lost digit.
int d = buf.charAt(maxDigits);
buf.setLength(maxDigits);
// We round half up. Not absolutely correct since Double has already rounded.
if (d >= '5') {
// Treat this as a "carry one" into the numeral buf[0:maxDigits].
for (p = maxDigits - 1; p >= 0; p--) {
// Each pass of the loop does one carry from buf[p+1] to buf[p].
d = buf.charAt(p) + 1;
if (d <= '9') {
// Carry propagation stops here.
buf.setCharAt(p, (char)d);
break;
} else {
// 9 + 1 -> 0 carry 1. Keep looping.
buf.setCharAt(p, '0');
}
}
if (p < 0) {
/*
* We fell off the bottom of the buffer with one carry still to propagate. You
* may expect: buf.insert(0, '1') here, but note that every digit in
* buf[0:maxDigits] is currently '0', so all we need is:
*/
buf.setCharAt(0, '1');
exp += 1;
}
}
}
return exp;
}
/**
* Append the trailing fractional zeros, as required by certain formats, so that the total
* number of fractional digits is no less than specified. If n<=0, the method
* leaves the {@link #result} buffer unchanged.
*
* @param n smallest number of fractional digits on return
*/
private void appendTrailingZeros(int n) {
int f = lenFraction;
if (n > f) {
if (lenPoint == 0) {
// First need to add a decimal point. (Implies lenFraction=0.)
result.append('.');
lenPoint = 1;
}
// Now make up the required number of zeros.
for (; f < n; f++) {
result.append('0');
}
lenFraction = f;
}
}
/**
* Append the trailing fractional zeros, as required by certain formats, so that the total
* number of fractional digits is no less than specified. If there is no decimal point
* originally (and therefore no fractional part), the method will add a decimal point, even if
* it adds no zeros.
*
* @param n smallest number of fractional digits on return
*/
private void appendPointAndTrailingZeros(int n) {
if (lenPoint == 0) {
// First need to add a decimal point. (Implies lenFraction=0.)
result.append('.');
lenPoint = 1;
}
// Now make up the required number of zeros.
int f;
for (f = lenFraction; f < n; f++) {
result.append('0');
}
lenFraction = f;
}
/**
* Remove trailing zeros from the fractional part, as required by certain formats, leaving at
* least the number of fractional digits specified. If the resultant number of fractional digits
* is zero, this method will also remove the trailing decimal point (if there is one).
*
* @param n smallest number of fractional digits on return
*/
private void removeTrailingZeros(int n) {
if (lenPoint > 0) {
// There's a decimal point at least, and there may be some fractional digits.
int f = lenFraction;
if (n == 0 || f > n) {
int fracStart = result.length() - f;
for (; f > n; --f) {
if (result.charAt(fracStart - 1 + f) != '0') {
// Keeping this one as it isn't a zero
break;
}
}
// f is now the number of fractional digits we wish to retain.
if (f == 0) {
// We will be stripping all the fractional digits. Take the decimal point too.
lenPoint = lenFraction = 0;
f = -1;
} else {
lenFraction = f;
}
// Snip the characters we are going to remove (if any).
if (fracStart + f < result.length()) {
result.setLength(fracStart + f);
}
}
}
}
/**
* Append the current value of {@code exp} in the format "e{:+02d}" (for example
* e+05, e-10, e+308 , etc.).
*
* @param exp exponent value to append
*/
private void appendExponent(int exp) {
int marker = result.length();
String e;
// Deal with sign and leading-zero convention by explicit tests.
if (exp < 0) {
e = (exp <= -10) ? "e-" : "e-0";
exp = -exp;
} else {
e = (exp < 10) ? "e+0" : "e+";
}
result.append(e).append(exp);
lenMarker = 1;
lenExponent = result.length() - marker - 1;
}
/**
* Return the index in {@link #result} of the first letter. This is a helper for
* {@link #uppercase()} and {@link #getExponent()}
*/
private int indexOfMarker() {
return start + lenSign + lenWhole + lenPoint + lenFraction;
}
}
Ads help maintain this website.