diff options
author | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2024-03-24 13:25:22 -0500 |
---|---|---|
committer | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2024-03-24 13:25:22 -0500 |
commit | ed53492243ecad8d975401a97f5b634328ad2c71 (patch) | |
tree | 8a744f46320710355a02c9b2c371602ce69aefec /src/main/java/sevenUnits | |
parent | c878761f737c90fc3fa1caedd48e2ee01637108f (diff) | |
parent | 91d51c3c49c4c0877483220ac0f12db4efab8f60 (diff) |
Release version 0.5.0 (merge into stable)
Diffstat (limited to 'src/main/java/sevenUnits')
26 files changed, 992 insertions, 824 deletions
diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index da66b4c..9c23c49 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-2023 Adrien Hopkins + * Copyright (C) 2021-2024 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -25,15 +25,15 @@ import sevenUnits.utils.SemanticVersionNumber; * @since 2021-06-28 */ public final class ProgramInfo { - - /** The version number (0.4.1) */ + + /** The version number (0.5.0) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber - .stableVersion(0, 4, 1); - + .stableVersion(0, 5, 0); + private ProgramInfo() { // this class is only for static variables, you shouldn't be able to // construct an instance throw new AssertionError(); } - + } diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java index 820d48c..3f1f75f 100644 --- a/src/main/java/sevenUnits/unit/BaseDimension.java +++ b/src/main/java/sevenUnits/unit/BaseDimension.java @@ -39,7 +39,7 @@ public final class BaseDimension implements Nameable { public static BaseDimension valueOf(final String name, final String symbol) { return new BaseDimension(name, symbol); } - + /** * The name of the dimension. */ @@ -49,7 +49,7 @@ public final class BaseDimension implements Nameable { * or two characters. */ private final String symbol; - + /** * Creates the {@code BaseDimension}. * @@ -62,7 +62,7 @@ public final class BaseDimension implements Nameable { this.name = Objects.requireNonNull(name, "name must not be null."); this.symbol = Objects.requireNonNull(symbol, "symbol must not be null."); } - + /** * @since v0.4.0 */ @@ -70,7 +70,7 @@ public final class BaseDimension implements Nameable { public NameSymbol getNameSymbol() { return NameSymbol.of(this.name, this.symbol); } - + @Override public String toString() { return String.format("%s (%s)", this.name, this.symbol); diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java index dba7f52..fe85a7b 100644 --- a/src/main/java/sevenUnits/unit/BaseUnit.java +++ b/src/main/java/sevenUnits/unit/BaseUnit.java @@ -46,7 +46,7 @@ public final class BaseUnit extends Unit { final String name, final String symbol) { return new BaseUnit(dimension, name, symbol, new HashSet<>()); } - + /** * Gets a base unit from the dimension it measures, its name and its symbol. * @@ -60,12 +60,12 @@ public final class BaseUnit extends Unit { final String name, final String symbol, final Set<String> otherNames) { return new BaseUnit(dimension, name, symbol, otherNames); } - + /** * The dimension measured by this base unit. */ private final BaseDimension dimension; - + /** * Creates the {@code BaseUnit}. * @@ -81,7 +81,7 @@ public final class BaseUnit extends Unit { this.dimension = Objects.requireNonNull(dimension, "dimension must not be null."); } - + /** * Returns a {@code LinearUnit} with this unit as a base and a conversion * factor of 1. This operation must be done in order to allow units to be @@ -93,17 +93,17 @@ public final class BaseUnit extends Unit { public LinearUnit asLinearUnit() { return LinearUnit.valueOf(this.getBase(), 1); } - + @Override protected double convertFromBase(final double value) { return value; } - + @Override protected double convertToBase(final double value) { return value; } - + /** * @return dimension * @since 2019-10-16 @@ -111,7 +111,7 @@ public final class BaseUnit extends Unit { public final BaseDimension getBaseDimension() { return this.dimension; } - + @Override public String toString() { return this.getPrimaryName().orElse("Unnamed unit") @@ -119,7 +119,7 @@ public final class BaseUnit extends Unit { ? String.format(" (%s)", this.getSymbol().get()) : ""); } - + @Override public BaseUnit withName(final NameSymbol ns) { Objects.requireNonNull(ns, "ns must not be null."); diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index 0ecba6d..69a3c05 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -39,7 +39,7 @@ public final class BritishImperial { public static final LinearUnit ROOD = Length.ROD.times(Length.FURLONG); public static final LinearUnit ACRE = Length.FURLONG.times(Length.CHAIN); } - + /** * Imperial units that measure length * @@ -59,15 +59,15 @@ public final class BritishImperial { public static final LinearUnit FURLONG = CHAIN.times(10); public static final LinearUnit MILE = FURLONG.times(8); public static final LinearUnit LEAGUE = MILE.times(3); - + public static final LinearUnit NAUTICAL_MILE = Metric.METRE.times(1852); public static final LinearUnit CABLE = NAUTICAL_MILE.dividedBy(10); public static final LinearUnit FATHOM = CABLE.dividedBy(100); - + public static final LinearUnit ROD = YARD.times(5.5); public static final LinearUnit LINK = ROD.dividedBy(25); } - + /** * British Imperial units that measure mass. * @@ -85,7 +85,7 @@ public final class BritishImperial { public static final LinearUnit LONG_TON = HUNDREDWEIGHT.times(20); public static final LinearUnit SLUG = Metric.KILOGRAM.times(14.59390294); } - + /** * British Imperial units that measure volume * @@ -101,23 +101,23 @@ public final class BritishImperial { public static final LinearUnit GALLON = QUART.times(4); public static final LinearUnit PECK = GALLON.times(2); public static final LinearUnit BUSHEL = PECK.times(4); - + public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3); public static final LinearUnit CUBIC_FOOT = Length.FOOT.toExponent(3); public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3); public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT); } - + public static final LinearUnit OUNCE_FORCE = Mass.OUNCE .times(Metric.Constants.EARTH_GRAVITY); public static final LinearUnit POUND_FORCE = Mass.POUND .times(Metric.Constants.EARTH_GRAVITY); - + public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE .times(1055.06); public static final LinearUnit CALORIE = Metric.JOULE.times(4.184); public static final LinearUnit KILOCALORIE = Metric.JOULE.times(4184); - + public static final Unit FAHRENHEIT = Unit .fromConversionFunctions(Metric.KELVIN.getBase(), tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8) diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index 720b0af..6de446f 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -30,14 +30,16 @@ import sevenUnits.utils.ObjectProduct; */ final class FunctionalUnit extends Unit { /** - * A function that accepts a value expressed in the unit's base and returns that value expressed in this unit. + * A function that accepts a value expressed in the unit's base and returns + * that value expressed in this unit. * * @since 2019-05-22 */ private final DoubleUnaryOperator converterFrom; /** - * A function that accepts a value expressed in the unit and returns that value expressed in the unit's base. + * A function that accepts a value expressed in the unit and returns that + * value expressed in the unit's base. * * @since 2019-05-22 */ @@ -46,45 +48,43 @@ final class FunctionalUnit extends Unit { /** * Creates the {@code FunctionalUnit}. * - * @param base - * unit's base - * @param converterFrom - * function that accepts a value expressed in the unit's base and returns that value expressed in this - * unit. - * @param converterTo - * function that accepts a value expressed in the unit and returns that value expressed in the unit's - * base. - * @throws NullPointerException - * if any argument is null + * @param base unit's base + * @param converterFrom function that accepts a value expressed in the unit's + * base and returns that value expressed in this unit. + * @param converterTo function that accepts a value expressed in the unit + * and returns that value expressed in the unit's base. + * @throws NullPointerException if any argument is null * @since 2019-05-22 */ - public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom, + public FunctionalUnit(final ObjectProduct<BaseUnit> base, + final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo) { super(base, NameSymbol.EMPTY); - this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null."); - this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null."); + this.converterFrom = Objects.requireNonNull(converterFrom, + "converterFrom must not be null."); + this.converterTo = Objects.requireNonNull(converterTo, + "converterTo must not be null."); } /** * Creates the {@code FunctionalUnit}. * - * @param base - * unit's base - * @param converterFrom - * function that accepts a value expressed in the unit's base and returns that value expressed in this - * unit. - * @param converterTo - * function that accepts a value expressed in the unit and returns that value expressed in the unit's - * base. - * @throws NullPointerException - * if any argument is null + * @param base unit's base + * @param converterFrom function that accepts a value expressed in the unit's + * base and returns that value expressed in this unit. + * @param converterTo function that accepts a value expressed in the unit + * and returns that value expressed in the unit's base. + * @throws NullPointerException if any argument is null * @since 2019-05-22 */ - public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom, + public FunctionalUnit(final ObjectProduct<BaseUnit> base, + final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo, final NameSymbol ns) { super(base, ns); - this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null."); - this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null."); + this.converterFrom = Objects.requireNonNull(converterFrom, + "converterFrom must not be null."); + this.converterTo = Objects.requireNonNull(converterTo, + "converterTo must not be null."); } /** diff --git a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java index d6046c0..e9b4d1f 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java @@ -35,13 +35,13 @@ final class FunctionalUnitlike<V> extends Unitlike<V> { * @since 2020-09-07 */ private final DoubleFunction<V> converterFrom; - + /** * A function that accepts a value in the unitlike form and returns a value * in the unitlike form's base. */ private final ToDoubleFunction<V> converterTo; - + /** * Creates the {@code FunctionalUnitlike}. * @@ -59,15 +59,15 @@ final class FunctionalUnitlike<V> extends Unitlike<V> { this.converterFrom = converterFrom; this.converterTo = converterTo; } - + @Override protected V convertFromBase(double value) { return this.converterFrom.apply(value); } - + @Override protected double convertToBase(V value) { return this.converterTo.applyAsDouble(value); } - + } diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 103b7f6..6489229 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -46,7 +46,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit and a value. For example, converts * '59 °F' to a linear unit with the value of '288.15 K' @@ -64,7 +64,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), ns); } - + /** * @return the base unit associated with {@code unit}, as a * {@code LinearUnit}. @@ -73,7 +73,7 @@ public final class LinearUnit extends Unit { public static LinearUnit getBase(final Unit unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - + /** * @return the base unit associated with {@code unitlike}, as a * {@code LinearUnit}. @@ -82,7 +82,7 @@ public final class LinearUnit extends Unit { public static LinearUnit getBase(final Unitlike<?> unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and @@ -98,7 +98,7 @@ public final class LinearUnit extends Unit { final double conversionFactor) { return new LinearUnit(unitBase, conversionFactor, NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and @@ -115,7 +115,7 @@ public final class LinearUnit extends Unit { final double conversionFactor, final NameSymbol ns) { return new LinearUnit(unitBase, conversionFactor, ns); } - + /** * The value of this unit as represented in its base form. Mathematically, * @@ -126,7 +126,7 @@ public final class LinearUnit extends Unit { * @since 2019-10-16 */ private final double conversionFactor; - + /** * Creates the {@code LinearUnit}. * @@ -139,7 +139,7 @@ public final class LinearUnit extends Unit { super(unitBase, ns); this.conversionFactor = conversionFactor; } - + /** * {@inheritDoc} * @@ -149,7 +149,7 @@ public final class LinearUnit extends Unit { protected double convertFromBase(final double value) { return value / this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} value expressed in this unit to an * {@code UncertainValue} value expressed in {@code other}. @@ -172,9 +172,9 @@ public final class LinearUnit extends Unit { else throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); - + } - + /** * {@inheritDoc} * @@ -184,7 +184,7 @@ public final class LinearUnit extends Unit { protected double convertToBase(final double value) { return value * this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} to the base unit. * @@ -193,7 +193,7 @@ public final class LinearUnit extends Unit { UncertainDouble convertToBase(final UncertainDouble value) { return value.timesExact(this.getConversionFactor()); } - + /** * Divides this unit by a scalar. * @@ -205,7 +205,7 @@ public final class LinearUnit extends Unit { public LinearUnit dividedBy(final double divisor) { return valueOf(this.getBase(), this.getConversionFactor() / divisor); } - + /** * Returns the quotient of this unit and another. * @@ -217,14 +217,14 @@ public final class LinearUnit extends Unit { */ public LinearUnit dividedBy(final LinearUnit divisor) { Objects.requireNonNull(divisor, "other must not be null"); - + // divide the units final ObjectProduct<BaseUnit> base = this.getBase() .dividedBy(divisor.getBase()); return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor()); } - + /** * {@inheritDoc} * @@ -239,7 +239,7 @@ public final class LinearUnit extends Unit { && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor()); } - + /** * @return conversion factor * @since 2019-10-16 @@ -247,7 +247,7 @@ public final class LinearUnit extends Unit { public double getConversionFactor() { return this.conversionFactor; } - + /** * {@inheritDoc} * @@ -258,7 +258,7 @@ public final class LinearUnit extends Unit { return 31 * this.getBase().hashCode() + DecimalComparison.hash(this.getConversionFactor()); } - + /** * @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there * is a {@code BaseUnit b} where @@ -268,7 +268,7 @@ public final class LinearUnit extends Unit { public boolean isBase() { return this.isCoherent() && this.getBase().isSingleObject(); } - + /** * @return whether this unit is coherent (i.e. has conversion factor 1) * @since 2019-10-16 @@ -276,7 +276,7 @@ public final class LinearUnit extends Unit { public boolean isCoherent() { return this.getConversionFactor() == 1; } - + /** * Returns the difference of this unit and another. * <p> @@ -296,18 +296,18 @@ public final class LinearUnit extends Unit { */ public LinearUnit minus(final LinearUnit subtrahend) { Objects.requireNonNull(subtrahend, "addend must not be null."); - + // reject subtrahends that cannot be added to this unit if (!this.getBase().equals(subtrahend.getBase())) throw new IllegalArgumentException(String.format( "Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahend)); - + // subtract the units return valueOf(this.getBase(), this.getConversionFactor() - subtrahend.getConversionFactor()); } - + /** * Returns the sum of this unit and another. * <p> @@ -327,18 +327,18 @@ public final class LinearUnit extends Unit { */ public LinearUnit plus(final LinearUnit addend) { Objects.requireNonNull(addend, "addend must not be null."); - + // reject addends that cannot be added to this unit if (!this.getBase().equals(addend.getBase())) throw new IllegalArgumentException(String.format( "Incompatible units for addition \"%s\" and \"%s\".", this, addend)); - + // add the units return valueOf(this.getBase(), this.getConversionFactor() + addend.getConversionFactor()); } - + /** * Multiplies this unit by a scalar. * @@ -350,7 +350,7 @@ public final class LinearUnit extends Unit { public LinearUnit times(final double multiplier) { return valueOf(this.getBase(), this.getConversionFactor() * multiplier); } - + /** * Returns the product of this unit and another. * @@ -362,21 +362,21 @@ public final class LinearUnit extends Unit { */ public LinearUnit times(final LinearUnit multiplier) { Objects.requireNonNull(multiplier, "other must not be null"); - + // multiply the units final ObjectProduct<BaseUnit> base = this.getBase() .times(multiplier.getBase()); return valueOf(base, this.getConversionFactor() * multiplier.getConversionFactor()); } - + @Override public String toDefinitionString() { return Double.toString(this.conversionFactor) + (this.getBase().equals(ObjectProduct.empty()) ? "" : " " + this.getBase().toString(BaseUnit::getShortName)); } - + /** * Returns this unit but to an exponent. * @@ -389,12 +389,12 @@ public final class LinearUnit extends Unit { return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); } - + @Override public LinearUnit withName(final NameSymbol ns) { return valueOf(this.getBase(), this.getConversionFactor(), ns); } - + /** * Returns the result of applying {@code prefix} to this unit. * <p> @@ -413,7 +413,7 @@ public final class LinearUnit extends Unit { */ public LinearUnit withPrefix(final UnitPrefix prefix) { final LinearUnit unit = this.times(prefix.getMultiplier()); - + // create new name and symbol, if possible final String name; if (this.getPrimaryName().isPresent() @@ -422,14 +422,14 @@ public final class LinearUnit extends Unit { } else { name = null; } - + final String symbol; if (this.getSymbol().isPresent() && prefix.getSymbol().isPresent()) { symbol = prefix.getSymbol().get() + this.getSymbol().get(); } else { symbol = null; } - + return unit.withName(NameSymbol.ofNullable(name, symbol)); } } diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index f91d30b..3a9428b 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -34,7 +34,7 @@ import sevenUnits.utils.UncertainDouble; */ public final class LinearUnitValue { public static final LinearUnitValue ONE = getExact(Metric.ONE, 1); - + /** * Gets an exact {@code LinearUnitValue} * @@ -49,7 +49,7 @@ public final class LinearUnitValue { Objects.requireNonNull(unit, "unit must not be null"), UncertainDouble.of(value, 0)); } - + /** * Gets an uncertain {@code LinearUnitValue} * @@ -65,11 +65,11 @@ public final class 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 @@ -79,7 +79,7 @@ public final class LinearUnitValue { this.unit = unit; this.value = value; } - + /** * @return this value as a {@code UnitValue}. All uncertainty information is * removed from the returned value. @@ -88,7 +88,7 @@ public final class LinearUnitValue { 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}. @@ -97,7 +97,7 @@ public final class LinearUnitValue { 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 @@ -109,7 +109,7 @@ public final class LinearUnitValue { public final LinearUnitValue convertTo(final LinearUnit other) { return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); } - + /** * Divides this value by a scalar * @@ -120,7 +120,7 @@ public final class LinearUnitValue { public LinearUnitValue dividedBy(final double divisor) { return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor)); } - + /** * Divides this value by another value * @@ -132,7 +132,7 @@ public final class LinearUnitValue { 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 @@ -150,7 +150,7 @@ public final class LinearUnitValue { && 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 @@ -171,7 +171,7 @@ public final class LinearUnitValue { && 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 @@ -185,10 +185,10 @@ public final class LinearUnitValue { 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-09-29 @@ -196,7 +196,7 @@ public final class LinearUnitValue { public final LinearUnit getUnit() { return this.unit; } - + /** * @return the value * @since 2020-09-29 @@ -204,7 +204,7 @@ public final class LinearUnitValue { public final UncertainDouble getValue() { return this.value; } - + /** * @return the exact value * @since 2020-09-07 @@ -212,13 +212,13 @@ public final class LinearUnitValue { 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 @@ -231,17 +231,17 @@ public final class LinearUnitValue { */ 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 * @@ -253,17 +253,17 @@ public final class LinearUnitValue { */ 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 * @@ -274,7 +274,7 @@ public final class LinearUnitValue { public LinearUnitValue times(final double multiplier) { return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier)); } - + /** * Multiplies this value by another value * @@ -286,7 +286,7 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.times(multiplier.unit), this.value.times(multiplier.value)); } - + /** * Raises a value to an exponent * @@ -298,18 +298,18 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.toExponent(exponent), this.value.toExponentExact(exponent)); } - + @Override public String toString() { return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN); } - + /** * 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 + * 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. @@ -321,9 +321,9 @@ public final class LinearUnitValue { final Optional<String> primaryName = this.unit.getPrimaryName(); final Optional<String> 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 ? "(" : "") @@ -332,7 +332,7 @@ public final class LinearUnitValue { final String baseValueString = (showUncertainty ? "(" : "") + baseValue.toString(showUncertainty, roundingMode) + (showUncertainty ? ")" : ""); - + // create string if (chosenName == null) return String.format("%s unnamed unit (= %s %s)", valueString, diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 05e82ba..7841987 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -59,13 +59,13 @@ public final class Metric { .valueOf("Information", "Info"); // non-SI public static final BaseDimension CURRENCY = BaseDimension .valueOf("Currency", "$$"); // non-SI - + // You may NOT get SI.BaseDimensions instances! private BaseDimensions() { throw new AssertionError(); } } - + /// base units of the SI // suppressing warnings since these are the same object, but in a different /// form (class) @@ -89,16 +89,16 @@ public final class Metric { .valueOf(BaseDimensions.INFORMATION, "bit", "b"); public static final BaseUnit DOLLAR = BaseUnit .valueOf(BaseDimensions.CURRENCY, "dollar", "$"); - + public static final Set<BaseUnit> BASE_UNITS = Set.of(METRE, KILOGRAM, SECOND, AMPERE, KELVIN, MOLE, CANDELA, BIT); - + // You may NOT get SI.BaseUnits instances! private BaseUnits() { throw new AssertionError(); } } - + /** * Constants that relate to the SI or other systems. * @@ -109,7 +109,7 @@ public final class Metric { public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND) .dividedBy(SECOND).times(9.80665); } - + // dimensions used in the SI, as ObjectProducts public static final class Dimensions { public static final ObjectProduct<BaseDimension> EMPTY = ObjectProduct @@ -139,7 +139,7 @@ public final class Metric { public static final ObjectProduct<BaseDimension> CURRENCY = ObjectProduct .oneOf(BaseDimensions.CURRENCY) .withName(NameSymbol.ofName("Currency")); - + // derived dimensions without named SI units public static final ObjectProduct<BaseDimension> AREA = LENGTH .times(LENGTH); @@ -175,7 +175,7 @@ public final class Metric { .dividedBy(LENGTH); public static final ObjectProduct<BaseDimension> SOLID_ANGLE = AREA .dividedBy(AREA); - + // derived dimensions with named SI units public static final ObjectProduct<BaseDimension> FREQUENCY = EMPTY .dividedBy(TIME); @@ -209,17 +209,17 @@ public final class Metric { .dividedBy(MASS); public static final ObjectProduct<BaseDimension> CATALYTIC_ACTIVITY = QUANTITY .dividedBy(TIME); - + // You may NOT get SI.Dimension instances! private Dimensions() { throw new AssertionError(); } } - + /// The units of the SI public static final LinearUnit ONE = LinearUnit .valueOf(ObjectProduct.empty(), 1); - + public static final LinearUnit METRE = BaseUnits.METRE.asLinearUnit() .withName(NameSymbol.of("metre", "m", "meter")); public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit() @@ -241,7 +241,7 @@ public final class Metric { // Non-base units public static final LinearUnit RADIAN = METRE.dividedBy(METRE) .withName(NameSymbol.of("radian", "rad")); - + public static final LinearUnit STERADIAN = RADIAN.times(RADIAN) .withName(NameSymbol.of("steradian", "sr")); public static final LinearUnit HERTZ = ONE.dividedBy(SECOND) @@ -290,7 +290,7 @@ public final class Metric { // common derived units included for convenience public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000) .withName(NameSymbol.of("gram", "g")); - + public static final LinearUnit SQUARE_METRE = METRE.toExponent(2) .withName(NameSymbol.of("square metre", "m^2", "square meter", "metre squared", "meter squared")); @@ -305,7 +305,7 @@ public final class Metric { .fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15, tempC -> tempC + 273.15) .withName(NameSymbol.of("degree Celsius", "\u00B0C")); - + public static final LinearUnit MINUTE = SECOND.times(60) .withName(NameSymbol.of("minute", "min")); public static final LinearUnit HOUR = MINUTE.times(60) @@ -349,7 +349,7 @@ public final class Metric { .fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr), dB -> Math.pow(10, dB / 10)) .withName(NameSymbol.of("decibel", "dB")); - + /// The prefixes of the SI // expanding decimal prefixes public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3) @@ -368,7 +368,7 @@ public final class Metric { .withName(NameSymbol.of("zetta", "Z")); public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24) .withName(NameSymbol.of("yotta", "Y")); - + // contracting decimal prefixes public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3) .withName(NameSymbol.of("milli", "m")); @@ -386,7 +386,7 @@ public final class Metric { .withName(NameSymbol.of("zepto", "z")); public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24) .withName(NameSymbol.of("yocto", "y")); - + // prefixes that don't match the pattern of thousands public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1) .withName(NameSymbol.of("deka", "da", "deca", "D")); @@ -408,7 +408,7 @@ public final class Metric { .withName(NameSymbol.of("pebi", "Pi")); public static final UnitPrefix EXBI = PEBI.times(1024) .withName(NameSymbol.of("exbi", "Ei")); - + // a few prefixed units public static final LinearUnit MICROMETRE = Metric.METRE .withPrefix(Metric.MICRO); @@ -418,7 +418,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAMETRE = Metric.METRE .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROLITRE = Metric.LITRE .withPrefix(Metric.MICRO); public static final LinearUnit MILLILITRE = Metric.LITRE @@ -427,7 +427,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGALITRE = Metric.LITRE .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROSECOND = Metric.SECOND .withPrefix(Metric.MICRO); public static final LinearUnit MILLISECOND = Metric.SECOND @@ -436,14 +436,14 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGASECOND = Metric.SECOND .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROGRAM = Metric.GRAM .withPrefix(Metric.MICRO); public static final LinearUnit MILLIGRAM = Metric.GRAM .withPrefix(Metric.MILLI); public static final LinearUnit MEGAGRAM = Metric.GRAM .withPrefix(Metric.MEGA); - + public static final LinearUnit MICRONEWTON = Metric.NEWTON .withPrefix(Metric.MICRO); public static final LinearUnit MILLINEWTON = Metric.NEWTON @@ -452,7 +452,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGANEWTON = Metric.NEWTON .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROJOULE = Metric.JOULE .withPrefix(Metric.MICRO); public static final LinearUnit MILLIJOULE = Metric.JOULE @@ -461,7 +461,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAJOULE = Metric.JOULE .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROWATT = Metric.WATT .withPrefix(Metric.MICRO); public static final LinearUnit MILLIWATT = Metric.WATT @@ -470,7 +470,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAWATT = Metric.WATT .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROCOULOMB = Metric.COULOMB .withPrefix(Metric.MICRO); public static final LinearUnit MILLICOULOMB = Metric.COULOMB @@ -479,12 +479,12 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGACOULOMB = Metric.COULOMB .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROAMPERE = Metric.AMPERE .withPrefix(Metric.MICRO); public static final LinearUnit MILLIAMPERE = Metric.AMPERE .withPrefix(Metric.MILLI); - + public static final LinearUnit MICROVOLT = Metric.VOLT .withPrefix(Metric.MICRO); public static final LinearUnit MILLIVOLT = Metric.VOLT @@ -493,16 +493,16 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAVOLT = Metric.VOLT .withPrefix(Metric.MEGA); - + public static final LinearUnit KILOOHM = Metric.OHM.withPrefix(Metric.KILO); public static final LinearUnit MEGAOHM = Metric.OHM.withPrefix(Metric.MEGA); - + // sets of prefixes public static final Set<UnitPrefix> ALL_PREFIXES = Set.of(DEKA, HECTO, KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO, KIBI, MEBI, GIBI, TEBI, PEBI, EXBI); - + public static final Set<UnitPrefix> DECIMAL_PREFIXES = Set.of(DEKA, HECTO, KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); @@ -514,7 +514,7 @@ public final class Metric { TEBI, PEBI, EXBI); public static final Set<UnitPrefix> REDUCING_PREFIXES = Set.of(DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); - + // You may NOT get SI instances! private Metric() { throw new AssertionError(); diff --git a/src/main/java/sevenUnits/unit/MultiUnit.java b/src/main/java/sevenUnits/unit/MultiUnit.java index bc240e3..950c547 100644 --- a/src/main/java/sevenUnits/unit/MultiUnit.java +++ b/src/main/java/sevenUnits/unit/MultiUnit.java @@ -39,7 +39,7 @@ public final class MultiUnit extends Unitlike<List<Double>> { public static final MultiUnit of(LinearUnit... units) { return of(Arrays.asList(units)); } - + /** * Creates a {@code MultiUnit} from its units. It will not have a name or * symbol. @@ -57,12 +57,12 @@ public final class MultiUnit extends Unitlike<List<Double>> { } return new MultiUnit(new ArrayList<>(units), unitBase, NameSymbol.EMPTY); } - + /** * The units that make up this value. */ private final List<LinearUnit> units; - + /** * Creates a {@code MultiUnit}. * @@ -73,24 +73,24 @@ public final class MultiUnit extends Unitlike<List<Double>> { super(unitBase, ns); this.units = units; } - + @Override protected List<Double> convertFromBase(double value) { final List<Double> values = new ArrayList<>(this.units.size()); double temp = value; - + for (final LinearUnit unit : this.units.subList(0, this.units.size() - 1)) { values.add(Math.floor(temp / unit.getConversionFactor())); temp %= unit.getConversionFactor(); } - + values.add(this.units.size() - 1, this.units.get(this.units.size() - 1).convertFromBase(temp)); - + return values; } - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -99,7 +99,7 @@ public final class MultiUnit extends Unitlike<List<Double>> { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -115,10 +115,10 @@ public final class MultiUnit extends Unitlike<List<Double>> { for (final double d : values) { valueList.add(d); } - + return this.convertTo(other, valueList); } - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -127,7 +127,7 @@ public final class MultiUnit extends Unitlike<List<Double>> { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -142,16 +142,16 @@ public final class MultiUnit extends Unitlike<List<Double>> { for (final double d : values) { valueList.add(d); } - + return this.convertTo(other, valueList); } - + @Override protected double convertToBase(List<Double> value) { if (value.size() != this.units.size()) throw new IllegalArgumentException("Wrong number of values for " + this.units.size() + "-unit MultiUnit."); - + double baseValue = 0; for (int i = 0; i < this.units.size(); i++) { baseValue += value.get(i) * this.units.get(i).getConversionFactor(); diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java index 459071f..fce829e 100644 --- a/src/main/java/sevenUnits/unit/USCustomary.java +++ b/src/main/java/sevenUnits/unit/USCustomary.java @@ -30,10 +30,14 @@ public final class USCustomary { * @since 2019-11-08 */ public static final class Area { - public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT.times(Length.SURVEY_FOOT); - public static final LinearUnit SQUARE_CHAIN = Length.SURVEY_CHAIN.times(Length.SURVEY_CHAIN); - public static final LinearUnit ACRE = Length.SURVEY_CHAIN.times(Length.SURVEY_FURLONG); - public static final LinearUnit SECTION = Length.SURVEY_MILE.times(Length.SURVEY_MILE); + public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT + .times(Length.SURVEY_FOOT); + public static final LinearUnit SQUARE_CHAIN = Length.SURVEY_CHAIN + .times(Length.SURVEY_CHAIN); + public static final LinearUnit ACRE = Length.SURVEY_CHAIN + .times(Length.SURVEY_FURLONG); + public static final LinearUnit SECTION = Length.SURVEY_MILE + .times(Length.SURVEY_MILE); public static final LinearUnit SURVEY_TOWNSHIP = SECTION.times(36); } @@ -52,8 +56,10 @@ public final class USCustomary { public static final LinearUnit YARD = BritishImperial.Length.YARD; public static final LinearUnit MILE = BritishImperial.Length.MILE; - public static final LinearUnit SURVEY_FOOT = Metric.METRE.times(1200.0 / 3937.0); - public static final LinearUnit SURVEY_LINK = SURVEY_FOOT.times(33.0 / 50.0); + public static final LinearUnit SURVEY_FOOT = Metric.METRE + .times(1200.0 / 3937.0); + public static final LinearUnit SURVEY_LINK = SURVEY_FOOT + .times(33.0 / 50.0); public static final LinearUnit SURVEY_ROD = SURVEY_FOOT.times(16.5); public static final LinearUnit SURVEY_CHAIN = SURVEY_ROD.times(4); public static final LinearUnit SURVEY_FURLONG = SURVEY_CHAIN.times(10); @@ -97,7 +103,8 @@ public final class USCustomary { public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3); public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT); - public static final LinearUnit MINIM = Metric.LITRE.withPrefix(Metric.MICRO).times(61.611519921875); + public static final LinearUnit MINIM = Metric.LITRE + .withPrefix(Metric.MICRO).times(61.611519921875); public static final LinearUnit FLUID_DRAM = MINIM.times(60); public static final LinearUnit TEASPOON = MINIM.times(80); public static final LinearUnit TABLESPOON = TEASPOON.times(3); @@ -112,7 +119,8 @@ public final class USCustomary { public static final LinearUnit OIL_BARREL = GALLON.times(42); public static final LinearUnit HOGSHEAD = GALLON.times(63); - public static final LinearUnit DRY_PINT = Metric.LITRE.times(0.5506104713575); + public static final LinearUnit DRY_PINT = Metric.LITRE + .times(0.5506104713575); public static final LinearUnit DRY_QUART = DRY_PINT.times(2); public static final LinearUnit DRY_GALLON = DRY_QUART.times(4); public static final LinearUnit PECK = DRY_GALLON.times(2); @@ -128,8 +136,10 @@ public final class USCustomary { public static final LinearUnit KILOCALORIE = BritishImperial.KILOCALORIE; public static final LinearUnit FOOT_POUND = POUND_FORCE.times(Length.FOOT); - public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE).dividedBy(Metric.MINUTE).times(33000); - public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE.dividedBy(Length.INCH.toExponent(2)); + public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE) + .dividedBy(Metric.MINUTE).times(33000); + public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE + .dividedBy(Length.INCH.toExponent(2)); public static final Unit FAHRENHEIT = BritishImperial.FAHRENHEIT; } diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 14478ba..59e928a 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -59,7 +59,7 @@ public abstract class Unit implements Nameable { final DoubleUnaryOperator converterTo) { return new FunctionalUnit(base, converterFrom, converterTo); } - + /** * Returns a unit from its base and the functions it uses to convert to and * from its base. @@ -87,28 +87,28 @@ public abstract class Unit implements Nameable { final DoubleUnaryOperator converterTo, final NameSymbol ns) { return new FunctionalUnit(base, converterFrom, converterTo, ns); } - + /** * The combination of units that this unit is based on. * * @since 2019-10-16 */ private final ObjectProduct<BaseUnit> unitBase; - + /** * This unit's name(s) and symbol * * @since 2020-09-07 */ private final NameSymbol nameSymbol; - + /** * Cache storing the result of getDimension() * * @since 2019-10-16 */ private transient ObjectProduct<BaseDimension> dimension = null; - + /** * A constructor that constructs {@code BaseUnit} instances. * @@ -121,7 +121,7 @@ public abstract class Unit implements Nameable { throw new AssertionError(); this.nameSymbol = nameSymbol; } - + /** * Creates the {@code Unit}. * @@ -135,7 +135,7 @@ public abstract class Unit implements Nameable { "unitBase may not be null"); this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); } - + /** * @return this unit as a {@link Unitlike} * @since 2020-09-07 @@ -144,7 +144,7 @@ public abstract class Unit implements Nameable { return Unitlike.fromConversionFunctions(this.getBase(), this::convertFromBase, this::convertToBase, this.getNameSymbol()); } - + /** * Checks if a value expressed in this unit can be converted to a value * expressed in {@code other} @@ -159,7 +159,7 @@ public abstract class Unit implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + /** * Checks if a value expressed in this unit can be converted to a value * expressed in {@code other} @@ -174,7 +174,7 @@ public abstract class Unit implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + /** * Converts from a value expressed in this unit's base unit to a value * expressed in this unit. @@ -190,14 +190,14 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in <b>base</b> unit * @return value expressed in <b>this</b> unit * @since 2018-12-22 * @since v0.1.0 */ protected abstract double convertFromBase(double value); - + /** * Converts a value expressed in this unit to a value expressed in * {@code other}. @@ -206,7 +206,7 @@ public abstract class Unit implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -224,7 +224,7 @@ public abstract class Unit implements Nameable { throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } - + /** * Converts a value expressed in this unit to a value expressed in * {@code other}. @@ -252,7 +252,7 @@ public abstract class Unit implements Nameable { throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } - + /** * Converts from a value expressed in this unit to a value expressed in this * unit's base unit. @@ -268,14 +268,14 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in <b>this</b> unit * @return value expressed in <b>base</b> unit * @since 2018-12-22 * @since v0.1.0 */ protected abstract double convertToBase(double value); - + /** * @return combination of units that this unit is based on * @since 2018-12-22 @@ -284,7 +284,7 @@ public abstract class Unit implements Nameable { public final ObjectProduct<BaseUnit> getBase() { return this.unitBase; } - + /** * @return dimension measured by this unit * @since 2018-12-22 @@ -294,16 +294,16 @@ public abstract class Unit implements Nameable { if (this.dimension == null) { final Map<BaseUnit, Integer> mapping = this.unitBase.exponentMap(); final Map<BaseDimension, Integer> dimensionMap = new HashMap<>(); - + for (final BaseUnit key : mapping.keySet()) { dimensionMap.put(key.getBaseDimension(), mapping.get(key)); } - + this.dimension = ObjectProduct.fromExponentMapping(dimensionMap); } return this.dimension; } - + /** * @return the nameSymbol * @since 2020-09-07 @@ -312,7 +312,7 @@ public abstract class Unit implements Nameable { public final NameSymbol getNameSymbol() { return this.nameSymbol; } - + /** * Returns true iff this unit is metric. * <p> @@ -337,18 +337,18 @@ public abstract class Unit implements Nameable { if (!(this instanceof LinearUnit)) return false; final LinearUnit linear = (LinearUnit) this; - + // second condition - check that for (final BaseUnit b : linear.getBase().getBaseSet()) { if (!Metric.BaseUnits.BASE_UNITS.contains(b)) return false; } - + // third condition - check that conversion factor is a power of 10 return DecimalComparison .equals(Math.log10(linear.getConversionFactor()) % 1.0, 0); } - + /** * @return a string representing this unit's definition * @since 2022-03-10 @@ -360,7 +360,7 @@ public abstract class Unit implements Nameable { return "derived from " + this.getBase().toString(BaseUnit::getShortName); } - + /** * @return a string containing both this unit's name and its definition * @since 2022-03-10 @@ -368,7 +368,7 @@ public abstract class Unit implements Nameable { public final String toFullString() { return this.toString() + " (" + this.toDefinitionString() + ")"; } - + @Override public String toString() { if (this.nameSymbol.getPrimaryName().isPresent() @@ -378,7 +378,7 @@ public abstract class Unit implements Nameable { else return this.getName(); } - + /** * @param ns name(s) and symbol to use * @return a copy of this unit with provided name(s) and symbol diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 12b78a7..0120067 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018-2024 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -123,7 +123,7 @@ public final class UnitDatabase { implements Entry<String, Unit> { private final String key; private final Unit value; - + /** * Creates the {@code PrefixedUnitEntry}. * @@ -136,7 +136,7 @@ public final class UnitDatabase { this.key = key; this.value = value; } - + /** * @since 2019-05-03 */ @@ -148,17 +148,17 @@ public final class UnitDatabase { return Objects.equals(this.getKey(), other.getKey()) && Objects.equals(this.getValue(), other.getValue()); } - + @Override public String getKey() { return this.key; } - + @Override public Unit getValue() { return this.value; } - + /** * @since 2019-05-03 */ @@ -168,13 +168,13 @@ public final class UnitDatabase { ^ (this.getValue() == null ? 0 : this.getValue().hashCode()); } - + @Override public Unit setValue(final Unit value) { throw new UnsupportedOperationException( "Cannot set value in an immutable entry"); } - + /** * Returns a string representation of the entry. The format of the * string is the string representation of the key, then the equals @@ -188,7 +188,7 @@ public final class UnitDatabase { return this.getKey() + "=" + this.getValue(); } } - + /** * An iterator that iterates over the units of a * {@code PrefixedUnitNameSet}. @@ -203,12 +203,12 @@ public final class UnitDatabase { private int unitNamePosition = 0; // the indices of the prefixes attached to the current unit private final List<Integer> prefixCoordinates = new ArrayList<>(); - + // values from the unit entry set private final Map<String, Unit> map; private transient final List<String> unitNames; private transient final List<String> prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -221,7 +221,7 @@ public final class UnitDatabase { this.unitNames = new ArrayList<>(map.units.keySet()); this.prefixNames = new ArrayList<>(map.prefixes.keySet()); } - + /** * @return current unit name * @since 2019-04-14 @@ -233,10 +233,10 @@ public final class UnitDatabase { unitName.append(this.prefixNames.get(i)); } unitName.append(this.unitNames.get(this.unitNamePosition)); - + return unitName.toString(); } - + @Override public boolean hasNext() { if (this.unitNames.isEmpty()) @@ -249,7 +249,7 @@ public final class UnitDatabase { return true; } } - + /** * Changes this iterator's position to the next available one. * @@ -258,11 +258,11 @@ public final class UnitDatabase { */ private void incrementPosition() { this.unitNamePosition++; - + if (this.unitNamePosition >= this.unitNames.size()) { // we have used all of our units, go to a different prefix this.unitNamePosition = 0; - + // if the prefix coordinates are empty, then set it to [0] if (this.prefixCoordinates.isEmpty()) { this.prefixCoordinates.add(0, 0); @@ -271,7 +271,7 @@ public final class UnitDatabase { int i = this.prefixCoordinates.size() - 1; this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1); - + // fix any carrying errors while (i >= 0 && this.prefixCoordinates .get(i) >= this.prefixNames.size()) { @@ -279,7 +279,7 @@ public final class UnitDatabase { this.prefixCoordinates.set(i--, 0); // null and // decrement at the // same time - + if (i < 0) { // we need to add a new coordinate this.prefixCoordinates.add(0, 0); } else { // increment an existing one @@ -290,18 +290,18 @@ public final class UnitDatabase { } } } - + @Override public Entry<String, Unit> next() { // get next element final Entry<String, Unit> nextEntry = this.peek(); - + // iterate to next position this.incrementPosition(); - + return nextEntry; } - + /** * @return the next element in the iterator, without iterating over * it @@ -310,7 +310,7 @@ public final class UnitDatabase { private Entry<String, Unit> peek() { if (!this.hasNext()) throw new NoSuchElementException("No units left!"); - + // if I have prefixes, ensure I'm not using a nonlinear unit // since all of the unprefixed stuff is done, just remove // nonlinear units @@ -321,12 +321,12 @@ public final class UnitDatabase { this.unitNames.remove(this.unitNamePosition); } } - + final String nextName = this.getCurrentUnitName(); - + return new PrefixedUnitEntry(nextName, this.map.get(nextName)); } - + /** * Returns a string representation of the object. The exact details * of the representation are unspecified and subject to change. @@ -340,10 +340,10 @@ public final class UnitDatabase { this.peek()); } } - + // the map that created this set private final PrefixedUnitMap map; - + /** * Creates the {@code PrefixedUnitNameSet}. * @@ -354,31 +354,31 @@ public final class UnitDatabase { public PrefixedUnitEntrySet(final PrefixedUnitMap map) { this.map = map; } - + @Override public boolean add(final Map.Entry<String, Unit> e) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public boolean addAll( final Collection<? extends Map.Entry<String, Unit>> c) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable set"); } - + @Override public boolean contains(final Object o) { // get the entry final Entry<String, Unit> entry; - + try { // This is OK because I'm in a try-catch block, catching the // exact exception that would be thrown. @@ -389,11 +389,11 @@ public final class UnitDatabase { throw new IllegalArgumentException( "Attempted to test for an entry using a non-entry."); } - + return this.map.containsKey(entry.getKey()) && this.map.get(entry.getKey()).equals(entry.getValue()); } - + @Override public boolean containsAll(final Collection<?> c) { for (final Object o : c) @@ -401,42 +401,42 @@ public final class UnitDatabase { return false; return true; } - + @Override public boolean isEmpty() { return this.map.isEmpty(); } - + @Override public Iterator<Entry<String, Unit>> iterator() { return new PrefixedUnitEntryIterator(this.map); } - + @Override public boolean remove(final Object o) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeIf( final Predicate<? super Entry<String, Unit>> filter) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean retainAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public int size() { if (this.map.units.isEmpty()) @@ -449,7 +449,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -462,7 +462,7 @@ public final class UnitDatabase { throw new IllegalStateException( "Cannot make an infinite set into an array."); } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -475,7 +475,7 @@ public final class UnitDatabase { throw new IllegalStateException( "Cannot make an infinite set into an array."); } - + @Override public String toString() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) @@ -486,7 +486,7 @@ public final class UnitDatabase { this.map.units, this.map.prefixes); } } - + /** * The class used for unit name sets. * @@ -518,12 +518,12 @@ public final class UnitDatabase { private int unitNamePosition = 0; // the indices of the prefixes attached to the current unit private final List<Integer> prefixCoordinates = new ArrayList<>(); - + // values from the unit name set private final Map<String, Unit> map; private transient final List<String> unitNames; private transient final List<String> prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -536,7 +536,7 @@ public final class UnitDatabase { this.unitNames = new ArrayList<>(map.units.keySet()); this.prefixNames = new ArrayList<>(map.prefixes.keySet()); } - + /** * @return current unit name * @since 2019-04-14 @@ -548,10 +548,10 @@ public final class UnitDatabase { unitName.append(this.prefixNames.get(i)); } unitName.append(this.unitNames.get(this.unitNamePosition)); - + return unitName.toString(); } - + @Override public boolean hasNext() { if (this.unitNames.isEmpty()) @@ -564,7 +564,7 @@ public final class UnitDatabase { return true; } } - + /** * Changes this iterator's position to the next available one. * @@ -573,11 +573,11 @@ public final class UnitDatabase { */ private void incrementPosition() { this.unitNamePosition++; - + if (this.unitNamePosition >= this.unitNames.size()) { // we have used all of our units, go to a different prefix this.unitNamePosition = 0; - + // if the prefix coordinates are empty, then set it to [0] if (this.prefixCoordinates.isEmpty()) { this.prefixCoordinates.add(0, 0); @@ -586,7 +586,7 @@ public final class UnitDatabase { int i = this.prefixCoordinates.size() - 1; this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1); - + // fix any carrying errors while (i >= 0 && this.prefixCoordinates .get(i) >= this.prefixNames.size()) { @@ -594,7 +594,7 @@ public final class UnitDatabase { this.prefixCoordinates.set(i--, 0); // null and // decrement at the // same time - + if (i < 0) { // we need to add a new coordinate this.prefixCoordinates.add(0, 0); } else { // increment an existing one @@ -605,16 +605,16 @@ public final class UnitDatabase { } } } - + @Override public String next() { final String nextName = this.peek(); - + this.incrementPosition(); - + return nextName; } - + /** * @return the next element in the iterator, without iterating over * it @@ -633,10 +633,10 @@ public final class UnitDatabase { this.unitNames.remove(this.unitNamePosition); } } - + return this.getCurrentUnitName(); } - + /** * Returns a string representation of the object. The exact details * of the representation are unspecified and subject to change. @@ -650,10 +650,10 @@ public final class UnitDatabase { this.peek()); } } - + // the map that created this set private final PrefixedUnitMap map; - + /** * Creates the {@code PrefixedUnitNameSet}. * @@ -664,30 +664,30 @@ public final class UnitDatabase { public PrefixedUnitNameSet(final PrefixedUnitMap map) { this.map = map; } - + @Override public boolean add(final String e) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public boolean addAll(final Collection<? extends String> c) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable set"); } - + @Override public boolean contains(final Object o) { return this.map.containsKey(o); } - + @Override public boolean containsAll(final Collection<?> c) { for (final Object o : c) @@ -695,41 +695,41 @@ public final class UnitDatabase { return false; return true; } - + @Override public boolean isEmpty() { return this.map.isEmpty(); } - + @Override public Iterator<String> iterator() { return new PrefixedUnitNameIterator(this.map); } - + @Override public boolean remove(final Object o) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeIf(final Predicate<? super String> filter) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean retainAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public int size() { if (this.map.units.isEmpty()) @@ -742,7 +742,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -754,9 +754,9 @@ public final class UnitDatabase { // infinite set throw new IllegalStateException( "Cannot make an infinite set into an array."); - + } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -769,7 +769,7 @@ public final class UnitDatabase { throw new IllegalStateException( "Cannot make an infinite set into an array."); } - + @Override public String toString() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) @@ -780,7 +780,7 @@ public final class UnitDatabase { this.map.units, this.map.prefixes); } } - + /** * The units stored in this collection, without prefixes. * @@ -788,7 +788,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, Unit> units; - + /** * The available prefixes for use. * @@ -796,12 +796,12 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, UnitPrefix> prefixes; - + // caches private transient Collection<Unit> values = null; private transient Set<String> keySet = null; private transient Set<Entry<String, Unit>> entrySet = null; - + /** * Creates the {@code PrefixedUnitMap}. * @@ -817,50 +817,50 @@ public final class UnitDatabase { this.units = Collections.unmodifiableMap(units); this.prefixes = Collections.unmodifiableMap(prefixes); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable map"); } - + @Override public Unit compute(final String key, final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfAbsent(final String key, final Function<? super String, ? extends Unit> mappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfPresent(final String key, final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public boolean containsKey(final Object key) { // First, test if there is a unit with the key if (this.units.containsKey(key)) return true; - + // Next, try to cast it to String if (!(key instanceof String)) throw new IllegalArgumentException( "Attempted to test for a unit using a non-string name."); final String unitName = (String) key; - + // Then, look for the longest prefix that is attached to a valid unit String longestPrefix = null; int longestLength = 0; - + for (final String prefixName : this.prefixes.keySet()) { // a prefix name is valid if: // - it is prefixed (i.e. the unit name starts with it) @@ -879,10 +879,10 @@ public final class UnitDatabase { } } } - + return longestPrefix != null; } - + /** * {@inheritDoc} * @@ -895,7 +895,7 @@ public final class UnitDatabase { public boolean containsValue(final Object value) { return this.units.containsValue(value); } - + @Override public Set<Entry<String, Unit>> entrySet() { if (this.entrySet == null) { @@ -903,23 +903,23 @@ public final class UnitDatabase { } return this.entrySet; } - + @Override public Unit get(final Object key) { // First, test if there is a unit with the key if (this.units.containsKey(key)) return this.units.get(key); - + // Next, try to cast it to String if (!(key instanceof String)) throw new IllegalArgumentException( "Attempted to obtain a unit using a non-string name."); final String unitName = (String) key; - + // Then, look for the longest prefix that is attached to a valid unit String longestPrefix = null; int longestLength = 0; - + for (final String prefixName : this.prefixes.keySet()) { // a prefix name is valid if: // - it is prefixed (i.e. the unit name starts with it) @@ -938,7 +938,7 @@ public final class UnitDatabase { } } } - + // if none found, returns null if (longestPrefix == null) return null; @@ -949,16 +949,16 @@ public final class UnitDatabase { // before selecting this prefix final LinearUnit unit = (LinearUnit) this.get(rest); final UnitPrefix prefix = this.prefixes.get(longestPrefix); - + return unit.withPrefix(prefix); } } - + @Override public boolean isEmpty() { return this.units.isEmpty(); } - + @Override public Set<String> keySet() { if (this.keySet == null) { @@ -966,64 +966,64 @@ public final class UnitDatabase { } return this.keySet; } - + @Override public Unit merge(final String key, final Unit value, final BiFunction<? super Unit, ? super Unit, ? extends Unit> remappingFunction) { throw new UnsupportedOperationException( "Cannot merge into an immutable map"); } - + @Override public Unit put(final String key, final Unit value) { throw new UnsupportedOperationException( "Cannot add entries to an immutable map"); } - + @Override public void putAll(final Map<? extends String, ? extends Unit> m) { throw new UnsupportedOperationException( "Cannot add entries to an immutable map"); } - + @Override public Unit putIfAbsent(final String key, final Unit value) { throw new UnsupportedOperationException( "Cannot add entries to an immutable map"); } - + @Override public Unit remove(final Object key) { throw new UnsupportedOperationException( "Cannot remove entries from an immutable map"); } - + @Override public boolean remove(final Object key, final Object value) { throw new UnsupportedOperationException( "Cannot remove entries from an immutable map"); } - + @Override public Unit replace(final String key, final Unit value) { throw new UnsupportedOperationException( "Cannot replace entries in an immutable map"); } - + @Override public boolean replace(final String key, final Unit oldValue, final Unit newValue) { throw new UnsupportedOperationException( "Cannot replace entries in an immutable map"); } - + @Override public void replaceAll( final BiFunction<? super String, ? super Unit, ? extends Unit> function) { throw new UnsupportedOperationException( "Cannot replace entries in an immutable map"); } - + @Override public int size() { if (this.units.isEmpty()) @@ -1036,7 +1036,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + @Override public String toString() { if (this.units.isEmpty() || this.prefixes.isEmpty()) @@ -1046,7 +1046,7 @@ public final class UnitDatabase { "Infinite map of name-unit entries created from units %s and prefixes %s", this.units, this.prefixes); } - + /** * {@inheritDoc} * @@ -1064,20 +1064,20 @@ public final class UnitDatabase { return this.values; } } - + /** * Replacements done to *all* expression types */ private static final Map<Pattern, String> EXPRESSION_REPLACEMENTS = new HashMap<>(); - + // add data to expression replacements static { // add spaces around operators - for (final String operator : Arrays.asList("\\*", "/", "\\^")) { + for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) { EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator), " " + operator + " "); } - + // replace multiple spaces with a single space EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " "); // place brackets around any expression of the form "number unit", with or @@ -1092,20 +1092,20 @@ public final class UnitDatabase { // "1e3") ), "\\($1 $2\\)"); } - + /** * A regular expression that separates names and expressions in unit files. */ private static final Pattern NAME_EXPRESSION = Pattern .compile("(\\S+)\\s+(\\S.*)"); - + /** * Like normal string comparisons, but shorter strings are always less than * longer strings. */ private static final Comparator<String> lengthFirstComparator = Comparator .comparingInt(String::length).thenComparing(Comparator.naturalOrder()); - + /** * The exponent operator * @@ -1132,7 +1132,7 @@ public final class UnitDatabase { // not a number throw new IllegalArgumentException("Exponents must be numbers."); } - + /** * The exponent operator * @@ -1158,7 +1158,7 @@ public final class UnitDatabase { // not a number throw new IllegalArgumentException("Exponents must be numbers."); } - + /** * @return true if entry represents a removable duplicate entry of map. * @since 2021-05-22 @@ -1174,7 +1174,7 @@ public final class UnitDatabase { } return false; } - + /** * The units in this system, excluding prefixes. * @@ -1182,7 +1182,7 @@ public final class UnitDatabase { * @since v0.1.0 */ private final Map<String, Unit> prefixlessUnits; - + /** * The unit prefixes in this system. * @@ -1190,7 +1190,7 @@ public final class UnitDatabase { * @since v0.1.0 */ private final Map<String, UnitPrefix> prefixes; - + /** * The dimensions in this system. * @@ -1198,7 +1198,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, ObjectProduct<BaseDimension>> dimensions; - + /** * A map mapping strings to units (including prefixes) * @@ -1206,7 +1206,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, Unit> units; - + /** * The rule that specifies when prefix repetition is allowed. It takes in one * argument: a list of the prefixes being applied to the unit @@ -1218,7 +1218,7 @@ public final class UnitDatabase { * {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))} */ private Predicate<List<UnitPrefix>> prefixRepetitionRule; - + /** * A parser that can parse unit expressions. * @@ -1227,13 +1227,13 @@ public final class UnitDatabase { */ private final ExpressionParser<LinearUnit> unitExpressionParser = new ExpressionParser.Builder<>( this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2) - .build(); - + .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) + .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) + .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2).build(); + /** * A parser that can parse unit value expressions. * @@ -1241,14 +1241,15 @@ public final class UnitDatabase { */ private final ExpressionParser<LinearUnitValue> unitValueExpressionParser = new ExpressionParser.Builder<>( this::getLinearUnitValue) - .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2) - .build(); - + .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) + .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) + .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) + .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2) + .build(); + /** * A parser that can parse unit prefix expressions * @@ -1256,13 +1257,16 @@ public final class UnitDatabase { * @since v0.2.0 */ private final ExpressionParser<UnitPrefix> prefixExpressionParser = new ExpressionParser.Builder<>( - this::getPrefix).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) - .addBinaryOperator("^", - (o1, o2) -> o1.toExponent(o2.getMultiplier()), 1) - .build(); - + this::getPrefix).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) + .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) + .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) + .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), + 2) + .build(); + /** * A parser that can parse unit dimension expressions. * @@ -1271,9 +1275,14 @@ public final class UnitDatabase { */ private final ExpressionParser<ObjectProduct<BaseDimension>> unitDimensionParser = new ExpressionParser.Builder<>( this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build(); - + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2) + .addNumericOperator("^", (o1, o2) -> { + int exponent = (int) Math.round(o2.value()); + return o1.toExponent(exponent); + }, 1).build(); + /** * Creates the {@code UnitsDatabase}. * @@ -1283,7 +1292,7 @@ public final class UnitDatabase { public UnitDatabase() { this(prefixes -> true); } - + /** * Creates the {@code UnitsDatabase} * @@ -1301,7 +1310,7 @@ public final class UnitDatabase { entry -> this.prefixRepetitionRule .test(this.getPrefixesFromName(entry.getKey()))); } - + /** * Adds a unit dimension to the database. * @@ -1319,7 +1328,7 @@ public final class UnitDatabase { .withName(dimension.getNameSymbol().withExtraName(name)); this.dimensions.put(name, namedDimension); } - + /** * Adds to the list from a line in a unit dimension file. * @@ -1338,7 +1347,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1347,12 +1356,12 @@ public final class UnitDatabase { lineCounter)); final String name = lineMatcher.group(1); final String expression = lineMatcher.group(2); - + // if (name.endsWith(" ")) { // System.err.printf("Warning - line %d's dimension name ends in a space", // lineCounter); // } - + // if expression is "!", search for an existing dimension // if no unit found, throw an error if (expression.equals("!")) { @@ -1368,11 +1377,11 @@ public final class UnitDatabase { System.err.printf("Parsing error on line %d:%n", lineCounter); throw e; } - + this.addDimension(name, dimension); } } - + /** * Adds a unit prefix to the database. * @@ -1389,7 +1398,7 @@ public final class UnitDatabase { this.prefixes.put(Objects.requireNonNull(name, "name must not be null."), namedPrefix); } - + /** * Adds a unit to the database. * @@ -1406,7 +1415,7 @@ public final class UnitDatabase { this.prefixlessUnits.put( Objects.requireNonNull(name, "name must not be null."), namedUnit); } - + /** * Adds to the list from a line in a unit file. * @@ -1425,7 +1434,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1433,15 +1442,15 @@ public final class UnitDatabase { "Error at line %d: Lines of a unit file must consist of a unit name, then spaces or tabs, then a unit expression.", lineCounter)); final String name = lineMatcher.group(1); - + final String expression = lineMatcher.group(2); - + // this code should never occur // if (name.endsWith(" ")) { // System.err.printf("Warning - line %d's unit name ends in a space", // lineCounter); // } - + // if expression is "!", search for an existing unit // if no unit found, throw an error if (expression.equals("!")) { @@ -1474,7 +1483,7 @@ public final class UnitDatabase { } } } - + /** * Removes all units, prefixes and dimensions from this database. * @@ -1485,7 +1494,7 @@ public final class UnitDatabase { this.prefixes.clear(); this.prefixlessUnits.clear(); } - + /** * Tests if the database has a unit dimension with this name. * @@ -1497,7 +1506,7 @@ public final class UnitDatabase { public boolean containsDimensionName(final String name) { return this.dimensions.containsKey(name); } - + /** * Tests if the database has a unit prefix with this name. * @@ -1509,7 +1518,7 @@ public final class UnitDatabase { public boolean containsPrefixName(final String name) { return this.prefixes.containsKey(name); } - + /** * Tests if the database has a unit with this name, taking prefixes into * consideration @@ -1522,7 +1531,7 @@ public final class UnitDatabase { public boolean containsUnitName(final String name) { return this.units.containsKey(name); } - + /** * @return a map mapping dimension names to dimensions * @since 2019-04-13 @@ -1531,7 +1540,7 @@ public final class UnitDatabase { public Map<String, ObjectProduct<BaseDimension>> dimensionMap() { return Collections.unmodifiableMap(this.dimensions); } - + /** * Evaluates a unit expression, following the same rules as * {@link #getUnitFromExpression}. @@ -1542,46 +1551,42 @@ public final class UnitDatabase { */ public LinearUnitValue evaluateUnitExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a unit as an alias, or a number with precision first if (this.containsUnitName(expression)) return this.getLinearUnitValue(expression); - + // force operators to have spaces String modifiedExpression = expression; modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ "); modifiedExpression = modifiedExpression.replaceAll("-", " - "); - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + // the previous operation breaks negative numbers, fix them! // (i.e. -2 becomes - 2) // FIXME the previous operaton also breaks stuff like "1e-5" for (int i = 0; i < modifiedExpression.length(); i++) { if (modifiedExpression.charAt(i) == '-' - && (i < 2 || Arrays.asList('+', '-', '*', '/', '^') + && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^') .contains(modifiedExpression.charAt(i - 2)))) { // found a broken negative number modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2); } } - + return this.unitValueExpressionParser.parseExpression(modifiedExpression); } - + /** * Gets a unit dimension from the database using its name. * - * <p> - * This method accepts exponents, like "L^3" - * </p> - * * @param name dimension's name * @return dimension * @since 2019-03-14 @@ -1589,32 +1594,14 @@ public final class UnitDatabase { */ public ObjectProduct<BaseDimension> getDimension(final String name) { Objects.requireNonNull(name, "name must not be null."); - if (name.contains("^")) { - final String[] baseAndExponent = name.split("\\^"); - - final ObjectProduct<BaseDimension> base = this - .getDimension(baseAndExponent[0]); - - final int exponent; - try { - exponent = Integer - .parseInt(baseAndExponent[baseAndExponent.length - 1]); - } catch (final NumberFormatException e2) { - throw new IllegalArgumentException("Exponent must be an integer."); - } - - return base.toExponent(exponent); - } else { - final ObjectProduct<BaseDimension> dimension = this.dimensions - .get(name); - if (dimension == null) - throw new NoSuchElementException( - "No dimension with name \"" + name + "\"."); - else - return dimension; - } + final ObjectProduct<BaseDimension> dimension = this.dimensions.get(name); + if (dimension == null) + throw new NoSuchElementException( + "No dimension with name \"" + name + "\"."); + else + return dimension; } - + /** * Uses the database's data to parse an expression into a unit dimension * <p> @@ -1637,25 +1624,24 @@ public final class UnitDatabase { public ObjectProduct<BaseDimension> getDimensionFromExpression( final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a dimension as an alias first if (this.containsDimensionName(expression)) return this.getDimension(expression); - + // force operators to have spaces String modifiedExpression = expression; - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^"); - + return this.unitDimensionParser.parseExpression(modifiedExpression); } - + /** * Gets a unit. If it is linear, cast it to a LinearUnit and return it. * Otherwise, throw an {@code IllegalArgumentException}. @@ -1674,7 +1660,7 @@ public final class UnitDatabase { if (parts.size() != 2) throw new IllegalArgumentException( "Format nonlinear units like: unit(value)."); - + // solve the function final Unit unit = this.getUnit(parts.get(0)); final double value = Double.parseDouble( @@ -1683,7 +1669,7 @@ public final class UnitDatabase { } else { // get a linear unit final Unit unit = this.getUnit(name); - + if (unit instanceof LinearUnit) return (LinearUnit) unit; else @@ -1691,7 +1677,7 @@ public final class UnitDatabase { String.format("%s is not a linear unit.", name)); } } - + /** * Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be * converted to their base units. @@ -1709,7 +1695,7 @@ public final class UnitDatabase { return LinearUnitValue.getExact(this.getLinearUnit(name), 1); } } - + /** * Gets a unit prefix from the database from its name * @@ -1730,7 +1716,7 @@ public final class UnitDatabase { return prefix; } } - + /** * Gets all of the prefixes that are on a unit name, in application order. * @@ -1741,12 +1727,12 @@ public final class UnitDatabase { List<UnitPrefix> getPrefixesFromName(final String unitName) { final List<UnitPrefix> prefixes = new ArrayList<>(); String name = unitName; - + while (!this.prefixlessUnits.containsKey(name)) { // find the longest prefix String longestPrefixName = null; int longestLength = name.length(); - + while (longestPrefixName == null) { longestLength--; if (longestLength <= 0) @@ -1756,7 +1742,7 @@ public final class UnitDatabase { longestPrefixName = name.substring(0, longestLength); } } - + // longest prefix found! final UnitPrefix prefix = this.getPrefix(longestPrefixName); prefixes.add(0, prefix); @@ -1764,7 +1750,7 @@ public final class UnitDatabase { } return prefixes; } - + /** * Gets a unit prefix from a prefix expression * <p> @@ -1781,24 +1767,24 @@ public final class UnitDatabase { */ public UnitPrefix getPrefixFromExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a unit as an alias first if (this.containsUnitName(expression)) return this.getPrefix(expression); - + // force operators to have spaces String modifiedExpression = expression; - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + return this.prefixExpressionParser.parseExpression(modifiedExpression); } - + /** * @return the prefixRepetitionRule * @since 2020-08-26 @@ -1806,7 +1792,7 @@ public final class UnitDatabase { public final Predicate<List<UnitPrefix>> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * Gets a unit from the database from its name, looking for prefixes. * @@ -1839,9 +1825,9 @@ public final class UnitDatabase { } else return unit; } - + } - + /** * Uses the database's unit data to parse an expression into a unit * <p> @@ -1865,38 +1851,38 @@ public final class UnitDatabase { */ public Unit getUnitFromExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a unit as an alias first if (this.containsUnitName(expression)) return this.getUnit(expression); - + // force operators to have spaces String modifiedExpression = expression; modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ "); modifiedExpression = modifiedExpression.replaceAll("-", " - "); - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + // the previous operation breaks negative numbers, fix them! // (i.e. -2 becomes - 2) for (int i = 0; i < modifiedExpression.length(); i++) { if (modifiedExpression.charAt(i) == '-' - && (i < 2 || Arrays.asList('+', '-', '*', '/', '^') + && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^') .contains(modifiedExpression.charAt(i - 2)))) { // found a broken negative number modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2); } } - + return this.unitExpressionParser.parseExpression(modifiedExpression); } - + /** * Adds all dimensions from a file, using data from the database to parse * them. @@ -1936,7 +1922,7 @@ public final class UnitDatabase { throw new IllegalArgumentException("Could not read file " + file, e); } } - + /** * Adds all dimensions from a {@code InputStream}. Otherwise, works like * {@link #loadDimensionFile}. @@ -1952,7 +1938,7 @@ public final class UnitDatabase { } } } - + /** * Adds all units from a file, using data from the database to parse them. * <p> @@ -1991,7 +1977,7 @@ public final class UnitDatabase { throw new IllegalArgumentException("Could not read file " + file, e); } } - + /** * Adds all units from a {@code InputStream}. Otherwise, works like * {@link #loadUnitsFile}. @@ -2007,7 +1993,7 @@ public final class UnitDatabase { } } } - + /** * @param includeDuplicates if false, duplicates are removed from the map * @return a map mapping prefix names to prefixes @@ -2022,7 +2008,7 @@ public final class UnitDatabase { .conditionalExistenceMap(this.prefixes, entry -> !isRemovableDuplicate(this.prefixes, entry))); } - + /** * @param prefixRepetitionRule the prefixRepetitionRule to set * @since 2020-08-26 @@ -2031,7 +2017,7 @@ public final class UnitDatabase { Predicate<List<UnitPrefix>> prefixRepetitionRule) { this.prefixRepetitionRule = prefixRepetitionRule; } - + /** * @return a string stating the number of units, prefixes and dimensions in * the database @@ -2043,7 +2029,7 @@ public final class UnitDatabase { this.prefixlessUnits.size(), this.prefixes.size(), this.dimensions.size()); } - + /** * Returns a map mapping unit names to units, including units with prefixes. * <p> @@ -2075,7 +2061,7 @@ public final class UnitDatabase { return this.units; // PrefixedUnitMap is immutable so I don't need to make // an unmodifiable map. } - + /** * @param includeDuplicates if true, duplicate units will all exist in the * map; if false, only one of each unit will exist, diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index e1f7788..9035969 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -40,7 +40,7 @@ public final class UnitPrefix implements Nameable { public static UnitPrefix valueOf(final double multiplier) { return new UnitPrefix(multiplier, NameSymbol.EMPTY); } - + /** * Gets a {@code UnitPrefix} from a multiplier and a name * @@ -55,21 +55,21 @@ public final class UnitPrefix implements Nameable { return new UnitPrefix(multiplier, Objects.requireNonNull(ns, "ns must not be null.")); } - + /** * This prefix's name(s) and symbol. * * @since 2022-04-16 */ private final NameSymbol nameSymbol; - + /** * The number that this prefix multiplies units by * * @since 2019-10-16 */ private final double multiplier; - + /** * Creates the {@code DefaultUnitPrefix}. * @@ -81,7 +81,7 @@ public final class UnitPrefix implements Nameable { this.multiplier = multiplier; this.nameSymbol = ns; } - + /** * Divides this prefix by a scalar * @@ -92,7 +92,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix dividedBy(final double divisor) { return valueOf(this.getMultiplier() / divisor); } - + /** * Divides this prefix by {@code other}. * @@ -104,7 +104,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix dividedBy(final UnitPrefix other) { return valueOf(this.getMultiplier() / other.getMultiplier()); } - + /** * {@inheritDoc} * @@ -122,7 +122,7 @@ public final class UnitPrefix implements Nameable { return DecimalComparison.equals(this.getMultiplier(), other.getMultiplier()); } - + /** * @return prefix's multiplier * @since 2019-11-26 @@ -130,12 +130,12 @@ public final class UnitPrefix implements Nameable { public double getMultiplier() { return this.multiplier; } - + @Override public NameSymbol getNameSymbol() { return this.nameSymbol; } - + /** * {@inheritDoc} * @@ -145,7 +145,7 @@ public final class UnitPrefix implements Nameable { public int hashCode() { return DecimalComparison.hash(this.getMultiplier()); } - + /** * Multiplies this prefix by a scalar * @@ -156,7 +156,25 @@ public final class UnitPrefix implements Nameable { public UnitPrefix times(final double multiplicand) { return valueOf(this.getMultiplier() * multiplicand); } - + + /** + * Adds {@code other} to this prefix and returns the result. + * + * @since 2024-03-03 + */ + public UnitPrefix plus(final UnitPrefix other) { + return valueOf(this.getMultiplier() + other.getMultiplier()); + } + + /** + * Subtracts {@code other} from this prefix and returns the result. + * + * @since 2024-03-03 + */ + public UnitPrefix minus(final UnitPrefix other) { + return valueOf(this.getMultiplier() - other.getMultiplier()); + } + /** * Multiplies this prefix by {@code other}. * @@ -168,7 +186,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix times(final UnitPrefix other) { return valueOf(this.getMultiplier() * other.getMultiplier()); } - + /** * Raises this prefix to an exponent. * @@ -180,7 +198,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix toExponent(final double exponent) { return valueOf(Math.pow(this.getMultiplier(), exponent)); } - + /** * @return a string describing the prefix and its multiplier */ @@ -195,7 +213,7 @@ public final class UnitPrefix implements Nameable { else return String.format("Unit Prefix (\u00D7 %s)", this.multiplier); } - + /** * @param ns name(s) and symbol to use * @return copy of this prefix with provided name(s) and symbol diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java index 7cebf2d..9a87288 100644 --- a/src/main/java/sevenUnits/unit/UnitType.java +++ b/src/main/java/sevenUnits/unit/UnitType.java @@ -33,7 +33,7 @@ import java.util.function.Predicate; */ public enum UnitType { METRIC, SEMI_METRIC, NON_METRIC; - + /** * Determines which type a unit is. The type will be: * <ul> diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index 339263d..2d01831 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -42,10 +42,10 @@ public final class UnitValue { return new UnitValue( Objects.requireNonNull(unit, "unit must not be null"), value); } - + private final Unit unit; private final double value; - + /** * @param unit the unit being used * @param value the value being represented @@ -54,7 +54,7 @@ public final class UnitValue { this.unit = unit; this.value = value; } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -62,7 +62,7 @@ public final class UnitValue { public final boolean canConvertTo(Unit other) { return this.unit.canConvertTo(other); } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -70,7 +70,7 @@ public final class UnitValue { public final <W> boolean canConvertTo(Unitlike<W> other) { return this.unit.canConvertTo(other); } - + /** * Returns a UnitlikeValue that represents the same value expressed in a * different unitlike form. @@ -83,7 +83,7 @@ public final class UnitValue { return UnitlikeValue.of(other, this.unit.convertTo(other, this.getValue())); } - + /** * Returns a UnitValue that represents the same value expressed in a * different unit @@ -95,7 +95,7 @@ public final class UnitValue { return UnitValue.of(other, this.getUnit().convertTo(other, this.getValue())); } - + /** * Returns this unit value represented as a {@code LinearUnitValue} with this * unit's base unit as the base. @@ -108,7 +108,7 @@ public final class UnitValue { final LinearUnit base = LinearUnit.getBase(this.unit).withName(ns); return this.convertToLinear(base); } - + /** * @return a {@code LinearUnitValue} that is equivalent to this value. It * will have zero uncertainty. @@ -118,7 +118,7 @@ public final class UnitValue { return LinearUnitValue.getExact(other, this.getUnit().convertTo(other, this.getValue())); } - + /** * 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 @@ -135,7 +135,7 @@ public final class UnitValue { .doubleToLongBits( other.getUnit().convertToBase(other.getValue())); } - + /** * @return the unit * @since 2020-09-29 @@ -143,7 +143,7 @@ public final class UnitValue { public final Unit getUnit() { return this.unit; } - + /** * @return the value * @since 2020-09-29 @@ -151,13 +151,13 @@ public final class UnitValue { public final double getValue() { return this.value; } - + @Override public int hashCode() { return Objects.hash(this.getUnit().getBase(), this.getUnit().convertFromBase(this.getValue())); } - + @Override public String toString() { final Optional<String> primaryName = this.getUnit().getPrimaryName(); diff --git a/src/main/java/sevenUnits/unit/Unitlike.java b/src/main/java/sevenUnits/unit/Unitlike.java index 68de2c2..fef424e 100644 --- a/src/main/java/sevenUnits/unit/Unitlike.java +++ b/src/main/java/sevenUnits/unit/Unitlike.java @@ -55,7 +55,7 @@ public abstract class Unitlike<V> implements Nameable { return new FunctionalUnitlike<>(base, NameSymbol.EMPTY, converterFrom, converterTo); } - + /** * Returns a unitlike form from its base and the functions it uses to convert * to and from its base. @@ -78,28 +78,28 @@ public abstract class Unitlike<V> implements Nameable { final ToDoubleFunction<W> converterTo, final NameSymbol ns) { return new FunctionalUnitlike<>(base, ns, converterFrom, converterTo); } - + /** * The combination of units that this unit is based on. * * @since 2019-10-16 */ private final ObjectProduct<BaseUnit> unitBase; - + /** * This unit's name(s) and symbol * * @since 2020-09-07 */ private final NameSymbol nameSymbol; - + /** * Cache storing the result of getDimension() * * @since 2019-10-16 */ private transient ObjectProduct<BaseDimension> dimension = null; - + /** * @param unitBase * @since 2020-09-07 @@ -109,7 +109,7 @@ public abstract class Unitlike<V> implements Nameable { "unitBase may not be null"); this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); } - + /** * Checks if a value expressed in this unitlike form can be converted to a * value expressed in {@code other} @@ -124,7 +124,7 @@ public abstract class Unitlike<V> implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + /** * Checks if a value expressed in this unitlike form can be converted to a * value expressed in {@code other} @@ -139,9 +139,9 @@ public abstract class Unitlike<V> implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + protected abstract V convertFromBase(double value); - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -150,7 +150,7 @@ public abstract class Unitlike<V> implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -168,7 +168,7 @@ public abstract class Unitlike<V> implements Nameable { throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -196,9 +196,9 @@ public abstract class Unitlike<V> implements Nameable { throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } - + protected abstract double convertToBase(V value); - + /** * @return combination of units that this unit is based on * @since 2018-12-22 @@ -207,7 +207,7 @@ public abstract class Unitlike<V> implements Nameable { public final ObjectProduct<BaseUnit> getBase() { return this.unitBase; } - + /** * @return dimension measured by this unit * @since 2018-12-22 @@ -217,16 +217,16 @@ public abstract class Unitlike<V> implements Nameable { if (this.dimension == null) { final Map<BaseUnit, Integer> mapping = this.unitBase.exponentMap(); final Map<BaseDimension, Integer> dimensionMap = new HashMap<>(); - + for (final BaseUnit key : mapping.keySet()) { dimensionMap.put(key.getBaseDimension(), mapping.get(key)); } - + this.dimension = ObjectProduct.fromExponentMapping(dimensionMap); } return this.dimension; } - + /** * @return the nameSymbol * @since 2020-09-07 @@ -235,7 +235,7 @@ public abstract class Unitlike<V> implements Nameable { public final NameSymbol getNameSymbol() { return this.nameSymbol; } - + @Override public String toString() { return this.getPrimaryName().orElse("Unnamed unitlike form") @@ -247,7 +247,7 @@ public abstract class Unitlike<V> implements Nameable { + (this.getOtherNames().isEmpty() ? "" : ", also called " + String.join(", ", this.getOtherNames())); } - + /** * @param ns name(s) and symbol to use * @return a copy of this unitlike form with provided name(s) and symbol diff --git a/src/main/java/sevenUnits/unit/UnitlikeValue.java b/src/main/java/sevenUnits/unit/UnitlikeValue.java index 26354b1..ad0d1ea 100644 --- a/src/main/java/sevenUnits/unit/UnitlikeValue.java +++ b/src/main/java/sevenUnits/unit/UnitlikeValue.java @@ -34,10 +34,10 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { V value) { return new UnitlikeValue<>(unitlike, value); } - + private final T unitlike; private final V value; - + /** * @param unitlike * @param value @@ -47,7 +47,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { this.unitlike = unitlike; this.value = value; } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -55,7 +55,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final boolean canConvertTo(Unit other) { return this.unitlike.canConvertTo(other); } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -63,7 +63,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final <W> boolean canConvertTo(Unitlike<W> other) { return this.unitlike.canConvertTo(other); } - + /** * Returns a UnitlikeValue that represents the same value expressed in a * different unitlike form. @@ -76,7 +76,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return UnitlikeValue.of(other, this.unitlike.convertTo(other, this.getValue())); } - + /** * Returns a UnitValue that represents the same value expressed in a * different unit @@ -88,7 +88,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return UnitValue.of(other, this.unitlike.convertTo(other, this.getValue())); } - + /** * Returns this unit value represented as a {@code LinearUnitValue} with this * unit's base unit as the base. @@ -101,7 +101,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { final LinearUnit base = LinearUnit.getBase(this.unitlike).withName(ns); return this.convertToLinear(base); } - + /** * @return a {@code LinearUnitValue} that is equivalent to this value. It * will have zero uncertainty. @@ -111,7 +111,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return LinearUnitValue.getExact(other, this.getUnitlike().convertTo(other, this.getValue())); } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -131,7 +131,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return false; return true; } - + /** * @return the unitlike * @since 2020-09-29 @@ -139,7 +139,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final Unitlike<V> getUnitlike() { return this.unitlike; } - + /** * @return the value * @since 2020-09-29 @@ -147,7 +147,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final V getValue() { return this.value; } - + @Override public int hashCode() { final int prime = 31; @@ -158,7 +158,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { + (this.getValue() == null ? 0 : this.getValue().hashCode()); return result; } - + @Override public String toString() { final Optional<String> primaryName = this.getUnitlike().getPrimaryName(); diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index bee4dd1..b71a4e0 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -67,7 +67,7 @@ public final class ConditionalExistenceCollections { extends AbstractCollection<E> { final Collection<E> collection; final Predicate<E> existenceCondition; - + /** * Creates the {@code ConditionalExistenceCollection}. * @@ -80,38 +80,38 @@ public final class ConditionalExistenceCollections { this.collection = collection; this.existenceCondition = existenceCondition; } - + @Override public boolean add(final E e) { return this.collection.add(e) && this.existenceCondition.test(e); } - + @Override public void clear() { this.collection.clear(); } - + @Override public boolean contains(final Object o) { if (!this.collection.contains(o)) return false; - + // this collection can only contain instances of E // since the object is in the collection, we know that it must be an // instance of E // therefore this cast will always work @SuppressWarnings("unchecked") final E e = (E) o; - + return this.existenceCondition.test(e); } - + @Override public Iterator<E> iterator() { return conditionalExistenceIterator(this.collection.iterator(), this.existenceCondition); } - + @Override public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not @@ -119,32 +119,32 @@ public final class ConditionalExistenceCollections { final boolean containedObject = this.contains(o); return this.collection.remove(o) && containedObject; } - + @Override public int size() { return (int) this.collection.stream().filter(this.existenceCondition) .count(); } - + @Override public Object[] toArray() { // ensure the toArray operation is supported this.collection.toArray(); - + // if it works, do it for real return super.toArray(); } - + @Override public <T> T[] toArray(T[] a) { // ensure the toArray operation is supported this.collection.toArray(); - + // if it works, do it for real return super.toArray(a); } } - + /** * Elements in this wrapper iterator only exist if they pass a condition. * @@ -157,7 +157,7 @@ public final class ConditionalExistenceCollections { final Predicate<E> existenceCondition; E nextElement; boolean hasNext; - + /** * Creates the {@code ConditionalExistenceIterator}. * @@ -171,7 +171,7 @@ public final class ConditionalExistenceCollections { this.existenceCondition = condition; this.getAndSetNextElement(); } - + /** * Gets the next element, and sets nextElement and hasNext accordingly. * @@ -188,12 +188,12 @@ public final class ConditionalExistenceCollections { } while (!this.existenceCondition.test(this.nextElement)); this.hasNext = true; } - + @Override public boolean hasNext() { return this.hasNext; } - + @Override public E next() { if (this.hasNext()) { @@ -203,13 +203,13 @@ public final class ConditionalExistenceCollections { } else throw new NoSuchElementException(); } - + @Override public void remove() { this.iterator.remove(); } } - + /** * Mappings in this map only exist if the entry passes some condition. * @@ -221,7 +221,7 @@ public final class ConditionalExistenceCollections { static final class ConditionalExistenceMap<K, V> extends AbstractMap<K, V> { Map<K, V> map; Predicate<Entry<K, V>> entryExistenceCondition; - + /** * Creates the {@code ConditionalExistenceMap}. * @@ -234,81 +234,81 @@ public final class ConditionalExistenceCollections { this.map = map; this.entryExistenceCondition = entryExistenceCondition; } - + @Override public boolean containsKey(final Object key) { if (!this.map.containsKey(key)) return false; - + // only instances of K have mappings in the backing map // since we know that key is a valid key, it must be an instance of K @SuppressWarnings("unchecked") final K keyAsK = (K) key; - + // get and test entry final V value = this.map.get(key); final Entry<K, V> entry = new SimpleEntry<>(keyAsK, value); return this.entryExistenceCondition.test(entry); } - + @Override public Set<Entry<K, V>> entrySet() { return conditionalExistenceSet(this.map.entrySet(), this.entryExistenceCondition); } - + @Override public V get(final Object key) { return this.containsKey(key) ? this.map.get(key) : null; } - + private final Entry<K, V> getEntry(K key) { return new Entry<>() { @Override public K getKey() { return key; } - + @Override public V getValue() { return ConditionalExistenceMap.this.map.get(key); } - + @Override public V setValue(V value) { return ConditionalExistenceMap.this.map.put(key, value); } }; } - + @Override public Set<K> keySet() { return conditionalExistenceSet(this.map.keySet(), k -> this.entryExistenceCondition.test(this.getEntry(k))); } - + @Override public V put(final K key, final V value) { final V oldValue = this.map.put(key, value); - + // get and test entry final Entry<K, V> entry = new SimpleEntry<>(key, oldValue); return this.entryExistenceCondition.test(entry) ? oldValue : null; } - + @Override public V remove(final Object key) { final V oldValue = this.map.remove(key); return this.containsKey(key) ? oldValue : null; } - + @Override public Collection<V> values() { // maybe change this to use ConditionalExistenceCollection return super.values(); } } - + /** * Elements in this set only exist if a certain condition is true. * @@ -319,7 +319,7 @@ public final class ConditionalExistenceCollections { static final class ConditionalExistenceSet<E> extends AbstractSet<E> { private final Set<E> set; private final Predicate<E> existenceCondition; - + /** * Creates the {@code ConditionalNonexistenceSet}. * @@ -332,7 +332,7 @@ public final class ConditionalExistenceCollections { this.set = set; this.existenceCondition = existenceCondition; } - + /** * {@inheritDoc} * <p> @@ -343,33 +343,33 @@ public final class ConditionalExistenceCollections { public boolean add(final E e) { return this.set.add(e) && this.existenceCondition.test(e); } - + @Override public void clear() { this.set.clear(); } - + @Override public boolean contains(final Object o) { if (!this.set.contains(o)) return false; - + // this set can only contain instances of E // since the object is in the set, we know that it must be an instance // of E // therefore this cast will always work @SuppressWarnings("unchecked") final E e = (E) o; - + return this.existenceCondition.test(e); } - + @Override public Iterator<E> iterator() { return conditionalExistenceIterator(this.set.iterator(), this.existenceCondition); } - + @Override public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not @@ -377,31 +377,31 @@ public final class ConditionalExistenceCollections { final boolean containedObject = this.contains(o); return this.set.remove(o) && containedObject; } - + @Override public int size() { return (int) this.set.stream().filter(this.existenceCondition).count(); } - + @Override public Object[] toArray() { // ensure the toArray operation is supported this.set.toArray(); - + // if it works, do it for real return super.toArray(); } - + @Override public <T> T[] toArray(T[] a) { // ensure the toArray operation is supported this.set.toArray(); - + // if it works, do it for real return super.toArray(a); } } - + /** * Elements in the returned wrapper collection are ignored if they don't pass * a condition. @@ -418,7 +418,7 @@ public final class ConditionalExistenceCollections { return new ConditionalExistenceCollection<>(collection, existenceCondition); } - + /** * Elements in the returned wrapper iterator are ignored if they don't pass a * condition. @@ -433,7 +433,7 @@ public final class ConditionalExistenceCollections { final Iterator<E> iterator, final Predicate<E> existenceCondition) { return new ConditionalExistenceIterator<>(iterator, existenceCondition); } - + /** * Mappings in the returned wrapper map are ignored if the corresponding * entry doesn't pass a condition @@ -450,7 +450,7 @@ public final class ConditionalExistenceCollections { final Predicate<Entry<K, V>> entryExistenceCondition) { return new ConditionalExistenceMap<>(map, entryExistenceCondition); } - + /** * Elements in the returned wrapper set are ignored if they don't pass a * condition. diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index a5cbbaa..0515b6b 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -34,7 +34,7 @@ public final class DecimalComparison { * @since v0.2.0 */ public static final double DOUBLE_EPSILON = 1.0e-15; - + /** * The value used for float comparison. If two float values are within this * value multiplied by the larger value, they are considered equal. @@ -43,7 +43,7 @@ public final class DecimalComparison { * @since v0.2.0 */ public static final float FLOAT_EPSILON = 1.0e-6f; - + /** * Tests for equality of double values using {@link #DOUBLE_EPSILON}. * <p> @@ -74,7 +74,7 @@ public final class DecimalComparison { public static final boolean equals(final double a, final double b) { return DecimalComparison.equals(a, b, DOUBLE_EPSILON); } - + /** * Tests for double equality using a custom epsilon value. * @@ -106,7 +106,7 @@ public final class DecimalComparison { final double epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } - + /** * Tests for equality of float values using {@link #FLOAT_EPSILON}. * @@ -136,7 +136,7 @@ public final class DecimalComparison { public static final boolean equals(final float a, final float b) { return DecimalComparison.equals(a, b, FLOAT_EPSILON); } - + /** * Tests for float equality using a custom epsilon value. * @@ -168,7 +168,7 @@ public final class DecimalComparison { final float epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } - + /** * Tests for equality of {@code UncertainDouble} values using * {@link #DOUBLE_EPSILON}. @@ -201,7 +201,7 @@ public final class DecimalComparison { return DecimalComparison.equals(a.value(), b.value()) && DecimalComparison.equals(a.uncertainty(), b.uncertainty()); } - + /** * Tests for {@code UncertainDouble} equality using a custom epsilon value. * @@ -235,7 +235,7 @@ public final class DecimalComparison { && DecimalComparison.equals(a.uncertainty(), b.uncertainty(), epsilon); } - + /** * Takes the hash code of doubles. Values that are equal according to * {@link #equals(double, double)} will have the same hash code. @@ -247,10 +247,10 @@ public final class DecimalComparison { public static final int hash(final double d) { return Float.hashCode((float) d); } - + // You may NOT get any DecimalComparison instances private DecimalComparison() { throw new AssertionError(); } - + } diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 941c2a4..e248ff0 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2024 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.UnaryOperator; @@ -55,7 +56,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Function<String, ? extends T> objectObtainer; - + /** * The function of the space as an operator (like 3 x y) * @@ -63,7 +64,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private String spaceFunction = null; - + /** * A map mapping operator strings to operator functions, for unary * operators. @@ -72,7 +73,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityUnaryOperator<T>> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. @@ -81,7 +82,14 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityBinaryOperator<T>> binaryOperators; - + + /** + * A map mapping operator strings to numeric functions. + * + * @since 2024-03-23 + */ + private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; + /** * Creates the {@code Builder}. * @@ -96,8 +104,9 @@ public final class ExpressionParser<T> { "objectObtainer must not be null."); this.unaryOperators = new HashMap<>(); this.binaryOperators = new HashMap<>(); + this.numericOperators = new HashMap<>(); } - + /** * Adds a binary operator to the builder. * @@ -115,7 +124,7 @@ public final class ExpressionParser<T> { final BinaryOperator<T> operator, final int priority) { Objects.requireNonNull(text, "text must not be null."); Objects.requireNonNull(operator, "operator must not be null."); - + // Unfortunately, I cannot use a lambda because the // PriorityBinaryOperator requires arguments. final PriorityBinaryOperator<T> priorityOperator = new PriorityBinaryOperator<>( @@ -124,12 +133,42 @@ public final class ExpressionParser<T> { public T apply(final T t, final T u) { return operator.apply(t, u); } - + }; this.binaryOperators.put(text, priorityOperator); return this; } - + + /** + * Adds a two-argument operator where the second operator is a number. + * This is used for operations like vector scaling and exponentation. + * + * @param text text used to reference the operator, like '^' + * @param operator operator to add + * @param priority operator's priority, which determines which operators + * are applied first + * @return this builder + */ + public Builder<T> addNumericOperator(final String text, + final BiFunction<T, UncertainDouble, T> operator, + final int priority) { + Objects.requireNonNull(text, "text must not be null."); + Objects.requireNonNull(operator, "operator must not be null."); + + // Unfortunately, I cannot use a lambda because the + // PriorityBinaryOperator requires arguments. + final PriorityBiFunction<T, UncertainDouble, T> priorityOperator = new PriorityBiFunction<>( + priority) { + @Override + public T apply(final T t, final UncertainDouble u) { + return operator.apply(t, u); + } + + }; + this.numericOperators.put(text, priorityOperator); + return this; + } + /** * Adds a function for spaces. You must use the text of an existing binary * operator. @@ -141,15 +180,15 @@ public final class ExpressionParser<T> { */ public Builder<T> addSpaceFunction(final String operator) { Objects.requireNonNull(operator, "operator must not be null."); - + if (!this.binaryOperators.containsKey(operator)) throw new IllegalArgumentException(String .format("Could not find binary operator '%s'", operator)); - + this.spaceFunction = operator; return this; } - + /** * Adds a unary operator to the builder. * @@ -167,7 +206,7 @@ public final class ExpressionParser<T> { final UnaryOperator<T> operator, final int priority) { Objects.requireNonNull(text, "text must not be null."); Objects.requireNonNull(operator, "operator must not be null."); - + // Unfortunately, I cannot use a lambda because the // PriorityUnaryOperator requires arguments. final PriorityUnaryOperator<T> priorityOperator = new PriorityUnaryOperator<>( @@ -180,7 +219,7 @@ public final class ExpressionParser<T> { this.unaryOperators.put(text, priorityOperator); return this; } - + /** * @return an {@code ExpressionParser<T>} instance with the properties * given to this builder @@ -189,10 +228,10 @@ public final class ExpressionParser<T> { */ public ExpressionParser<T> build() { return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, - this.binaryOperators, this.spaceFunction); + this.binaryOperators, this.numericOperators, this.spaceFunction); } } - + /** * A binary operator with a priority field that determines which operators * apply first. @@ -212,7 +251,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final int priority; - + /** * Creates the {@code PriorityBinaryOperator}. * @@ -223,7 +262,7 @@ public final class ExpressionParser<T> { public PriorityBinaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. * @@ -243,7 +282,68 @@ public final class ExpressionParser<T> { else return 0; } - + + /** + * @return priority + * @since 2019-03-22 + * @since v0.2.0 + */ + public final int getPriority() { + return this.priority; + } + } + + /** + * A binary operator with a priority field that determines which operators + * apply first. + * + * @author Adrien Hopkins + * @param <T> type of operand and result + * @since 2019-03-17 + * @since v0.2.0 + */ + private static abstract class PriorityBiFunction<T, U, R> implements + BiFunction<T, U, R>, Comparable<PriorityBiFunction<T, U, R>> { + /** + * The operator's priority. Higher-priority operators are applied before + * lower-priority operators + * + * @since 2019-03-17 + * @since v0.2.0 + */ + private final int priority; + + /** + * Creates the {@code PriorityBinaryOperator}. + * + * @param priority operator's priority + * @since 2019-03-17 + * @since v0.2.0 + */ + public PriorityBiFunction(final int priority) { + this.priority = priority; + } + + /** + * Compares this object to another by priority. + * + * <p> + * {@inheritDoc} + * </p> + * + * @since 2019-03-17 + * @since v0.2.0 + */ + @Override + public int compareTo(final PriorityBiFunction<T, U, R> o) { + if (this.priority < o.priority) + return -1; + else if (this.priority > o.priority) + return 1; + else + return 0; + } + /** * @return priority * @since 2019-03-22 @@ -253,7 +353,7 @@ public final class ExpressionParser<T> { return this.priority; } } - + /** * A unary operator with a priority field that determines which operators * apply first. @@ -273,7 +373,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final int priority; - + /** * Creates the {@code PriorityUnaryOperator}. * @@ -284,7 +384,7 @@ public final class ExpressionParser<T> { public PriorityUnaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. * @@ -304,7 +404,7 @@ public final class ExpressionParser<T> { else return 0; } - + /** * @return priority * @since 2019-03-22 @@ -314,7 +414,7 @@ public final class ExpressionParser<T> { return this.priority; } } - + /** * The types of tokens that are available. * @@ -323,9 +423,9 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private static enum TokenType { - OBJECT, UNARY_OPERATOR, BINARY_OPERATOR; + OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR; } - + /** * The opening bracket. * @@ -333,7 +433,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ public static final char OPENING_BRACKET = '('; - + /** * The closing bracket. * @@ -341,7 +441,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ public static final char CLOSING_BRACKET = ')'; - + /** * Finds the other bracket in a pair of brackets, given the position of one. * @@ -355,9 +455,9 @@ public final class ExpressionParser<T> { private static int findBracketPair(final String string, final int bracketPosition) { Objects.requireNonNull(string, "string must not be null."); - + final char openingBracket = string.charAt(bracketPosition); - + // figure out what closing bracket to look for final char closingBracket; switch (openingBracket) { @@ -374,16 +474,16 @@ public final class ExpressionParser<T> { throw new IllegalArgumentException( String.format("Invalid bracket '%s'", openingBracket)); } - + // level of brackets. every opening bracket increments this; every closing // bracket decrements it int bracketLevel = 0; - + // iterate over the string to find the closing bracket for (int currentPosition = bracketPosition; currentPosition < string .length(); currentPosition++) { final char currentCharacter = string.charAt(currentPosition); - + if (currentCharacter == openingBracket) { bracketLevel++; } else if (currentCharacter == closingBracket) { @@ -392,10 +492,10 @@ public final class ExpressionParser<T> { return currentPosition; } } - + throw new IllegalArgumentException("No matching bracket found."); } - + /** * A function that obtains a parseable object from a string. For example, an * integer {@code ExpressionParser} would use {@code Integer::parseInt}. @@ -404,7 +504,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Function<String, ? extends T> objectObtainer; - + /** * A map mapping operator strings to operator functions, for unary operators. * @@ -412,7 +512,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityUnaryOperator<T>> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. @@ -421,7 +521,14 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityBinaryOperator<T>> binaryOperators; - + + /** + * A map mapping operator strings to numeric functions. + * + * @since 2024-03-23 + */ + private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; + /** * The operator for space, or null if spaces have no function. * @@ -429,27 +536,30 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final String spaceOperator; - + /** * Creates the {@code ExpressionParser}. * - * @param objectObtainer function to get objects from strings - * @param unaryOperators unary operators available to the parser - * @param binaryOperators binary operators available to the parser - * @param spaceOperator operator used by spaces + * @param objectObtainer function to get objects from strings + * @param unaryOperators unary operators available to the parser + * @param binaryOperators binary operators available to the parser + * @param numericOperators numeric operators available to the parser + * @param spaceOperator operator used by spaces * @since 2019-03-14 * @since v0.2.0 */ private ExpressionParser(final Function<String, ? extends T> objectObtainer, final Map<String, PriorityUnaryOperator<T>> unaryOperators, final Map<String, PriorityBinaryOperator<T>> binaryOperators, + final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators, final String spaceOperator) { this.objectObtainer = objectObtainer; this.unaryOperators = unaryOperators; this.binaryOperators = binaryOperators; + this.numericOperators = numericOperators; this.spaceOperator = spaceOperator; } - + /** * Converts a given mathematical expression to reverse Polish notation * (operators after operands). @@ -468,19 +578,19 @@ public final class ExpressionParser<T> { */ String convertExpressionToReversePolish(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final List<String> components = new ArrayList<>(); - + // the part of the expression remaining to parse String partialExpression = expression; - + // find and deal with brackets while (partialExpression.indexOf(OPENING_BRACKET) != -1) { final int openingBracketPosition = partialExpression .indexOf(OPENING_BRACKET); final int closingBracketPosition = findBracketPair(partialExpression, openingBracketPosition); - + // check for function if (openingBracketPosition > 0 && partialExpression.charAt(openingBracketPosition - 1) != ' ') { @@ -510,15 +620,15 @@ public final class ExpressionParser<T> { .substring(closingBracketPosition + 1); } } - + // add everything else components.addAll(Arrays.asList(partialExpression.split(" "))); - + // remove empty entries while (components.contains("")) { components.remove(""); } - + // deal with space multiplication (x y) if (this.spaceOperator != null) { for (int i = 0; i < components.size() - 1; i++) { @@ -528,7 +638,7 @@ public final class ExpressionParser<T> { } } } - + // turn the expression into reverse Polish while (true) { final int highestPriorityOperatorPosition = this @@ -536,7 +646,7 @@ public final class ExpressionParser<T> { if (highestPriorityOperatorPosition == -1) { break; } - + // swap components based on what kind of operator there is // 1 + 2 becomes 2 1 + // - 1 becomes 1 - @@ -554,6 +664,7 @@ public final class ExpressionParser<T> { operand + " " + unaryOperator); break; case BINARY_OPERATOR: + case NUMERIC_OPERATOR: if (components.size() < 3) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); @@ -570,11 +681,11 @@ public final class ExpressionParser<T> { throw new AssertionError("Expected operator, found non-operator."); } } - + // join all of the components together, then ensure there is only one // space in a row String expressionRPN = String.join(" ", components).replaceAll(" +", " "); - + while (expressionRPN.charAt(0) == ' ') { expressionRPN = expressionRPN.substring(1); } @@ -583,7 +694,7 @@ public final class ExpressionParser<T> { } return expressionRPN; } - + /** * Finds the position of the highest-priority operator in a list * @@ -601,18 +712,18 @@ public final class ExpressionParser<T> { // find highest priority int maxPriority = Integer.MIN_VALUE; int maxPriorityPosition = -1; - + // go over components one by one // if it is an operator, test its priority to see if it's max // if it is, update maxPriority and maxPriorityPosition for (int i = 0; i < components.size(); i++) { - + switch (this.getTokenType(components.get(i))) { case UNARY_OPERATOR: final PriorityUnaryOperator<T> unaryOperator = this.unaryOperators .get(components.get(i)); final int unaryPriority = unaryOperator.getPriority(); - + if (unaryPriority > maxPriority) { maxPriority = unaryPriority; maxPriorityPosition = i; @@ -622,21 +733,31 @@ public final class ExpressionParser<T> { final PriorityBinaryOperator<T> binaryOperator = this.binaryOperators .get(components.get(i)); final int binaryPriority = binaryOperator.getPriority(); - + if (binaryPriority > maxPriority) { maxPriority = binaryPriority; maxPriorityPosition = i; } break; + case NUMERIC_OPERATOR: + final PriorityBiFunction<T, UncertainDouble, T> numericOperator = this.numericOperators + .get(components.get(i)); + final int numericPriority = numericOperator.getPriority(); + + if (numericPriority > maxPriority) { + maxPriority = numericPriority; + maxPriorityPosition = i; + } + break; default: break; } } - + // max priority position found return maxPriorityPosition; } - + /** * Determines whether an inputted string is an object or an operator * @@ -648,15 +769,17 @@ public final class ExpressionParser<T> { */ private TokenType getTokenType(final String token) { Objects.requireNonNull(token, "token must not be null."); - + if (this.unaryOperators.containsKey(token)) return TokenType.UNARY_OPERATOR; else if (this.binaryOperators.containsKey(token)) return TokenType.BINARY_OPERATOR; + else if (this.numericOperators.containsKey(token)) + return TokenType.NUMERIC_OPERATOR; else return TokenType.OBJECT; } - + /** * Parses an expression. * @@ -670,7 +793,7 @@ public final class ExpressionParser<T> { return this.parseReversePolishExpression( this.convertExpressionToReversePolish(expression)); } - + /** * Parses an expression expressed in reverse Polish notation. * @@ -682,55 +805,86 @@ public final class ExpressionParser<T> { */ T parseReversePolishExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final Deque<T> stack = new ArrayDeque<>(); - + final Deque<UncertainDouble> doubleStack = new ArrayDeque<>(); + // iterate over every item in the expression, then for (final String item : expression.split(" ")) { // choose a path based on what kind of thing was just read switch (this.getTokenType(item)) { - + case BINARY_OPERATOR: if (stack.size() < 2) throw new IllegalStateException(String.format( "Attempted to call binary operator %s with only %d arguments.", item, stack.size())); - + // get two arguments and operator, then apply! final T o1 = stack.pop(); final T o2 = stack.pop(); final BinaryOperator<T> binaryOperator = this.binaryOperators .get(item); - + stack.push(binaryOperator.apply(o1, o2)); break; - + + case NUMERIC_OPERATOR: + if (stack.size() < 1 || doubleStack.size() < 1) + throw new IllegalStateException(String.format( + "Attempted to call binary operator %s with insufficient arguments.", + item)); + + final T ot = stack.pop(); + final UncertainDouble on = doubleStack.pop(); + final BiFunction<T, UncertainDouble, T> op = this.numericOperators + .get(item); + stack.push(op.apply(ot, on)); + break; + case OBJECT: // just add it to the stack - stack.push(this.objectObtainer.apply(item)); + // these try-catch statements are necessary + // to make the code as generalizable as possible + // also they're required for number formatting code because + // that's the only way to tell if an expression is a number or not. + try { + stack.push(this.objectObtainer.apply(item)); + } catch (Exception e) { + try { + doubleStack.push(UncertainDouble.fromString(item)); + } catch (IllegalArgumentException e2) { + try { + doubleStack.push( + UncertainDouble.of(Double.parseDouble(item), 0)); + } catch (NumberFormatException e3) { + throw e; + } + } + } break; - + case UNARY_OPERATOR: if (stack.size() < 1) throw new IllegalStateException(String.format( "Attempted to call unary operator %s with only %d arguments.", item, stack.size())); - + // get one argument and operator, then apply! final T o = stack.pop(); final UnaryOperator<T> unaryOperator = this.unaryOperators .get(item); - + stack.push(unaryOperator.apply(o)); break; default: throw new AssertionError( String.format("Internal error: Invalid token type %s.", this.getTokenType(item))); - + } } - + // return answer, or throw an exception if I can't if (stack.size() > 1) throw new IllegalStateException( diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 9388f63..49c44fa 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -33,7 +33,7 @@ import java.util.Set; public final class NameSymbol { public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), Optional.empty(), new HashSet<>()); - + /** * Creates a {@code NameSymbol}, ensuring that if primaryName is null and * otherNames is not empty, one name is moved from otherNames to primaryName @@ -43,7 +43,7 @@ public final class NameSymbol { private static final NameSymbol create(final String name, final String symbol, final Set<String> otherNames) { final Optional<String> primaryName; - + if (name == null && !otherNames.isEmpty()) { // get primary name and remove it from savedNames final Iterator<String> it = otherNames.iterator(); @@ -53,11 +53,11 @@ public final class NameSymbol { } else { primaryName = Optional.ofNullable(name); } - + return new NameSymbol(primaryName, Optional.ofNullable(symbol), otherNames); } - + /** * Gets a {@code NameSymbol} with a primary name, a symbol and no other * names. @@ -72,7 +72,7 @@ public final class NameSymbol { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>()); } - + /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -90,7 +90,7 @@ public final class NameSymbol { new HashSet<>(Objects.requireNonNull(otherNames, "otherNames must not be null."))); } - + /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -108,7 +108,7 @@ public final class NameSymbol { new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, "otherNames must not be null.")))); } - + /** * Gets a {@code NameSymbol} with a primary name, no symbol, and no other * names. @@ -122,7 +122,7 @@ public final class NameSymbol { return new NameSymbol(Optional.of(name), Optional.empty(), new HashSet<>()); } - + /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -145,7 +145,7 @@ public final class NameSymbol { return NameSymbol.create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); } - + /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -168,7 +168,7 @@ public final class NameSymbol { return create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(Arrays.asList(otherNames))); } - + /** * Gets a {@code NameSymbol} with a symbol and no names. * @@ -181,12 +181,12 @@ public final class NameSymbol { return new NameSymbol(Optional.empty(), Optional.of(symbol), new HashSet<>()); } - + private final Optional<String> primaryName; private final Optional<String> symbol; - + private final Set<String> otherNames; - + /** * Creates the {@code NameSymbol}. * @@ -202,12 +202,12 @@ public final class NameSymbol { this.symbol = symbol; otherNames.remove(null); this.otherNames = Collections.unmodifiableSet(otherNames); - + if (this.primaryName.isEmpty()) { assert this.otherNames.isEmpty(); } } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -232,7 +232,7 @@ public final class NameSymbol { return false; return true; } - + /** * @return otherNames * @since 2019-10-21 @@ -240,7 +240,7 @@ public final class NameSymbol { public final Set<String> getOtherNames() { return this.otherNames; } - + /** * @return primaryName * @since 2019-10-21 @@ -248,7 +248,7 @@ public final class NameSymbol { public final Optional<String> getPrimaryName() { return this.primaryName; } - + /** * @return symbol * @since 2019-10-21 @@ -256,7 +256,7 @@ public final class NameSymbol { public final Optional<String> getSymbol() { return this.symbol; } - + @Override public int hashCode() { final int prime = 31; @@ -269,7 +269,7 @@ public final class NameSymbol { + (this.symbol == null ? 0 : this.symbol.hashCode()); return result; } - + /** * @return true iff this {@code NameSymbol} contains no names or symbols. */ @@ -277,7 +277,7 @@ public final class NameSymbol { // if primaryName is empty, otherNames must also be empty return this.primaryName.isEmpty() && this.symbol.isEmpty(); } - + @Override public String toString() { if (this.isEmpty()) @@ -288,7 +288,7 @@ public final class NameSymbol { else return this.primaryName.orElseGet(this.symbol::orElseThrow); } - + /** * Creates and returns a copy of this {@code NameSymbol} with the provided * extra name. If this {@code NameSymbol} has a primary name, the provided diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index e469d04..3959a64 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -35,14 +35,14 @@ public interface Nameable { final NameSymbol ns = this.getNameSymbol(); return ns.getPrimaryName().or(ns::getSymbol).orElse("Unnamed"); } - + /** * @return a {@code NameSymbol} that contains this object's primary name, * symbol and other names * @since 2020-09-07 */ NameSymbol getNameSymbol(); - + /** * @return set of alternate names * @since 2020-09-07 @@ -50,7 +50,7 @@ public interface Nameable { default Set<String> getOtherNames() { return this.getNameSymbol().getOtherNames(); } - + /** * @return preferred name of object * @since 2020-09-07 @@ -58,7 +58,7 @@ public interface Nameable { default Optional<String> getPrimaryName() { return this.getNameSymbol().getPrimaryName(); } - + /** * @return a short name for the object - if there's a symbol, it's that, * otherwise the symbol, otherwise "Unnamed" @@ -68,7 +68,7 @@ public interface Nameable { final NameSymbol ns = this.getNameSymbol(); return ns.getSymbol().or(ns::getPrimaryName).orElse("Unnamed"); } - + /** * @return short symbol representing object * @since 2020-09-07 diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 66bb773..5a29d79 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -44,7 +44,7 @@ public class ObjectProduct<T> implements Nameable { public static final <T> ObjectProduct<T> empty() { return new ObjectProduct<>(new HashMap<>()); } - + /** * Gets an {@code ObjectProduct} from an object-to-integer mapping * @@ -57,7 +57,7 @@ public class ObjectProduct<T> implements Nameable { final Map<T, Integer> map) { return new ObjectProduct<>(new HashMap<>(map)); } - + /** * Gets an ObjectProduct that has one of the inputted argument, and nothing * else. @@ -73,7 +73,7 @@ public class ObjectProduct<T> implements Nameable { map.put(object, 1); return new ObjectProduct<>(map); } - + /** * The objects that make up the product, mapped to their exponents. This map * treats zero as null, and is immutable. @@ -81,12 +81,12 @@ public class ObjectProduct<T> implements Nameable { * @since 2019-10-16 */ final Map<T, Integer> exponents; - + /** * The object's name and symbol */ private final NameSymbol nameSymbol; - + /** * Creates a {@code ObjectProduct} without a name/symbol. * @@ -96,7 +96,7 @@ public class ObjectProduct<T> implements Nameable { ObjectProduct(final Map<T, Integer> exponents) { this(exponents, NameSymbol.EMPTY); } - + /** * Creates the {@code ObjectProduct}. * @@ -110,7 +110,7 @@ public class ObjectProduct<T> implements Nameable { e -> !Integer.valueOf(0).equals(e.getValue()))); this.nameSymbol = nameSymbol; } - + /** * Calculates the quotient of two products * @@ -125,17 +125,17 @@ public class ObjectProduct<T> implements Nameable { final Set<T> objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map<T, Integer> map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) - other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + // this method relies on the use of ZeroIsNullMap @Override public boolean equals(final Object obj) { @@ -146,7 +146,7 @@ public class ObjectProduct<T> implements Nameable { final ObjectProduct<?> other = (ObjectProduct<?>) obj; return Objects.equals(this.exponents, other.exponents); } - + /** * @return immutable map mapping objects to exponents * @since 2019-10-16 @@ -154,7 +154,7 @@ public class ObjectProduct<T> implements Nameable { public Map<T, Integer> exponentMap() { return this.exponents; } - + /** * @return a set of all of the base objects with non-zero exponents that make * up this dimension. @@ -163,7 +163,7 @@ public class ObjectProduct<T> implements Nameable { */ public final Set<T> getBaseSet() { final Set<T> dimensions = new HashSet<>(); - + // add all dimensions with a nonzero exponent - zero exponents shouldn't // be there in the first place for (final T dimension : this.exponents.keySet()) { @@ -171,10 +171,10 @@ public class ObjectProduct<T> implements Nameable { dimensions.add(dimension); } } - + return dimensions; } - + /** * Gets the exponent for a specific dimension. * @@ -186,17 +186,17 @@ public class ObjectProduct<T> implements Nameable { public int getExponent(final T dimension) { return this.exponents.getOrDefault(dimension, 0); } - + @Override public NameSymbol getNameSymbol() { return this.nameSymbol; } - + @Override public int hashCode() { return Objects.hash(this.exponents); } - + /** * @return true if this product is a single object, i.e. it has one exponent * of one and no other nonzero exponents @@ -214,7 +214,7 @@ public class ObjectProduct<T> implements Nameable { } return oneCount == 1 && !twoOrMore; } - + /** * Multiplies this product by another * @@ -229,17 +229,17 @@ public class ObjectProduct<T> implements Nameable { final Set<T> objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map<T, Integer> map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) + other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + /** * Returns this product, but to an exponent * @@ -254,7 +254,7 @@ public class ObjectProduct<T> implements Nameable { } return new ObjectProduct<>(map); } - + /** * Converts this product to a string using the objects' * {@link Object#toString()} method (or {@link Nameable#getShortName} if @@ -271,7 +271,7 @@ public class ObjectProduct<T> implements Nameable { .toString(o -> o instanceof Nameable ? ((Nameable) o).getShortName() : o.toString()); } - + /** * Converts this product to a string. The objects that make up this product * are represented by {@code objectToString} @@ -283,7 +283,7 @@ public class ObjectProduct<T> implements Nameable { public String toString(final Function<T, String> objectToString) { final List<String> positiveStringComponents = new ArrayList<>(); final List<String> negativeStringComponents = new ArrayList<>(); - + // for each base object that makes up this object, add it and its exponent for (final T object : this.getBaseSet()) { final int exponent = this.exponents.get(object); @@ -297,15 +297,15 @@ public class ObjectProduct<T> implements Nameable { objectToString.apply(object), -exponent)); } } - + final String positiveString = positiveStringComponents.isEmpty() ? "1" : String.join(" * ", positiveStringComponents); final String negativeString = negativeStringComponents.isEmpty() ? "" : " / " + String.join(" * ", negativeStringComponents); - + return positiveString + negativeString; } - + /** * @return named version of this {@code ObjectProduct}, using data from * {@code nameSymbol} diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index e80e16e..fc47baa 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -61,7 +61,7 @@ public final class SemanticVersionNumber private final int patch; private final List<String> preReleaseIdentifiers; private final List<String> buildMetadata; - + /** * Creates a builder which can be used to create a * {@code SemanticVersionNumber} @@ -79,7 +79,7 @@ public final class SemanticVersionNumber this.preReleaseIdentifiers = new ArrayList<>(); this.buildMetadata = new ArrayList<>(); } - + /** * @return version number created by this builder * @since v0.4.0 @@ -89,7 +89,7 @@ public final class SemanticVersionNumber return new SemanticVersionNumber(this.major, this.minor, this.patch, this.preReleaseIdentifiers, this.buildMetadata); } - + /** * Adds one or more build metadata identifiers * @@ -109,7 +109,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds one or more build metadata identifiers * @@ -129,7 +129,7 @@ public final class SemanticVersionNumber } return this; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -142,13 +142,13 @@ public final class SemanticVersionNumber && this.patch == other.patch && Objects.equals( this.preReleaseIdentifiers, other.preReleaseIdentifiers); } - + @Override public int hashCode() { return Objects.hash(this.buildMetadata, this.major, this.minor, this.patch, this.preReleaseIdentifiers); } - + /** * Adds one or more numeric identifiers to the version number * @@ -167,7 +167,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds one or more pre-release identifier(s) to the version number * @@ -187,7 +187,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds one or more pre-release identifier(s) to the version number * @@ -207,7 +207,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds a string identifier and an integer identifer to pre-release data * @@ -229,13 +229,13 @@ public final class SemanticVersionNumber this.preReleaseIdentifiers.add(Integer.toString(identifier2)); return this; } - + @Override public String toString() { return "Semantic Version Builder: " + this.build().toString(); } } - + /** * An alternative comparison method for version numbers. This uses the * version's natural order, but the build metadata will be compared (using @@ -257,21 +257,21 @@ public final class SemanticVersionNumber return naturalComparison; }; }; - + /** The alphanumeric pattern all identifiers must follow */ private static final Pattern VALID_IDENTIFIER = Pattern .compile("[0-9A-Za-z-]+"); - + /** The numeric pattern which causes special behaviour */ private static final Pattern NUMERIC_IDENTIFER = Pattern.compile("[0-9]+"); - + /** The pattern for a version number */ private static final Pattern VERSION_NUMBER = Pattern .compile("(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)" // main // version + "(?:-([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?" // pre-release + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"); // build data - + /** * Creates a builder that can be used to create a version number * @@ -296,7 +296,7 @@ public final class SemanticVersionNumber "Patch version must be non-negative."); return new SemanticVersionNumber.Builder(major, minor, patch); } - + /** * Compares two lists of strings based on SemVer's precedence rules * @@ -311,24 +311,24 @@ public final class SemanticVersionNumber // test pre-release size final int aSize = a.size(); final int bSize = b.size(); - + // no identifiers is greater than any identifiers if (aSize != 0 && bSize == 0) return -1; else if (aSize == 0 && bSize != 0) return 1; - + // test identifiers one by one for (int i = 0; i < Math.min(aSize, bSize); i++) { final String aElement = a.get(i); final String bElement = b.get(i); - + if (NUMERIC_IDENTIFER.matcher(aElement).matches()) { if (NUMERIC_IDENTIFER.matcher(bElement).matches()) { // both are numbers, compare them final int aNumber = Integer.parseInt(aElement); final int bNumber = Integer.parseInt(bElement); - + if (aNumber < bNumber) return -1; else if (aNumber > bNumber) @@ -350,7 +350,7 @@ public final class SemanticVersionNumber } } } - + // we just tested the stuff that's in common, maybe someone has more if (aSize < bSize) return -1; @@ -359,7 +359,7 @@ public final class SemanticVersionNumber else return 0; } - + /** * Gets a version number from a string in the official format * @@ -377,12 +377,12 @@ public final class SemanticVersionNumber throw new IllegalArgumentException( String.format("Provided string \"%s\" is not a version number", versionString)); - + // main parts final int major = Integer.parseInt(m.group(1)); final int minor = Integer.parseInt(m.group(2)); final int patch = Integer.parseInt(m.group(3)); - + // pre release final List<String> preRelease; if (m.group(4) == null) { @@ -390,7 +390,7 @@ public final class SemanticVersionNumber } else { preRelease = Arrays.asList(m.group(4).split("\\.")); } - + // build metadata final List<String> buildMetadata; if (m.group(5) == null) { @@ -398,12 +398,12 @@ public final class SemanticVersionNumber } else { buildMetadata = Arrays.asList(m.group(5).split("\\.")); } - + // return number return new SemanticVersionNumber(major, minor, patch, preRelease, buildMetadata); } - + /** * Tests whether a string is a valid Semantic Version string * @@ -415,7 +415,7 @@ public final class SemanticVersionNumber public static final boolean isValidVersionString(String versionString) { return VERSION_NUMBER.matcher(versionString).matches(); } - + /** * Creates a simple pre-release version number of the form * MAJOR.MINOR.PATH-TYPE.NUMBER (e.g. 1.2.3-alpha.4). @@ -454,7 +454,7 @@ public final class SemanticVersionNumber List.of(preReleaseType, Integer.toString(preReleaseNumber)), List.of()); } - + /** * Creates a {@code SemanticVersionNumber} instance without pre-release * identifiers or build metadata. @@ -484,14 +484,14 @@ public final class SemanticVersionNumber return new SemanticVersionNumber(major, minor, patch, List.of(), List.of()); } - + // parts of the version number private final int major; private final int minor; private final int patch; private final List<String> preReleaseIdentifiers; private final List<String> buildMetadata; - + /** * Creates a version number * @@ -511,7 +511,7 @@ public final class SemanticVersionNumber this.preReleaseIdentifiers = preReleaseIdentifiers; this.buildMetadata = buildMetadata; } - + /** * @return build metadata (empty if there is none) * @since v0.4.0 @@ -520,7 +520,7 @@ public final class SemanticVersionNumber public List<String> buildMetadata() { return Collections.unmodifiableList(this.buildMetadata); } - + /** * Compares two version numbers according to the official Semantic Versioning * order. @@ -538,23 +538,23 @@ public final class SemanticVersionNumber return -1; else if (this.major > o.major) return 1; - + if (this.minor < o.minor) return -1; else if (this.minor > o.minor) return 1; - + if (this.patch < o.patch) return -1; else if (this.patch > o.patch) return 1; - + // now we just compare pre-release identifiers // (remember: build metadata is ignored) return SemanticVersionNumber.compareIdentifiers( this.preReleaseIdentifiers, o.preReleaseIdentifiers); } - + /** * Determines the compatibility of code written for this version to * {@code other}. More specifically: @@ -590,11 +590,11 @@ public final class SemanticVersionNumber */ public boolean compatibleWith(SemanticVersionNumber other) { Objects.requireNonNull(other, "other may not be null"); - + return this.compareTo(other) == 0 || this.major != 0 && this.major == other.major && this.compareTo(other) < 0; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -621,7 +621,7 @@ public final class SemanticVersionNumber return false; return true; } - + @Override public int hashCode() { final int prime = 31; @@ -635,7 +635,7 @@ public final class SemanticVersionNumber : this.preReleaseIdentifiers.hashCode()); return result; } - + /** * @return true iff this version is stable (major version > 0 and not a * pre-release) @@ -645,7 +645,7 @@ public final class SemanticVersionNumber public boolean isStable() { return this.major > 0 && this.preReleaseIdentifiers.isEmpty(); } - + /** * @return the MAJOR version number, incremented when you make backwards * incompatible API changes @@ -655,7 +655,7 @@ public final class SemanticVersionNumber public int majorVersion() { return this.major; } - + /** * @return the MINOR version number, incremented when you add backwards * compatible functionality @@ -665,7 +665,7 @@ public final class SemanticVersionNumber public int minorVersion() { return this.minor; } - + /** * @return the PATCH version number, incremented when you make backwards * compatible bug fixes @@ -675,7 +675,7 @@ public final class SemanticVersionNumber public int patchVersion() { return this.patch; } - + /** * @return identifiers describing this pre-release (empty if not a * pre-release) @@ -685,7 +685,7 @@ public final class SemanticVersionNumber public List<String> preReleaseIdentifiers() { return Collections.unmodifiableList(this.preReleaseIdentifiers); } - + /** * Converts a version number to a string using the official SemVer format. * The core of a version is MAJOR.MINOR.PATCH, without zero-padding. If diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index ac523b3..66d8103 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -23,7 +23,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * A double with an associated uncertainty value. For example, 3.2 ± 0.2. + * A double with an associated uncertainty value. For example, 3.2 � 0.2. * <p> * All methods in this class throw a NullPointerException if any of their * arguments is null. @@ -35,20 +35,20 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * The exact value 0 */ public static final UncertainDouble ZERO = UncertainDouble.of(0, 0); - + static final String NUMBER_REGEX = "(\\d+(?:[\\.,]\\d+))"; - + /** * A regular expression that can recognize toString forms */ static final Pattern TO_STRING = Pattern.compile(NUMBER_REGEX - // optional "± [number]" - + "(?:\\s*(?:±|\\+-)\\s*" + NUMBER_REGEX + ")?"); - + // optional "� [number]" + + "(?:\\s*(?:�|\\+-)\\s*" + NUMBER_REGEX + ")?"); + /** * Gets an UncertainDouble from a double string. The uncertainty of the * double will be one of the lowest decimal place of the number. For example, - * "12345.678" will become 12345.678 ± 0.001. + * "12345.678" will become 12345.678 � 0.001. * * @throws NumberFormatException if the argument is not a number * @@ -59,13 +59,13 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { final double uncertainty = Math.pow(10, -value.scale()); return UncertainDouble.of(value.doubleValue(), uncertainty); } - + /** * Parses a string in the form of {@link UncertainDouble#toString(boolean)} * and returns the corresponding {@code UncertainDouble} instance. * <p> * This method allows some alternative forms of the string representation, - * such as using "+-" instead of "±". + * such as using "+-" instead of "�". * * @param s string to parse * @return {@code UncertainDouble} instance @@ -75,11 +75,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public static final UncertainDouble fromString(String s) { Objects.requireNonNull(s, "s may not be null"); final Matcher matcher = TO_STRING.matcher(s); - + if (!matcher.matches()) throw new IllegalArgumentException( "Could not parse stirng \"" + s + "\"."); - + double value, uncertainty; try { value = Double.parseDouble(matcher.group(1)); @@ -87,7 +87,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { throw new IllegalArgumentException( "String " + s + " not in correct format."); } - + final String uncertaintyString = matcher.group(2); if (uncertaintyString == null) { uncertainty = 0; @@ -99,10 +99,10 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { "String " + s + " not in correct format."); } } - + return UncertainDouble.of(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and <b>absolute</b> * uncertainty. @@ -112,7 +112,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public static final UncertainDouble of(double value, double uncertainty) { return new UncertainDouble(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and <b>relative</b> * uncertainty. @@ -123,11 +123,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { double relativeUncertainty) { return new UncertainDouble(value, value * relativeUncertainty); } - + private final double value; - + private final double uncertainty; - + /** * @param value * @param uncertainty @@ -138,13 +138,13 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { // uncertainty should only ever be positive this.uncertainty = Math.abs(uncertainty); } - + /** * Compares this {@code UncertainDouble} with another * {@code UncertainDouble}. * <p> - * This method only compares the values, not the uncertainties. So 3.1 ± 0.5 - * is considered less than 3.2 ± 0.5, even though they are equivalent. + * This method only compares the values, not the uncertainties. So 3.1 � 0.5 + * is considered less than 3.2 � 0.5, even though they are equivalent. * <p> * <b>Note:</b> The natural ordering of this class is inconsistent with * equals. Specifically, if two {@code UncertainDouble} instances {@code a} @@ -156,7 +156,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final int compareTo(UncertainDouble o) { return Double.compare(this.value, o.value); } - + /** * Returns the quotient of {@code this} and {@code other}. * @@ -167,7 +167,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.ofRelative(this.value / other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); } - + /** * Returns the quotient of {@code this} and the exact value {@code other}. * @@ -176,7 +176,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble dividedByExact(double other) { return UncertainDouble.of(this.value / other, this.uncertainty / other); } - + @Override public final boolean equals(Object obj) { if (this == obj) @@ -190,7 +190,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return false; return true; } - + /** * @param other another {@code UncertainDouble} * @return true iff this and {@code other} are within each other's @@ -202,7 +202,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return Math.abs(this.value - other.value) <= Math.min(this.uncertainty, other.uncertainty); } - + /** * Gets the preferred scale for rounding a value for toString. * @@ -217,19 +217,19 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { // the value is rounded to the same number of decimal places as the // uncertainty. final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); - + // the scale that will give the uncertainty two decimal places final int twoDecimalPlacesScale = bigUncertainty.scale() - bigUncertainty.precision() + 2; final BigDecimal roundedUncertainty = bigUncertainty .setScale(twoDecimalPlacesScale, RoundingMode.HALF_EVEN); - + if (roundedUncertainty.unscaledValue().intValue() >= 20) return twoDecimalPlacesScale - 1; // one decimal place else return twoDecimalPlacesScale; } - + @Override public final int hashCode() { final int prime = 31; @@ -238,7 +238,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { result = prime * result + Double.hashCode(this.uncertainty); return result; } - + /** * @return true iff the value has no uncertainty * @@ -247,7 +247,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final boolean isExact() { return this.uncertainty == 0; } - + /** * Returns the difference of {@code this} and {@code other}. * @@ -258,7 +258,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.of(this.value - other.value, Math.hypot(this.uncertainty, other.uncertainty)); } - + /** * Returns the difference of {@code this} and the exact value {@code other}. * @@ -267,7 +267,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble minusExact(double other) { return UncertainDouble.of(this.value - other, this.uncertainty); } - + /** * Returns the sum of {@code this} and {@code other}. * @@ -278,7 +278,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.of(this.value + other.value, Math.hypot(this.uncertainty, other.uncertainty)); } - + /** * Returns the sum of {@code this} and the exact value {@code other}. * @@ -287,7 +287,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble plusExact(double other) { return UncertainDouble.of(this.value + other, this.uncertainty); } - + /** * @return relative uncertainty * @since 2020-09-07 @@ -295,7 +295,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final double relativeUncertainty() { return this.uncertainty / this.value; } - + /** * Returns the product of {@code this} and {@code other}. * @@ -306,7 +306,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.ofRelative(this.value * other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); } - + /** * Returns the product of {@code this} and the exact value {@code other}. * @@ -315,7 +315,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble timesExact(double other) { return UncertainDouble.of(this.value * other, this.uncertainty * other); } - + /** * Returns the result of {@code this} raised to the exponent {@code other}. * @@ -323,15 +323,15 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { */ public final UncertainDouble toExponent(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); - + final double result = Math.pow(this.value, other.value); final double relativeUncertainty = Math.hypot( other.value * this.relativeUncertainty(), Math.log(this.value) * other.uncertainty); - + return UncertainDouble.ofRelative(result, relativeUncertainty); } - + /** * Returns the result of {@code this} raised the exact exponent * {@code other}. @@ -342,7 +342,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.ofRelative(Math.pow(this.value, other), this.relativeUncertainty() * other); } - + /** * Returns a string representation of this {@code UncertainDouble}. * <p> @@ -354,8 +354,8 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * Examples: * * <pre> - * UncertainDouble.of(3.27, 0.22).toString() = "3.3 ± 0.2" - * UncertainDouble.of(3.27, 0.13).toString() = "3.27 ± 0.13" + * UncertainDouble.of(3.27, 0.22).toString() = "3.3 � 0.2" + * UncertainDouble.of(3.27, 0.13).toString() = "3.27 � 0.13" * UncertainDouble.of(-5.01, 0).toString() = "-5.01" * </pre> * @@ -365,12 +365,12 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final String toString() { return this.toString(!this.isExact(), RoundingMode.HALF_EVEN); } - + /** * Returns a string representation of this {@code UncertainDouble}. * <p> * If {@code showUncertainty} is true, the string will be of the form "VALUE - * ± UNCERTAINTY", and if it is false the string will be of the form "VALUE" + * � UNCERTAINTY", and if it is false the string will be of the form "VALUE" * <p> * VALUE represents a string representation of this {@code UncertainDouble}'s * value. If the uncertainty is non-zero, the string will be rounded to the @@ -385,11 +385,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * * <pre> * UncertainDouble.of(3.27, 0.22).toString(false) = "3.3" - * UncertainDouble.of(3.27, 0.22).toString(true) = "3.3 ± 0.2" + * UncertainDouble.of(3.27, 0.22).toString(true) = "3.3 � 0.2" * UncertainDouble.of(3.27, 0.13).toString(false) = "3.27" - * UncertainDouble.of(3.27, 0.13).toString(true) = "3.27 ± 0.13" + * UncertainDouble.of(3.27, 0.13).toString(true) = "3.27 � 0.13" * UncertainDouble.of(-5.01, 0).toString(false) = "-5.01" - * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 ± 0.0" + * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 � 0.0" * </pre> * * @since 2020-09-07 @@ -397,31 +397,31 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final String toString(boolean showUncertainty, RoundingMode roundingMode) { String valueString, uncertaintyString; - + // generate the string representation of value and uncertainty if (this.isExact()) { uncertaintyString = "0.0"; valueString = Double.toString(this.value); - + } else { // round the value and uncertainty according to getDisplayScale() final BigDecimal bigValue = BigDecimal.valueOf(this.value); final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); - + final int displayScale = this.getDisplayScale(); final BigDecimal roundedUncertainty = bigUncertainty .setScale(displayScale, roundingMode); final BigDecimal roundedValue = bigValue.setScale(displayScale, roundingMode); - + valueString = roundedValue.toString(); uncertaintyString = roundedUncertainty.toString(); } - - // return "value" or "value ± uncertainty" depending on showUncertainty - return valueString + (showUncertainty ? " ± " + uncertaintyString : ""); + + // return "value" or "value � uncertainty" depending on showUncertainty + return valueString + (showUncertainty ? " � " + uncertaintyString : ""); } - + /** * @return absolute uncertainty * @since 2020-09-07 @@ -429,7 +429,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final double uncertainty() { return this.uncertainty; } - + /** * @return value without uncertainty * @since 2020-09-07 |