From 0245594222bfa0bd9a47d8326ed323c7356ac27c Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 27 Aug 2020 07:06:35 -0500 Subject: Added Complex Repetition. --- .../math/ConditionalExistenceCollections.java | 295 +++++++++++++-------- 1 file changed, 178 insertions(+), 117 deletions(-) (limited to 'src/org/unitConverter/math') diff --git a/src/org/unitConverter/math/ConditionalExistenceCollections.java b/src/org/unitConverter/math/ConditionalExistenceCollections.java index 9522885..ac1c0cf 100644 --- a/src/org/unitConverter/math/ConditionalExistenceCollections.java +++ b/src/org/unitConverter/math/ConditionalExistenceCollections.java @@ -30,20 +30,25 @@ import java.util.function.Predicate; /** * Elements in these wrapper collections only exist if they pass a condition. *

- * All of the collections in this class are "views" of the provided collections. They are mutable if the provided - * collections are mutable, they allow null if the provided collections allow null, they will reflect changes in the + * All of the collections in this class are "views" of the provided collections. + * They are mutable if the provided collections are mutable, they allow null if + * the provided collections allow null, they will reflect changes in the * provided collection, etc. *

- * The modification operations will always run the corresponding operations, even if the conditional existence - * collection doesn't change. For example, if you have a set that ignores even numbers, add(2) will still add a 2 to the + * The modification operations will always run the corresponding operations, + * even if the conditional existence collection doesn't change. For example, if + * you have a set that ignores even numbers, add(2) will still add a 2 to the * backing set (but the conditional existence set will say it doesn't exist). *

- * The returned collections do not pass the hashCode and equals operations through to the backing collections, - * but rely on {@code Object}'s {@code equals} and {@code hashCode} methods. This is necessary to preserve the contracts - * of these operations in the case that the backing collections are sets or lists. + * The returned collections do not pass the hashCode and equals + * operations through to the backing collections, but rely on {@code Object}'s + * {@code equals} and {@code hashCode} methods. This is necessary to preserve + * the contracts of these operations in the case that the backing collections + * are sets or lists. *

- * Other than that, 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. + * Other than that, 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. * * * @author Adrien Hopkins @@ -56,13 +61,13 @@ public final class ConditionalExistenceCollections { * * @author Adrien Hopkins * @since 2019-10-17 - * @param - * type of element in collection + * @param type of element in collection */ - static final class ConditionalExistenceCollection extends AbstractCollection { + static final class ConditionalExistenceCollection + extends AbstractCollection { final Collection collection; final Predicate existenceCondition; - + /** * Creates the {@code ConditionalExistenceCollection}. * @@ -70,67 +75,89 @@ public final class ConditionalExistenceCollections { * @param existenceCondition * @since 2019-10-17 */ - private ConditionalExistenceCollection(final Collection collection, final Predicate existenceCondition) { + private ConditionalExistenceCollection(final Collection collection, + final Predicate existenceCondition) { 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 + // 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 iterator() { - return conditionalExistenceIterator(this.collection.iterator(), this.existenceCondition); + 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 execute + // remove() must be first in the && statement, otherwise it may not + // execute 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(); + 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[] 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. * * @author Adrien Hopkins * @since 2019-10-17 - * @param - * type of elements in iterator + * @param type of elements in iterator */ static final class ConditionalExistenceIterator implements Iterator { final Iterator iterator; final Predicate existenceCondition; E nextElement; boolean hasNext; - + /** * Creates the {@code ConditionalExistenceIterator}. * @@ -138,12 +165,13 @@ public final class ConditionalExistenceCollections { * @param condition * @since 2019-10-17 */ - private ConditionalExistenceIterator(final Iterator iterator, final Predicate condition) { + private ConditionalExistenceIterator(final Iterator iterator, + final Predicate condition) { this.iterator = iterator; this.existenceCondition = condition; this.getAndSetNextElement(); } - + /** * Gets the next element, and sets nextElement and hasNext accordingly. * @@ -160,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()) { @@ -175,27 +203,25 @@ 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. * * @author Adrien Hopkins * @since 2019-10-17 - * @param - * key type - * @param - * value type + * @param key type + * @param value type */ static final class ConditionalExistenceMap extends AbstractMap { Map map; Predicate> entryExistenceCondition; - + /** * Creates the {@code ConditionalExistenceMap}. * @@ -203,205 +229,240 @@ public final class ConditionalExistenceCollections { * @param entryExistenceCondition * @since 2019-10-17 */ - private ConditionalExistenceMap(final Map map, final Predicate> entryExistenceCondition) { + private ConditionalExistenceMap(final Map map, + final Predicate> entryExistenceCondition) { 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 entry = new SimpleEntry<>(keyAsK, value); return this.entryExistenceCondition.test(entry); } - + @Override public Set> entrySet() { - return conditionalExistenceSet(this.map.entrySet(), this.entryExistenceCondition); + 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 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 keySet() { - // maybe change this to use ConditionalExistenceSet - return super.keySet(); + return conditionalExistenceSet(super.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 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 values() { // maybe change this to use ConditionalExistenceCollection return super.values(); } - } - + /** * Elements in this set only exist if a certain condition is true. * * @author Adrien Hopkins * @since 2019-10-17 - * @param - * type of element in set + * @param type of element in set */ static final class ConditionalExistenceSet extends AbstractSet { private final Set set; private final Predicate existenceCondition; - + /** * Creates the {@code ConditionalNonexistenceSet}. * - * @param set - * set to use - * @param existenceCondition - * condition where element exists + * @param set set to use + * @param existenceCondition condition where element exists * @since 2019-10-17 */ - private ConditionalExistenceSet(final Set set, final Predicate existenceCondition) { + private ConditionalExistenceSet(final Set set, + final Predicate existenceCondition) { this.set = set; this.existenceCondition = existenceCondition; } - + /** * {@inheritDoc} *

- * Note that this method returns {@code false} if {@code e} does not pass the existence condition. + * Note that this method returns {@code false} if {@code e} does not pass + * the existence condition. */ @Override 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 + // 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 iterator() { - return conditionalExistenceIterator(this.set.iterator(), this.existenceCondition); + 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 execute + // remove() must be first in the && statement, otherwise it may not + // execute 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[] 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. + * Elements in the returned wrapper collection are ignored if they don't pass + * a condition. * - * @param - * type of elements in collection - * @param collection - * collection to wrap - * @param existenceCondition - * elements only exist if this returns true + * @param 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 */ - public static final Collection conditionalExistenceCollection(final Collection collection, + public static final Collection conditionalExistenceCollection( + final Collection collection, final Predicate existenceCondition) { - return new ConditionalExistenceCollection<>(collection, existenceCondition); + return new ConditionalExistenceCollection<>(collection, + existenceCondition); } - + /** - * Elements in the returned wrapper iterator are ignored if they don't pass a condition. + * Elements in the returned wrapper iterator are ignored if they don't pass a + * condition. * - * @param - * type of elements in iterator - * @param iterator - * iterator to wrap - * @param existenceCondition - * elements only exist if this returns true + * @param 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 */ - public static final Iterator conditionalExistenceIterator(final Iterator iterator, - final Predicate existenceCondition) { + public static final Iterator conditionalExistenceIterator( + final Iterator iterator, final Predicate existenceCondition) { return new ConditionalExistenceIterator<>(iterator, existenceCondition); } - + /** - * Mappings in the returned wrapper map are ignored if the corresponding entry doesn't pass a condition + * Mappings in the returned wrapper map are ignored if the corresponding + * entry doesn't pass a condition * - * @param - * type of key in map - * @param - * type of value in map - * @param map - * map to wrap - * @param entryExistenceCondition - * mappings only exist if this returns true + * @param type of key in map + * @param 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 */ - public static final Map conditionalExistenceMap(final Map map, + public static final Map conditionalExistenceMap( + final Map map, final Predicate> entryExistenceCondition) { return new ConditionalExistenceMap<>(map, entryExistenceCondition); } - + /** - * Elements in the returned wrapper set are ignored if they don't pass a condition. + * Elements in the returned wrapper set are ignored if they don't pass a + * condition. * - * @param - * type of elements in set - * @param set - * set to wrap - * @param existenceCondition - * elements only exist if this returns true + * @param 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 */ - public static final Set conditionalExistenceSet(final Set set, final Predicate existenceCondition) { + public static final Set conditionalExistenceSet(final Set set, + final Predicate existenceCondition) { return new ConditionalExistenceSet<>(set, existenceCondition); } } -- cgit v1.2.3 From 5a7e8f6fcb175b238eb1d5481513b35039107a3e Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Mon, 7 Sep 2020 15:15:20 -0500 Subject: Created an UncertainDouble class for uncertainty operations. --- .settings/org.eclipse.jdt.core.prefs | 100 ++++++ src/org/unitConverter/math/DecimalComparison.java | 207 +++++++---- src/org/unitConverter/math/UncertainDouble.java | 411 ++++++++++++++++++++++ src/org/unitConverter/unit/LinearUnit.java | 302 +++++++++------- src/org/unitConverter/unit/LinearUnitValue.java | 252 +++---------- src/org/unitConverter/unit/UnitDatabase.java | 6 +- src/org/unitConverter/unit/UnitTest.java | 7 +- 7 files changed, 885 insertions(+), 400 deletions(-) create mode 100644 src/org/unitConverter/math/UncertainDouble.java (limited to 'src/org/unitConverter/math') diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index ea7a397..f77f6a1 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,13 @@ eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve @@ -6,11 +15,102 @@ org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.APILeak=warning +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=error +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=info +org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=info +org.eclipse.jdt.core.compiler.problem.rawTypeReference=error +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=info +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=info +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=info +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=info org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=info +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.terminalDeprecation=error +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled +org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=info +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=info +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=disabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=info +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=info +org.eclipse.jdt.core.compiler.problem.unusedParameter=info +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=info +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=info +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled org.eclipse.jdt.core.compiler.source=1.8 diff --git a/src/org/unitConverter/math/DecimalComparison.java b/src/org/unitConverter/math/DecimalComparison.java index 859e8da..0f5b91e 100644 --- a/src/org/unitConverter/math/DecimalComparison.java +++ b/src/org/unitConverter/math/DecimalComparison.java @@ -27,42 +27,45 @@ import java.math.BigDecimal; */ 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. + * 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 */ 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. + * 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 */ public static final float FLOAT_EPSILON = 1.0e-6f; - + /** * Tests for equality of double values using {@link #DOUBLE_EPSILON}. *

- * WARNING: this method is not technically transitive. If a and b are off by slightly less than - * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than - * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c) - * will return false. However, this situation is very unlikely to ever happen in a real programming situation. + * WARNING: this method is not technically transitive. If a + * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, + * and b and c are off by slightly less than + * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) + * will both return true, but equals(a, c) will return false. However, this + * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    - *
  1. Raise the value of epsilon using {@link #equals(double, double, double)} (this does not make a violation of - * transitivity impossible, it just significantly reduces the chances of it happening) - *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a violation of transitivity 100% impossible) + *
  3. Raise the value of epsilon using + * {@link #equals(double, double, double)} (this does not make a violation of + * transitivity impossible, it just significantly reduces the chances of it + * happening) + *
  4. Use {@link BigDecimal} instead of {@code double} (this will make a + * violation of transitivity 100% impossible) *
* - * @param a - * first value to test - * @param b - * second value to test + * @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 @@ -71,57 +74,61 @@ 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. * *

- * WARNING: this method is not technically transitive. If a and b are off by slightly less than - * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than - * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c) - * will return false. However, this situation is very unlikely to ever happen in a real programming situation. + * WARNING: this method is not technically transitive. If a + * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, + * and b and c are off by slightly less than + * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) + * will both return true, but equals(a, c) will return false. However, this + * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    - *
  1. Raise the value of epsilon (this does not make a violation of transitivity impossible, it just significantly - * reduces the chances of it happening) - *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a violation of transitivity 100% impossible) + *
  3. Raise the value of epsilon (this does not make a violation of + * transitivity impossible, it just significantly reduces the chances of it + * happening) + *
  4. Use {@link BigDecimal} instead of {@code double} (this will make a + * violation of transitivity 100% impossible) *
* - * @param a - * first value to test - * @param b - * second value to test - * @param epsilon - * allowed difference + * @param a first value to test + * @param b second value to test + * @param epsilon allowed difference * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final double a, final double b, final double epsilon) { + public static final 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}. * *

