/**
*
*/
package org.unitConverter.unit;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
import java.util.Optional;
/**
* 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 {
private final LinearUnit unit;
private final double value;
private final double uncertainty;
/**
* Gets an exact {@code UnitValue}
*
* @param unit unit to express with
* @param value value to express
* @return exact {@code UnitValue} instance
* @since 2020-07-26
*/
public static final LinearUnitValue getExact(LinearUnit unit, double value) {
return new LinearUnitValue(Objects.requireNonNull(unit, "unit must not be null"), value, 0);
}
/**
* Gets an uncertain {@code UnitValue}
*
* @param unit unit to express with
* @param value value to express
* @param uncertainty absolute uncertainty of value
* @return uncertain {@code UnitValue} instance
* @since 2020-07-26
*/
public static final LinearUnitValue of(LinearUnit unit, double value, double uncertainty) {
return new LinearUnitValue(Objects.requireNonNull(unit, "unit must not be null"), value, uncertainty);
}
/**
* @param unit unit to express as
* @param value value to express
* @param uncertainty absolute uncertainty of value
* @since 2020-07-26
*/
private LinearUnitValue(LinearUnit unit, double value, double uncertainty) {
this.unit = unit;
this.value = value;
this.uncertainty = uncertainty;
}
/**
* @param other a {@code LinearUnit}
* @return true iff this value can be represented with {@code other}.
* @since 2020-07-26
*/
public final boolean canConvertTo(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(LinearUnit other) {
return LinearUnitValue.of(other, this.unit.convertTo(other, value), this.unit.convertTo(other, uncertainty));
}
/**
* 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
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof LinearUnitValue))
return false;
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());
}
/**
* @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(LinearUnitValue other) {
if (other == null || !Objects.equals(this.unit.getBase(), other.unit.getBase()))
return false;
double thisBaseValue = this.unit.convertToBase(this.value);
double otherBaseValue = other.unit.convertToBase(other.value);
double thisBaseUncertainty = this.unit.convertToBase(this.uncertainty);
double otherBaseUncertainty = other.unit.convertToBase(other.uncertainty);
return Math.abs(thisBaseValue - otherBaseValue) <= Math.min(thisBaseUncertainty, otherBaseUncertainty);
}
/**
* @return the unit
*
* @since 2020-07-26
*/
public final LinearUnit getUnit() {
return unit;
}
/**
* @return the value
*
* @since 2020-07-26
*/
public final double getValue() {
return value;
}
/**
* @return absolute uncertainty of value
*
* @since 2020-07-26
*/
public final double getUncertainty() {
return uncertainty;
}
/**
* @return relative uncertainty of value
*
* @since 2020-07-26
*/
public final double getRelativeUncertainty() {
return uncertainty / 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 uncertainty == 0;
}
/**
* 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(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));
}
/**
* 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(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));
}
@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(boolean showUncertainty) {
Optional