in endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/AngleFormat.java [1043:1210]
public StringBuffer format(final double angle, StringBuffer toAppendTo, final FieldPosition pos) {
final int offset = toAppendTo.length();
final int fieldPos = getField(pos);
if (!Double.isFinite(angle)) {
toAppendTo = numberFormat().format(angle, toAppendTo, dummyFieldPosition());
if (fieldPos >= DEGREES_FIELD && fieldPos <= SECONDS_FIELD) {
pos.setBeginIndex(offset);
pos.setEndIndex(toAppendTo.length());
}
return toAppendTo;
}
/*
* Computes the numerical values of minutes and seconds fields.
* If those fiels are not written, then store NaN.
*/
double degrees = angle;
double minutes = NaN;
double seconds = NaN;
int maximumFractionDigits = fractionFieldWidth;
if (minutesFieldWidth == 0) {
final double p = pow10(maximumFractionDigits);
degrees = round(angle, degrees * p) / p;
} else {
minutes = Math.abs(degrees - (degrees = truncate(degrees))) * 60;
/*
* Limit the maximal number of fraction digits to the number of significant digits for a `double` value.
* The intent is to avoid non-significant garbage that are pure artifacts from the conversion from base
* 2 to base 10.
*/
final int n = fractionDigitsForDelta(Math.ulp(angle) * (secondsFieldWidth == 0 ? 60 : 3600), false);
maximumFractionDigits = Math.max(minimumFractionDigits,
Math.min(maximumFractionDigits, n - 1));
final double p = pow10(maximumFractionDigits);
if (secondsFieldWidth != 0) {
seconds = (minutes - (minutes = truncate(minutes))) * 60;
seconds = round(angle, seconds * p) / p; // Correction for rounding errors.
if (seconds >= 60) { // We do not expect > 60 (only == 60), but let be safe.
seconds = 0;
minutes++;
}
} else {
minutes = round(angle, minutes * p) / p; // Correction for rounding errors.
}
if (minutes >= 60) { // We do not expect > 60 (only == 60), but let be safe.
minutes = 0;
degrees += Math.signum(angle);
}
/*
* Note: a previous version was doing a unconditional addition to the 'degrees' variable,
* in the form 'degrees += correction'. However, -0.0 + 0 == +0.0, while we really need to
* preserve the sign of negative zero. See [SIS-120].
*/
}
/*
* Avoid formatting values like 12.01°N as 12°36″N because of the risk of confusion.
* In such cases, force the formatting of minutes field as in 12°00′36″.
*/
byte effectiveOptionalFields = optionalFields;
if (showLeadingFields) {
effectiveOptionalFields &= ~(1 << DEGREES_FIELD);
if (minutes == 0 && ((effectiveOptionalFields & (1 << SECONDS_FIELD)) == 0 || seconds != 0)) {
effectiveOptionalFields &= ~(1 << MINUTES_FIELD);
}
}
/*
* At this point the 'degrees', 'minutes' and 'seconds' variables contain the final values to format.
* The following loop will format fields from DEGREES_FIELD to SECONDS_FIELD inclusive.
* The NumberFormat will be reconfigured at each iteration.
*/
int field = PREFIX_FIELD;
if (prefix != null) {
toAppendTo.append(prefix);
}
final NumberFormat numberFormat = numberFormat();
boolean hasMore;
do {
int width;
double value;
String suffix;
switch (++field) {
case DEGREES_FIELD: value=degrees; width=degreesFieldWidth; suffix=degreesSuffix; hasMore=(minutesFieldWidth != 0); break;
case MINUTES_FIELD: value=minutes; width=minutesFieldWidth; suffix=minutesSuffix; hasMore=(secondsFieldWidth != 0); break;
case SECONDS_FIELD: value=seconds; width=secondsFieldWidth; suffix=secondsSuffix; hasMore=false; break;
default: throw new AssertionError(field);
}
/*
* If the value is zero and the field is optional, propagate the sign to the next field
* and skip the whole field. Otherwise process to the formatting of current field.
*/
if (value == 0 && (effectiveOptionalFields & (1 << field)) != 0) {
switch (field) {
case DEGREES_FIELD: minutes = Math.copySign(minutes, degrees); break;
case MINUTES_FIELD: seconds = Math.copySign(seconds, minutes); break;
}
continue;
}
/*
* Configure the NumberFormat for the number of digits to write, but do not write anything yet.
*/
if (hasMore) {
numberFormat.setMinimumIntegerDigits(width);
numberFormat.setMaximumFractionDigits(0);
} else if (useDecimalSeparator) {
numberFormat.setMinimumIntegerDigits(width);
if (maximumTotalWidth != 0) {
/*
* If we are required to fit the formatted angle in some maximal total width
* (i.e. the user called the setMaximumWidth(int) method), compute the space
* available for fraction digits after we removed the space for the integer
* digits, the decimal separator (this is the +1 below) and the suffix.
*/
int available = maximumTotalWidth - toAppendTo.codePointCount(offset, toAppendTo.length());
available -= (width + 1); // Remove the number of code points that we plan to write.
if (suffix != null) {
width -= suffix.length();
}
for (double scale=pow10(width); value >= scale; scale *= 10) {
if (--available <= 0) break;
}
if (available < maximumFractionDigits) {
maximumFractionDigits = Math.max(available, 0);
}
}
numberFormat.setMinimumFractionDigits(minimumFractionDigits);
numberFormat.setMaximumFractionDigits(maximumFractionDigits);
} else {
value *= pow10(fractionFieldWidth);
numberFormat.setMaximumFractionDigits(0);
numberFormat.setMinimumIntegerDigits(width + fractionFieldWidth);
}
/*
* At this point, we known the value to format and the NumberFormat instance has been
* configured. If the user asked for an attributed character iterator and assuming that
* we want also the attributes produced by the NumberFormat, then we have to invoke the
* heavy formatToCharacterIterator(…). Otherwise the usual format(…) method fits well.
*/
final int startPosition = toAppendTo.length();
if (characterIterator != null) {
final FormattedCharacterIterator it = characterIterator;
it.append(numberFormat.formatToCharacterIterator(value), toAppendTo);
if (suffix != null) {
toAppendTo.append(suffix);
}
final Number userObject;
if (hasMore) {
userObject = Math.toIntExact(Math.round(value));
} else {
/*
* Use Float instead of Double because we don't want to give a false impression of accuracy
* (when formatting the seconds field, at least the 10 last bits of the 'double' value are
* non-significant).
*/
userObject = (float) value;
}
it.addFieldLimit(Field.forCode(field), userObject, startPosition);
} else {
toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition());
if (suffix != null) {
toAppendTo.append(suffix);
}
}
if (field == fieldPos) {
pos.setBeginIndex(startPosition);
pos.setEndIndex(toAppendTo.length());
}
} while (hasMore);
return toAppendTo;
}