diff options
Diffstat (limited to 'src/org/unitConverter/unit/LinearUnitValue.java')
-rw-r--r-- | src/org/unitConverter/unit/LinearUnitValue.java | 601 |
1 files changed, 337 insertions, 264 deletions
diff --git a/src/org/unitConverter/unit/LinearUnitValue.java b/src/org/unitConverter/unit/LinearUnitValue.java index 8daabf7..f16b19f 100644 --- a/src/org/unitConverter/unit/LinearUnitValue.java +++ b/src/org/unitConverter/unit/LinearUnitValue.java @@ -18,283 +18,356 @@ import java.util.Optional; * @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 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 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); - } + /** + * 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); + } - /** - * @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; - } + /** + * 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); + } - /** - * @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); - } + private final LinearUnit unit; + private final double value; + private final double uncertainty; - /** - * 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)); - } + /** + * @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; + } - /** - * 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); - } + /** + * @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); + } - /** - * @return the unit - * - * @since 2020-07-26 - */ - public final LinearUnit getUnit() { - return unit; - } + /** + * 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)); + } - /** - * @return the value - * - * @since 2020-07-26 - */ - public final double getValue() { - return 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 / divisor, this.uncertainty / divisor); + } - /** - * @return absolute uncertainty of value - * - * @since 2020-07-26 - */ - public final double getUncertainty() { - return uncertainty; - } + /** + * 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())); + } - /** - * @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()); - } + /** + * 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(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()); + } - /** - * @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)); - } + /** + * @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); + } - @Override - public String toString() { - return this.toString(!this.isExact()); - } + /** + * @return relative uncertainty of value + * + * @since 2020-07-26 + */ + public final double getRelativeUncertainty() { + return this.uncertainty / this.value; + } - /** - * Returns a string representing the object. <br> - * 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)". - * <p> - * If showUncertainty is true, strings like "35 ± 8" are shown instead of single - * numbers. - * <p> - * Non-exact values are rounded intelligently based on their uncertainty. - * - * @since 2020-07-26 - */ - public String toString(boolean showUncertainty) { - Optional<String> primaryName = this.unit.getPrimaryName(); - Optional<String> symbol = this.unit.getSymbol(); - String chosenName = symbol.orElse(primaryName.orElse(null)); - - final double baseValue = this.unit.convertToBase(this.value); - final double baseUncertainty = this.unit.convertToBase(this.uncertainty); - - // get rounded strings - final String valueString, baseValueString, uncertaintyString, baseUncertaintyString; - if (this.isExact()) { - valueString = Double.toString(value); - baseValueString = Double.toString(baseValue); - uncertaintyString = "0"; - baseUncertaintyString = "0"; - } else { - final BigDecimal bigValue = BigDecimal.valueOf(this.value); - final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); - - // round based on uncertainty - // if uncertainty starts with 1 (ignoring zeroes and the decimal point), rounds - // so that uncertainty has 2 significant digits. - // otherwise, rounds so that uncertainty has 1 significant digits. - // the value is rounded to the same number of decimal places as the uncertainty. - BigDecimal roundedUncertainty = bigUncertainty - .setScale(bigUncertainty.scale() - bigUncertainty.precision() + 2, RoundingMode.HALF_EVEN); - if (roundedUncertainty.unscaledValue().intValue() >= 20) { - roundedUncertainty = bigUncertainty.setScale(bigUncertainty.scale() - bigUncertainty.precision() + 1, - RoundingMode.HALF_EVEN); - } - final BigDecimal roundedValue = bigValue.setScale(roundedUncertainty.scale(), RoundingMode.HALF_EVEN); - - valueString = roundedValue.toString(); - uncertaintyString = roundedUncertainty.toString(); - - if (primaryName.isEmpty() && symbol.isEmpty()) { - final BigDecimal bigBaseValue = BigDecimal.valueOf(baseValue); - final BigDecimal bigBaseUncertainty = BigDecimal.valueOf(baseUncertainty); - - BigDecimal roundedBaseUncertainty = bigBaseUncertainty.setScale( - bigBaseUncertainty.scale() - bigBaseUncertainty.precision() + 2, RoundingMode.HALF_EVEN); - if (roundedBaseUncertainty.unscaledValue().intValue() >= 20) { - roundedBaseUncertainty = bigBaseUncertainty.setScale( - bigBaseUncertainty.scale() - bigBaseUncertainty.precision() + 1, RoundingMode.HALF_EVEN); - } - final BigDecimal roundedBaseValue = bigBaseValue.setScale(roundedBaseUncertainty.scale(), - RoundingMode.HALF_EVEN); - - baseValueString = roundedBaseValue.toString(); - baseUncertaintyString = roundedBaseUncertainty.toString(); - } else { - // unused - baseValueString = ""; - baseUncertaintyString = ""; - } - } + /** + * @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()); + } - // create string - if (showUncertainty) { - if (primaryName.isEmpty() && symbol.isEmpty()) { - return String.format("(%s ± %s) unnamed unit (= %s ± %s %s)", valueString, uncertaintyString, - baseValueString, baseUncertaintyString, this.unit.getBase()); - } else { - return String.format("(%s ± %s) %s", valueString, uncertaintyString, chosenName); - } - } else { - 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); - } + /** + * @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. <br> + * 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)". + * <p> + * If showUncertainty is true, strings like "35 ± 8" are shown instead of single + * numbers. + * <p> + * Non-exact values are rounded intelligently based on their uncertainty. + * + * @since 2020-07-26 + */ + public String toString(final boolean showUncertainty) { + final Optional<String> primaryName = this.unit.getPrimaryName(); + final Optional<String> symbol = this.unit.getSymbol(); + final String chosenName = symbol.orElse(primaryName.orElse(null)); + + final double baseValue = this.unit.convertToBase(this.value); + final double baseUncertainty = this.unit.convertToBase(this.uncertainty); + + // get rounded strings + final String valueString, baseValueString, uncertaintyString, baseUncertaintyString; + if (this.isExact()) { + valueString = Double.toString(this.value); + baseValueString = Double.toString(baseValue); + uncertaintyString = "0"; + baseUncertaintyString = "0"; + } else { + final BigDecimal bigValue = BigDecimal.valueOf(this.value); + final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); + + // round based on uncertainty + // if uncertainty starts with 1 (ignoring zeroes and the decimal point), rounds + // so that uncertainty has 2 significant digits. + // otherwise, rounds so that uncertainty has 1 significant digits. + // the value is rounded to the same number of decimal places as the uncertainty. + BigDecimal roundedUncertainty = bigUncertainty + .setScale(bigUncertainty.scale() - bigUncertainty.precision() + 2, RoundingMode.HALF_EVEN); + if (roundedUncertainty.unscaledValue().intValue() >= 20) { + roundedUncertainty = bigUncertainty.setScale(bigUncertainty.scale() - bigUncertainty.precision() + 1, + RoundingMode.HALF_EVEN); + } + final BigDecimal roundedValue = bigValue.setScale(roundedUncertainty.scale(), RoundingMode.HALF_EVEN); + + valueString = roundedValue.toString(); + uncertaintyString = roundedUncertainty.toString(); + + if (primaryName.isEmpty() && symbol.isEmpty()) { + final BigDecimal bigBaseValue = BigDecimal.valueOf(baseValue); + final BigDecimal bigBaseUncertainty = BigDecimal.valueOf(baseUncertainty); + + BigDecimal roundedBaseUncertainty = bigBaseUncertainty.setScale( + bigBaseUncertainty.scale() - bigBaseUncertainty.precision() + 2, RoundingMode.HALF_EVEN); + if (roundedBaseUncertainty.unscaledValue().intValue() >= 20) { + roundedBaseUncertainty = bigBaseUncertainty.setScale( + bigBaseUncertainty.scale() - bigBaseUncertainty.precision() + 1, RoundingMode.HALF_EVEN); } + final BigDecimal roundedBaseValue = bigBaseValue.setScale(roundedBaseUncertainty.scale(), + RoundingMode.HALF_EVEN); + + baseValueString = roundedBaseValue.toString(); + baseUncertaintyString = roundedBaseUncertainty.toString(); + } else { + // unused + baseValueString = ""; + baseUncertaintyString = ""; + } + } + + // create string + if (showUncertainty) { + if (primaryName.isEmpty() && symbol.isEmpty()) + return String.format("(%s ± %s) unnamed unit (= %s ± %s %s)", valueString, uncertaintyString, + baseValueString, baseUncertaintyString, this.unit.getBase()); + else + return String.format("(%s ± %s) %s", valueString, uncertaintyString, chosenName); + } else { + 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); } + } } |