/** * */ package org.unitConverter.unit; import java.util.Objects; import java.util.Optional; import org.unitConverter.math.DecimalComparison; import org.unitConverter.math.UncertainDouble; /** * A possibly uncertain value expressed in a linear unit. * * Unless otherwise indicated, all methods in this class throw a * {@code NullPointerException} when an argument is null. * * @author Adrien Hopkins * @since 2020-07-26 */ public final class LinearUnitValue { public static final LinearUnitValue ONE = getExact(SI.ONE, 1); /** * Gets an exact {@code LinearUnitValue} * * @param unit unit to express with * @param value value to express * @return exact {@code LinearUnitValue} instance * @since 2020-07-26 */ public static final LinearUnitValue getExact(final LinearUnit unit, final double value) { return new LinearUnitValue( Objects.requireNonNull(unit, "unit must not be null"), UncertainDouble.of(value, 0)); } /** * Gets an uncertain {@code LinearUnitValue} * * @param unit unit to express with * @param value value to express * @param uncertainty absolute uncertainty of value * @return uncertain {@code LinearUnitValue} instance * @since 2020-07-26 */ public static final LinearUnitValue of(final LinearUnit unit, final UncertainDouble value) { return new LinearUnitValue( Objects.requireNonNull(unit, "unit must not be null"), Objects.requireNonNull(value, "value may not be null")); } private final LinearUnit unit; private final UncertainDouble value; /** * @param unit unit to express as * @param value value to express * @since 2020-07-26 */ private LinearUnitValue(final LinearUnit unit, final UncertainDouble value) { this.unit = unit; this.value = value; } /** * @return this value as a {@code UnitValue}. All uncertainty information is * removed from the returned value. * @since 2020-08-04 */ public final UnitValue asUnitValue() { return UnitValue.of(this.unit, this.value.value()); } /** * @param other a {@code LinearUnit} * @return true iff this value can be represented with {@code other}. * @since 2020-07-26 */ public final boolean canConvertTo(final LinearUnit other) { return this.unit.canConvertTo(other); } /** * Returns a LinearUnitValue that represents the same value expressed in a * different unit * * @param other new unit to express value in * @return value expressed in {@code other} * @since 2020-07-26 */ public final LinearUnitValue convertTo(final LinearUnit other) { return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); } /** * Divides this value by a scalar * * @param divisor value to divide by * @return multiplied value * @since 2020-07-28 */ public LinearUnitValue dividedBy(final double divisor) { return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor)); } /** * Divides this value by another value * * @param divisor value to multiply by * @return quotient * @since 2020-07-28 */ public LinearUnitValue dividedBy(final LinearUnitValue divisor) { return LinearUnitValue.of(this.unit.dividedBy(divisor.unit), this.value.dividedBy(divisor.value)); } /** * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 * km) returns true. * * @since 2020-07-26 * @see #equals(Object, boolean) */ @Override public boolean equals(final Object obj) { if (!(obj instanceof LinearUnitValue)) return false; final LinearUnitValue other = (LinearUnitValue) obj; return Objects.equals(this.unit.getBase(), other.unit.getBase()) && this.unit.convertToBase(this.value) .equals(other.unit.convertToBase(other.value)); } /** * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 * km) returns true. *

* If avoidFPErrors is true, this method will attempt to avoid floating-point * errors, at the cost of not always being transitive. * * @since 2020-07-28 */ public boolean equals(final Object obj, final boolean avoidFPErrors) { if (!avoidFPErrors) return this.equals(obj); if (!(obj instanceof LinearUnitValue)) return false; final LinearUnitValue other = (LinearUnitValue) obj; return Objects.equals(this.unit.getBase(), other.unit.getBase()) && DecimalComparison.equals(this.unit.convertToBase(this.value), other.unit.convertToBase(other.value)); } /** * @param other another {@code LinearUnitValue} * @return true iff this and other are within each other's uncertainty range * * @since 2020-07-26 */ public boolean equivalent(final LinearUnitValue other) { if (other == null || !Objects.equals(this.unit.getBase(), other.unit.getBase())) return false; final LinearUnit base = LinearUnit.valueOf(this.unit.getBase(), 1); final LinearUnitValue thisBase = this.convertTo(base); final LinearUnitValue otherBase = other.convertTo(base); return thisBase.value.equivalent(otherBase.value); } /** * @return the unit * * @since 2020-07-26 */ public final LinearUnit getUnit() { return this.unit; } /** * @return the value * * @since 2020-07-26 */ public final UncertainDouble getValue() { return this.value; } /** * @return the exact value * @since 2020-09-07 */ public final double getValueExact() { return this.value.value(); } @Override public int hashCode() { return Objects.hash(this.unit.getBase(), this.unit.convertToBase(this.getValue())); } /** * Returns the difference of this value and another, expressed in this * value's unit * * @param subtrahend value to subtract * @return difference of values * @throws IllegalArgumentException if {@code subtrahend} has a unit that is * not compatible for addition * @since 2020-07-26 */ public LinearUnitValue minus(final LinearUnitValue subtrahend) { Objects.requireNonNull(subtrahend, "subtrahend may not be null"); if (!this.canConvertTo(subtrahend.unit)) throw new IllegalArgumentException(String.format( "Incompatible units for subtraction \"%s\" and \"%s\".", this.unit, subtrahend.unit)); final LinearUnitValue otherConverted = subtrahend.convertTo(this.unit); return LinearUnitValue.of(this.unit, this.value.minus(otherConverted.value)); } /** * Returns the sum of this value and another, expressed in this value's unit * * @param addend value to add * @return sum of values * @throws IllegalArgumentException if {@code addend} has a unit that is not * compatible for addition * @since 2020-07-26 */ public LinearUnitValue plus(final LinearUnitValue addend) { Objects.requireNonNull(addend, "addend may not be null"); if (!this.canConvertTo(addend.unit)) throw new IllegalArgumentException(String.format( "Incompatible units for addition \"%s\" and \"%s\".", this.unit, addend.unit)); final LinearUnitValue otherConverted = addend.convertTo(this.unit); return LinearUnitValue.of(this.unit, this.value.plus(otherConverted.value)); } /** * Multiplies this value by a scalar * * @param multiplier value to multiply by * @return multiplied value * @since 2020-07-28 */ public LinearUnitValue times(final double multiplier) { return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier)); } /** * Multiplies this value by another value * * @param multiplier value to multiply by * @return product * @since 2020-07-28 */ public LinearUnitValue times(final LinearUnitValue multiplier) { return LinearUnitValue.of(this.unit.times(multiplier.unit), this.value.times(multiplier.value)); } /** * Raises a value to an exponent * * @param exponent exponent to raise to * @return result of exponentiation * @since 2020-07-28 */ public LinearUnitValue toExponent(final int exponent) { return LinearUnitValue.of(this.unit.toExponent(exponent), this.value.toExponentExact(exponent)); } @Override public String toString() { return this.toString(!this.value.isExact()); } /** * Returns a string representing the object.
* If the attached unit has a name or symbol, the string looks like "12 km". * Otherwise, it looks like "13 unnamed unit (= 2 m/s)". *

* If showUncertainty is true, strings like "35 ± 8" are shown instead of * single numbers. *

* Non-exact values are rounded intelligently based on their uncertainty. * * @since 2020-07-26 */ public String toString(final boolean showUncertainty) { final Optional primaryName = this.unit.getPrimaryName(); final Optional symbol = this.unit.getSymbol(); final String chosenName = symbol.orElse(primaryName.orElse(null)); final UncertainDouble baseValue = this.unit.convertToBase(this.value); // get rounded strings // if showUncertainty is true, add brackets around the string final String valueString = showUncertainty ? "(" : "" + this.value.toString(showUncertainty) + (showUncertainty ? ")" : ""); final String baseValueString = showUncertainty ? "(" : "" + baseValue.toString(showUncertainty) + (showUncertainty ? ")" : ""); // create string if (primaryName.isEmpty() && symbol.isEmpty()) return String.format("%s unnamed unit (= %s %s)", valueString, baseValueString, this.unit.getBase()); else return String.format("%s %s", valueString, chosenName); } }