package net.sf.openrocket.unit; import java.text.DecimalFormat; import net.sf.openrocket.util.Chars; public abstract class Unit { /** No unit */ public static final Unit NOUNIT = new GeneralUnit(1, "" + Chars.ZWSP, 2); protected final double multiplier; // meters = units * multiplier protected final String unit; /** * Creates a new Unit with a given multiplier and unit name. * * Multiplier e.g. 1 in = 0.0254 meter * * @param multiplier The multiplier to use on the value, 1 this unit == multiplier SI units * @param unit The unit's short form. */ public Unit(double multiplier, String unit) { if (multiplier == 0) throw new IllegalArgumentException("Unit has multiplier=0"); this.multiplier = multiplier; this.unit = unit; } /** * Converts from SI units to this unit. The default implementation simply divides by the * multiplier. * * @param value Value in SI unit * @return Value in these units */ public double toUnit(double value) { return value / multiplier; } /** * Convert from this type of units to SI units. The default implementation simply * multiplies by the multiplier. * * @param value Value in these units * @return Value in SI units */ public double fromUnit(double value) { return value * multiplier; } /** * Return the unit name. * * @return the unit. */ public String getUnit() { return unit; } /** * Whether the value and unit should be separated by a whitespace. This method * returns true as most units have a space between the value and unit, but may be * overridden. * * @return true if the value and unit should be separated */ public boolean hasSpace() { return true; } @Override public String toString() { return unit; } // TODO: Should this use grouping separator ("#,##0.##")? private static final DecimalFormat intFormat = new DecimalFormat("#"); private static final DecimalFormat decFormat = new DecimalFormat("0.0##"); private static final DecimalFormat expFormat = new DecimalFormat("0.00E0"); /** * Format the given value (in SI units) to a string representation of the value in this * units. An suitable amount of decimals for the unit are used in the representation. * The unit is not appended to the numerical value. * * @param value Value in SI units. * @return A string representation of the number in these units. */ public String toString(double value) { double val = toUnit(value); if (Math.abs(val) > 1E6) { return expFormat.format(val); } if (Math.abs(val) >= 100) { return intFormat.format(val); } if (Math.abs(val) <= 0.0005) { return "0"; } val = roundForDecimalFormat(val); // Check for approximate integer if (Math.abs(val - Math.floor(val)) < 0.0001) { return intFormat.format(val); } return decFormat.format(val); } protected double roundForDecimalFormat(double val) { double sign = Math.signum(val); val = Math.abs(val); double mul = 1.0; while (val < 100) { mul *= 10; val *= 10; } val = Math.rint(val) / mul * sign; return val; } /** * Return a string with the specified value and unit. The value is converted into * this unit. If <code>value</code> is NaN, returns "N/A" (not applicable). * * @param value the value to print in SI units. * @return the value and unit, or "N/A". */ public String toStringUnit(double value) { if (Double.isNaN(value)) return "N/A"; String s = toString(value); if (hasSpace()) s += " "; s += unit; return s; } /** * Creates a new Value object with the specified value and this unit. * * @param value the value to set. * @return a new Value object. */ public Value toValue(double value) { return new Value(value, this); } /** * Round the value (in the current units) to a precision suitable for rough valuing * (approximately 2 significant numbers). * * @param value Value in current units * @return Rounded value. */ public abstract double round(double value); /** * Return the next rounded value after the given value. * @param value Value in these units. * @return The next suitable rounded value. */ public abstract double getNextValue(double value); /** * Return the previous rounded value before the given value. * @param value Value in these units. * @return The previous suitable rounded value. */ public abstract double getPreviousValue(double value); //public abstract ArrayList<Tick> getTicks(double start, double end, double scale); /** * Return ticks in the range start - end (in current units). minor is the minimum * distance between minor, non-notable ticks and major the minimum distance between * major non-notable ticks. The values are in current units, i.e. no conversion is * performed. */ public abstract Tick[] getTicks(double start, double end, double minor, double major); /** * Compares whether the two units are equal. Equality requires the unit classes, * multiplier values and units to be equal. */ @Override public boolean equals(Object other) { if (other == null) return false; if (this.getClass() != other.getClass()) return false; return ((this.multiplier == ((Unit) other).multiplier) && this.unit.equals(((Unit) other).unit)); } @Override public int hashCode() { return this.getClass().hashCode() + this.unit.hashCode(); } }