/** * */ package org.unitConverter.unit; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Objects; import java.util.Optional; import org.unitConverter.math.DecimalComparison; /** * 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"), 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 double value, final double uncertainty) { return new LinearUnitValue( Objects.requireNonNull(unit, "unit must not be null"), value, uncertainty); } /** * Gets an uncertain {@code LinearUnitValue} * * @param unit unit to express with * @param value value to express * @param relativeUncertainty relative uncertainty of value * @return uncertain {@code LinearUnitValue} instance * @since 2020-07-28 */ public static final LinearUnitValue ofRelative(final LinearUnit unit, final double value, final double relativeUncertainty) { return LinearUnitValue.of(unit, value, relativeUncertainty * value); } private final LinearUnit unit; private final double value; private final double uncertainty; /** * @param unit unit to express as * @param value value to express * @param uncertainty absolute uncertainty of value * @since 2020-07-26 */ private LinearUnitValue(final LinearUnit unit, final double value, final double uncertainty) { this.unit = unit; this.value = value; this.uncertainty = uncertainty; } /** * @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); } /** * @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), this.unit.convertTo(other, this.uncertainty)); } /** * 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 / divisor, this.uncertainty / 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.ofRelative(this.unit.dividedBy(divisor.unit), this.value / divisor.value, Math.hypot(this.getRelativeUncertainty(), divisor.getRelativeUncertainty())); } /** * 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()) && Double.doubleToLongBits( this.unit.convertToBase(this.getValue())) == Double .doubleToLongBits( other.unit.convertToBase(other.getValue())) && Double.doubleToLongBits(this.getRelativeUncertainty()) == Double .doubleToLongBits(other.getRelativeUncertainty()); } /** * 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))
&& DecimalComparison.equals(this.getRelativeUncertainty(),
other.getRelativeUncertainty());
}
/**
* @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 double thisBaseValue = this.unit.convertToBase(this.value);
final double otherBaseValue = other.unit.convertToBase(other.value);
final double thisBaseUncertainty = this.unit
.convertToBase(this.uncertainty);
final double otherBaseUncertainty = other.unit
.convertToBase(other.uncertainty);
return Math.abs(thisBaseValue - otherBaseValue) <= Math
.min(thisBaseUncertainty, otherBaseUncertainty);
}
/**
* @return relative uncertainty of value
*
* @since 2020-07-26
*/
public final double getRelativeUncertainty() {
return this.uncertainty / this.value;
}
/**
* @return absolute uncertainty of value
*
* @since 2020-07-26
*/
public final double getUncertainty() {
return this.uncertainty;
}
/**
* @return the unit
*
* @since 2020-07-26
*/
public final LinearUnit getUnit() {
return this.unit;
}
/**
* @return the value
*
* @since 2020-07-26
*/
public final double getValue() {
return this.value;
}
@Override
public int hashCode() {
return Objects.hash(this.unit.getBase(),
this.unit.convertToBase(this.getValue()),
this.getRelativeUncertainty());
}
/**
* @return true iff the value has no uncertainty
*
* @since 2020-07-26
*/
public final boolean isExact() {
return this.uncertainty == 0;
}
/**
* 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 - otherConverted.value,
Math.hypot(this.uncertainty, otherConverted.uncertainty));
}
/**
* 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 + otherConverted.value,
Math.hypot(this.uncertainty, otherConverted.uncertainty));
}
/**
* 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 * multiplier,
this.uncertainty * 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.ofRelative(this.unit.times(multiplier.unit),
this.value * multiplier.value,
Math.hypot(this.getRelativeUncertainty(),
multiplier.getRelativeUncertainty()));
}
/**
* 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.ofRelative(this.unit.toExponent(exponent),
Math.pow(this.value, exponent),
this.getRelativeUncertainty() * Math.sqrt(exponent));
}
@Override
public String toString() {
return this.toString(!this.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