/** * Copyright (C) 2019 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package sevenUnits.unit; import java.util.Objects; import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; /** * A unit that can be expressed as a product of its base and a number. For * example, kilometres, inches and pounds. * * @author Adrien Hopkins * @since 2019-10-16 */ public final class LinearUnit extends Unit { /** * Gets a {@code LinearUnit} from a unit and a value. For example, converts * '59 °F' to a linear unit with the value of '288.15 K' * * @param unit unit to convert * @param value value to convert * @return value expressed as a {@code LinearUnit} * @since 2019-10-16 * @throws NullPointerException if unit is null */ public static LinearUnit fromUnitValue(final Unit unit, final double value) { return new LinearUnit( Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), NameSymbol.EMPTY); } /** * Gets a {@code LinearUnit} from a unit and a value. For example, converts * '59 °F' to a linear unit with the value of '288.15 K' * * @param unit unit to convert * @param value value to convert * @param ns name(s) and symbol of unit * @return value expressed as a {@code LinearUnit} * @since 2019-10-21 * @throws NullPointerException if unit or ns is null */ public static LinearUnit fromUnitValue(final Unit unit, final double value, final NameSymbol ns) { return new LinearUnit( Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), ns); } /** * @return the base unit associated with {@code unit}, as a * {@code LinearUnit}. * @since 2020-10-02 */ public static LinearUnit getBase(final Unit unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and * {@code conversionFactor}, expressed as a {@code LinearUnit}. * * @param unitBase unit base to multiply by * @param conversionFactor number to multiply base by * @return product of base and conversion factor * @since 2019-10-16 * @throws NullPointerException if unitBase is null */ public static LinearUnit valueOf(final ObjectProduct unitBase, final double conversionFactor) { return new LinearUnit(unitBase, conversionFactor, NameSymbol.EMPTY); } /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and * {@code conversionFactor}, expressed as a {@code LinearUnit}. * * @param unitBase unit base to multiply by * @param conversionFactor number to multiply base by * @param ns name(s) and symbol of unit * @return product of base and conversion factor * @since 2019-10-21 * @throws NullPointerException if unitBase is null */ public static LinearUnit valueOf(final ObjectProduct unitBase, final double conversionFactor, final NameSymbol ns) { return new LinearUnit(unitBase, conversionFactor, ns); } /** * The value of this unit as represented in its base form. Mathematically, * *
	 * this = conversionFactor * getBase()
	 * 
* * @since 2019-10-16 */ private final double conversionFactor; /** * Creates the {@code LinearUnit}. * * @param unitBase base of linear unit * @param conversionFactor conversion factor between base and unit * @since 2019-10-16 */ private LinearUnit(final ObjectProduct unitBase, final double conversionFactor, final NameSymbol ns) { super(unitBase, ns); this.conversionFactor = conversionFactor; } /** * {@inheritDoc} * * Converts by dividing by {@code conversionFactor} */ @Override protected double convertFromBase(final double value) { return value / this.getConversionFactor(); } /** * Converts an {@code UncertainDouble} value expressed in this unit to an * {@code UncertainValue} value expressed in {@code other}. * * @param other unit to convert to * @param value value to convert * @return converted value * @since 2019-09-07 * @throws IllegalArgumentException if {@code other} is incompatible for * conversion with this unit (as tested by * {@link Unit#canConvertTo}). * @throws NullPointerException if value or other is null */ public UncertainDouble convertTo(LinearUnit other, UncertainDouble value) { Objects.requireNonNull(other, "other must not be null."); Objects.requireNonNull(value, "value may not be null."); if (this.canConvertTo(other)) return value.timesExact( this.getConversionFactor() / other.getConversionFactor()); else throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } /** * {@inheritDoc} * * Converts by multiplying by {@code conversionFactor} */ @Override protected double convertToBase(final double value) { return value * this.getConversionFactor(); } /** * Converts an {@code UncertainDouble} to the base unit. * * @since 2020-09-07 */ UncertainDouble convertToBase(final UncertainDouble value) { return value.timesExact(this.getConversionFactor()); } /** * Divides this unit by a scalar. * * @param divisor scalar to divide by * @return quotient * @since 2018-12-23 * @since v0.1.0 */ public LinearUnit dividedBy(final double divisor) { return valueOf(this.getBase(), this.getConversionFactor() / divisor); } /** * Returns the quotient of this unit and another. * * @param divisor unit to divide by * @return quotient of two units * @throws NullPointerException if {@code divisor} is null * @since 2018-12-22 * @since v0.1.0 */ public LinearUnit dividedBy(final LinearUnit divisor) { Objects.requireNonNull(divisor, "other must not be null"); // divide the units final ObjectProduct base = this.getBase() .dividedBy(divisor.getBase()); return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor()); } /** * {@inheritDoc} * * Uses the base and conversion factor of units to test for equality. */ @Override public boolean equals(final Object obj) { if (!(obj instanceof LinearUnit)) return false; final LinearUnit other = (LinearUnit) obj; return Objects.equals(this.getBase(), other.getBase()) && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor()); } /** * @return conversion factor * @since 2019-10-16 */ public double getConversionFactor() { return this.conversionFactor; } /** * {@inheritDoc} * * Uses the base and conversion factor to compute a hash code. */ @Override public int hashCode() { return 31 * this.getBase().hashCode() + DecimalComparison.hash(this.getConversionFactor()); } /** * @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there * is a {@code BaseUnit b} where * {@code b.asLinearUnit().equals(this)} returns {@code true}.) * @since 2019-10-16 */ public boolean isBase() { return this.isCoherent() && this.getBase().isSingleObject(); } /** * @return whether this unit is coherent (i.e. has conversion factor 1) * @since 2019-10-16 */ public boolean isCoherent() { return this.getConversionFactor() == 1; } /** * Returns the difference of this unit and another. *

* Two units can be subtracted if they have the same base. Note that * {@link #canConvertTo} can be used to determine this. If {@code subtrahend} * does not meet this condition, an {@code IllegalArgumentException} will be * thrown. *

* * @param subtrahend unit to subtract * @return difference of units * @throws IllegalArgumentException if {@code subtrahend} is not compatible * for subtraction as described above * @throws NullPointerException if {@code subtrahend} is null * @since 2019-03-17 * @since v0.2.0 */ public LinearUnit minus(final LinearUnit subtrahend) { Objects.requireNonNull(subtrahend, "addend must not be null."); // reject subtrahends that cannot be added to this unit if (!this.getBase().equals(subtrahend.getBase())) throw new IllegalArgumentException(String.format( "Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahend)); // subtract the units return valueOf(this.getBase(), this.getConversionFactor() - subtrahend.getConversionFactor()); } /** * Returns the sum of this unit and another. *

* Two units can be added if they have the same base. Note that * {@link #canConvertTo} can be used to determine this. If {@code addend} * does not meet this condition, an {@code IllegalArgumentException} will be * thrown. *

* * @param addend unit to add * @return sum of units * @throws IllegalArgumentException if {@code addend} is not compatible for * addition as described above * @throws NullPointerException if {@code addend} is null * @since 2019-03-17 * @since v0.2.0 */ public LinearUnit plus(final LinearUnit addend) { Objects.requireNonNull(addend, "addend must not be null."); // reject addends that cannot be added to this unit if (!this.getBase().equals(addend.getBase())) throw new IllegalArgumentException(String.format( "Incompatible units for addition \"%s\" and \"%s\".", this, addend)); // add the units return valueOf(this.getBase(), this.getConversionFactor() + addend.getConversionFactor()); } /** * Multiplies this unit by a scalar. * * @param multiplier scalar to multiply by * @return product * @since 2018-12-23 * @since v0.1.0 */ public LinearUnit times(final double multiplier) { return valueOf(this.getBase(), this.getConversionFactor() * multiplier); } /** * Returns the product of this unit and another. * * @param multiplier unit to multiply by * @return product of two units * @throws NullPointerException if {@code multiplier} is null * @since 2018-12-22 * @since v0.1.0 */ public LinearUnit times(final LinearUnit multiplier) { Objects.requireNonNull(multiplier, "other must not be null"); // multiply the units final ObjectProduct base = this.getBase() .times(multiplier.getBase()); return valueOf(base, this.getConversionFactor() * multiplier.getConversionFactor()); } @Override public String toDefinitionString() { return Double.toString(this.conversionFactor) + (this.getBase().equals(ObjectProduct.empty()) ? "" : " " + this.getBase().toString(BaseUnit::getShortName)); } /** * Returns this unit but to an exponent. * * @param exponent exponent to exponentiate unit to * @return exponentiated unit * @since 2019-01-15 * @since v0.1.0 */ public LinearUnit toExponent(final int exponent) { return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); } /** * Returns this unit to an exponent, rounding the resulting dimensions to the * nearest integer. * * @since 2024-08-22 * @see ObjectProduct#toExponentRounded */ public LinearUnit toExponentRounded(final double exponent) { return valueOf(this.getBase().toExponentRounded(exponent), Math.pow(this.conversionFactor, exponent)); } @Override public LinearUnit withName(final NameSymbol ns) { return valueOf(this.getBase(), this.getConversionFactor(), ns); } /** * Returns the result of applying {@code prefix} to this unit. *

* If this unit and the provided prefix have a primary name, the returned * unit will have a primary name (prefix's name + unit's name).
* If this unit and the provided prefix have a symbol, the returned unit will * have a symbol.
* This method ignores alternate names of both this unit and the provided * prefix. * * @param prefix prefix to apply * @return unit with prefix * @since 2019-03-18 * @since v0.2.0 * @throws NullPointerException if prefix is null */ public LinearUnit withPrefix(final UnitPrefix prefix) { final LinearUnit unit = this.times(prefix.getMultiplier()); // create new name and symbol, if possible final String name; if (this.getPrimaryName().isPresent() && prefix.getPrimaryName().isPresent()) { name = prefix.getPrimaryName().get() + this.getPrimaryName().get(); } else { name = null; } final String symbol; if (this.getSymbol().isPresent() && prefix.getSymbol().isPresent()) { symbol = prefix.getSymbol().get() + this.getSymbol().get(); } else { symbol = null; } return unit.withName(NameSymbol.ofNullable(name, symbol)); } }