diff options
Diffstat (limited to 'src/main/java/sevenUnits/utils')
-rw-r--r-- | src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java | 80 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/DecimalComparison.java | 55 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/ExpressionParser.java | 217 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/NameSymbol.java | 102 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/Nameable.java | 13 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/ObjectProduct.java | 88 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/SemanticVersionNumber.java | 168 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/UncertainDouble.java | 251 | ||||
-rw-r--r-- | src/main/java/sevenUnits/utils/package-info.java | 4 |
9 files changed, 548 insertions, 430 deletions
diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index b71a4e0..6244ee6 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 @@ -49,18 +49,19 @@ import java.util.function.Predicate; * Other than that, <i>the only difference between the provided collections and * the returned collections are that elements don't exist if they don't pass the * provided condition</i>. - * - * + * + * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 */ -// TODO add conditional existence Lists and Sorted/Navigable Sets/Maps public final class ConditionalExistenceCollections { /** * Elements in this collection only exist if they meet a condition. - * + * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param <E> type of element in collection */ static final class ConditionalExistenceCollection<E> @@ -70,10 +71,11 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalExistenceCollection}. - * + * * @param collection * @param existenceCondition * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceCollection(final Collection<E> collection, final Predicate<E> existenceCondition) { @@ -101,7 +103,7 @@ public final class ConditionalExistenceCollections { // instance of E // therefore this cast will always work @SuppressWarnings("unchecked") - final E e = (E) o; + final var e = (E) o; return this.existenceCondition.test(e); } @@ -116,7 +118,7 @@ public final class ConditionalExistenceCollections { public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not // execute - final boolean containedObject = this.contains(o); + final var containedObject = this.contains(o); return this.collection.remove(o) && containedObject; } @@ -147,9 +149,10 @@ public final class ConditionalExistenceCollections { /** * Elements in this wrapper iterator only exist if they pass a condition. - * + * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param <E> type of elements in iterator */ static final class ConditionalExistenceIterator<E> implements Iterator<E> { @@ -160,10 +163,11 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalExistenceIterator}. - * + * * @param iterator * @param condition * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceIterator(final Iterator<E> iterator, final Predicate<E> condition) { @@ -174,8 +178,9 @@ public final class ConditionalExistenceCollections { /** * Gets the next element, and sets nextElement and hasNext accordingly. - * + * * @since 2019-10-17 + * @since v0.3.0 */ private void getAndSetNextElement() { do { @@ -197,11 +202,11 @@ public final class ConditionalExistenceCollections { @Override public E next() { if (this.hasNext()) { - final E next = this.nextElement; + final var next = this.nextElement; this.getAndSetNextElement(); return next; - } else - throw new NoSuchElementException(); + } + throw new NoSuchElementException(); } @Override @@ -212,9 +217,10 @@ public final class ConditionalExistenceCollections { /** * Mappings in this map only exist if the entry passes some condition. - * + * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param <K> key type * @param <V> value type */ @@ -224,10 +230,11 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalExistenceMap}. - * + * * @param map * @param entryExistenceCondition * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceMap(final Map<K, V> map, final Predicate<Entry<K, V>> entryExistenceCondition) { @@ -243,10 +250,10 @@ public final class ConditionalExistenceCollections { // 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; + final var keyAsK = (K) key; // get and test entry - final V value = this.map.get(key); + final var value = this.map.get(key); final Entry<K, V> entry = new SimpleEntry<>(keyAsK, value); return this.entryExistenceCondition.test(entry); } @@ -262,7 +269,7 @@ public final class ConditionalExistenceCollections { return this.containsKey(key) ? this.map.get(key) : null; } - private final Entry<K, V> getEntry(K key) { + private Entry<K, V> getEntry(K key) { return new Entry<>() { @Override public K getKey() { @@ -289,7 +296,7 @@ public final class ConditionalExistenceCollections { @Override public V put(final K key, final V value) { - final V oldValue = this.map.put(key, value); + final var oldValue = this.map.put(key, value); // get and test entry final Entry<K, V> entry = new SimpleEntry<>(key, oldValue); @@ -298,7 +305,7 @@ public final class ConditionalExistenceCollections { @Override public V remove(final Object key) { - final V oldValue = this.map.remove(key); + final var oldValue = this.map.remove(key); return this.containsKey(key) ? oldValue : null; } @@ -311,9 +318,10 @@ public final class ConditionalExistenceCollections { /** * Elements in this set only exist if a certain condition is true. - * + * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param <E> type of element in set */ static final class ConditionalExistenceSet<E> extends AbstractSet<E> { @@ -322,10 +330,11 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalNonexistenceSet}. - * + * * @param set set to use * @param existenceCondition condition where element exists * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceSet(final Set<E> set, final Predicate<E> existenceCondition) { @@ -359,7 +368,7 @@ public final class ConditionalExistenceCollections { // of E // therefore this cast will always work @SuppressWarnings("unchecked") - final E e = (E) o; + final var e = (E) o; return this.existenceCondition.test(e); } @@ -374,7 +383,7 @@ public final class ConditionalExistenceCollections { public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not // execute - final boolean containedObject = this.contains(o); + final var containedObject = this.contains(o); return this.set.remove(o) && containedObject; } @@ -405,14 +414,15 @@ public final class ConditionalExistenceCollections { /** * Elements in the returned wrapper collection are ignored if they don't pass * a condition. - * + * * @param <E> type of elements in collection * @param collection collection to wrap * @param existenceCondition elements only exist if this returns true * @return wrapper collection * @since 2019-10-17 + * @since v0.3.0 */ - public static final <E> Collection<E> conditionalExistenceCollection( + public static <E> Collection<E> conditionalExistenceCollection( final Collection<E> collection, final Predicate<E> existenceCondition) { return new ConditionalExistenceCollection<>(collection, @@ -422,14 +432,15 @@ public final class ConditionalExistenceCollections { /** * Elements in the returned wrapper iterator are ignored if they don't pass a * condition. - * + * * @param <E> type of elements in iterator * @param iterator iterator to wrap * @param existenceCondition elements only exist if this returns true * @return wrapper iterator * @since 2019-10-17 + * @since v0.3.0 */ - public static final <E> Iterator<E> conditionalExistenceIterator( + public static <E> Iterator<E> conditionalExistenceIterator( final Iterator<E> iterator, final Predicate<E> existenceCondition) { return new ConditionalExistenceIterator<>(iterator, existenceCondition); } @@ -437,16 +448,16 @@ public final class ConditionalExistenceCollections { /** * Mappings in the returned wrapper map are ignored if the corresponding * entry doesn't pass a condition - * + * * @param <K> type of key in map * @param <V> type of value in map * @param map map to wrap * @param entryExistenceCondition mappings only exist if this returns true * @return wrapper map * @since 2019-10-17 + * @since v0.3.0 */ - public static final <K, V> Map<K, V> conditionalExistenceMap( - final Map<K, V> map, + public static <K, V> Map<K, V> conditionalExistenceMap(final Map<K, V> map, final Predicate<Entry<K, V>> entryExistenceCondition) { return new ConditionalExistenceMap<>(map, entryExistenceCondition); } @@ -454,14 +465,15 @@ public final class ConditionalExistenceCollections { /** * Elements in the returned wrapper set are ignored if they don't pass a * condition. - * + * * @param <E> type of elements in set * @param set set to wrap * @param existenceCondition elements only exist if this returns true * @return wrapper set * @since 2019-10-17 + * @since v0.3.0 */ - public static final <E> Set<E> conditionalExistenceSet(final Set<E> set, + public static <E> Set<E> conditionalExistenceSet(final Set<E> set, final Predicate<E> existenceCondition) { return new ConditionalExistenceSet<>(set, existenceCondition); } diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index 0515b6b..1366fd3 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 @@ -20,7 +20,7 @@ import java.math.BigDecimal; /** * A class that contains methods to compare float and double values. - * + * * @author Adrien Hopkins * @since 2019-03-18 * @since v0.2.0 @@ -29,7 +29,7 @@ public final class DecimalComparison { /** * The value used for double comparison. If two double values are within this * value multiplied by the larger value, they are considered equal. - * + * * @since 2019-03-18 * @since v0.2.0 */ @@ -38,7 +38,7 @@ public final class DecimalComparison { /** * The value used for float comparison. If two float values are within this * value multiplied by the larger value, they are considered equal. - * + * * @since 2019-03-18 * @since v0.2.0 */ @@ -63,21 +63,20 @@ public final class DecimalComparison { * <li>Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * </ol> - * + * * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 - * @see #hashCode(double) */ - public static final boolean equals(final double a, final double b) { + public static boolean equals(final double a, final double b) { return DecimalComparison.equals(a, b, DOUBLE_EPSILON); } /** * Tests for double equality using a custom epsilon value. - * + * * <p> * <strong>WARNING: </strong>this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -94,7 +93,7 @@ public final class DecimalComparison { * <li>Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * </ol> - * + * * @param a first value to test * @param b second value to test * @param epsilon allowed difference @@ -102,14 +101,14 @@ public final class DecimalComparison { * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final double a, final double b, + public static boolean equals(final double a, final double b, 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}. - * + * * <p> * <strong>WARNING: </strong>this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -126,20 +125,20 @@ public final class DecimalComparison { * <li>Use {@link BigDecimal} instead of {@code float} (this will make a * violation of transitivity 100% impossible) * </ol> - * + * * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final float a, final float b) { + public static boolean equals(final float a, final float b) { return DecimalComparison.equals(a, b, FLOAT_EPSILON); } /** * Tests for float equality using a custom epsilon value. - * + * * <p> * <strong>WARNING: </strong>this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -156,7 +155,7 @@ public final class DecimalComparison { * <li>Use {@link BigDecimal} instead of {@code float} (this will make a * violation of transitivity 100% impossible) * </ol> - * + * * @param a first value to test * @param b second value to test * @param epsilon allowed difference @@ -164,7 +163,7 @@ public final class DecimalComparison { * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final float a, final float b, + public static boolean equals(final float a, final float b, final float epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } @@ -189,14 +188,14 @@ public final class DecimalComparison { * <li>Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * </ol> - * + * * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2020-09-07 - * @see #hashCode(double) + * @since v0.3.0 */ - public static final boolean equals(final UncertainDouble a, + public static boolean equals(final UncertainDouble a, final UncertainDouble b) { return DecimalComparison.equals(a.value(), b.value()) && DecimalComparison.equals(a.uncertainty(), b.uncertainty()); @@ -204,7 +203,7 @@ public final class DecimalComparison { /** * Tests for {@code UncertainDouble} equality using a custom epsilon value. - * + * * <p> * <strong>WARNING: </strong>this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -221,7 +220,7 @@ public final class DecimalComparison { * <li>Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * </ol> - * + * * @param a first value to test * @param b second value to test * @param epsilon allowed difference @@ -229,25 +228,13 @@ public final class DecimalComparison { * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final UncertainDouble a, + public static boolean equals(final UncertainDouble a, final UncertainDouble b, final double epsilon) { return DecimalComparison.equals(a.value(), b.value(), epsilon) && 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. - * - * @param d double to hash - * @return hash code of double - * @since 2019-10-16 - */ - 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 e248ff0..03c763c 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019, 2024 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 @@ -31,7 +31,7 @@ import java.util.function.UnaryOperator; /** * An object that can parse expressions with unary or binary operators. - * + * * @author Adrien Hopkins * @param <T> type of object that exists in parsed expressions * @since 2019-03-14 @@ -40,7 +40,7 @@ import java.util.function.UnaryOperator; public final class ExpressionParser<T> { /** * A builder that can create {@code ExpressionParser<T>} instances. - * + * * @author Adrien Hopkins * @param <T> type of object that exists in parsed expressions * @since 2019-03-17 @@ -51,7 +51,7 @@ public final class ExpressionParser<T> { * A function that obtains a parseable object from a string. For example, * an integer {@code ExpressionParser} would use * {@code Integer::parseInt}. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -59,7 +59,7 @@ public final class ExpressionParser<T> { /** * The function of the space as an operator (like 3 x y) - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -68,7 +68,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for unary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -77,7 +77,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for binary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -85,14 +85,15 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to numeric functions. - * + * * @since 2024-03-23 + * @since v0.5.0 */ private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; /** * Creates the {@code Builder}. - * + * * @param objectObtainer a function that can turn strings into objects of * the type handled by the parser. * @throws NullPointerException if {@code objectObtainer} is null @@ -109,7 +110,7 @@ public final class ExpressionParser<T> { /** * Adds a binary operator to the builder. - * + * * @param text text used to reference the operator, like '+' * @param operator operator to add * @param priority operator's priority, which determines which operators @@ -142,7 +143,7 @@ public final class ExpressionParser<T> { /** * 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 @@ -172,7 +173,7 @@ public final class ExpressionParser<T> { /** * Adds a function for spaces. You must use the text of an existing binary * operator. - * + * * @param operator text of operator to use * @return this builder * @since 2019-03-22 @@ -191,7 +192,7 @@ public final class ExpressionParser<T> { /** * Adds a unary operator to the builder. - * + * * @param text text used to reference the operator, like '-' * @param operator operator to add * @param priority operator's priority, which determines which operators @@ -235,18 +236,18 @@ public final class ExpressionParser<T> { /** * 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 PriorityBinaryOperator<T> - implements BinaryOperator<T>, Comparable<PriorityBinaryOperator<T>> { + 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 */ @@ -254,33 +255,32 @@ public final class ExpressionParser<T> { /** * Creates the {@code PriorityBinaryOperator}. - * + * * @param priority operator's priority * @since 2019-03-17 * @since v0.2.0 */ - public PriorityBinaryOperator(final int priority) { + 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 PriorityBinaryOperator<T> o) { + public int compareTo(final PriorityBiFunction<T, U, R> o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } /** @@ -296,18 +296,18 @@ public final class ExpressionParser<T> { /** * 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>> { + private static abstract class PriorityBinaryOperator<T> + implements BinaryOperator<T>, Comparable<PriorityBinaryOperator<T>> { /** * The operator's priority. Higher-priority operators are applied before * lower-priority operators - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -315,33 +315,32 @@ public final class ExpressionParser<T> { /** * Creates the {@code PriorityBinaryOperator}. - * + * * @param priority operator's priority * @since 2019-03-17 * @since v0.2.0 */ - public PriorityBiFunction(final int priority) { + public PriorityBinaryOperator(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) { + public int compareTo(final PriorityBinaryOperator<T> o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } /** @@ -357,7 +356,7 @@ public final class ExpressionParser<T> { /** * A unary 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 @@ -368,7 +367,7 @@ public final class ExpressionParser<T> { /** * The operator's priority. Higher-priority operators are applied before * lower-priority operators - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -376,7 +375,7 @@ public final class ExpressionParser<T> { /** * Creates the {@code PriorityUnaryOperator}. - * + * * @param priority operator's priority * @since 2019-03-17 * @since v0.2.0 @@ -387,11 +386,11 @@ public final class ExpressionParser<T> { /** * Compares this object to another by priority. - * + * * <p> * {@inheritDoc} * </p> - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -399,10 +398,9 @@ public final class ExpressionParser<T> { public int compareTo(final PriorityUnaryOperator<T> o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } /** @@ -417,18 +415,18 @@ public final class ExpressionParser<T> { /** * The types of tokens that are available. - * + * * @author Adrien Hopkins * @since 2019-03-14 * @since v0.2.0 */ - private static enum TokenType { + private enum TokenType { OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR; } /** * The opening bracket. - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -436,7 +434,7 @@ public final class ExpressionParser<T> { /** * The closing bracket. - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -444,7 +442,7 @@ public final class ExpressionParser<T> { /** * Finds the other bracket in a pair of brackets, given the position of one. - * + * * @param string string that contains brackets * @param bracketPosition position of first bracket * @return position of matching bracket @@ -456,7 +454,7 @@ public final class ExpressionParser<T> { final int bracketPosition) { Objects.requireNonNull(string, "string must not be null."); - final char openingBracket = string.charAt(bracketPosition); + final var openingBracket = string.charAt(bracketPosition); // figure out what closing bracket to look for final char closingBracket; @@ -477,12 +475,12 @@ public final class ExpressionParser<T> { // level of brackets. every opening bracket increments this; every closing // bracket decrements it - int bracketLevel = 0; + var bracketLevel = 0; // iterate over the string to find the closing bracket - for (int currentPosition = bracketPosition; currentPosition < string + for (var currentPosition = bracketPosition; currentPosition < string .length(); currentPosition++) { - final char currentCharacter = string.charAt(currentPosition); + final var currentCharacter = string.charAt(currentPosition); if (currentCharacter == openingBracket) { bracketLevel++; @@ -499,7 +497,7 @@ public final class ExpressionParser<T> { /** * A function that obtains a parseable object from a string. For example, an * integer {@code ExpressionParser} would use {@code Integer::parseInt}. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -507,7 +505,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for unary operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -516,7 +514,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for binary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -524,14 +522,15 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to numeric functions. - * + * * @since 2024-03-23 + * @since v0.5.0 */ private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; /** * The operator for space, or null if spaces have no function. - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -539,7 +538,7 @@ public final class ExpressionParser<T> { /** * 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 @@ -568,7 +567,7 @@ public final class ExpressionParser<T> { * {@code 2 * (3 + 4)}<br> * becomes<br> * {@code 2 3 4 + *}. - * + * * @param expression expression * @return expression in RPN * @throws IllegalArgumentException if expression is invalid (e.g. @@ -582,13 +581,13 @@ public final class ExpressionParser<T> { final List<String> components = new ArrayList<>(); // the part of the expression remaining to parse - String partialExpression = expression; + var partialExpression = expression; // find and deal with brackets while (partialExpression.indexOf(OPENING_BRACKET) != -1) { - final int openingBracketPosition = partialExpression + final var openingBracketPosition = partialExpression .indexOf(OPENING_BRACKET); - final int closingBracketPosition = findBracketPair(partialExpression, + final var closingBracketPosition = findBracketPair(partialExpression, openingBracketPosition); // check for function @@ -596,7 +595,7 @@ public final class ExpressionParser<T> { && partialExpression.charAt(openingBracketPosition - 1) != ' ') { // function like sin(2) or tempF(32) // find the position of the last space - int spacePosition = openingBracketPosition; + var spacePosition = openingBracketPosition; while (spacePosition >= 0 && partialExpression.charAt(spacePosition) != ' ') { spacePosition--; @@ -607,8 +606,6 @@ public final class ExpressionParser<T> { .substring(0, spacePosition + 1).split(" "))); components.add(partialExpression.substring(spacePosition + 1, closingBracketPosition + 1)); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); } else { // normal brackets like (1 + 2) * (3 / 5) components.addAll(Arrays.asList(partialExpression @@ -616,9 +613,9 @@ public final class ExpressionParser<T> { components.add(this.convertExpressionToReversePolish( partialExpression.substring(openingBracketPosition + 1, closingBracketPosition))); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); } + partialExpression = partialExpression + .substring(closingBracketPosition + 1); } // add everything else @@ -631,7 +628,7 @@ public final class ExpressionParser<T> { // deal with space multiplication (x y) if (this.spaceOperator != null) { - for (int i = 0; i < components.size() - 1; i++) { + for (var i = 0; i < components.size() - 1; i++) { if (this.getTokenType(components.get(i)) == TokenType.OBJECT && this .getTokenType(components.get(i + 1)) == TokenType.OBJECT) { components.add(++i, this.spaceOperator); @@ -641,7 +638,7 @@ public final class ExpressionParser<T> { // turn the expression into reverse Polish while (true) { - final int highestPriorityOperatorPosition = this + final var highestPriorityOperatorPosition = this .findHighestPriorityOperatorPosition(components); if (highestPriorityOperatorPosition == -1) { break; @@ -656,9 +653,9 @@ public final class ExpressionParser<T> { if (components.size() < 2) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); - final String unaryOperator = components + final var unaryOperator = components .remove(highestPriorityOperatorPosition); - final String operand = components + final var operand = components .remove(highestPriorityOperatorPosition); components.add(highestPriorityOperatorPosition, operand + " " + unaryOperator); @@ -668,14 +665,14 @@ public final class ExpressionParser<T> { if (components.size() < 3) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); - final String binaryOperator = components + final var binaryOperator = components .remove(highestPriorityOperatorPosition); - final String operand1 = components + final var operand1 = components .remove(highestPriorityOperatorPosition - 1); - final String operand2 = components + final var operand2 = components .remove(highestPriorityOperatorPosition - 1); components.add(highestPriorityOperatorPosition - 1, - operand2 + " " + operand1 + " " + binaryOperator); + operand1 + " " + operand2 + " " + binaryOperator); break; default: throw new AssertionError("Expected operator, found non-operator."); @@ -684,20 +681,15 @@ public final class ExpressionParser<T> { // 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); - } - while (expressionRPN.charAt(expressionRPN.length() - 1) == ' ') { - expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1); - } - return expressionRPN; + if (components.size() != 1) + throw new IllegalArgumentException( + "Invalid expression \"" + expression + "\"."); + return components.get(0).replaceAll(" +", " ").trim(); } /** * Finds the position of the highest-priority operator in a list - * + * * @param components components to test * @param blacklist positions of operators that should be ignored * @return position of highest priority, or -1 if the list contains no @@ -710,19 +702,19 @@ public final class ExpressionParser<T> { final List<String> components) { Objects.requireNonNull(components, "components must not be null."); // find highest priority - int maxPriority = Integer.MIN_VALUE; - int maxPriorityPosition = -1; + var maxPriority = Integer.MIN_VALUE; + var 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++) { + for (var i = 0; i < components.size(); i++) { switch (this.getTokenType(components.get(i))) { case UNARY_OPERATOR: - final PriorityUnaryOperator<T> unaryOperator = this.unaryOperators + final var unaryOperator = this.unaryOperators .get(components.get(i)); - final int unaryPriority = unaryOperator.getPriority(); + final var unaryPriority = unaryOperator.getPriority(); if (unaryPriority > maxPriority) { maxPriority = unaryPriority; @@ -730,9 +722,9 @@ public final class ExpressionParser<T> { } break; case BINARY_OPERATOR: - final PriorityBinaryOperator<T> binaryOperator = this.binaryOperators + final var binaryOperator = this.binaryOperators .get(components.get(i)); - final int binaryPriority = binaryOperator.getPriority(); + final var binaryPriority = binaryOperator.getPriority(); if (binaryPriority > maxPriority) { maxPriority = binaryPriority; @@ -740,9 +732,9 @@ public final class ExpressionParser<T> { } break; case NUMERIC_OPERATOR: - final PriorityBiFunction<T, UncertainDouble, T> numericOperator = this.numericOperators + final var numericOperator = this.numericOperators .get(components.get(i)); - final int numericPriority = numericOperator.getPriority(); + final var numericPriority = numericOperator.getPriority(); if (numericPriority > maxPriority) { maxPriority = numericPriority; @@ -760,7 +752,7 @@ public final class ExpressionParser<T> { /** * Determines whether an inputted string is an object or an operator - * + * * @param token string to input * @return type of token it is * @throws NullPointerException if {@code expression} is null @@ -772,17 +764,16 @@ public final class ExpressionParser<T> { if (this.unaryOperators.containsKey(token)) return TokenType.UNARY_OPERATOR; - else if (this.binaryOperators.containsKey(token)) + if (this.binaryOperators.containsKey(token)) return TokenType.BINARY_OPERATOR; - else if (this.numericOperators.containsKey(token)) + if (this.numericOperators.containsKey(token)) return TokenType.NUMERIC_OPERATOR; - else - return TokenType.OBJECT; + return TokenType.OBJECT; } /** * Parses an expression. - * + * * @param expression expression to parse * @return result * @throws NullPointerException if {@code expression} is null @@ -796,7 +787,7 @@ public final class ExpressionParser<T> { /** * Parses an expression expressed in reverse Polish notation. - * + * * @param expression expression to parse * @return result * @throws NullPointerException if {@code expression} is null @@ -821,12 +812,12 @@ public final class ExpressionParser<T> { item, stack.size())); // get two arguments and operator, then apply! - final T o1 = stack.pop(); - final T o2 = stack.pop(); + final var o1 = stack.pop(); + final var o2 = stack.pop(); final BinaryOperator<T> binaryOperator = this.binaryOperators .get(item); - stack.push(binaryOperator.apply(o1, o2)); + stack.push(binaryOperator.apply(o2, o1)); break; case NUMERIC_OPERATOR: @@ -835,8 +826,8 @@ public final class ExpressionParser<T> { "Attempted to call binary operator %s with insufficient arguments.", item)); - final T ot = stack.pop(); - final UncertainDouble on = doubleStack.pop(); + final var ot = stack.pop(); + final var on = doubleStack.pop(); final BiFunction<T, UncertainDouble, T> op = this.numericOperators .get(item); stack.push(op.apply(ot, on)); @@ -850,14 +841,14 @@ public final class ExpressionParser<T> { // 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) { + } catch (final Exception e) { try { doubleStack.push(UncertainDouble.fromString(item)); - } catch (IllegalArgumentException e2) { + } catch (final IllegalArgumentException e2) { try { doubleStack.push( UncertainDouble.of(Double.parseDouble(item), 0)); - } catch (NumberFormatException e3) { + } catch (final NumberFormatException e3) { throw e; } } @@ -871,7 +862,7 @@ public final class ExpressionParser<T> { item, stack.size())); // get one argument and operator, then apply! - final T o = stack.pop(); + final var o = stack.pop(); final UnaryOperator<T> unaryOperator = this.unaryOperators .get(item); @@ -889,7 +880,7 @@ public final class ExpressionParser<T> { if (stack.size() > 1) throw new IllegalStateException( "Computation ended up with more than one answer."); - else if (stack.size() == 0) + if (stack.size() == 0) throw new IllegalStateException( "Computation ended up without an answer."); return stack.pop(); diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 49c44fa..089978b 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2022, 2024, 2025 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 @@ -19,34 +19,35 @@ package sevenUnits.utils; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Set; /** * A class that can be used to specify names and a symbol for a unit. - * + * * @author Adrien Hopkins * @since 2019-10-21 + * @since v0.3.0 */ public final class NameSymbol { + /** The {@code NameSymbol} with all fields empty. */ 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 - * + * * Ensure that otherNames is not a copy of the inputted argument. */ - private static final NameSymbol create(final String name, - final String symbol, final Set<String> otherNames) { + private static 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(); + final var it = otherNames.iterator(); assert it.hasNext(); primaryName = Optional.of(it.next()); otherNames.remove(primaryName.get()); @@ -61,14 +62,15 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a primary name, a symbol and no other * names. - * + * * @param name name to use * @param symbol symbol to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if name or symbol is null */ - public static final NameSymbol of(final String name, final String symbol) { + public static NameSymbol of(final String name, final String symbol) { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>()); } @@ -76,15 +78,16 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if any argument is null */ - public static final NameSymbol of(final String name, final String symbol, + public static NameSymbol of(final String name, final String symbol, final Set<String> otherNames) { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>(Objects.requireNonNull(otherNames, @@ -94,12 +97,13 @@ public final class NameSymbol { /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if any argument is null */ public static final NameSymbol of(final String name, final String symbol, @@ -112,13 +116,14 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a primary name, no symbol, and no other * names. - * + * * @param name name to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if name is null */ - public static final NameSymbol ofName(final String name) { + public static NameSymbol ofName(final String name) { return new NameSymbol(Optional.of(name), Optional.empty(), new HashSet<>()); } @@ -133,15 +138,16 @@ public final class NameSymbol { * If {@code name} is null and {@code otherNames} is not empty, a primary * name will be picked from {@code otherNames}. This name will not appear in * getOtherNames(). - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-11-26 + * @since v0.3.0 */ - public static final NameSymbol ofNullable(final String name, - final String symbol, final Set<String> otherNames) { + public static NameSymbol ofNullable(final String name, final String symbol, + final Set<String> otherNames) { return NameSymbol.create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); } @@ -156,12 +162,13 @@ public final class NameSymbol { * If {@code name} is null and {@code otherNames} is not empty, a primary * name will be picked from {@code otherNames}. This name will not appear in * getOtherNames(). - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-11-26 + * @since v0.3.0 */ public static final NameSymbol ofNullable(final String name, final String symbol, final String... otherNames) { @@ -171,13 +178,14 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a symbol and no names. - * + * * @param symbol symbol to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if symbol is null */ - public static final NameSymbol ofSymbol(final String symbol) { + public static NameSymbol ofSymbol(final String symbol) { return new NameSymbol(Optional.empty(), Optional.of(symbol), new HashSet<>()); } @@ -189,21 +197,26 @@ public final class NameSymbol { /** * Creates the {@code NameSymbol}. - * + * * @param primaryName primary name of unit * @param symbol symbol used to represent unit * @param otherNames other names and/or spellings, should be a mutable copy * of the argument * @since 2019-10-21 + * @since v0.3.0 */ - private NameSymbol(final Optional<String> primaryName, - final Optional<String> symbol, final Set<String> otherNames) { + NameSymbol(final Optional<String> primaryName, final Optional<String> symbol, + final Set<String> otherNames) { this.primaryName = primaryName; this.symbol = symbol; - otherNames.remove(null); - this.otherNames = Collections.unmodifiableSet(otherNames); + if (otherNames != null) { + otherNames.remove(null); + this.otherNames = Collections.unmodifiableSet(otherNames); + } else { + this.otherNames = Set.of(); + } - if (this.primaryName.isEmpty()) { + if (this.primaryName == null || this.primaryName.isEmpty()) { assert this.otherNames.isEmpty(); } } @@ -214,7 +227,7 @@ public final class NameSymbol { return true; if (!(obj instanceof NameSymbol)) return false; - final NameSymbol other = (NameSymbol) obj; + final var other = (NameSymbol) obj; if (this.otherNames == null) { if (other.otherNames != null) return false; @@ -236,31 +249,34 @@ public final class NameSymbol { /** * @return otherNames * @since 2019-10-21 + * @since v0.3.0 */ - public final Set<String> getOtherNames() { + public Set<String> getOtherNames() { return this.otherNames; } /** * @return primaryName * @since 2019-10-21 + * @since v0.3.0 */ - public final Optional<String> getPrimaryName() { + public Optional<String> getPrimaryName() { return this.primaryName; } /** * @return symbol * @since 2019-10-21 + * @since v0.3.0 */ - public final Optional<String> getSymbol() { + public Optional<String> getSymbol() { return this.symbol; } @Override public int hashCode() { - final int prime = 31; - int result = 1; + final var prime = 31; + var result = 1; result = prime * result + (this.otherNames == null ? 0 : this.otherNames.hashCode()); result = prime * result @@ -270,10 +286,8 @@ public final class NameSymbol { return result; } - /** - * @return true iff this {@code NameSymbol} contains no names or symbols. - */ - public final boolean isEmpty() { + /** @return true iff this {@code NameSymbol} contains no names or symbols. */ + public boolean isEmpty() { // if primaryName is empty, otherNames must also be empty return this.primaryName.isEmpty() && this.symbol.isEmpty(); } @@ -282,11 +296,10 @@ public final class NameSymbol { public String toString() { if (this.isEmpty()) return "NameSymbol.EMPTY"; - else if (this.primaryName.isPresent() && this.symbol.isPresent()) + if (this.primaryName.isPresent() && this.symbol.isPresent()) return this.primaryName.orElseThrow() + " (" + this.symbol.orElseThrow() + ")"; - else - return this.primaryName.orElseGet(this.symbol::orElseThrow); + return this.primaryName.orElseGet(this.symbol::orElseThrow); } /** @@ -294,16 +307,19 @@ public final class NameSymbol { * extra name. If this {@code NameSymbol} has a primary name, the provided * name will become an other name, otherwise it will become the primary name. * - * @since v0.4.0 + * @param name additional name to add + * @return copy of this NameSymbol with the additional name + * * @since 2022-04-19 + * @since v0.4.0 */ - public final NameSymbol withExtraName(String name) { + public NameSymbol withExtraName(String name) { if (this.primaryName.isPresent()) { final var otherNames = new HashSet<>(this.otherNames); otherNames.add(name); return NameSymbol.ofNullable(this.primaryName.orElse(null), this.symbol.orElse(null), otherNames); - } else - return NameSymbol.ofNullable(name, this.symbol.orElse(null)); + } + return NameSymbol.ofNullable(name, this.symbol.orElse(null)); } }
\ No newline at end of file diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index 3959a64..166de55 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2020 Adrien Hopkins + * Copyright (C) 2020, 2022, 2024, 2025 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,15 +24,17 @@ import java.util.Set; * and symbol data should be immutable. * * @since 2020-09-07 + * @since v0.3.0 */ public interface Nameable { /** * @return a name for the object - if there's a primary name, it's that, * otherwise the symbol, otherwise "Unnamed" * @since 2022-02-26 + * @since v0.4.0 */ default String getName() { - final NameSymbol ns = this.getNameSymbol(); + final var ns = this.getNameSymbol(); return ns.getPrimaryName().or(ns::getSymbol).orElse("Unnamed"); } @@ -40,12 +42,14 @@ public interface Nameable { * @return a {@code NameSymbol} that contains this object's primary name, * symbol and other names * @since 2020-09-07 + * @since v0.3.0 */ NameSymbol getNameSymbol(); /** * @return set of alternate names * @since 2020-09-07 + * @since v0.3.0 */ default Set<String> getOtherNames() { return this.getNameSymbol().getOtherNames(); @@ -54,6 +58,7 @@ public interface Nameable { /** * @return preferred name of object * @since 2020-09-07 + * @since v0.3.0 */ default Optional<String> getPrimaryName() { return this.getNameSymbol().getPrimaryName(); @@ -63,15 +68,17 @@ public interface Nameable { * @return a short name for the object - if there's a symbol, it's that, * otherwise the symbol, otherwise "Unnamed" * @since 2022-02-26 + * @since v0.4.0 */ default String getShortName() { - final NameSymbol ns = this.getNameSymbol(); + final var ns = this.getNameSymbol(); return ns.getSymbol().or(ns::getPrimaryName).orElse("Unnamed"); } /** * @return short symbol representing object * @since 2020-09-07 + * @since v0.3.0 */ default Optional<String> getSymbol() { return this.getNameSymbol().getSymbol(); diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 5a29d79..23fe41c 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2021, 2024, 2025 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 @@ -29,17 +29,26 @@ import java.util.function.Function; /** * An immutable product of multiple objects of a type, such as base units. The * objects can be multiplied and exponentiated. - * + * * @author Adrien Hopkins + * @param <T> type of object that is being multiplied * @since 2019-10-16 + * @since v0.3.0 */ public class ObjectProduct<T> implements Nameable { /** + * If {@link #toExponentRounded}'s rounding changes exponents by more than + * this value, a warning will be printed to standard error. + */ + private static final double ROUND_WARN_THRESHOLD = 1e-12; + + /** * Returns an empty ObjectProduct of a certain type - * + * * @param <T> type of objects that can be multiplied * @return empty product * @since 2019-10-16 + * @since v0.3.0 */ public static final <T> ObjectProduct<T> empty() { return new ObjectProduct<>(new HashMap<>()); @@ -47,11 +56,12 @@ public class ObjectProduct<T> implements Nameable { /** * Gets an {@code ObjectProduct} from an object-to-integer mapping - * + * * @param <T> type of object in product * @param map map mapping objects to exponents * @return object product * @since 2019-10-16 + * @since v0.3.0 */ public static final <T> ObjectProduct<T> fromExponentMapping( final Map<T, Integer> map) { @@ -61,10 +71,12 @@ public class ObjectProduct<T> implements Nameable { /** * Gets an ObjectProduct that has one of the inputted argument, and nothing * else. - * + * * @param object object that will be in the product + * @param <T> type of object contained in returned ObjectProduct * @return product * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if object is null */ public static final <T> ObjectProduct<T> oneOf(final T object) { @@ -77,21 +89,21 @@ public class ObjectProduct<T> implements Nameable { /** * The objects that make up the product, mapped to their exponents. This map * treats zero as null, and is immutable. - * + * * @since 2019-10-16 + * @since v0.3.0 */ final Map<T, Integer> exponents; - /** - * The object's name and symbol - */ + /** The object's name and symbol */ private final NameSymbol nameSymbol; /** * Creates a {@code ObjectProduct} without a name/symbol. - * + * * @param exponents objects that make up this product * @since 2019-10-16 + * @since v0.3.0 */ ObjectProduct(final Map<T, Integer> exponents) { this(exponents, NameSymbol.EMPTY); @@ -99,10 +111,11 @@ public class ObjectProduct<T> implements Nameable { /** * Creates the {@code ObjectProduct}. - * + * * @param exponents objects that make up this product * @param nameSymbol name and symbol of object product * @since 2019-10-16 + * @since v0.3.0 */ ObjectProduct(final Map<T, Integer> exponents, NameSymbol nameSymbol) { this.exponents = Collections.unmodifiableMap( @@ -117,6 +130,7 @@ public class ObjectProduct<T> implements Nameable { * @param other other product * @return quotient of two products * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if other is null */ public ObjectProduct<T> dividedBy(final ObjectProduct<T> other) { @@ -150,6 +164,7 @@ public class ObjectProduct<T> implements Nameable { /** * @return immutable map mapping objects to exponents * @since 2019-10-16 + * @since v0.3.0 */ public Map<T, Integer> exponentMap() { return this.exponents; @@ -177,7 +192,7 @@ public class ObjectProduct<T> implements Nameable { /** * Gets the exponent for a specific dimension. - * + * * @param dimension dimension to check * @return exponent for that dimension * @since 2018-12-12 @@ -201,10 +216,11 @@ public class ObjectProduct<T> implements Nameable { * @return true if this product is a single object, i.e. it has one exponent * of one and no other nonzero exponents * @since 2019-10-16 + * @since v0.3.0 */ public boolean isSingleObject() { - int oneCount = 0; - boolean twoOrMore = false; // has exponents of 2 or more + var oneCount = 0; + var twoOrMore = false; // has exponents of 2 or more for (final T b : this.getBaseSet()) { if (this.getExponent(b) == 1) { oneCount++; @@ -221,6 +237,7 @@ public class ObjectProduct<T> implements Nameable { * @param other other product * @return product of two products * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if other is null */ public ObjectProduct<T> times(final ObjectProduct<T> other) { @@ -242,10 +259,11 @@ public class ObjectProduct<T> implements Nameable { /** * Returns this product, but to an exponent - * + * * @param exponent exponent * @return result of exponentiation * @since 2019-10-16 + * @since v0.3.0 */ public ObjectProduct<T> toExponent(final int exponent) { final Map<T, Integer> map = new HashMap<>(this.exponents); @@ -256,12 +274,41 @@ public class ObjectProduct<T> implements Nameable { } /** + * Returns this product to an exponent, where every dimension is rounded to + * the nearest integer. + * + * This function will send a warning (via standard error) if the rounding + * significantly changes the value. + * + * @param exponent exponent to raise this product to + * @return result of exponentiation + * + * @since 2024-08-22 + * @since v0.3.0 + */ + public ObjectProduct<T> toExponentRounded(final double exponent) { + final Map<T, Integer> map = new HashMap<>(this.exponents); + for (final T key : this.exponents.keySet()) { + final var newExponent = this.getExponent(key) * exponent; + if (Math.abs( + newExponent - Math.round(newExponent)) > ROUND_WARN_THRESHOLD) { + System.err.printf( + "Exponent Rounding Warning: Dimension exponents must be integers, so %d ^ %g = %g was rounded to %d.\n", + this.getExponent(key), exponent, newExponent, + Math.round(newExponent)); + } + map.put(key, (int) Math.round(newExponent)); + } + return new ObjectProduct<>(map); + } + + /** * Converts this product to a string using the objects' * {@link Object#toString()} method (or {@link Nameable#getShortName} if * available). If objects have a long toString representation, it is * recommended to use {@link #toString(Function)} instead to shorten the * returned string. - * + * * <p> * {@inheritDoc} */ @@ -275,10 +322,11 @@ public class ObjectProduct<T> implements Nameable { /** * Converts this product to a string. The objects that make up this product * are represented by {@code objectToString} - * + * * @param objectToString function to convert objects to strings * @return string representation of product * @since 2019-10-16 + * @since v0.3.0 */ public String toString(final Function<T, String> objectToString) { final List<String> positiveStringComponents = new ArrayList<>(); @@ -298,18 +346,20 @@ public class ObjectProduct<T> implements Nameable { } } - final String positiveString = positiveStringComponents.isEmpty() ? "1" + final var positiveString = positiveStringComponents.isEmpty() ? "1" : String.join(" * ", positiveStringComponents); - final String negativeString = negativeStringComponents.isEmpty() ? "" + final var negativeString = negativeStringComponents.isEmpty() ? "" : " / " + String.join(" * ", negativeStringComponents); return positiveString + negativeString; } /** + * @param nameSymbol name to add to this product * @return named version of this {@code ObjectProduct}, using data from * {@code nameSymbol} * @since 2021-12-15 + * @since v0.3.0 */ public ObjectProduct<T> withName(NameSymbol nameSymbol) { return new ObjectProduct<>(this.exponents, nameSymbol); diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index fc47baa..4bb7ce5 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -39,8 +38,8 @@ import java.util.regex.Pattern; * are made * </ol> * - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public final class SemanticVersionNumber implements Comparable<SemanticVersionNumber> { @@ -52,8 +51,8 @@ public final class SemanticVersionNumber * throw NullPointerExceptions, everything else throws * IllegalArgumentException. * - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public static final class Builder { private final int major; @@ -69,8 +68,8 @@ public final class SemanticVersionNumber * @param major major version number of final version * @param minor minor version number of final version * @param patch patch version number of final version - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ private Builder(int major, int minor, int patch) { this.major = major; @@ -82,8 +81,8 @@ public final class SemanticVersionNumber /** * @return version number created by this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public SemanticVersionNumber build() { return new SemanticVersionNumber(this.major, this.minor, this.patch, @@ -95,8 +94,8 @@ public final class SemanticVersionNumber * * @param identifiers build metadata * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder buildMetadata(List<String> identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -115,8 +114,8 @@ public final class SemanticVersionNumber * * @param identifiers build metadata * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder buildMetadata(String... identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -136,7 +135,7 @@ public final class SemanticVersionNumber return true; if (!(obj instanceof Builder)) return false; - final Builder other = (Builder) obj; + final var other = (Builder) obj; return Objects.equals(this.buildMetadata, other.buildMetadata) && this.major == other.major && this.minor == other.minor && this.patch == other.patch && Objects.equals( @@ -154,8 +153,8 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(int... identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -173,8 +172,8 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(List<String> identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -193,8 +192,8 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(String... identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -214,8 +213,8 @@ public final class SemanticVersionNumber * @param identifier1 first identifier * @param identifier2 second identifier * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(String identifier1, int identifier2) { Objects.requireNonNull(identifier1, "identifier1 may not be null"); @@ -249,12 +248,11 @@ public final class SemanticVersionNumber public int compare(SemanticVersionNumber o1, SemanticVersionNumber o2) { Objects.requireNonNull(o1, "o1 may not be null"); Objects.requireNonNull(o2, "o2 may not be null"); - final int naturalComparison = o1.compareTo(o2); + final var naturalComparison = o1.compareTo(o2); if (naturalComparison == 0) return SemanticVersionNumber.compareIdentifiers(o1.buildMetadata, o2.buildMetadata); - else - return naturalComparison; + return naturalComparison; }; }; @@ -280,11 +278,11 @@ public final class SemanticVersionNumber * @param patch patch version number of final version * @return version number builder * @throws IllegalArgumentException if any argument is negative - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ - public static final SemanticVersionNumber.Builder builder(int major, - int minor, int patch) { + public static SemanticVersionNumber.Builder builder(int major, int minor, + int patch) { if (major < 0) throw new IllegalArgumentException( "Major version must be non-negative."); @@ -304,60 +302,57 @@ public final class SemanticVersionNumber * @param b second list * @return result of comparison as in a comparator * @see Comparator - * @since v0.4.0 * @since 2022-02-20 + * @since v0.4.0 */ - private static final int compareIdentifiers(List<String> a, List<String> b) { + private static int compareIdentifiers(List<String> a, List<String> b) { // test pre-release size - final int aSize = a.size(); - final int bSize = b.size(); + final var aSize = a.size(); + final var bSize = b.size(); // no identifiers is greater than any identifiers if (aSize != 0 && bSize == 0) return -1; - else if (aSize == 0 && bSize != 0) + 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); + for (var i = 0; i < Math.min(aSize, bSize); i++) { + final var aElement = a.get(i); + final var 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) - return 1; - } else + if (!NUMERIC_IDENTIFER.matcher(bElement).matches()) // aElement is a number and bElement is not a number // by the rules, a goes before b return -1; - } else { - if (NUMERIC_IDENTIFER.matcher(bElement).matches()) - // aElement is not a number but bElement is - // by the rules, a goes after b + // both are numbers, compare them + final var aNumber = Integer.parseInt(aElement); + final var bNumber = Integer.parseInt(bElement); + + if (aNumber < bNumber) + return -1; + if (aNumber > bNumber) return 1; - else { - // both are not numbers, compare them - final int comparison = aElement.compareTo(bElement); - if (comparison != 0) - return comparison; - } + } else if (NUMERIC_IDENTIFER.matcher(bElement).matches()) + // aElement is not a number but bElement is + // by the rules, a goes after b + return 1; + else { + // both are not numbers, compare them + final var comparison = aElement.compareTo(bElement); + if (comparison != 0) + return comparison; } } - - // we just tested the stuff that's in common, maybe someone has more if (aSize < bSize) return -1; - else if (aSize > bSize) + if (aSize > bSize) return 1; - else - return 0; + return 0; + + // we just tested the stuff that's in common, maybe someone has more + } /** @@ -365,23 +360,23 @@ public final class SemanticVersionNumber * * @param versionString string to parse * @return {@code SemanticVersionNumber} instance - * @since v0.4.0 * @since 2022-02-19 - * @see {@link #toString} + * @since v0.4.0 + * @see #toString */ - public static final SemanticVersionNumber fromString(String versionString) { + public static SemanticVersionNumber fromString(String versionString) { // parse & validate version string Objects.requireNonNull(versionString, "versionString may not be null"); - final Matcher m = VERSION_NUMBER.matcher(versionString); + final var m = VERSION_NUMBER.matcher(versionString); if (!m.matches()) 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)); + final var major = Integer.parseInt(m.group(1)); + final var minor = Integer.parseInt(m.group(2)); + final var patch = Integer.parseInt(m.group(3)); // pre release final List<String> preRelease; @@ -406,13 +401,13 @@ public final class SemanticVersionNumber /** * Tests whether a string is a valid Semantic Version string - * + * * @param versionString string to test * @return true iff string is valid - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ - public static final boolean isValidVersionString(String versionString) { + public static boolean isValidVersionString(String versionString) { return VERSION_NUMBER.matcher(versionString).matches(); } @@ -429,10 +424,10 @@ public final class SemanticVersionNumber * @throws IllegalArgumentException if any argument is negative or if the * preReleaseType is null, empty or not * alphanumeric (0-9, A-Z, a-z, - only) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ - public static final SemanticVersionNumber preRelease(int major, int minor, + public static SemanticVersionNumber preRelease(int major, int minor, int patch, String preReleaseType, int preReleaseNumber) { if (major < 0) throw new IllegalArgumentException( @@ -467,10 +462,10 @@ public final class SemanticVersionNumber * @param patch patch version number * @return {@code SemanticVersionNumber} instance * @throws IllegalArgumentException if any argument is negative - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ - public static final SemanticVersionNumber stableVersion(int major, int minor, + public static SemanticVersionNumber stableVersion(int major, int minor, int patch) { if (major < 0) throw new IllegalArgumentException( @@ -500,8 +495,8 @@ public final class SemanticVersionNumber * @param patch patch version number * @param preReleaseIdentifiers pre-release version data * @param buildMetadata build metadata - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ private SemanticVersionNumber(int major, int minor, int patch, List<String> preReleaseIdentifiers, List<String> buildMetadata) { @@ -514,8 +509,8 @@ public final class SemanticVersionNumber /** * @return build metadata (empty if there is none) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public List<String> buildMetadata() { return Collections.unmodifiableList(this.buildMetadata); @@ -536,17 +531,17 @@ public final class SemanticVersionNumber // test the three big numbers in order first if (this.major < o.major) return -1; - else if (this.major > o.major) + if (this.major > o.major) return 1; if (this.minor < o.minor) return -1; - else if (this.minor > o.minor) + if (this.minor > o.minor) return 1; if (this.patch < o.patch) return -1; - else if (this.patch > o.patch) + if (this.patch > o.patch) return 1; // now we just compare pre-release identifiers @@ -569,7 +564,7 @@ public final class SemanticVersionNumber * </ul> * If this function returns <b>false</b>, you may have to change your code to * upgrade it to {@code other} - * + * * <p> * Two version numbers that are identical (ignoring build metadata) are * always compatible. Different version numbers are compatible as long as: @@ -585,8 +580,8 @@ public final class SemanticVersionNumber * @param other version to compare with * @return true if you can definitely upgrade to {@code other} without * changing code - * @since v0.4.0 * @since 2022-02-20 + * @since v0.4.0 */ public boolean compatibleWith(SemanticVersionNumber other) { Objects.requireNonNull(other, "other may not be null"); @@ -601,17 +596,14 @@ public final class SemanticVersionNumber return true; if (!(obj instanceof SemanticVersionNumber)) return false; - final SemanticVersionNumber other = (SemanticVersionNumber) obj; + final var other = (SemanticVersionNumber) obj; if (this.buildMetadata == null) { if (other.buildMetadata != null) return false; } else if (!this.buildMetadata.equals(other.buildMetadata)) return false; - if (this.major != other.major) - return false; - if (this.minor != other.minor) - return false; - if (this.patch != other.patch) + if ((this.major != other.major) || (this.minor != other.minor) + || (this.patch != other.patch)) return false; if (this.preReleaseIdentifiers == null) { if (other.preReleaseIdentifiers != null) @@ -624,8 +616,8 @@ public final class SemanticVersionNumber @Override public int hashCode() { - final int prime = 31; - int result = 1; + final var prime = 31; + var result = 1; result = prime * result + (this.buildMetadata == null ? 0 : this.buildMetadata.hashCode()); result = prime * result + this.major; @@ -639,8 +631,8 @@ public final class SemanticVersionNumber /** * @return true iff this version is stable (major version > 0 and not a * pre-release) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public boolean isStable() { return this.major > 0 && this.preReleaseIdentifiers.isEmpty(); @@ -649,8 +641,8 @@ public final class SemanticVersionNumber /** * @return the MAJOR version number, incremented when you make backwards * incompatible API changes - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public int majorVersion() { return this.major; @@ -659,8 +651,8 @@ public final class SemanticVersionNumber /** * @return the MINOR version number, incremented when you add backwards * compatible functionality - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public int minorVersion() { return this.minor; @@ -669,8 +661,8 @@ public final class SemanticVersionNumber /** * @return the PATCH version number, incremented when you make backwards * compatible bug fixes - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public int patchVersion() { return this.patch; @@ -679,8 +671,8 @@ public final class SemanticVersionNumber /** * @return identifiers describing this pre-release (empty if not a * pre-release) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public List<String> preReleaseIdentifiers() { return Collections.unmodifiableList(this.preReleaseIdentifiers); @@ -697,13 +689,13 @@ public final class SemanticVersionNumber * For example, the version with major number 3, minor number 2, patch number * 1, pre-release identifiers "alpha" and "1" and build metadata "2022-02-19" * has a string representation "3.2.1-alpha.1+2022-02-19". - * + * * @since v0.4.0 * @see <a href="https://semver.org">The official SemVer specification</a> */ @Override public String toString() { - String versionString = String.format("%d.%d.%d", this.major, this.minor, + var versionString = String.format("%d.%d.%d", this.major, this.minor, this.patch); if (!this.preReleaseIdentifiers.isEmpty()) { versionString += "-" + String.join(".", this.preReleaseIdentifiers); diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index 66d8103..24ada20 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2020 Adrien Hopkins + * Copyright (C) 2020-2022, 2024, 2025 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 @@ -19,7 +19,6 @@ package sevenUnits.utils; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Objects; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -29,56 +28,59 @@ import java.util.regex.Pattern; * arguments is null. * * @since 2020-09-07 + * @since v0.3.0 */ public final class UncertainDouble implements Comparable<UncertainDouble> { - /** - * The exact value 0 - */ + /** 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 - */ + /** 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. + * + * @param s string to parse + * @return parsed {@code UncertainDouble} + * * @throws NumberFormatException if the argument is not a number * * @since 2022-04-18 + * @since v0.4.0 */ - public static final UncertainDouble fromRoundedString(String s) { - final BigDecimal value = new BigDecimal(s); - final double uncertainty = Math.pow(10, -value.scale()); + public static UncertainDouble fromRoundedString(String s) { + final var value = new BigDecimal(s); + final var 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. + * Parses a string in the form of + * {@link UncertainDouble#toString(boolean, RoundingMode)} 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 * @throws IllegalArgumentException if the string is invalid * @since 2020-09-07 + * @since v0.3.0 */ - public static final UncertainDouble fromString(String s) { + public static UncertainDouble fromString(String s) { Objects.requireNonNull(s, "s may not be null"); - final Matcher matcher = TO_STRING.matcher(s); + final var matcher = TO_STRING.matcher(s); if (!matcher.matches()) throw new IllegalArgumentException( - "Could not parse stirng \"" + s + "\"."); + "Could not parse string \"" + s + "\"."); double value, uncertainty; try { @@ -88,7 +90,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { "String " + s + " not in correct format."); } - final String uncertaintyString = matcher.group(2); + final var uncertaintyString = matcher.group(2); if (uncertaintyString == null) { uncertainty = 0; } else { @@ -106,20 +108,32 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Gets an {@code UncertainDouble} from its value and <b>absolute</b> * uncertainty. - * + * + * @param value double's value + * @param uncertainty double's uncertainty (non-negative) + * @return {@code UncertainDouble} instance with these parameters + * * @since 2020-09-07 + * @since v0.3.0 */ - public static final UncertainDouble of(double value, double uncertainty) { + public static UncertainDouble of(double value, double uncertainty) { return new UncertainDouble(value, uncertainty); } /** * Gets an {@code UncertainDouble} from its value and <b>relative</b> * uncertainty. - * + * + * @param value double's value + * @param relativeUncertainty double's uncertainty (non-negative); the + * absolute uncertainty is equal to this value + * multiplied by {@code relativeUncertainty} + * @return {@code UncertainDouble} instance with these parameters + * * @since 2020-09-07 + * @since v0.3.0 */ - public static final UncertainDouble ofRelative(double value, + public static UncertainDouble ofRelative(double value, double relativeUncertainty) { return new UncertainDouble(value, value * relativeUncertainty); } @@ -132,6 +146,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * @param value * @param uncertainty * @since 2020-09-07 + * @since v0.3.0 */ private UncertainDouble(double value, double uncertainty) { this.value = value; @@ -153,16 +168,20 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * {@code false}. */ @Override - public final int compareTo(UncertainDouble o) { + public int compareTo(UncertainDouble o) { return Double.compare(this.value, o.value); } /** * Returns the quotient of {@code this} and {@code other}. - * + * + * @param other number to divide by + * @return quotient + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble dividedBy(UncertainDouble other) { + public UncertainDouble dividedBy(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.ofRelative(this.value / other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); @@ -170,23 +189,26 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Returns the quotient of {@code this} and the exact value {@code other}. - * + * + * @param other number to divide by + * @return quotient + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble dividedByExact(double other) { + public UncertainDouble dividedByExact(double other) { return UncertainDouble.of(this.value / other, this.uncertainty / other); } @Override - public final boolean equals(Object obj) { + public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof UncertainDouble)) return false; - final UncertainDouble other = (UncertainDouble) obj; - if (Double.compare(this.value, other.value) != 0) - return false; - if (Double.compare(this.uncertainty, other.uncertainty) != 0) + final var other = (UncertainDouble) obj; + if ((Double.compare(this.value, other.value) != 0) + || (Double.compare(this.uncertainty, other.uncertainty) != 0)) return false; return true; } @@ -196,8 +218,9 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * @return true iff this and {@code other} are within each other's * uncertainty range. * @since 2020-09-07 + * @since v0.3.0 */ - public final boolean equivalent(UncertainDouble other) { + public boolean equivalent(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return Math.abs(this.value - other.value) <= Math.min(this.uncertainty, other.uncertainty); @@ -205,10 +228,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Gets the preferred scale for rounding a value for toString. - * + * * @since 2020-09-07 + * @since v0.3.0 */ - private final int getDisplayScale() { + private int getDisplayScale() { // round based on uncertainty // if uncertainty starts with 1 (ignoring zeroes and the decimal // point), rounds @@ -216,24 +240,23 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { // otherwise, rounds so that uncertainty has 1 significant digits. // the value is rounded to the same number of decimal places as the // uncertainty. - final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); + final var bigUncertainty = BigDecimal.valueOf(this.uncertainty); // the scale that will give the uncertainty two decimal places - final int twoDecimalPlacesScale = bigUncertainty.scale() + final var twoDecimalPlacesScale = bigUncertainty.scale() - bigUncertainty.precision() + 2; - final BigDecimal roundedUncertainty = bigUncertainty + final var roundedUncertainty = bigUncertainty .setScale(twoDecimalPlacesScale, RoundingMode.HALF_EVEN); if (roundedUncertainty.unscaledValue().intValue() >= 20) return twoDecimalPlacesScale - 1; // one decimal place - else - return twoDecimalPlacesScale; + return twoDecimalPlacesScale; } @Override - public final int hashCode() { - final int prime = 31; - int result = 1; + public int hashCode() { + final var prime = 31; + var result = 1; result = prime * result + Double.hashCode(this.value); result = prime * result + Double.hashCode(this.uncertainty); return result; @@ -241,19 +264,24 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * @return true iff the value has no uncertainty - * + * * @since 2020-09-07 + * @since v0.3.0 */ - public final boolean isExact() { + public boolean isExact() { return this.uncertainty == 0; } /** * Returns the difference of {@code this} and {@code other}. - * + * + * @param other number to subtract + * @return result of subtraction + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble minus(UncertainDouble other) { + public UncertainDouble minus(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.of(this.value - other.value, Math.hypot(this.uncertainty, other.uncertainty)); @@ -261,19 +289,27 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Returns the difference of {@code this} and the exact value {@code other}. - * + * + * @param other number to subtract + * @return result of subtraction + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble minusExact(double other) { + public UncertainDouble minusExact(double other) { return UncertainDouble.of(this.value - other, this.uncertainty); } /** * Returns the sum of {@code this} and {@code other}. - * + * + * @param other number to add + * @return result of addition + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble plus(UncertainDouble other) { + public UncertainDouble plus(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.of(this.value + other.value, Math.hypot(this.uncertainty, other.uncertainty)); @@ -281,27 +317,36 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Returns the sum of {@code this} and the exact value {@code other}. - * + * + * @param other number to add + * @return result of addition + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble plusExact(double other) { + public UncertainDouble plusExact(double other) { return UncertainDouble.of(this.value + other, this.uncertainty); } /** * @return relative uncertainty * @since 2020-09-07 + * @since v0.3.0 */ - public final double relativeUncertainty() { + public double relativeUncertainty() { return this.uncertainty / this.value; } /** * Returns the product of {@code this} and {@code other}. - * + * + * @param other number to multiply + * @return product + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble times(UncertainDouble other) { + public UncertainDouble times(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.ofRelative(this.value * other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); @@ -309,23 +354,31 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Returns the product of {@code this} and the exact value {@code other}. - * + * + * @param other number to multiply + * @return product + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble timesExact(double other) { + public 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}. - * + * + * @param other exponent + * @return result of exponentation + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble toExponent(UncertainDouble other) { + public 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( + final var result = Math.pow(this.value, other.value); + final var relativeUncertainty = Math.hypot( other.value * this.relativeUncertainty(), Math.log(this.value) * other.uncertainty); @@ -335,10 +388,14 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Returns the result of {@code this} raised the exact exponent * {@code other}. - * + * + * @param other exponent + * @return result of exponentation + * * @since 2020-09-07 + * @since v0.3.0 */ - public final UncertainDouble toExponentExact(double other) { + public UncertainDouble toExponentExact(double other) { return UncertainDouble.ofRelative(Math.pow(this.value, other), this.relativeUncertainty() * other); } @@ -346,23 +403,24 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { /** * Returns a string representation of this {@code UncertainDouble}. * <p> - * This method returns the same value as {@link #toString(boolean)}, but - * {@code showUncertainty} is true if and only if the uncertainty is - * non-zero. - * + * This method returns the same value as + * {@link #toString(boolean, RoundingMode)}, but {@code showUncertainty} is + * true if and only if the uncertainty is non-zero. + * * <p> * 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(-5.01, 0).toString() = "-5.01" * </pre> - * + * * @since 2020-09-07 + * @since v0.3.0 */ @Override - public final String toString() { + public String toString() { return this.toString(!this.isExact(), RoundingMode.HALF_EVEN); } @@ -370,7 +428,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * 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 @@ -382,20 +440,24 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * digits otherwise it will be rounded to one significant digit. * <p> * Examples: - * + * * <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> - * + * + * @param showUncertainty uncertainty is only shown if this parameter is true + * @param roundingMode how to round values + * @return string representation of this {@code UncertainDouble} + * * @since 2020-09-07 + * @since v0.3.0 */ - public final String toString(boolean showUncertainty, - RoundingMode roundingMode) { + public String toString(boolean showUncertainty, RoundingMode roundingMode) { String valueString, uncertaintyString; // generate the string representation of value and uncertainty @@ -405,36 +467,37 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { } else { // round the value and uncertainty according to getDisplayScale() - final BigDecimal bigValue = BigDecimal.valueOf(this.value); - final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); + final var bigValue = BigDecimal.valueOf(this.value); + final var bigUncertainty = BigDecimal.valueOf(this.uncertainty); - final int displayScale = this.getDisplayScale(); - final BigDecimal roundedUncertainty = bigUncertainty - .setScale(displayScale, roundingMode); - final BigDecimal roundedValue = bigValue.setScale(displayScale, + final var displayScale = this.getDisplayScale(); + final var roundedUncertainty = bigUncertainty.setScale(displayScale, roundingMode); + final var 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 + * @since v0.3.0 */ - public final double uncertainty() { + public double uncertainty() { return this.uncertainty; } /** * @return value without uncertainty * @since 2020-09-07 + * @since v0.3.0 */ - public final double value() { + public double value() { return this.value; } } diff --git a/src/main/java/sevenUnits/utils/package-info.java b/src/main/java/sevenUnits/utils/package-info.java index 350c62d..b600c17 100644 --- a/src/main/java/sevenUnits/utils/package-info.java +++ b/src/main/java/sevenUnits/utils/package-info.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018-2020 Adrien Hopkins + * Copyright (C) 2018-2025 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 @@ -17,7 +17,7 @@ /** * Supplementary classes that are not related to units, but are necessary for * their function. - * + * * @author Adrien Hopkins * @since 2019-03-14 * @since v0.2.0 |