- * WARNING: this method is not technically transitive. If a and b are off by slightly less than - * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than - * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c) - * will return false. However, this situation is very unlikely to ever happen in a real programming situation. + * WARNING: this method is not technically transitive. If a + * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, + * and b and c are off by slightly less than + * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) + * will both return true, but equals(a, c) will return false. However, this + * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    - *
  1. Raise the value of epsilon using {@link #equals(float, float, float)} (this does not make a violation of - * transitivity impossible, it just significantly reduces the chances of it happening) - *
  2. Use {@link BigDecimal} instead of {@code float} (this will make a violation of transitivity 100% impossible) + *
  3. Raise the value of epsilon using {@link #equals(float, float, float)} + * (this does not make a violation of transitivity impossible, it just + * significantly reduces the chances of it happening) + *
  4. Use {@link BigDecimal} instead of {@code float} (this will make a + * violation of transitivity 100% impossible) *
* - * @param a - * first value to test - * @param b - * second value to test + * @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 @@ -129,53 +136,121 @@ 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. * *

- * WARNING: this method is not technically transitive. If a and b are off by slightly less than - * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than - * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c) - * will return false. However, this situation is very unlikely to ever happen in a real programming situation. + * WARNING: this method is not technically transitive. If a + * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, + * and b and c are off by slightly less than + * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) + * will both return true, but equals(a, c) will return false. However, this + * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    - *
  1. Raise the value of epsilon (this does not make a violation of transitivity impossible, it just significantly - * reduces the chances of it happening) - *
  2. Use {@link BigDecimal} instead of {@code float} (this will make a violation of transitivity 100% impossible) + *
  3. Raise the value of epsilon (this does not make a violation of + * transitivity impossible, it just significantly reduces the chances of it + * happening) + *
  4. Use {@link BigDecimal} instead of {@code float} (this will make a + * violation of transitivity 100% impossible) *
* - * @param a - * first value to test - * @param b - * second value to test - * @param epsilon - * allowed difference + * @param a first value to test + * @param b second value to test + * @param epsilon allowed difference * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final float a, final float b, final float epsilon) { + public static final 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)); } - + + /** + * Tests for equality of {@code UncertainDouble} values using + * {@link #DOUBLE_EPSILON}. + *

+ * WARNING: this method is not technically transitive. If a + * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, + * and b and c are off by slightly less than + * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) + * will both return true, but equals(a, c) will return false. However, this + * situation is very unlikely to ever happen in a real programming situation. + *

+ * If this does become a concern, some ways to solve this problem: + *

    + *
  1. Raise the value of epsilon using + * {@link #equals(UncertainDouble, UncertainDouble, double)} (this does not + * make a violation of transitivity impossible, it just significantly reduces + * the chances of it happening) + *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a + * violation of transitivity 100% impossible) + *
+ * + * @param a first value to test + * @param b second value to test + * @return whether they are equal + * @since 2020-09-07 + * @see #hashCode(double) + */ + public static final boolean equals(final UncertainDouble a, + final UncertainDouble b) { + return DecimalComparison.equals(a.value(), b.value()) + && DecimalComparison.equals(a.uncertainty(), b.uncertainty()); + } + + /** + * Tests for {@code UncertainDouble} equality using a custom epsilon value. + * + *

+ * WARNING: this method is not technically transitive. If a + * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, + * and b and c are off by slightly less than + * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) + * will both return true, but equals(a, c) will return false. However, this + * situation is very unlikely to ever happen in a real programming situation. + *

+ * If this does become a concern, some ways to solve this problem: + *

    + *
  1. Raise the value of epsilon (this does not make a violation of + * transitivity impossible, it just significantly reduces the chances of it + * happening) + *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a + * violation of transitivity 100% impossible) + *
+ * + * @param a first value to test + * @param b second value to test + * @param epsilon allowed difference + * @return whether they are equal + * @since 2019-03-18 + * @since v0.2.0 + */ + public static final 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. + * 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 + * @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/org/unitConverter/math/UncertainDouble.java b/src/org/unitConverter/math/UncertainDouble.java new file mode 100644 index 0000000..e948df9 --- /dev/null +++ b/src/org/unitConverter/math/UncertainDouble.java @@ -0,0 +1,411 @@ +/** + * @since 2020-09-07 + */ +package org.unitConverter.math; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A double with an associated uncertainty value. For example, 3.2 ± 0.2. + *

