summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/org/unitConverter/unit/LinearUnitValue.java763
-rw-r--r--src/org/unitConverter/unit/UnitTest.java77
2 files changed, 467 insertions, 373 deletions
diff --git a/src/org/unitConverter/unit/LinearUnitValue.java b/src/org/unitConverter/unit/LinearUnitValue.java
index f16b19f..74b0400 100644
--- a/src/org/unitConverter/unit/LinearUnitValue.java
+++ b/src/org/unitConverter/unit/LinearUnitValue.java
@@ -8,6 +8,8 @@ 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.
*
@@ -18,356 +20,417 @@ import java.util.Optional;
* @since 2020-07-26
*/
public final class LinearUnitValue {
- /**
- * 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;
- }
-
- /**
- * @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
- */
- @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());
- }
-
- /**
- * @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. <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 = "";
- }
+ /**
+ * 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);
}
-
- // 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);
+
+ /**
+ * 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;
+ }
+
+ /**
+ * @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
+ */
+ @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.
+ * <p>
+ * 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. <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);
+ }
}
- }
}
diff --git a/src/org/unitConverter/unit/UnitTest.java b/src/org/unitConverter/unit/UnitTest.java
index c078cfc..2cf3126 100644
--- a/src/org/unitConverter/unit/UnitTest.java
+++ b/src/org/unitConverter/unit/UnitTest.java
@@ -17,6 +17,8 @@
package org.unitConverter.unit;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
@@ -25,7 +27,8 @@ import org.junit.jupiter.api.Test;
import org.unitConverter.math.DecimalComparison;
/**
- * Testing the various Unit classes. This is NOT part of this program's public API.
+ * Testing the various Unit classes. This is NOT part of this program's public
+ * API.
*
* @author Adrien Hopkins
* @since 2018-12-22
@@ -34,69 +37,97 @@ import org.unitConverter.math.DecimalComparison;
class UnitTest {
/** A random number generator */
private static final Random rng = ThreadLocalRandom.current();
-
+
@Test
public void testAdditionAndSubtraction() {
- final LinearUnit inch = SI.METRE.times(0.0254);
- final LinearUnit foot = SI.METRE.times(0.3048);
-
+ final LinearUnit inch = SI.METRE.times(0.0254)
+ .withName(NameSymbol.of("inch", "in"));
+ final LinearUnit foot = SI.METRE.times(0.3048)
+ .withName(NameSymbol.of("foot", "ft"));
+
assertEquals(inch.plus(foot), SI.METRE.times(0.3302));
assertEquals(foot.minus(inch), SI.METRE.times(0.2794));
+
+ // test with LinearUnitValue
+ final LinearUnitValue value1 = LinearUnitValue.getExact(SI.METRE, 15);
+ final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120);
+ final LinearUnitValue value3 = LinearUnitValue.getExact(SI.METRE, 0.5);
+ final LinearUnitValue value4 = LinearUnitValue.getExact(SI.KILOGRAM, 60);
+
+ // make sure addition is done correctly
+ assertEquals(51.576, value1.plus(value2).getValue(), 0.001);
+ assertEquals(15.5, value1.plus(value3).getValue());
+ assertEquals(52.076, value1.plus(value2).plus(value3).getValue(), 0.001);
+
+ // make sure addition uses the correct unit, and is still associative
+ // (ignoring floating-point rounding errors)
+ assertEquals(SI.METRE, value1.plus(value2).getUnit());
+ assertEquals(SI.METRE, value1.plus(value2).plus(value3).getUnit());
+ assertEquals(foot, value2.plus(value1).getUnit());
+ assertTrue(value1.plus(value2).equals(value2.plus(value1), true));
+
+ // make sure errors happen when they should
+ assertThrows(IllegalArgumentException.class, () -> value1.plus(value4));
}
-
+
@Test
public void testConversion() {
final LinearUnit metre = SI.METRE;
final Unit inch = metre.times(0.0254);
-
+
+ final UnitValue value = UnitValue.of(inch, 75);
+
assertEquals(1.9, inch.convertTo(metre, 75), 0.01);
-
+ assertEquals(1.9, value.convertTo(metre).getValue(), 0.01);
+
// try random stuff
for (int i = 0; i < 1000; i++) {
// initiate random values
- final double conversionFactor = rng.nextDouble() * 1000000;
- final double testValue = rng.nextDouble() * 1000000;
+ final double conversionFactor = UnitTest.rng.nextDouble() * 1000000;
+ final double testValue = UnitTest.rng.nextDouble() * 1000000;
final double expected = testValue * conversionFactor;
-
+
// test
final Unit unit = SI.METRE.times(conversionFactor);
final double actual = unit.convertToBase(testValue);
-
- assertEquals(actual, expected, expected * DecimalComparison.DOUBLE_EPSILON);
+
+ assertEquals(actual, expected,
+ expected * DecimalComparison.DOUBLE_EPSILON);
}
}
-
+
@Test
public void testEquals() {
final LinearUnit metre = SI.METRE;
final Unit meter = SI.BaseUnits.METRE.asLinearUnit();
-
+
assertEquals(metre, meter);
}
-
+
@Test
public void testMultiplicationAndDivision() {
// test unit-times-unit multiplication
- final LinearUnit generatedJoule = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
+ final LinearUnit generatedJoule = SI.KILOGRAM
+ .times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
final LinearUnit actualJoule = SI.JOULE;
-
+
assertEquals(generatedJoule, actualJoule);
-
+
// test multiplication by conversion factors
final LinearUnit kilometre = SI.METRE.times(1000);
final LinearUnit hour = SI.SECOND.times(3600);
final LinearUnit generatedKPH = kilometre.dividedBy(hour);
-
+
final LinearUnit actualKPH = SI.METRE.dividedBy(SI.SECOND).dividedBy(3.6);
-
+
assertEquals(generatedKPH, actualKPH);
}
-
+
@Test
public void testPrefixes() {
final LinearUnit generatedKilometre = SI.METRE.withPrefix(SI.KILO);
final LinearUnit actualKilometre = SI.METRE.times(1000);
-
+
assertEquals(generatedKilometre, actualKilometre);
}
}