public static MeasurementNumericValueAndUnits fit(Double origin, MeasurementUnits units, MeasurementUnits lowUnits,
MeasurementUnits highUnits) {
// work-around for the various Chart descendants not properly setting their units field;
if (null == units) {
return new MeasurementNumericValueAndUnits(origin, units);
}
// by definition, absolutely specified units don't scale to anything
if ((MeasurementUnits.Family.ABSOLUTE == units.getFamily())
|| (MeasurementUnits.Family.DURATION == units.getFamily())) {
return new MeasurementNumericValueAndUnits(origin, units);
}
// by definition relative-valued units are self-scaled (converted at formatting)
if (MeasurementUnits.Family.RELATIVE == units.getFamily()) {
return new MeasurementNumericValueAndUnits(origin, units);
}
if (MeasurementUnits.Family.TEMPERATURE == units.getFamily()) {
return new MeasurementNumericValueAndUnits(origin, units);
}
// if the magnitude is zero, the best-fit also will spin around forever since it won't change
if (Math.abs(origin) < 1e-9) {
return new MeasurementNumericValueAndUnits(origin, units);
}
boolean wasNegative = false;
if (origin < 0) {
wasNegative = true;
origin = -origin;
}
MeasurementNumericValueAndUnits currentValueAndUnits;
MeasurementNumericValueAndUnits nextValueAndUnits = new MeasurementNumericValueAndUnits(origin, units);
// first, make the value smaller if it's too big
int maxOrdinal = (highUnits != null) ? (highUnits.ordinal() + 1) : MeasurementUnits.values().length;
do {
currentValueAndUnits = nextValueAndUnits;
int nextOrdinal = currentValueAndUnits.getUnits().ordinal() + 1;
if (nextOrdinal == maxOrdinal) {
// we could theoretically get bigger, but we don't have any units to represent that
break;
}
MeasurementUnits biggerUnits = MeasurementUnits.values()[nextOrdinal];
if (biggerUnits.getFamily() != currentValueAndUnits.getUnits().getFamily()) {
// we're as big as we can get, break out of the loop so we can return
break;
}
Double smallerValue = scale(currentValueAndUnits, biggerUnits);
nextValueAndUnits = new MeasurementNumericValueAndUnits(smallerValue, biggerUnits);
} while (nextValueAndUnits.getValue() > 1.0);
// next, make the value bigger if it's too small
int minOrdinal = (lowUnits != null) ? (lowUnits.ordinal() - 1) : -1;
while (currentValueAndUnits.getValue() < 1.0) {
int nextOrdinal = currentValueAndUnits.getUnits().ordinal() - 1;
if (nextOrdinal == minOrdinal) {
// we could theoretically get smaller, but we don't have any units to represent that
break;
}
MeasurementUnits smallerUnits = MeasurementUnits.values()[nextOrdinal];
if (smallerUnits.getFamily() != currentValueAndUnits.getUnits().getFamily()) {
// we're as small as we can get, break out of the loop so we can return
break;
}
Double biggerValue = scale(currentValueAndUnits, smallerUnits);
nextValueAndUnits = new MeasurementNumericValueAndUnits(biggerValue, smallerUnits);
currentValueAndUnits = nextValueAndUnits;
}
if (wasNegative) {
return new MeasurementNumericValueAndUnits(-currentValueAndUnits.getValue(),
currentValueAndUnits.getUnits());
}
return currentValueAndUnits;
}