+ * All methods in this class throw a NullPointerException if any of their + * arguments is null. + * + * @since 2020-09-07 + */ +public final class UncertainDouble implements Comparable { + /** + * The exact value 0 + */ + public static final UncertainDouble ZERO = UncertainDouble.of(0, 0); + + /** + * A regular expression that can recognize toString forms + */ + private static final Pattern TO_STRING = Pattern + .compile("([a-zA-Z_0-9\\.\\,]+)" // a number + // optional "± [number]" + + "(?:\\s*(?:±|\\+-)\\s*([a-zA-Z_0-9\\.\\,]+))?"); + + /** + * Parses a string in the form of {@link UncertainDouble#toString(boolean)} + * and returns the corresponding {@code UncertainDouble} instance. + *

+ * This method allows some alternative forms of the string representation, + * 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 + */ + public static final UncertainDouble fromString(String s) { + Objects.requireNonNull(s, "s may not be null"); + final Matcher matcher = TO_STRING.matcher(s); + + double value, uncertainty; + try { + value = Double.parseDouble(matcher.group(1)); + } catch (IllegalStateException | NumberFormatException e) { + throw new IllegalArgumentException( + "String " + s + " not in correct format."); + } + + final String uncertaintyString = matcher.group(2); + if (uncertaintyString == null) { + uncertainty = 0; + } else { + try { + uncertainty = Double.parseDouble(uncertaintyString); + } catch (final NumberFormatException e) { + throw new IllegalArgumentException( + "String " + s + " not in correct format."); + } + } + + return UncertainDouble.of(value, uncertainty); + } + + /** + * Gets an {@code UncertainDouble} from its value and absolute + * uncertainty. + * + * @since 2020-09-07 + */ + public static final UncertainDouble of(double value, double uncertainty) { + return new UncertainDouble(value, uncertainty); + } + + /** + * Gets an {@code UncertainDouble} from its value and relative + * uncertainty. + * + * @since 2020-09-07 + */ + public static final UncertainDouble ofRelative(double value, + double relativeUncertainty) { + return new UncertainDouble(value, value * relativeUncertainty); + } + + private final double value; + + private final double uncertainty; + + /** + * @param value + * @param uncertainty + * @since 2020-09-07 + */ + private UncertainDouble(double value, double uncertainty) { + this.value = value; + // uncertainty should only ever be positive + this.uncertainty = Math.abs(uncertainty); + } + + /** + * Compares this {@code UncertainDouble} with another + * {@code UncertainDouble}. + *

+ * 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. + *

+ * Note: The natural ordering of this class is inconsistent with + * equals. Specifically, if two {@code UncertainDouble} instances {@code a} + * and {@code b} have the same value but different uncertainties, + * {@code a.compareTo(b)} will return 0 but {@code a.equals(b)} will return + * {@code false}. + */ + @Override + public final int compareTo(UncertainDouble o) { + return Double.compare(this.value, o.value); + } + + /** + * Returns the quotient of {@code this} and {@code other}. + * + * @since 2020-09-07 + */ + public final 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())); + } + + /** + * Returns the quotient of {@code this} and the exact value {@code other}. + * + * @since 2020-09-07 + */ + 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) + return true; + if (!(obj instanceof UncertainDouble)) + return false; + final UncertainDouble other = (UncertainDouble) obj; + if (Double.doubleToLongBits(this.uncertainty) != Double + .doubleToLongBits(other.uncertainty)) + return false; + if (Double.doubleToLongBits(this.value) != Double + .doubleToLongBits(other.value)) + return false; + return true; + } + + /** + * @param other another {@code UncertainDouble} + * @return true iff this and {@code other} are within each other's + * uncertainty range. + * @since 2020-09-07 + */ + public final 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); + } + + /** + * Gets the preferred scale for rounding a value for toString. + * + * @since 2020-09-07 + */ + private final int getDisplayScale() { + // round based on uncertainty + // if uncertainty starts with 1 (ignoring zeroes and the decimal + // point), rounds + // so that uncertainty has 2 significant digits. + // otherwise, rounds so that uncertainty has 1 significant digits. + // the value is rounded to the same number of decimal places as the + // uncertainty. + 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; + int result = 1; + long temp; + temp = Double.doubleToLongBits(this.uncertainty); + result = prime * result + (int) (temp ^ temp >>> 32); + temp = Double.doubleToLongBits(this.value); + result = prime * result + (int) (temp ^ temp >>> 32); + return result; + } + + /** + * @return true iff the value has no uncertainty + * + * @since 2020-09-07 + */ + public final boolean isExact() { + return this.uncertainty == 0; + } + + /** + * Returns the difference of {@code this} and {@code other}. + * + * @since 2020-09-07 + */ + public final 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)); + } + + /** + * Returns the difference of {@code this} and the exact value {@code other}. + * + * @since 2020-09-07 + */ + public final UncertainDouble minusExact(double other) { + return UncertainDouble.of(this.value - other, this.uncertainty); + } + + /** + * Returns the sum of {@code this} and {@code other}. + * + * @since 2020-09-07 + */ + public final 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)); + } + + /** + * Returns the sum of {@code this} and the exact value {@code other}. + * + * @since 2020-09-07 + */ + public final UncertainDouble plusExact(double other) { + return UncertainDouble.of(this.value + other, this.uncertainty); + } + + /** + * @return relative uncertainty + * @since 2020-09-07 + */ + public final double relativeUncertainty() { + return this.uncertainty / this.value; + } + + /** + * Returns the product of {@code this} and {@code other}. + * + * @since 2020-09-07 + */ + public final 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())); + } + + /** + * Returns the product of {@code this} and the exact value {@code other}. + * + * @since 2020-09-07 + */ + 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}. + * + * @since 2020-09-07 + */ + 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}. + * + * @since 2020-09-07 + */ + public final UncertainDouble toExponentExact(double other) { + return UncertainDouble.ofRelative(Math.pow(this.value, other), + this.relativeUncertainty() * other); + } + + /** + * Returns a string representation of this {@code UncertainDouble}. + *

+ * This method returns the same value as {@link #toString(boolean)}, but + * {@code showUncertainty} is true if and only if the uncertainty is + * non-zero. + * + *

+ * Examples: + * + *

+	 * 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"
+	 * 
+ * + * @since 2020-09-07 + */ + @Override + public final String toString() { + return this.toString(!this.isExact()); + } + + /** + * Returns a string representation of this {@code UncertainDouble}. + *

+ * 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" + *

+ * VALUE represents a string representation of this {@code UncertainDouble}'s + * value. If the uncertainty is non-zero, the string will be rounded to the + * same precision as the uncertainty, otherwise it will not be rounded. The + * string is still rounded if {@code showUncertainty} is false.
+ * UNCERTAINTY represents a string representation of this + * {@code UncertainDouble}'s uncertainty. If the uncertainty ends in 1X + * (where X represents any digit) it will be rounded to two significant + * digits otherwise it will be rounded to one significant digit. + *

+ * Examples: + * + *

+	 * 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.13).toString(false) = "3.27"
+	 * 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"
+	 * 
+ * + * @since 2020-09-07 + */ + public final String toString(boolean showUncertainty) { + 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.HALF_EVEN); + final BigDecimal roundedValue = bigValue.setScale(displayScale, + RoundingMode.HALF_EVEN); + + valueString = roundedValue.toString(); + uncertaintyString = roundedUncertainty.toString(); + } + + // return "value" or "value ± uncertainty" depending on showUncertainty + return valueString + (showUncertainty ? " ± " + uncertaintyString : ""); + } + + /** + * @return absolute uncertainty + * @since 2020-09-07 + */ + public final double uncertainty() { + return this.uncertainty; + } + + /** + * @return value without uncertainty + * @since 2020-09-07 + */ + public final double value() { + return this.value; + } +} diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java index 762572a..2d63ca7 100644 --- a/src/org/unitConverter/unit/LinearUnit.java +++ b/src/org/unitConverter/unit/LinearUnit.java @@ -20,89 +20,83 @@ import java.util.Objects; import org.unitConverter.math.DecimalComparison; import org.unitConverter.math.ObjectProduct; +import org.unitConverter.math.UncertainDouble; /** - * A unit that can be expressed as a product of its base and a number. For example, kilometres, inches and pounds. + * A unit that can be expressed as a product of its base and a number. For + * example, kilometres, inches and pounds. * * @author Adrien Hopkins * @since 2019-10-16 */ public final class LinearUnit extends Unit { /** - * 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' + * 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' * - * @param unit - * unit to convert - * @param value - * value to convert + * @param unit unit to convert + * @param value value to convert * @return value expressed as a {@code LinearUnit} * @since 2019-10-16 - * @throws NullPointerException - * if unit is null + * @throws NullPointerException if unit is null */ public static LinearUnit fromUnitValue(final Unit unit, final double value) { - return new LinearUnit(Objects.requireNonNull(unit, "unit must not be null.").getBase(), + return new LinearUnit( + 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' + * 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' * - * @param unit - * unit to convert - * @param value - * value to convert - * @param ns - * name(s) and symbol of unit + * @param unit unit to convert + * @param value value to convert + * @param ns name(s) and symbol of unit * @return value expressed as a {@code LinearUnit} * @since 2019-10-21 - * @throws NullPointerException - * if unit or ns is null + * @throws NullPointerException if unit or ns is null */ - public static LinearUnit fromUnitValue(final Unit unit, final double value, final NameSymbol ns) { - return new LinearUnit(Objects.requireNonNull(unit, "unit must not be null.").getBase(), + public static LinearUnit fromUnitValue(final Unit unit, final double value, + final NameSymbol ns) { + return new LinearUnit( + Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), ns); } - + /** - * Gets a {@code LinearUnit} from a unit base and a conversion factor. In other words, gets the product of - * {@code unitBase} and {@code conversionFactor}, expressed as a {@code LinearUnit}. + * Gets a {@code LinearUnit} from a unit base and a conversion factor. In + * other words, gets the product of {@code unitBase} and + * {@code conversionFactor}, expressed as a {@code LinearUnit}. * - * @param unitBase - * unit base to multiply by - * @param conversionFactor - * number to multiply base by + * @param unitBase unit base to multiply by + * @param conversionFactor number to multiply base by * @return product of base and conversion factor * @since 2019-10-16 - * @throws NullPointerException - * if unitBase is null + * @throws NullPointerException if unitBase is null */ - public static LinearUnit valueOf(final ObjectProduct unitBase, final double conversionFactor) { + public static LinearUnit valueOf(final ObjectProduct unitBase, + 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 {@code conversionFactor}, expressed as a {@code LinearUnit}. + * Gets a {@code LinearUnit} from a unit base and a conversion factor. In + * other words, gets the product of {@code unitBase} and + * {@code conversionFactor}, expressed as a {@code LinearUnit}. * - * @param unitBase - * unit base to multiply by - * @param conversionFactor - * number to multiply base by - * @param ns - * name(s) and symbol of unit + * @param unitBase unit base to multiply by + * @param conversionFactor number to multiply base by + * @param ns name(s) and symbol of unit * @return product of base and conversion factor * @since 2019-10-21 - * @throws NullPointerException - * if unitBase is null + * @throws NullPointerException if unitBase is null */ - public static LinearUnit valueOf(final ObjectProduct unitBase, final double conversionFactor, - final NameSymbol ns) { + public static LinearUnit valueOf(final ObjectProduct unitBase, + final double conversionFactor, final NameSymbol ns) { return new LinearUnit(unitBase, conversionFactor, ns); } - + /** * The value of this unit as represented in its base form. Mathematically, * @@ -113,21 +107,20 @@ public final class LinearUnit extends Unit { * @since 2019-10-16 */ private final double conversionFactor; - + /** * Creates the {@code LinearUnit}. * - * @param unitBase - * base of linear unit - * @param conversionFactor - * conversion factor between base and unit + * @param unitBase base of linear unit + * @param conversionFactor conversion factor between base and unit * @since 2019-10-16 */ - private LinearUnit(final ObjectProduct unitBase, final double conversionFactor, final NameSymbol ns) { + private LinearUnit(final ObjectProduct unitBase, + final double conversionFactor, final NameSymbol ns) { super(unitBase, ns); this.conversionFactor = conversionFactor; } - + /** * {@inheritDoc} * @@ -137,7 +130,32 @@ 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}. + * + * @param other unit to convert to + * @param value value to convert + * @return converted value + * @since 2019-09-07 + * @throws IllegalArgumentException if {@code other} is incompatible for + * conversion with this unit (as tested by + * {@link Unit#canConvertTo}). + * @throws NullPointerException if value or other is null + */ + public UncertainDouble convertTo(LinearUnit other, UncertainDouble value) { + Objects.requireNonNull(other, "other must not be null."); + Objects.requireNonNull(value, "value may not be null."); + if (this.canConvertTo(other)) + return value.timesExact( + this.getConversionFactor() / other.getConversionFactor()); + else + throw new IllegalArgumentException( + String.format("Cannot convert from %s to %s.", this, other)); + + } + /** * {@inheritDoc} * @@ -147,12 +165,20 @@ public final class LinearUnit extends Unit { protected double convertToBase(final double value) { return value * this.getConversionFactor(); } - + + /** + * Converts an {@code UncertainDouble} to the base unit. + * + * @since 2020-09-07 + */ + UncertainDouble convertToBase(final UncertainDouble value) { + return value.timesExact(this.getConversionFactor()); + } + /** * Divides this unit by a scalar. * - * @param divisor - * scalar to divide by + * @param divisor scalar to divide by * @return quotient * @since 2018-12-23 * @since v0.1.0 @@ -160,26 +186,26 @@ 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. * - * @param divisor - * unit to divide by + * @param divisor unit to divide by * @return quotient of two units - * @throws NullPointerException - * if {@code divisor} is null + * @throws NullPointerException if {@code divisor} is null * @since 2018-12-22 * @since v0.1.0 */ public LinearUnit dividedBy(final LinearUnit divisor) { Objects.requireNonNull(divisor, "other must not be null"); - + // divide the units - final ObjectProduct base = this.getBase().dividedBy(divisor.getBase()); - return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor()); + final ObjectProduct base = this.getBase() + .dividedBy(divisor.getBase()); + return valueOf(base, + this.getConversionFactor() / divisor.getConversionFactor()); } - + /** * {@inheritDoc} * @@ -191,9 +217,10 @@ public final class LinearUnit extends Unit { return false; final LinearUnit other = (LinearUnit) obj; return Objects.equals(this.getBase(), other.getBase()) - && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor()); + && DecimalComparison.equals(this.getConversionFactor(), + other.getConversionFactor()); } - + /** * @return conversion factor * @since 2019-10-16 @@ -201,7 +228,7 @@ public final class LinearUnit extends Unit { public double getConversionFactor() { return this.conversionFactor; } - + /** * {@inheritDoc} * @@ -209,18 +236,20 @@ public final class LinearUnit extends Unit { */ @Override public int hashCode() { - return 31 * this.getBase().hashCode() + DecimalComparison.hash(this.getConversionFactor()); + 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 + * @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there + * is a {@code BaseUnit b} where * {@code b.asLinearUnit().equals(this)} returns {@code true}.) * @since 2019-10-16 */ 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 @@ -228,70 +257,73 @@ public final class LinearUnit extends Unit { public boolean isCoherent() { return this.getConversionFactor() == 1; } - + /** * Returns the difference of this unit and another. *

- * Two units can be subtracted if they have the same base. Note that {@link #canConvertTo} can be used to determine - * this. If {@code subtrahend} does not meet this condition, an {@code IllegalArgumentException} will be thrown. + * Two units can be subtracted if they have the same base. Note that + * {@link #canConvertTo} can be used to determine this. If {@code subtrahend} + * does not meet this condition, an {@code IllegalArgumentException} will be + * thrown. *

* - * @param subtrahend - * unit to subtract + * @param subtrahend unit to subtract * @return difference of units - * @throws IllegalArgumentException - * if {@code subtrahend} is not compatible for subtraction as described above - * @throws NullPointerException - * if {@code subtrahend} is null + * @throws IllegalArgumentException if {@code subtrahend} is not compatible + * for subtraction as described above + * @throws NullPointerException if {@code subtrahend} is null * @since 2019-03-17 * @since v0.2.0 */ 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)); - + 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()); + return valueOf(this.getBase(), + this.getConversionFactor() - subtrahend.getConversionFactor()); } - + /** * Returns the sum of this unit and another. *

- * Two units can be added if they have the same base. Note that {@link #canConvertTo} can be used to determine this. - * If {@code addend} does not meet this condition, an {@code IllegalArgumentException} will be thrown. + * Two units can be added if they have the same base. Note that + * {@link #canConvertTo} can be used to determine this. If {@code addend} + * does not meet this condition, an {@code IllegalArgumentException} will be + * thrown. *

* - * @param addend - * unit to add + * @param addend unit to add * @return sum of units - * @throws IllegalArgumentException - * if {@code addend} is not compatible for addition as described above - * @throws NullPointerException - * if {@code addend} is null + * @throws IllegalArgumentException if {@code addend} is not compatible for + * addition as described above + * @throws NullPointerException if {@code addend} is null * @since 2019-03-17 * @since v0.2.0 */ 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)); - + 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()); + return valueOf(this.getBase(), + this.getConversionFactor() + addend.getConversionFactor()); } - + /** * Multiplies this unit by a scalar. * - * @param multiplier - * scalar to multiply by + * @param multiplier scalar to multiply by * @return product * @since 2018-12-23 * @since v0.1.0 @@ -299,39 +331,39 @@ 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. * - * @param multiplier - * unit to multiply by + * @param multiplier unit to multiply by * @return product of two units - * @throws NullPointerException - * if {@code multiplier} is null + * @throws NullPointerException if {@code multiplier} is null * @since 2018-12-22 * @since v0.1.0 */ public LinearUnit times(final LinearUnit multiplier) { Objects.requireNonNull(multiplier, "other must not be null"); - + // multiply the units - final ObjectProduct base = this.getBase().times(multiplier.getBase()); - return valueOf(base, this.getConversionFactor() * multiplier.getConversionFactor()); + final ObjectProduct base = this.getBase() + .times(multiplier.getBase()); + return valueOf(base, + this.getConversionFactor() * multiplier.getConversionFactor()); } - + /** * Returns this unit but to an exponent. * - * @param exponent - * exponent to exponentiate unit to + * @param exponent exponent to exponentiate unit to * @return exponentiated unit * @since 2019-01-15 * @since v0.1.0 */ public LinearUnit toExponent(final int exponent) { - return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); + return valueOf(this.getBase().toExponent(exponent), + Math.pow(this.conversionFactor, exponent)); } - + /** * @return a string providing a definition of this unit * @since 2019-10-21 @@ -339,10 +371,13 @@ public final class LinearUnit extends Unit { @Override public String toString() { return this.getPrimaryName().orElse("Unnamed unit") - + (this.getSymbol().isPresent() ? String.format(" (%s)", this.getSymbol().get()) : "") + ", " - + Double.toString(this.conversionFactor) + " * " + this.getBase().toString(u -> u.getSymbol().get()); + + (this.getSymbol().isPresent() + ? String.format(" (%s)", this.getSymbol().get()) + : "") + + ", " + Double.toString(this.conversionFactor) + " * " + + this.getBase().toString(u -> u.getSymbol().get()); } - + @Override public LinearUnit withName(final NameSymbol ns) { return valueOf(this.getBase(), this.getConversionFactor(), ns); @@ -351,37 +386,38 @@ public final class LinearUnit extends Unit { /** * Returns the result of applying {@code prefix} to this unit. *

- * If this unit and the provided prefix have a primary name, the returned unit will have a primary name (prefix's - * name + unit's name).
- * If this unit and the provided prefix have a symbol, the returned unit will have a symbol.
- * This method ignores alternate names of both this unit and the provided prefix. + * If this unit and the provided prefix have a primary name, the returned + * unit will have a primary name (prefix's name + unit's name).
+ * If this unit and the provided prefix have a symbol, the returned unit will + * have a symbol.
+ * This method ignores alternate names of both this unit and the provided + * prefix. * - * @param prefix - * prefix to apply + * @param prefix prefix to apply * @return unit with prefix * @since 2019-03-18 * @since v0.2.0 - * @throws NullPointerException - * if prefix is null + * @throws NullPointerException if prefix is null */ 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() && prefix.getPrimaryName().isPresent()) { + if (this.getPrimaryName().isPresent() + && prefix.getPrimaryName().isPresent()) { name = prefix.getPrimaryName().get() + this.getPrimaryName().get(); } 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/org/unitConverter/unit/LinearUnitValue.java b/src/org/unitConverter/unit/LinearUnitValue.java index 5685a6d..d86d344 100644 --- a/src/org/unitConverter/unit/LinearUnitValue.java +++ b/src/org/unitConverter/unit/LinearUnitValue.java @@ -3,12 +3,11 @@ */ package org.unitConverter.unit; -import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.Objects; import java.util.Optional; import org.unitConverter.math.DecimalComparison; +import org.unitConverter.math.UncertainDouble; /** * A possibly uncertain value expressed in a linear unit. @@ -33,7 +32,8 @@ public final class LinearUnitValue { public static final LinearUnitValue getExact(final LinearUnit unit, final double value) { return new LinearUnitValue( - Objects.requireNonNull(unit, "unit must not be null"), value, 0); + Objects.requireNonNull(unit, "unit must not be null"), + UncertainDouble.of(value, 0)); } /** @@ -46,41 +46,23 @@ public final class LinearUnitValue { * @since 2020-07-26 */ public static final LinearUnitValue of(final LinearUnit unit, - final double value, final double uncertainty) { + final UncertainDouble value) { return new LinearUnitValue( - Objects.requireNonNull(unit, "unit must not be null"), value, - uncertainty); - } - - /** - * Gets an uncertain {@code LinearUnitValue} - * - * @param unit unit to express with - * @param value value to express - * @param relativeUncertainty relative uncertainty of value - * @return uncertain {@code LinearUnitValue} instance - * @since 2020-07-28 - */ - public static final LinearUnitValue ofRelative(final LinearUnit unit, - final double value, final double relativeUncertainty) { - return LinearUnitValue.of(unit, value, relativeUncertainty * value); + Objects.requireNonNull(unit, "unit must not be null"), + Objects.requireNonNull(value, "value may not be null")); } private final LinearUnit unit; - private final double value; - private final double uncertainty; + private final UncertainDouble value; /** - * @param unit unit to express as - * @param value value to express - * @param uncertainty absolute uncertainty of value + * @param unit unit to express as + * @param value value to express * @since 2020-07-26 */ - private LinearUnitValue(final LinearUnit unit, final double value, - final double uncertainty) { + private LinearUnitValue(final LinearUnit unit, final UncertainDouble value) { this.unit = unit; this.value = value; - this.uncertainty = uncertainty; } /** @@ -89,7 +71,7 @@ public final class LinearUnitValue { * @since 2020-08-04 */ public final UnitValue asUnitValue() { - return UnitValue.of(this.unit, this.value); + return UnitValue.of(this.unit, this.value.value()); } /** @@ -110,8 +92,7 @@ public final class LinearUnitValue { * @since 2020-07-26 */ public final LinearUnitValue convertTo(final LinearUnit other) { - return LinearUnitValue.of(other, this.unit.convertTo(other, this.value), - this.unit.convertTo(other, this.uncertainty)); + return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); } /** @@ -122,8 +103,7 @@ public final class LinearUnitValue { * @since 2020-07-28 */ public LinearUnitValue dividedBy(final double divisor) { - return LinearUnitValue.of(this.unit, this.value / divisor, - this.uncertainty / divisor); + return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor)); } /** @@ -134,10 +114,8 @@ public final class LinearUnitValue { * @since 2020-07-28 */ public LinearUnitValue dividedBy(final LinearUnitValue divisor) { - return LinearUnitValue.ofRelative(this.unit.dividedBy(divisor.unit), - this.value / divisor.value, - Math.hypot(this.getRelativeUncertainty(), - divisor.getRelativeUncertainty())); + return LinearUnitValue.of(this.unit.dividedBy(divisor.unit), + this.value.dividedBy(divisor.value)); } /** @@ -154,12 +132,8 @@ public final class LinearUnitValue { return false; final LinearUnitValue other = (LinearUnitValue) obj; return Objects.equals(this.unit.getBase(), other.unit.getBase()) - && Double.doubleToLongBits( - this.unit.convertToBase(this.getValue())) == Double - .doubleToLongBits( - other.unit.convertToBase(other.getValue())) - && Double.doubleToLongBits(this.getRelativeUncertainty()) == Double - .doubleToLongBits(other.getRelativeUncertainty()); + && this.unit.convertToBase(this.value) + .equals(other.unit.convertToBase(other.value)); } /** @@ -180,9 +154,7 @@ public final class LinearUnitValue { final LinearUnitValue other = (LinearUnitValue) obj; return Objects.equals(this.unit.getBase(), other.unit.getBase()) && DecimalComparison.equals(this.unit.convertToBase(this.value), - other.unit.convertToBase(other.value)) - && DecimalComparison.equals(this.getRelativeUncertainty(), - other.getRelativeUncertainty()); + other.unit.convertToBase(other.value)); } /** @@ -195,32 +167,11 @@ public final class LinearUnitValue { if (other == null || !Objects.equals(this.unit.getBase(), other.unit.getBase())) return false; - final double thisBaseValue = this.unit.convertToBase(this.value); - final double otherBaseValue = other.unit.convertToBase(other.value); - final double thisBaseUncertainty = this.unit - .convertToBase(this.uncertainty); - final double otherBaseUncertainty = other.unit - .convertToBase(other.uncertainty); - return Math.abs(thisBaseValue - otherBaseValue) <= Math - .min(thisBaseUncertainty, otherBaseUncertainty); - } - - /** - * @return relative uncertainty of value - * - * @since 2020-07-26 - */ - public final double getRelativeUncertainty() { - return this.uncertainty / this.value; - } - - /** - * @return absolute uncertainty of value - * - * @since 2020-07-26 - */ - public final double getUncertainty() { - return this.uncertainty; + 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); } /** @@ -237,24 +188,22 @@ public final class LinearUnitValue { * * @since 2020-07-26 */ - public final double getValue() { + public final UncertainDouble getValue() { return this.value; } + /** + * @return the exact value + * @since 2020-09-07 + */ + public final double getValueExact() { + return this.value.value(); + } + @Override public int hashCode() { return Objects.hash(this.unit.getBase(), - this.unit.convertToBase(this.getValue()), - this.getRelativeUncertainty()); - } - - /** - * @return true iff the value has no uncertainty - * - * @since 2020-07-26 - */ - public final boolean isExact() { - return this.uncertainty == 0; + this.unit.convertToBase(this.getValue())); } /** @@ -276,8 +225,8 @@ public final class LinearUnitValue { this.unit, subtrahend.unit)); final LinearUnitValue otherConverted = subtrahend.convertTo(this.unit); - return LinearUnitValue.of(this.unit, this.value - otherConverted.value, - Math.hypot(this.uncertainty, otherConverted.uncertainty)); + return LinearUnitValue.of(this.unit, + this.value.minus(otherConverted.value)); } /** @@ -298,8 +247,8 @@ public final class LinearUnitValue { addend.unit)); final LinearUnitValue otherConverted = addend.convertTo(this.unit); - return LinearUnitValue.of(this.unit, this.value + otherConverted.value, - Math.hypot(this.uncertainty, otherConverted.uncertainty)); + return LinearUnitValue.of(this.unit, + this.value.plus(otherConverted.value)); } /** @@ -310,8 +259,7 @@ public final class LinearUnitValue { * @since 2020-07-28 */ public LinearUnitValue times(final double multiplier) { - return LinearUnitValue.of(this.unit, this.value * multiplier, - this.uncertainty * multiplier); + return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier)); } /** @@ -322,10 +270,8 @@ public final class LinearUnitValue { * @since 2020-07-28 */ public LinearUnitValue times(final LinearUnitValue multiplier) { - return LinearUnitValue.ofRelative(this.unit.times(multiplier.unit), - this.value * multiplier.value, - Math.hypot(this.getRelativeUncertainty(), - multiplier.getRelativeUncertainty())); + return LinearUnitValue.of(this.unit.times(multiplier.unit), + this.value.times(multiplier.value)); } /** @@ -336,14 +282,13 @@ public final class LinearUnitValue { * @since 2020-07-28 */ public LinearUnitValue toExponent(final int exponent) { - return LinearUnitValue.ofRelative(this.unit.toExponent(exponent), - Math.pow(this.value, exponent), - this.getRelativeUncertainty() * Math.sqrt(exponent)); + return LinearUnitValue.of(this.unit.toExponent(exponent), + this.value.toExponentExact(exponent)); } @Override public String toString() { - return this.toString(!this.isExact()); + return this.toString(!this.value.isExact()); } /** @@ -363,107 +308,22 @@ public final class LinearUnitValue { final Optional symbol = this.unit.getSymbol(); final String chosenName = symbol.orElse(primaryName.orElse(null)); - final double baseValue = this.unit.convertToBase(this.value); - final double baseUncertainty = this.unit.convertToBase(this.uncertainty); + final UncertainDouble baseValue = this.unit.convertToBase(this.value); // get rounded strings - String valueString, baseValueString, uncertaintyString, - baseUncertaintyString; - if (this.isExact()) { - valueString = Double.toString(this.value); - baseValueString = Double.toString(baseValue); - uncertaintyString = "0"; - baseUncertaintyString = "0"; - } else { - final BigDecimal bigValue = BigDecimal.valueOf(this.value); - final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); - - // round based on uncertainty - // if uncertainty starts with 1 (ignoring zeroes and the decimal - // point), rounds - // so that uncertainty has 2 significant digits. - // otherwise, rounds so that uncertainty has 1 significant digits. - // the value is rounded to the same number of decimal places as the - // uncertainty. - BigDecimal roundedUncertainty = bigUncertainty.setScale( - bigUncertainty.scale() - bigUncertainty.precision() + 2, - RoundingMode.HALF_EVEN); - if (roundedUncertainty.unscaledValue().intValue() >= 20) { - roundedUncertainty = bigUncertainty.setScale( - bigUncertainty.scale() - bigUncertainty.precision() + 1, - RoundingMode.HALF_EVEN); - } - final BigDecimal roundedValue = bigValue - .setScale(roundedUncertainty.scale(), RoundingMode.HALF_EVEN); - - valueString = roundedValue.toString(); - uncertaintyString = roundedUncertainty.toString(); - - if (primaryName.isEmpty() && symbol.isEmpty()) { - final BigDecimal bigBaseValue = BigDecimal.valueOf(baseValue); - final BigDecimal bigBaseUncertainty = BigDecimal - .valueOf(baseUncertainty); - - BigDecimal roundedBaseUncertainty = bigBaseUncertainty - .setScale( - bigBaseUncertainty.scale() - - bigBaseUncertainty.precision() + 2, - RoundingMode.HALF_EVEN); - if (roundedBaseUncertainty.unscaledValue().intValue() >= 20) { - roundedBaseUncertainty = bigBaseUncertainty - .setScale( - bigBaseUncertainty.scale() - - bigBaseUncertainty.precision() + 1, - RoundingMode.HALF_EVEN); - } - final BigDecimal roundedBaseValue = bigBaseValue.setScale( - roundedBaseUncertainty.scale(), RoundingMode.HALF_EVEN); - - baseValueString = roundedBaseValue.toString(); - baseUncertaintyString = roundedBaseUncertainty.toString(); - } else { - // unused - baseValueString = ""; - baseUncertaintyString = ""; - } - } + // if showUncertainty is true, add brackets around the string + final String valueString = showUncertainty ? "(" + : "" + this.value.toString(showUncertainty) + + (showUncertainty ? ")" : ""); + final String baseValueString = showUncertainty ? "(" + : "" + baseValue.toString(showUncertainty) + + (showUncertainty ? ")" : ""); // create string - if (showUncertainty) { - if (primaryName.isEmpty() && symbol.isEmpty()) - return String.format("(%s ± %s) unnamed unit (= %s ± %s %s)", - valueString, uncertaintyString, baseValueString, - baseUncertaintyString, this.unit.getBase()); - else - return String.format("(%s ± %s) %s", valueString, uncertaintyString, - chosenName); - } else { - // truncate excess zeroes - if (valueString.contains(".")) { - while (valueString.endsWith("0")) { - valueString = valueString.substring(0, valueString.length() - 1); - } - if (valueString.endsWith(".")) { - valueString = valueString.substring(0, valueString.length() - 1); - } - } - - if (baseValueString.contains(".")) { - while (baseValueString.endsWith("0")) { - baseValueString = baseValueString.substring(0, - baseValueString.length() - 1); - } - if (baseValueString.endsWith(".")) { - baseValueString = baseValueString.substring(0, - baseValueString.length() - 1); - } - } - - if (primaryName.isEmpty() && symbol.isEmpty()) - return String.format("%s unnamed unit (= %s %s)", valueString, - baseValueString, this.unit.getBase()); - else - return String.format("%s %s", valueString, chosenName); - } + if (primaryName.isEmpty() && symbol.isEmpty()) + return String.format("%s unnamed unit (= %s %s)", valueString, + baseValueString, this.unit.getBase()); + else + return String.format("%s %s", valueString, chosenName); } } diff --git a/src/org/unitConverter/unit/UnitDatabase.java b/src/org/unitConverter/unit/UnitDatabase.java index 9812bd0..9ca9617 100644 --- a/src/org/unitConverter/unit/UnitDatabase.java +++ b/src/org/unitConverter/unit/UnitDatabase.java @@ -46,6 +46,7 @@ import org.unitConverter.math.ConditionalExistenceCollections; import org.unitConverter.math.DecimalComparison; import org.unitConverter.math.ExpressionParser; import org.unitConverter.math.ObjectProduct; +import org.unitConverter.math.UncertainDouble; /** * A database of units, prefixes and dimensions, and their names. @@ -1134,7 +1135,7 @@ public final class UnitDatabase { // exponent function - first check if o2 is a number, if (exponentValue.canConvertTo(SI.ONE)) { // then check if it is an integer, - final double exponent = exponentValue.getValue(); + final double exponent = exponentValue.getValueExact(); if (DecimalComparison.equals(exponent % 1, 0)) // then exponentiate return base.toExponent((int) (exponent + 0.5)); @@ -1650,7 +1651,8 @@ public final class UnitDatabase { final BigDecimal number = new BigDecimal(name); final double uncertainty = Math.pow(10, -number.scale()); - return LinearUnitValue.of(SI.ONE, number.doubleValue(), uncertainty); + return LinearUnitValue.of(SI.ONE, + UncertainDouble.of(number.doubleValue(), uncertainty)); } catch (final NumberFormatException e) { return LinearUnitValue.getExact(this.getLinearUnit(name), 1); } diff --git a/src/org/unitConverter/unit/UnitTest.java b/src/org/unitConverter/unit/UnitTest.java index ff83805..c0711dc 100644 --- a/src/org/unitConverter/unit/UnitTest.java +++ b/src/org/unitConverter/unit/UnitTest.java @@ -56,9 +56,10 @@ class UnitTest { final LinearUnitValue value4 = LinearUnitValue.getExact(SI.KILOGRAM, 60); // make sure addition is done correctly - assertEquals(51.576, value1.plus(value2).getValue(), 0.001); - assertEquals(15.5, value1.plus(value3).getValue()); - assertEquals(52.076, value1.plus(value2).plus(value3).getValue(), 0.001); + assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001); + assertEquals(15.5, value1.plus(value3).getValueExact()); + assertEquals(52.076, value1.plus(value2).plus(value3).getValueExact(), + 0.001); // make sure addition uses the correct unit, and is still associative // (ignoring floating-point rounding errors) -- cgit v1.2.3 From cd33f886dfbd35c0ee3d8cf5b553ea3481b0b3a1 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 2 Oct 2020 10:16:10 -0500 Subject: Added Unitlike objects --- src/org/unitConverter/math/UncertainDouble.java | 15 +- src/org/unitConverter/unit/FunctionalUnitlike.java | 72 ++++++ src/org/unitConverter/unit/LinearUnit.java | 18 ++ src/org/unitConverter/unit/LinearUnitValue.java | 22 +- src/org/unitConverter/unit/Nameable.java | 59 +++++ src/org/unitConverter/unit/Unit.java | 118 ++++++---- src/org/unitConverter/unit/UnitValue.java | 149 ++++++++---- src/org/unitConverter/unit/Unitlike.java | 260 +++++++++++++++++++++ src/org/unitConverter/unit/UnitlikeValue.java | 172 ++++++++++++++ 9 files changed, 785 insertions(+), 100 deletions(-) create mode 100644 src/org/unitConverter/unit/FunctionalUnitlike.java create mode 100644 src/org/unitConverter/unit/Nameable.java create mode 100644 src/org/unitConverter/unit/Unitlike.java create mode 100644 src/org/unitConverter/unit/UnitlikeValue.java (limited to 'src/org/unitConverter/math') diff --git a/src/org/unitConverter/math/UncertainDouble.java b/src/org/unitConverter/math/UncertainDouble.java index e948df9..9601c75 100644 --- a/src/org/unitConverter/math/UncertainDouble.java +++ b/src/org/unitConverter/math/UncertainDouble.java @@ -1,5 +1,18 @@ /** - * @since 2020-09-07 + * Copyright (C) 2020 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package org.unitConverter.math; diff --git a/src/org/unitConverter/unit/FunctionalUnitlike.java b/src/org/unitConverter/unit/FunctionalUnitlike.java new file mode 100644 index 0000000..21c1fca --- /dev/null +++ b/src/org/unitConverter/unit/FunctionalUnitlike.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2020 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.unitConverter.unit; + +import java.util.function.DoubleFunction; +import java.util.function.ToDoubleFunction; + +import org.unitConverter.math.ObjectProduct; + +/** + * A unitlike form that converts using two conversion functions. + * + * @since 2020-09-07 + */ +final class FunctionalUnitlike extends Unitlike { + /** + * A function that accepts a value in the unitlike form's base and returns a + * value in the unitlike form. + * + * @since 2020-09-07 + */ + private final DoubleFunction converterFrom; + + /** + * A function that accepts a value in the unitlike form and returns a value + * in the unitlike form's base. + */ + private final ToDoubleFunction converterTo; + + /** + * Creates the {@code FunctionalUnitlike}. + * + * @param base unitlike form's base + * @param converterFrom function that accepts a value in the unitlike form's + * base and returns a value in the unitlike form. + * @param converterTo function that accepts a value in the unitlike form + * and returns a value in the unitlike form's base. + * @throws NullPointerException if any argument is null + * @since 2019-05-22 + */ + protected FunctionalUnitlike(ObjectProduct unitBase, NameSymbol ns, + DoubleFunction converterFrom, ToDoubleFunction converterTo) { + super(unitBase, ns); + 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/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java index 2d63ca7..b7f33d5 100644 --- a/src/org/unitConverter/unit/LinearUnit.java +++ b/src/org/unitConverter/unit/LinearUnit.java @@ -64,6 +64,24 @@ public final class LinearUnit extends Unit { unit.convertToBase(value), ns); } + /** + * @return the base unit associated with {@code unit}, as a + * {@code LinearUnit}. + * @since 2020-10-02 + */ + 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}. + * @since 2020-10-02 + */ + 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 diff --git a/src/org/unitConverter/unit/LinearUnitValue.java b/src/org/unitConverter/unit/LinearUnitValue.java index d86d344..8de734e 100644 --- a/src/org/unitConverter/unit/LinearUnitValue.java +++ b/src/org/unitConverter/unit/LinearUnitValue.java @@ -1,5 +1,18 @@ /** - * + * Copyright (C) 2019 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package org.unitConverter.unit; @@ -53,6 +66,7 @@ public final class LinearUnitValue { } private final LinearUnit unit; + private final UncertainDouble value; /** @@ -176,8 +190,7 @@ public final class LinearUnitValue { /** * @return the unit - * - * @since 2020-07-26 + * @since 2020-09-29 */ public final LinearUnit getUnit() { return this.unit; @@ -185,8 +198,7 @@ public final class LinearUnitValue { /** * @return the value - * - * @since 2020-07-26 + * @since 2020-09-29 */ public final UncertainDouble getValue() { return this.value; diff --git a/src/org/unitConverter/unit/Nameable.java b/src/org/unitConverter/unit/Nameable.java new file mode 100644 index 0000000..36740ab --- /dev/null +++ b/src/org/unitConverter/unit/Nameable.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2020 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.unitConverter.unit; + +import java.util.Optional; +import java.util.Set; + +/** + * An object that can hold one or more names, and possibly a symbol. The name + * and symbol data should be immutable. + * + * @since 2020-09-07 + */ +public interface Nameable { + /** + * @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 + */ + default Set getOtherNames() { + return this.getNameSymbol().getOtherNames(); + } + + /** + * @return preferred name of object + * @since 2020-09-07 + */ + default Optional getPrimaryName() { + return this.getNameSymbol().getPrimaryName(); + } + + /** + * @return short symbol representing object + * @since 2020-09-07 + */ + default Optional getSymbol() { + return this.getNameSymbol().getSymbol(); + } +} diff --git a/src/org/unitConverter/unit/Unit.java b/src/org/unitConverter/unit/Unit.java index eb9b000..0a3298f 100644 --- a/src/org/unitConverter/unit/Unit.java +++ b/src/org/unitConverter/unit/Unit.java @@ -16,12 +16,10 @@ */ package org.unitConverter.unit; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.function.DoubleUnaryOperator; @@ -34,7 +32,7 @@ import org.unitConverter.math.ObjectProduct; * @author Adrien Hopkins * @since 2019-10-16 */ -public abstract class Unit { +public abstract class Unit implements Nameable { /** * Returns a unit from its base and the functions it uses to convert to and * from its base. @@ -98,19 +96,11 @@ public abstract class Unit { private final ObjectProduct unitBase; /** - * The primary name used by this unit. - */ - private final Optional primaryName; - - /** - * A short symbol used to represent this unit. - */ - private final Optional symbol; - - /** - * A set of any additional names and/or spellings that the unit uses. + * This unit's name(s) and symbol + * + * @since 2020-09-07 */ - private final Set otherNames; + private final NameSymbol nameSymbol; /** * Cache storing the result of getDimension() @@ -120,20 +110,17 @@ public abstract class Unit { private transient ObjectProduct dimension = null; /** - * Creates the {@code AbstractUnit}. + * Creates the {@code Unit}. * * @param unitBase base of unit * @param ns names and symbol of unit * @since 2019-10-16 * @throws NullPointerException if unitBase or ns is null */ - protected Unit(final ObjectProduct unitBase, final NameSymbol ns) { + Unit(ObjectProduct unitBase, NameSymbol ns) { this.unitBase = Objects.requireNonNull(unitBase, - "unitBase must not be null."); - this.primaryName = Objects.requireNonNull(ns, "ns must not be null.") - .getPrimaryName(); - this.symbol = ns.getSymbol(); - this.otherNames = ns.getOtherNames(); + "unitBase may not be null"); + this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); } /** @@ -147,18 +134,25 @@ public abstract class Unit { this.unitBase = ObjectProduct.oneOf((BaseUnit) this); } else throw new AssertionError(); - this.primaryName = Optional.of(primaryName); - this.symbol = Optional.of(symbol); - this.otherNames = Collections.unmodifiableSet(new HashSet<>(Objects - .requireNonNull(otherNames, "additionalNames must not be null."))); + this.nameSymbol = NameSymbol.of(primaryName, symbol, + new HashSet<>(otherNames)); + } + + /** + * @return this unit as a {@link Unitlike} + * @since 2020-09-07 + */ + public final Unitlike asUnitlike() { + 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} * - * @param other unit to test with - * @return true if the units are compatible + * @param other unit or unitlike form to test with + * @return true if they are compatible * @since 2019-01-13 * @since v0.1.0 * @throws NullPointerException if other is null @@ -168,6 +162,21 @@ public abstract class Unit { 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} + * + * @param other unit or unitlike form to test with + * @return true if they are compatible + * @since 2019-01-13 + * @since v0.1.0 + * @throws NullPointerException if other is null + */ + public final boolean canConvertTo(final Unitlike other) { + 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. @@ -218,6 +227,34 @@ public abstract class Unit { String.format("Cannot convert from %s to %s.", this, other)); } + /** + * Converts a value expressed in this unit to a value expressed in + * {@code other}. + * + * @implSpec If conversion is possible, this implementation returns + * {@code other.convertFromBase(this.convertToBase(value))}. + * Therefore, overriding either of those methods will change the + * output of this method. + * + * @param other unitlike form to convert to + * @param value value to convert + * @param type of value to convert to + * @return converted value + * @since 2020-09-07 + * @throws IllegalArgumentException if {@code other} is incompatible for + * conversion with this unit (as tested by + * {@link Unit#canConvertTo}). + * @throws NullPointerException if other is null + */ + public final W convertTo(final Unitlike other, final double value) { + Objects.requireNonNull(other, "other must not be null."); + if (this.canConvertTo(other)) + return other.convertFromBase(this.convertToBase(value)); + else + 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. @@ -270,27 +307,12 @@ public abstract class Unit { } /** - * @return additionalNames - * @since 2019-10-21 - */ - public final Set getOtherNames() { - return this.otherNames; - } - - /** - * @return primaryName - * @since 2019-10-21 + * @return the nameSymbol + * @since 2020-09-07 */ - public final Optional getPrimaryName() { - return this.primaryName; - } - - /** - * @return symbol - * @since 2019-10-21 - */ - public final Optional getSymbol() { - return this.symbol; + @Override + public final NameSymbol getNameSymbol() { + return this.nameSymbol; } /** diff --git a/src/org/unitConverter/unit/UnitValue.java b/src/org/unitConverter/unit/UnitValue.java index 9e565d9..8932ccc 100644 --- a/src/org/unitConverter/unit/UnitValue.java +++ b/src/org/unitConverter/unit/UnitValue.java @@ -1,3 +1,19 @@ +/** + * Copyright (C) 2019 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package org.unitConverter.unit; import java.util.Objects; @@ -14,60 +30,57 @@ import java.util.Optional; */ public final class UnitValue { /** + * Creates a {@code UnitValue} from a unit and the associated value. + * * @param unit unit to use * @param value value to use * @return {@code UnitValue} instance */ public static UnitValue of(Unit unit, double value) { - return new UnitValue(Objects.requireNonNull(unit, "unit must not be null"), value); + 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 */ - private UnitValue(Unit unit, double value) { + private UnitValue(Unit unit, Double value) { this.unit = unit; this.value = value; } - + /** - * @return the unit + * @return true if this value can be converted to {@code other}. + * @since 2020-10-01 */ - public final Unit getUnit() { - return unit; + public final boolean canConvertTo(Unit other) { + return this.unit.canConvertTo(other); } - + /** - * @return the value + * @return true if this value can be converted to {@code other}. + * @since 2020-10-01 */ - public final double getValue() { - return value; + public final boolean canConvertTo(Unitlike other) { + return this.unit.canConvertTo(other); } - + /** - * Converts this {@code UnitValue} into an equivalent {@code LinearUnitValue} by - * using this unit's base unit. + * Returns a UnitValue that represents the same value expressed in a + * different unit * - * @param newName A new name for the base unit. Use {@link NameSymbol#EMPTY} if - * you don't want one. - */ - public final LinearUnitValue asLinearUnitValue(NameSymbol newName) { - LinearUnit base = LinearUnit.valueOf(unit.getBase(), 1, newName); - return LinearUnitValue.getExact(base, base.convertToBase(value)); - } - - /** - * @param other a {@code Unit} - * @return true iff this value can be represented with {@code other}. + * @param other new unit to express value in + * @return value expressed in {@code other} */ - public final boolean canConvertTo(Unit other) { - return this.unit.canConvertTo(other); + public final UnitValue convertTo(Unit other) { + return UnitValue.of(other, + this.getUnit().convertTo(other, this.getValue())); } - + /** * Returns a UnitValue that represents the same value expressed in a * different unit @@ -75,39 +88,83 @@ public final class UnitValue { * @param other new unit to express value in * @return value expressed in {@code other} */ - public final UnitValue convertTo(Unit other) { - return UnitValue.of(other, this.getUnit().convertTo(other, this.getValue())); + public final UnitlikeValue convertTo(Unitlike other) { + return UnitlikeValue.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. + * + * @param ns name and symbol for the base unit, use NameSymbol.EMPTY if not + * needed. + * @since 2020-09-29 + */ + public final LinearUnitValue convertToBase(NameSymbol ns) { + 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. + * @since 2020-09-29 + */ + public final LinearUnitValue convertToLinear(LinearUnit other) { + 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 km) returns - * true. + * Returns true if this and obj represent the same value, regardless of + * whether or not they are expressed in the same unit. So (1000 m).equals(1 + * km) returns true. */ @Override public boolean equals(Object obj) { if (!(obj instanceof UnitValue)) return false; final UnitValue other = (UnitValue) obj; - return Objects.equals(this.unit.getBase(), other.unit.getBase()) - && Double.doubleToLongBits(this.unit.convertToBase(this.getValue())) == Double - .doubleToLongBits(other.unit.convertToBase(other.getValue())); + return Objects.equals(this.getUnit().getBase(), other.getUnit().getBase()) + && Double.doubleToLongBits( + this.getUnit().convertToBase(this.getValue())) == Double + .doubleToLongBits( + other.getUnit().convertToBase(other.getValue())); } - + + /** + * @return the unit + * @since 2020-09-29 + */ + public final Unit getUnit() { + return this.unit; + } + + /** + * @return the value + * @since 2020-09-29 + */ + public final double getValue() { + return this.value; + } + @Override public int hashCode() { - return Objects.hash(this.unit.getBase(), this.unit.convertFromBase(this.getValue())); + return Objects.hash(this.getUnit().getBase(), + this.getUnit().convertFromBase(this.getValue())); } - + @Override public String toString() { - Optional primaryName = this.getUnit().getPrimaryName(); - Optional symbol = this.getUnit().getSymbol(); + final Optional primaryName = this.getUnit().getPrimaryName(); + final Optional symbol = this.getUnit().getSymbol(); if (primaryName.isEmpty() && symbol.isEmpty()) { - double baseValue = this.getUnit().convertToBase(this.getValue()); - return String.format("%s unnamed unit (= %s %s)", this.getValue(), baseValue, this.getUnit().getBase()); + final double baseValue = this.getUnit().convertToBase(this.getValue()); + return String.format("%s unnamed unit (= %s %s)", this.getValue(), + baseValue, this.getUnit().getBase()); } else { - String unitName = symbol.orElse(primaryName.get()); + final String unitName = symbol.orElse(primaryName.get()); return this.getValue() + " " + unitName; } } diff --git a/src/org/unitConverter/unit/Unitlike.java b/src/org/unitConverter/unit/Unitlike.java new file mode 100644 index 0000000..a6ddb04 --- /dev/null +++ b/src/org/unitConverter/unit/Unitlike.java @@ -0,0 +1,260 @@ +/** + * Copyright (C) 2020 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.unitConverter.unit; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.DoubleFunction; +import java.util.function.ToDoubleFunction; + +import org.unitConverter.math.ObjectProduct; + +/** + * An object that can convert a value between multiple forms (instances of the + * object); like a unit but the "converted value" can be any type. + * + * @since 2020-09-07 + */ +public abstract class Unitlike implements Nameable { + /** + * Returns a unitlike form from its base and the functions it uses to convert + * to and from its base. + * + * @param base unitlike form's base + * @param converterFrom function that accepts a value expressed in the + * unitlike form's base and returns that value expressed + * in this unitlike form. + * @param converterTo function that accepts a value expressed in the + * unitlike form and returns that value expressed in the + * unit's base. + * @return a unitlike form that uses the provided functions to convert. + * @since 2020-09-07 + * @throws NullPointerException if any argument is null + */ + public static final Unitlike fromConversionFunctions( + final ObjectProduct base, + final DoubleFunction converterFrom, + final ToDoubleFunction converterTo) { + 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. + * + * @param base unitlike form's base + * @param converterFrom function that accepts a value expressed in the + * unitlike form's base and returns that value expressed + * in this unitlike form. + * @param converterTo function that accepts a value expressed in the + * unitlike form and returns that value expressed in the + * unit's base. + * @param ns names and symbol of unit + * @return a unitlike form that uses the provided functions to convert. + * @since 2020-09-07 + * @throws NullPointerException if any argument is null + */ + public static final Unitlike fromConversionFunctions( + final ObjectProduct base, + final DoubleFunction converterFrom, + final ToDoubleFunction 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 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 dimension = null; + + /** + * @param unitBase + * @since 2020-09-07 + */ + Unitlike(ObjectProduct unitBase, NameSymbol ns) { + this.unitBase = Objects.requireNonNull(unitBase, + "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} + * + * @param other unit or unitlike form to test with + * @return true if they are compatible + * @since 2019-01-13 + * @since v0.1.0 + * @throws NullPointerException if other is null + */ + public final boolean canConvertTo(final Unit other) { + 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} + * + * @param other unit or unitlike form to test with + * @return true if they are compatible + * @since 2019-01-13 + * @since v0.1.0 + * @throws NullPointerException if other is null + */ + public final boolean canConvertTo(final Unitlike other) { + 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}. + * + * @implSpec If conversion is possible, this implementation returns + * {@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 + * @since 2019-05-22 + * @throws IllegalArgumentException if {@code other} is incompatible for + * conversion with this unitlike form (as + * tested by {@link Unit#canConvertTo}). + * @throws NullPointerException if other is null + */ + public final double convertTo(final Unit other, final V value) { + Objects.requireNonNull(other, "other must not be null."); + if (this.canConvertTo(other)) + return other.convertFromBase(this.convertToBase(value)); + else + 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}. + * + * @implSpec If conversion is possible, this implementation returns + * {@code other.convertFromBase(this.convertToBase(value))}. + * Therefore, overriding either of those methods will change the + * output of this method. + * + * @param other unitlike form to convert to + * @param value value to convert + * @param type of value to convert to + * @return converted value + * @since 2020-09-07 + * @throws IllegalArgumentException if {@code other} is incompatible for + * conversion with this unitlike form (as + * tested by {@link Unit#canConvertTo}). + * @throws NullPointerException if other is null + */ + public final W convertTo(final Unitlike other, final V value) { + Objects.requireNonNull(other, "other must not be null."); + if (this.canConvertTo(other)) + return other.convertFromBase(this.convertToBase(value)); + else + 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 + * @since v0.1.0 + */ + public final ObjectProduct getBase() { + return this.unitBase; + } + + /** + * @return dimension measured by this unit + * @since 2018-12-22 + * @since v0.1.0 + */ + public final ObjectProduct getDimension() { + if (this.dimension == null) { + final Map mapping = this.unitBase.exponentMap(); + final Map 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 + */ + @Override + public final NameSymbol getNameSymbol() { + return this.nameSymbol; + } + + @Override + public String toString() { + return this.getPrimaryName().orElse("Unnamed unitlike form") + + (this.getSymbol().isPresent() + ? String.format(" (%s)", this.getSymbol().get()) + : "") + + ", derived from " + + this.getBase().toString(u -> u.getSymbol().get()) + + (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 + * @since 2020-09-07 + * @throws NullPointerException if ns is null + */ + public Unitlike withName(final NameSymbol ns) { + return fromConversionFunctions(this.getBase(), this::convertFromBase, + this::convertToBase, + Objects.requireNonNull(ns, "ns must not be null.")); + } +} diff --git a/src/org/unitConverter/unit/UnitlikeValue.java b/src/org/unitConverter/unit/UnitlikeValue.java new file mode 100644 index 0000000..669a123 --- /dev/null +++ b/src/org/unitConverter/unit/UnitlikeValue.java @@ -0,0 +1,172 @@ +/** + * Copyright (C) 2020 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.unitConverter.unit; + +import java.util.Optional; + +/** + * + * @since 2020-09-07 + */ +final class UnitlikeValue { + /** + * Gets a {@code UnitlikeValue}. + * + * @since 2020-10-02 + */ + public static UnitlikeValue of(Unitlike unitlike, V value) { + return new UnitlikeValue<>(unitlike, value); + } + + private final Unitlike unitlike; + private final V value; + + /** + * @param unitlike + * @param value + * @since 2020-09-07 + */ + private UnitlikeValue(Unitlike unitlike, V value) { + this.unitlike = unitlike; + this.value = value; + } + + /** + * @return true if this value can be converted to {@code other}. + * @since 2020-10-01 + */ + 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 + */ + public final boolean canConvertTo(Unitlike other) { + return this.unitlike.canConvertTo(other); + } + + /** + * Returns a UnitValue that represents the same value expressed in a + * different unit + * + * @param other new unit to express value in + * @return value expressed in {@code other} + */ + public final UnitValue convertTo(Unit other) { + return UnitValue.of(other, + this.unitlike.convertTo(other, this.getValue())); + } + + /** + * Returns a UnitValue that represents the same value expressed in a + * different unit + * + * @param other new unit to express value in + * @return value expressed in {@code other} + */ + public final UnitlikeValue convertTo(Unitlike other) { + return UnitlikeValue.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. + * + * @param ns name and symbol for the base unit, use NameSymbol.EMPTY if not + * needed. + * @since 2020-09-29 + */ + public final LinearUnitValue convertToBase(NameSymbol ns) { + 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. + * @since 2020-09-29 + */ + public final LinearUnitValue convertToLinear(LinearUnit other) { + return LinearUnitValue.getExact(other, + this.getUnitlike().convertTo(other, this.getValue())); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof UnitlikeValue)) + return false; + final UnitlikeValue other = (UnitlikeValue) obj; + if (this.getUnitlike() == null) { + if (other.getUnitlike() != null) + return false; + } else if (!this.getUnitlike().equals(other.getUnitlike())) + return false; + if (this.getValue() == null) { + if (other.getValue() != null) + return false; + } else if (!this.getValue().equals(other.getValue())) + return false; + return true; + } + + /** + * @return the unitlike + * @since 2020-09-29 + */ + public final Unitlike getUnitlike() { + return this.unitlike; + } + + /** + * @return the value + * @since 2020-09-29 + */ + public final V getValue() { + return this.value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + (this.getUnitlike() == null ? 0 : this.getUnitlike().hashCode()); + result = prime * result + + (this.getValue() == null ? 0 : this.getValue().hashCode()); + return result; + } + + @Override + public String toString() { + final Optional primaryName = this.getUnitlike().getPrimaryName(); + final Optional symbol = this.getUnitlike().getSymbol(); + if (primaryName.isEmpty() && symbol.isEmpty()) { + final double baseValue = this.getUnitlike() + .convertToBase(this.getValue()); + return String.format("%s unnamed unit (= %s %s)", this.getValue(), + baseValue, this.getUnitlike().getBase()); + } else { + final String unitName = symbol.orElse(primaryName.get()); + return this.getValue() + " " + unitName; + } + } +} -- cgit v1.2.3 From 8def3bbda9331b9178e24400d8189afbdaf47e36 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 13 Mar 2021 14:58:01 -0500 Subject: Small internal changes to some classes (no feature changes) --- src/org/unitConverter/math/UncertainDouble.java | 13 +- src/org/unitConverter/unit/BaseUnit.java | 86 ++++++----- src/org/unitConverter/unit/NameSymbol.java | 193 ++++++++++-------------- 3 files changed, 124 insertions(+), 168 deletions(-) (limited to 'src/org/unitConverter/math') diff --git a/src/org/unitConverter/math/UncertainDouble.java b/src/org/unitConverter/math/UncertainDouble.java index 9601c75..3651bd5 100644 --- a/src/org/unitConverter/math/UncertainDouble.java +++ b/src/org/unitConverter/math/UncertainDouble.java @@ -164,11 +164,9 @@ public final class UncertainDouble implements Comparable { if (!(obj instanceof UncertainDouble)) return false; final UncertainDouble other = (UncertainDouble) obj; - if (Double.doubleToLongBits(this.uncertainty) != Double - .doubleToLongBits(other.uncertainty)) + if (Double.compare(this.value, other.value) != 0) return false; - if (Double.doubleToLongBits(this.value) != Double - .doubleToLongBits(other.value)) + if (Double.compare(this.uncertainty, other.uncertainty) != 0) return false; return true; } @@ -216,11 +214,8 @@ public final class UncertainDouble implements Comparable { public final int hashCode() { final int prime = 31; int result = 1; - long temp; - temp = Double.doubleToLongBits(this.uncertainty); - result = prime * result + (int) (temp ^ temp >>> 32); - temp = Double.doubleToLongBits(this.value); - result = prime * result + (int) (temp ^ temp >>> 32); + result = prime * result + Double.hashCode(this.value); + result = prime * result + Double.hashCode(this.uncertainty); return result; } diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java index d9f7965..6757bd0 100644 --- a/src/org/unitConverter/unit/BaseUnit.java +++ b/src/org/unitConverter/unit/BaseUnit.java @@ -23,8 +23,9 @@ import java.util.Set; /** * A unit that other units are defined by. *

- * Note that BaseUnits must have names and symbols. This is because they are used for toString code. Therefore, - * the Optionals provided by {@link #getPrimaryName} and {@link #getSymbol} will always contain a value. + * Note that BaseUnits must have names and symbols. This is because they + * are used for toString code. Therefore, the Optionals provided by + * {@link #getPrimaryName} and {@link #getSymbol} will always contain a value. * * @author Adrien Hopkins * @since 2019-10-16 @@ -33,63 +34,56 @@ public final class BaseUnit extends Unit { /** * Gets a base unit from the dimension it measures, its name and its symbol. * - * @param dimension - * dimension measured by this unit - * @param name - * name of unit - * @param symbol - * symbol of unit + * @param dimension dimension measured by this unit + * @param name name of unit + * @param symbol symbol of unit * @return base unit * @since 2019-10-16 */ - public static BaseUnit valueOf(final BaseDimension dimension, final String name, final String symbol) { + public static BaseUnit valueOf(final BaseDimension dimension, + 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. * - * @param dimension - * dimension measured by this unit - * @param name - * name of unit - * @param symbol - * symbol of unit + * @param dimension dimension measured by this unit + * @param name name of unit + * @param symbol symbol of unit * @return base unit * @since 2019-10-21 */ - public static BaseUnit valueOf(final BaseDimension dimension, final String name, final String symbol, - final Set otherNames) { + public static BaseUnit valueOf(final BaseDimension dimension, + final String name, final String symbol, final Set otherNames) { return new BaseUnit(dimension, name, symbol, otherNames); } - + /** * The dimension measured by this base unit. */ private final BaseDimension dimension; - + /** * Creates the {@code BaseUnit}. * - * @param dimension - * dimension of unit - * @param primaryName - * name of unit - * @param symbol - * symbol of unit - * @throws NullPointerException - * if any argument is null + * @param dimension dimension of unit + * @param primaryName name of unit + * @param symbol symbol of unit + * @throws NullPointerException if any argument is null * @since 2019-10-16 */ - private BaseUnit(final BaseDimension dimension, final String primaryName, final String symbol, - final Set otherNames) { + private BaseUnit(final BaseDimension dimension, final String primaryName, + final String symbol, final Set otherNames) { super(primaryName, symbol, otherNames); - this.dimension = Objects.requireNonNull(dimension, "dimension must not be null."); + 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 created with operations. + * 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 + * created with operations. * * @return this unit as a {@code LinearUnit} * @since 2019-10-16 @@ -97,17 +91,17 @@ public final class BaseUnit extends Unit { public LinearUnit asLinearUnit() { return LinearUnit.valueOf(this.getBase(), 1); } - + @Override - public double convertFromBase(final double value) { + protected double convertFromBase(final double value) { return value; } - + @Override - public double convertToBase(final double value) { + protected double convertToBase(final double value) { return value; } - + /** * @return dimension * @since 2019-10-16 @@ -115,21 +109,25 @@ public final class BaseUnit extends Unit { public final BaseDimension getBaseDimension() { return this.dimension; } - + @Override public String toString() { return this.getPrimaryName().orElse("Unnamed unit") - + (this.getSymbol().isPresent() ? String.format(" (%s)", this.getSymbol().get()) : ""); + + (this.getSymbol().isPresent() + ? String.format(" (%s)", this.getSymbol().get()) + : ""); } - + @Override public BaseUnit withName(final NameSymbol ns) { Objects.requireNonNull(ns, "ns must not be null."); if (!ns.getPrimaryName().isPresent()) - throw new IllegalArgumentException("BaseUnits must have primary names."); + throw new IllegalArgumentException( + "BaseUnits must have primary names."); if (!ns.getSymbol().isPresent()) throw new IllegalArgumentException("BaseUnits must have symbols."); - return BaseUnit.valueOf(this.getBaseDimension(), ns.getPrimaryName().get(), ns.getSymbol().get(), + return BaseUnit.valueOf(this.getBaseDimension(), + ns.getPrimaryName().get(), ns.getSymbol().get(), ns.getOtherNames()); } } diff --git a/src/org/unitConverter/unit/NameSymbol.java b/src/org/unitConverter/unit/NameSymbol.java index 7fa5304..8d8302a 100644 --- a/src/org/unitConverter/unit/NameSymbol.java +++ b/src/org/unitConverter/unit/NameSymbol.java @@ -31,32 +31,36 @@ import java.util.Set; * @since 2019-10-21 */ public final class NameSymbol { - public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), Optional.empty(), new HashSet<>()); - + 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 a copy of the inputted argument. */ - private static final NameSymbol create(final String name, final String symbol, final Set otherNames) { + private static final NameSymbol create(final String name, + final String symbol, final Set otherNames) { final Optional primaryName; - + if (name == null && !otherNames.isEmpty()) { // get primary name and remove it from savedNames - Iterator it = otherNames.iterator(); + final Iterator it = otherNames.iterator(); assert it.hasNext(); primaryName = Optional.of(it.next()); otherNames.remove(primaryName.get()); } else { primaryName = Optional.ofNullable(name); } - - return new NameSymbol(primaryName, Optional.ofNullable(symbol), otherNames); + + return new NameSymbol(primaryName, Optional.ofNullable(symbol), + otherNames); } - + /** - * Gets a {@code NameSymbol} with a primary name, a symbol and no other names. + * Gets a {@code NameSymbol} with a primary name, a symbol and no other + * names. * * @param name name to use * @param symbol symbol to use @@ -65,11 +69,13 @@ public final class NameSymbol { * @throws NullPointerException if name or symbol is null */ public static final NameSymbol of(final String name, final String symbol) { - return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>()); + return new NameSymbol(Optional.of(name), Optional.of(symbol), + new HashSet<>()); } - + /** - * Gets a {@code NameSymbol} with a primary name, a symbol and additional names. + * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. * * @param name name to use * @param symbol symbol to use @@ -78,11 +84,13 @@ public final class NameSymbol { * @since 2019-10-21 * @throws NullPointerException if any argument is null */ - public static final NameSymbol of(final String name, final String symbol, final Set otherNames) { + public static final NameSymbol of(final String name, final String symbol, + final Set otherNames) { return new NameSymbol(Optional.of(name), Optional.of(symbol), - new HashSet<>(Objects.requireNonNull(otherNames, "otherNames must not be null."))); + new HashSet<>(Objects.requireNonNull(otherNames, + "otherNames must not be null."))); } - + /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -94,72 +102,16 @@ public final class NameSymbol { * @since 2019-10-21 * @throws NullPointerException if any argument is null */ - public static final NameSymbol of(final String name, final String symbol, final String... otherNames) { + public static final NameSymbol of(final String name, final String symbol, + final String... otherNames) { return new NameSymbol(Optional.of(name), Optional.of(symbol), - new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, "otherNames must not be null.")))); + new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, + "otherNames must not be null.")))); } - - /** - * Gets a {@code NameSymbol} with a primary name, a symbol and an additional - * name. - * - * @param name name to use - * @param symbol symbol to use - * @param otherNames other names to use - * @param name2 alternate name - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if any argument is null - */ - public static final NameSymbol of(final String name, final String symbol, final String name2) { - final Set otherNames = new HashSet<>(); - otherNames.add(Objects.requireNonNull(name2, "name2 must not be null.")); - return new NameSymbol(Optional.of(name), Optional.of(symbol), otherNames); - } - - /** - * 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 - * @param name2 alternate name - * @param name3 alternate name - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if any argument is null - */ - public static final NameSymbol of(final String name, final String symbol, final String name2, final String name3) { - final Set otherNames = new HashSet<>(); - otherNames.add(Objects.requireNonNull(name2, "name2 must not be null.")); - otherNames.add(Objects.requireNonNull(name3, "name3 must not be null.")); - return new NameSymbol(Optional.of(name), Optional.of(symbol), otherNames); - } - - /** - * 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 - * @param name2 alternate name - * @param name3 alternate name - * @param name4 alternate name - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if any argument is null - */ - public static final NameSymbol of(final String name, final String symbol, final String name2, final String name3, - final String name4) { - final Set otherNames = new HashSet<>(); - otherNames.add(Objects.requireNonNull(name2, "name2 must not be null.")); - otherNames.add(Objects.requireNonNull(name3, "name3 must not be null.")); - otherNames.add(Objects.requireNonNull(name4, "name4 must not be null.")); - return new NameSymbol(Optional.of(name), Optional.of(symbol), otherNames); - } - + /** - * Gets a {@code NameSymbol} with a primary name, no symbol, and no other names. + * Gets a {@code NameSymbol} with a primary name, no symbol, and no other + * names. * * @param name name to use * @return NameSymbol instance @@ -167,17 +119,19 @@ public final class NameSymbol { * @throws NullPointerException if name is null */ public static final NameSymbol ofName(final String name) { - return new NameSymbol(Optional.of(name), Optional.empty(), new HashSet<>()); + return new NameSymbol(Optional.of(name), Optional.empty(), + new HashSet<>()); } - + /** - * Gets a {@code NameSymbol} with a primary name, a symbol and additional names. + * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. *

* If any argument is null, this static factory replaces it with an empty * Optional or empty Set. *

- * 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 + * 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 @@ -186,10 +140,12 @@ public final class NameSymbol { * @return NameSymbol instance * @since 2019-11-26 */ - public static final NameSymbol ofNullable(final String name, final String symbol, final Set otherNames) { - return NameSymbol.create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); + public static final NameSymbol ofNullable(final String name, + final String symbol, final Set otherNames) { + 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. @@ -197,8 +153,8 @@ public final class NameSymbol { * If any argument is null, this static factory replaces it with an empty * Optional or empty Set. *

- * 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 + * 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 @@ -207,10 +163,12 @@ public final class NameSymbol { * @return NameSymbol instance * @since 2019-11-26 */ - public static final NameSymbol ofNullable(final String name, final String symbol, final String... otherNames) { - return create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(Arrays.asList(otherNames))); + public static final NameSymbol ofNullable(final String name, + final String symbol, final String... otherNames) { + return create(name, symbol, otherNames == null ? new HashSet<>() + : new HashSet<>(Arrays.asList(otherNames))); } - + /** * Gets a {@code NameSymbol} with a symbol and no names. * @@ -220,59 +178,61 @@ public final class NameSymbol { * @throws NullPointerException if symbol is null */ public static final NameSymbol ofSymbol(final String symbol) { - return new NameSymbol(Optional.empty(), Optional.of(symbol), new HashSet<>()); + return new NameSymbol(Optional.empty(), Optional.of(symbol), + new HashSet<>()); } - + private final Optional primaryName; private final Optional symbol; - + private final Set otherNames; - + /** * 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 + * @param otherNames other names and/or spellings, should be a mutable copy + * of the argument * @since 2019-10-21 */ - private NameSymbol(final Optional primaryName, final Optional symbol, - final Set otherNames) { + private NameSymbol(final Optional primaryName, + final Optional symbol, final Set otherNames) { this.primaryName = primaryName; this.symbol = symbol; otherNames.remove(null); this.otherNames = Collections.unmodifiableSet(otherNames); - if (this.primaryName.isEmpty()) + if (this.primaryName.isEmpty()) { assert this.otherNames.isEmpty(); + } } - + @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof NameSymbol)) return false; - NameSymbol other = (NameSymbol) obj; - if (otherNames == null) { + final NameSymbol other = (NameSymbol) obj; + if (this.otherNames == null) { if (other.otherNames != null) return false; - } else if (!otherNames.equals(other.otherNames)) + } else if (!this.otherNames.equals(other.otherNames)) return false; - if (primaryName == null) { + if (this.primaryName == null) { if (other.primaryName != null) return false; - } else if (!primaryName.equals(other.primaryName)) + } else if (!this.primaryName.equals(other.primaryName)) return false; - if (symbol == null) { + if (this.symbol == null) { if (other.symbol != null) return false; - } else if (!symbol.equals(other.symbol)) + } else if (!this.symbol.equals(other.symbol)) return false; return true; } - + /** * @return otherNames * @since 2019-10-21 @@ -280,7 +240,7 @@ public final class NameSymbol { public final Set getOtherNames() { return this.otherNames; } - + /** * @return primaryName * @since 2019-10-21 @@ -296,17 +256,20 @@ public final class NameSymbol { public final Optional getSymbol() { return this.symbol; } - + @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((otherNames == null) ? 0 : otherNames.hashCode()); - result = prime * result + ((primaryName == null) ? 0 : primaryName.hashCode()); - result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); + result = prime * result + + (this.otherNames == null ? 0 : this.otherNames.hashCode()); + result = prime * result + + (this.primaryName == null ? 0 : this.primaryName.hashCode()); + result = prime * result + + (this.symbol == null ? 0 : this.symbol.hashCode()); return result; } - + /** * @return true iff this {@code NameSymbol} contains no names or symbols. */ -- cgit v1.2.3