// // ScaledUnit.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad; import java.io.Serializable; import java.util.Arrays; /** * A class that represents a certain amount of a derived unit. * * Instances are immutable. * * @author Steven R. Emmerson * * This is part of Steve Emerson's Unit package that has been * incorporated into VisAD. */ public final class ScaledUnit extends Unit implements Serializable { private static final long serialVersionUID = 1L; /** * The amount of the associated derived unit. */ final double amount; /** * The underlying unit that's scaled. */ final Unit underUnit; /** * Construct a dimensionless scaled unit. The identifier will be empty. * * @param amount * The given amount of this unit. */ public ScaledUnit(final double amount) { this(amount, ""); } /** * Construct a dimensionless scaled unit with an identifier. * * @param amount * The given amount of this unit. * @param identifier * Name or abbreviation for the unit. May be <code>null</code> or * empty. */ public ScaledUnit(final double amount, final String identifier) { super(identifier); this.amount = amount; underUnit = new DerivedUnit(); } /** * Constructs a scaled unit from another unit. The identifier will be that * of the other unit if the amount is 1; otherwise, the identifier will be * <code>null</code>. * * @param amount * The given amount of the other unit (e.g. 0.9144 to create a * yard unit if <code>unit</code> represents a meter). * @param unit * The other unit. */ public ScaledUnit(final double amount, final Unit unit) { this(amount, unit, null); } /** * Constructs a scaled unit from another unit and an identifier. * * @param amount * The given amount of the base unit (e.g. 0.9144 to create a * yard unit if <code>unit</code> represents a meter). * @param unit * The other unit. * @param identifier * Name or abbreviation for the unit. May be <code>null</code> or * empty. If {@code null} and if {@code amount} is {@code 1}, * then the identifier of {@code unit} is used. */ public ScaledUnit(final double amount, final Unit unit, final String identifier) { super(identifier != null ? identifier : amount == 1 ? unit.getIdentifier() : null); if (unit instanceof ScaledUnit) { final ScaledUnit that = (ScaledUnit) unit; this.amount = amount * that.amount; this.underUnit = that.underUnit; } else { this.amount = amount; this.underUnit = unit; } } public ScaledUnit(final double amount, final BaseUnit unit) { this(amount, (Unit) unit); } public ScaledUnit(final double amount, final BaseUnit unit, final String id) { this(amount, (Unit) unit, id); } public ScaledUnit(final double amount, final DerivedUnit unit) { this(amount, (Unit) unit); } public ScaledUnit(final double amount, final DerivedUnit unit, final String id) { this(amount, (Unit) unit, id); } /** * Factory method for creating a scaled unit. The identifier will be that of * the input unit if the scaling amount is 1; otherwise, the identifier will * be <code>null</code>. * * @param amount * The given amount of the scaled unit (e.g. 3.0 to create a yard * unit if <code>unit</code> represents a foot. * @param unit * The given unit. * @return A corresponding scaled unit. * @throws UnitException * Can't create Scaled Unit from <code>unit</code>. */ public static ScaledUnit create(final double amount, final Unit unit) throws UnitException { return (amount == 1 && unit instanceof ScaledUnit) ? (ScaledUnit) unit : new ScaledUnit(amount, unit); } /** * Returns an instance based on a scale amount and an underlying unit. * * @param amount * The amount of {@code unit} * @param unit * The underlying unit. * @return An instance corresponding to the input. * @throws UnitException * If the instance can't be created. */ static Unit getInstance(final double amount, final Unit unit) throws UnitException { if (amount == 0) { throw new IllegalArgumentException("Zero amount argument"); } return (amount == 1) ? unit : new ScaledUnit(amount, unit); } /** * <p> * Indicates if this instance is dimensionless. A unit is dimensionless if * it is a measure of a dimensionless quantity like angle or concentration. * Examples of dimensionless units include radian, degree, steradian, and * "g/kg". * </p> * * @return True if an only if this unit is dimensionless. */ @Override public boolean isDimensionless() { return underUnit.isDimensionless(); } /** * Clones this unit, changing the identifier. * * @param identifier * The name or abbreviation for the cloned unit. May be * <code>null</code> or empty. * @return A unit equal to this instance but with the given identifier. */ @Override protected Unit protectedClone(final String identifier) { return new ScaledUnit(amount, underUnit, identifier); } @Override public Unit scale(final double amount) throws UnitException { return ScaledUnit.getInstance(amount * this.amount, underUnit); } @Override public Unit shift(final double offset) throws UnitException { return OffsetUnit.getInstance(offset, this); } @Override public Unit log(final double base) { return LogarithmicUnit.getInstance(base, this); } /** * Raises this unit to a power. * * @param power * The power to raise this unit by. * @return The unit resulting from raising this unit to <code>power</code>. * @throws UnitException * if the underlying unit can't be raised to the given power. * promise: This unit has not been modified. */ @Override public Unit pow(final int power) throws UnitException { return new ScaledUnit(Math.pow(amount, power), underUnit.pow(power)); } /** * Raises this unit to a power. * * @param power * The power to raise this unit by. If this unit is not * dimensionless, then the value must be integral. * @return The unit resulting from raising this unit to <code>power</code>. * @throws IllegalArgumentException * This unit is not dimensionless and <code>power</code> has a * non-integral value. * @throws UnitException * if the underlying unit can't be raised to the given power. * promise: The unit has not been modified. */ @Override public Unit pow(final double power) throws IllegalArgumentException, UnitException { return new ScaledUnit(Math.pow(amount, power), underUnit.pow(power)); } /** * Returns the N-th root of this unit. * * @param root * The root to take (e.g. 2 means square root). May not be zero. * @return The unit corresponding to the <code>root</code>-th root of this * unit. * @throws IllegalArgumentException * The root value is zero or the resulting unit would have a * non-integral unit dimension. * @throws UnitException * if the underlying unit given can't have the given root taken. * promise: This unit has not been modified. */ @Override public Unit root(final int root) throws IllegalArgumentException, UnitException { return new ScaledUnit(Math.pow(amount, 1. / root), underUnit.root(root)); } /** * Returns the definition of this unit. * * @return The definition of this unit (e.g. "0.9144 m" for a yard). */ @Override public String getDefinition() { String definition; if (underUnit == null) { /* Probably exception thrown during construction */ definition = "<unconstructed ScaledUnit>"; } else { final String derivedString = underUnit.toString(); definition = amount == 1 ? derivedString : derivedString.length() == 0 ? Double.toString(amount) : Double.toString(amount) + " " + derivedString; } return definition; } /** * Get the scale amount * * @return The scale amount */ public double getAmount() { return amount; } /** * Get the underlying unit * * @return The underlying unit */ public Unit getUnit() { return underUnit; } /** * Multiplies this unit by another unit. * * @param that * The unit with which to multiply this unit. * @return The product of the two units. * promise: Neither unit has been modified. * @throws UnitException * Meaningless operation. */ @Override public Unit multiply(final Unit that) throws UnitException { return create(amount, underUnit.multiply(that)); } /** * Divides this unit by another unit. * * @param that * The unit to divide into this unit. * @return The quotient of the two units. * promise: Neither unit has been modified. * @throws UnitException * Meaningless operation. */ @Override public Unit divide(final Unit that) throws UnitException { return create(amount, underUnit.divide(that)); } /** * Divides this unit into another unit. * * @param that * The unit to be divided by this unit. * @return The quotient of the two units. * promise: Neither unit has been modified. * @throws UnitException * Meaningless operation. */ @Override protected Unit divideInto(final Unit that) throws UnitException { return create(1. / amount, underUnit.divideInto(that)); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @return The converted values in units of this unit. * require: The units are convertible. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThis(final double[] values, final Unit that) throws UnitException { return toThis(values, that, true); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @return The converted values in units of this unit. * require: The units are convertible. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThis(final float[] values, final Unit that) throws UnitException { return toThis(values, that, true); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values in units of this unit. * require: The units are convertible. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThis(final double[] values, final Unit that, final boolean copy) throws UnitException { double[] newValues; if (equals(that) || that instanceof PromiscuousUnit) { newValues = (copy) ? (double[]) values.clone() : values; } else { newValues = that.toThat(values, underUnit, copy); for (int i = 0; i < newValues.length; ++i) { if (newValues[i] == newValues[i]) { newValues[i] /= amount; } } } return newValues; } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values in units of this unit. * require: The units are convertible. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThis(final float[] values, final Unit that, final boolean copy) throws UnitException { float[] newValues; if (equals(that) || that instanceof PromiscuousUnit) { newValues = (copy) ? (float[]) values.clone() : values; } else { newValues = that.toThat(values, underUnit, copy); for (int i = 0; i < newValues.length; ++i) { if (newValues[i] == newValues[i]) { newValues[i] /= amount; } } } return newValues; } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @return The converted values. * require: The units are identical. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThat(final double[] values, final Unit that) throws UnitException { return toThat(values, that, true); } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @return The converted values. * require: The units are identical. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThat(final float[] values, final Unit that) throws UnitException { return toThat(values, that, true); } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values. * require: The units are convertible. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThat(final double values[], final Unit that, final boolean copy) throws UnitException { double[] newValues = (copy) ? (double[]) values.clone() : values; if (!(equals(that) || that instanceof PromiscuousUnit)) { for (int i = 0; i < newValues.length; ++i) { newValues[i] *= amount; } newValues = that.toThis(newValues, underUnit); } return newValues; } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values. * require: The units are convertible. * promise: Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThat(final float values[], final Unit that, final boolean copy) throws UnitException { float[] newValues = (copy) ? (float[]) values.clone() : values; if (!(equals(that) || that instanceof PromiscuousUnit)) { for (int i = 0; i < newValues.length; ++i) { newValues[i] *= amount; } newValues = that.toThis(newValues, underUnit); } return newValues; } /** * Indicate whether this unit is convertible with another unit. If one unit * is convertible with another, then the <code>toThis(...)</code>/ and * <code>toThat(...)</code> methods will not throw a UnitException. Unit A * is convertible with unit B if and only if unit B is convertible with unit * A; hence, calling-order is irrelevant. * * @param unit * The other unit. * @return True if and only if this unit is convertible with the other unit. */ @Override public boolean isConvertible(final Unit unit) { return unit == null ? false : underUnit.isConvertible(unit); } private static void myAssert(final boolean assertion) { if (!assertion) { throw new AssertionError(); } } private static void myAssert(final Unit have, final Unit expect) { if (!have.equals(expect)) { throw new AssertionError(have.toString() + " != " + expect); } } private static void myAssert(final double have, final double expect) { if (have != expect) { throw new AssertionError("" + have + " != " + expect); } } private static void myAssert(final double[] have, final double[] expect) { if (!Arrays.equals(have, expect)) { throw new AssertionError("" + have + " != " + expect); } } /** * Test this class. * * @param args * Arguments (ignored). * @exception UnitException * A problem occurred. */ public static void main(final String[] args) throws UnitException { final BaseUnit meter = BaseUnit.addBaseUnit("Length", "meter"); final BaseUnit second = BaseUnit.addBaseUnit("Time", "second"); final DerivedUnit meterPerSec = new DerivedUnit(new BaseUnit[] { meter, second }, new int[] { 1, -1 }); final Unit milePerHour = new ScaledUnit(0.44704, meterPerSec); final Unit milePerHour2 = milePerHour.pow(2); myAssert(milePerHour, milePerHour); myAssert(!milePerHour.equals(meterPerSec)); myAssert(milePerHour.isConvertible(milePerHour)); myAssert(milePerHour.isConvertible(meterPerSec)); myAssert(!milePerHour.equals(milePerHour2)); myAssert(!milePerHour.isConvertible(milePerHour2)); myAssert(!milePerHour.equals(meter)); myAssert(!milePerHour.isConvertible(meter)); myAssert(milePerHour2, milePerHour2); myAssert(milePerHour2.isConvertible(milePerHour2)); myAssert(milePerHour2.sqrt(), milePerHour); final BaseUnit kg = BaseUnit.addBaseUnit("Mass", "kilogram"); final DerivedUnit kgPerSec = new DerivedUnit(new BaseUnit[] { kg, second }, new int[] { 1, -1 }); final Unit poundPerSec = new ScaledUnit(0.453592, kgPerSec); myAssert(milePerHour.multiply(poundPerSec), poundPerSec .multiply(milePerHour)); myAssert(milePerHour.divide(poundPerSec), poundPerSec.divide( milePerHour).pow(-1)); myAssert(milePerHour.toThis(1, meterPerSec) != 1); myAssert(milePerHour.toThat(1, meterPerSec) != 1); myAssert(milePerHour.toThis(1, meterPerSec), meterPerSec.toThat(1, milePerHour)); myAssert(meterPerSec.toThat(milePerHour.toThat(1, meterPerSec), milePerHour), 1); final double[] values = { 1, 2 }; myAssert(milePerHour.toThis(values, meterPerSec), meterPerSec.toThat( values, milePerHour)); myAssert(meterPerSec.toThat(milePerHour.toThat(values, meterPerSec), milePerHour), values); System.out.println("Checking exceptions:"); try { milePerHour.toThis(5, poundPerSec); throw new AssertionError(); } catch (final UnitException e) { System.out.println(e.getMessage()); } System.out.println("Done"); } /** * Indicates if this instance is equal to a unit. * * @param unit * The unit. * @return <code>true</code> if and only if this instance equals the unit. */ @Override public boolean equals(final Unit unit) { if (this == unit) { return true; } if (amount == 1) return underUnit.equals(unit); if (!(unit instanceof ScaledUnit)) { return false; } final ScaledUnit that = (ScaledUnit) unit; return amount == that.amount && underUnit.equals(that.underUnit); } /** * Returns the hash code of this instance. {@link Object#hashCode()} should * be overridden whenever {@link Object#equals(Object)} is. * * @return The hash code of this instance (includes the values). */ @Override public int hashCode() { if (hashCode == 0) { hashCode = amount == 1 ? underUnit.hashCode() : underUnit.hashCode() ^ Double.valueOf(amount).hashCode(); } return hashCode; } @Override public DerivedUnit getDerivedUnit() { return underUnit.getDerivedUnit(); } }