From 78af49e0e5b2ab2eaab87e62c33089c5caa834f8 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Mon, 28 Jun 2021 17:16:12 -0500 Subject: Renamed project to 7Units --- src/test/java/sevenUnits/unit/UnitTest.java | 153 ++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/test/java/sevenUnits/unit/UnitTest.java (limited to 'src/test/java/sevenUnits/unit/UnitTest.java') diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java new file mode 100644 index 0000000..d216ae8 --- /dev/null +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -0,0 +1,153 @@ +/** + * Copyright (C) 2018 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 sevenUnits.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.Test; + +import sevenUnits.math.DecimalComparison; +import sevenUnits.unit.LinearUnit; +import sevenUnits.unit.LinearUnitValue; +import sevenUnits.unit.NameSymbol; +import sevenUnits.unit.SI; +import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitValue; + +/** + * Testing the various Unit classes. This is NOT part of this program's public + * API. + * + * @author Adrien Hopkins + * @since 2018-12-22 + * @since v0.1.0 + */ +class UnitTest { + /** A random number generator */ + private static final Random rng = ThreadLocalRandom.current(); + + @Test + public void testAdditionAndSubtraction() { + final LinearUnit inch = SI.METRE.times(0.0254) + .withName(NameSymbol.of("inch", "in")); + final LinearUnit foot = SI.METRE.times(0.3048) + .withName(NameSymbol.of("foot", "ft")); + + assertEquals(inch.plus(foot), SI.METRE.times(0.3302)); + assertEquals(foot.minus(inch), SI.METRE.times(0.2794)); + + // test with LinearUnitValue + final LinearUnitValue value1 = LinearUnitValue.getExact(SI.METRE, 15); + final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120); + final LinearUnitValue value3 = LinearUnitValue.getExact(SI.METRE, 0.5); + final LinearUnitValue value4 = LinearUnitValue.getExact(SI.KILOGRAM, 60); + + // make sure addition is done correctly + assertEquals(51.576, value1.plus(value2).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) + assertEquals(SI.METRE, value1.plus(value2).getUnit()); + assertEquals(SI.METRE, value1.plus(value2).plus(value3).getUnit()); + assertEquals(foot, value2.plus(value1).getUnit()); + assertTrue(value1.plus(value2).equals(value2.plus(value1), true)); + + // make sure errors happen when they should + assertThrows(IllegalArgumentException.class, () -> value1.plus(value4)); + } + + @Test + public void testConversion() { + final LinearUnit metre = SI.METRE; + final Unit inch = metre.times(0.0254); + + final UnitValue value = UnitValue.of(inch, 75); + + assertEquals(1.9, inch.convertTo(metre, 75), 0.01); + assertEquals(1.9, value.convertTo(metre).getValue(), 0.01); + + // try random stuff + for (int i = 0; i < 1000; i++) { + // initiate random values + final double conversionFactor = UnitTest.rng.nextDouble() * 1000000; + final double testValue = UnitTest.rng.nextDouble() * 1000000; + final double expected = testValue * conversionFactor; + + // test + final Unit unit = SI.METRE.times(conversionFactor); + final double actual = unit.convertToBase(testValue); + + assertEquals(actual, expected, + expected * DecimalComparison.DOUBLE_EPSILON); + } + } + + @Test + public void testEquals() { + final LinearUnit metre = SI.METRE; + final Unit meter = SI.BaseUnits.METRE.asLinearUnit(); + + assertEquals(metre, meter); + } + + @Test + public void testIsMetric() { + final Unit metre = SI.METRE; + final Unit megasecond = SI.SECOND.withPrefix(SI.MEGA); + final Unit hour = SI.HOUR; + + assertTrue(metre.isMetric()); + assertTrue(megasecond.isMetric()); + assertFalse(hour.isMetric()); + } + + @Test + public void testMultiplicationAndDivision() { + // test unit-times-unit multiplication + final LinearUnit generatedJoule = SI.KILOGRAM + .times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2)); + final LinearUnit actualJoule = SI.JOULE; + + assertEquals(generatedJoule, actualJoule); + + // test multiplication by conversion factors + final LinearUnit kilometre = SI.METRE.times(1000); + final LinearUnit hour = SI.SECOND.times(3600); + final LinearUnit generatedKPH = kilometre.dividedBy(hour); + + final LinearUnit actualKPH = SI.METRE.dividedBy(SI.SECOND).dividedBy(3.6); + + assertEquals(generatedKPH, actualKPH); + } + + @Test + public void testPrefixes() { + final LinearUnit generatedKilometre = SI.METRE.withPrefix(SI.KILO); + final LinearUnit actualKilometre = SI.METRE.times(1000); + + assertEquals(generatedKilometre, actualKilometre); + } +} -- cgit v1.2.3 From 72eed55d9f834de8302564fbe6dfa3d1d446afb4 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 7 Aug 2021 15:25:54 -0500 Subject: Renamed sevenUnits.math to sevenUnits.utils --- .classpath | 7 - .../sevenUnits/converterGUI/SevenUnitsGUI.java | 4 +- .../math/ConditionalExistenceCollections.java | 468 ------------- .../java/sevenUnits/math/DecimalComparison.java | 256 ------- .../java/sevenUnits/math/ExpressionParser.java | 735 --------------------- src/main/java/sevenUnits/math/ObjectProduct.java | 284 -------- src/main/java/sevenUnits/math/UncertainDouble.java | 419 ------------ src/main/java/sevenUnits/math/package-info.java | 24 - src/main/java/sevenUnits/unit/FunctionalUnit.java | 2 +- .../java/sevenUnits/unit/FunctionalUnitlike.java | 2 +- src/main/java/sevenUnits/unit/LinearUnit.java | 6 +- src/main/java/sevenUnits/unit/LinearUnitValue.java | 4 +- src/main/java/sevenUnits/unit/MultiUnit.java | 2 +- src/main/java/sevenUnits/unit/SI.java | 2 +- src/main/java/sevenUnits/unit/Unit.java | 4 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 10 +- src/main/java/sevenUnits/unit/UnitPrefix.java | 2 +- src/main/java/sevenUnits/unit/Unitlike.java | 2 +- .../utils/ConditionalExistenceCollections.java | 468 +++++++++++++ .../java/sevenUnits/utils/DecimalComparison.java | 256 +++++++ .../java/sevenUnits/utils/ExpressionParser.java | 735 +++++++++++++++++++++ src/main/java/sevenUnits/utils/ObjectProduct.java | 284 ++++++++ .../java/sevenUnits/utils/UncertainDouble.java | 419 ++++++++++++ src/main/java/sevenUnits/utils/package-info.java | 25 + .../math/ConditionalExistenceCollectionsTest.java | 161 ----- .../java/sevenUnits/math/ExpressionParserTest.java | 54 -- .../java/sevenUnits/math/ObjectProductTest.java | 80 --- src/test/java/sevenUnits/unit/UnitTest.java | 2 +- .../utils/ConditionalExistenceCollectionsTest.java | 161 +++++ .../sevenUnits/utils/ExpressionParserTest.java | 54 ++ .../java/sevenUnits/utils/ObjectProductTest.java | 80 +++ 31 files changed, 2503 insertions(+), 2509 deletions(-) delete mode 100644 src/main/java/sevenUnits/math/ConditionalExistenceCollections.java delete mode 100644 src/main/java/sevenUnits/math/DecimalComparison.java delete mode 100644 src/main/java/sevenUnits/math/ExpressionParser.java delete mode 100644 src/main/java/sevenUnits/math/ObjectProduct.java delete mode 100644 src/main/java/sevenUnits/math/UncertainDouble.java delete mode 100644 src/main/java/sevenUnits/math/package-info.java create mode 100644 src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java create mode 100644 src/main/java/sevenUnits/utils/DecimalComparison.java create mode 100644 src/main/java/sevenUnits/utils/ExpressionParser.java create mode 100644 src/main/java/sevenUnits/utils/ObjectProduct.java create mode 100644 src/main/java/sevenUnits/utils/UncertainDouble.java create mode 100644 src/main/java/sevenUnits/utils/package-info.java delete mode 100644 src/test/java/sevenUnits/math/ConditionalExistenceCollectionsTest.java delete mode 100644 src/test/java/sevenUnits/math/ExpressionParserTest.java delete mode 100644 src/test/java/sevenUnits/math/ObjectProductTest.java create mode 100644 src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java create mode 100644 src/test/java/sevenUnits/utils/ExpressionParserTest.java create mode 100644 src/test/java/sevenUnits/utils/ObjectProductTest.java (limited to 'src/test/java/sevenUnits/unit/UnitTest.java') diff --git a/.classpath b/.classpath index 66f8050..79126ac 100644 --- a/.classpath +++ b/.classpath @@ -19,13 +19,6 @@ - - - - - - - diff --git a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java index fb498da..e1eb2f4 100644 --- a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java +++ b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java @@ -65,8 +65,6 @@ import javax.swing.WindowConstants; import javax.swing.border.TitledBorder; import sevenUnits.ProgramInfo; -import sevenUnits.math.ConditionalExistenceCollections; -import sevenUnits.math.ObjectProduct; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.BritishImperial; import sevenUnits.unit.LinearUnit; @@ -77,6 +75,8 @@ import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; import sevenUnits.unit.UnitValue; +import sevenUnits.utils.ConditionalExistenceCollections; +import sevenUnits.utils.ObjectProduct; /** * @author Adrien Hopkins diff --git a/src/main/java/sevenUnits/math/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/math/ConditionalExistenceCollections.java deleted file mode 100644 index 5ce1bd7..0000000 --- a/src/main/java/sevenUnits/math/ConditionalExistenceCollections.java +++ /dev/null @@ -1,468 +0,0 @@ -/** - * 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 sevenUnits.math; - -import java.util.AbstractCollection; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Set; -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 - * 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 - * 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. - *

- * 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 - * @since 2019-10-17 - */ -// TODO add conditional existence Lists and Sorted/Navigable Sets/Maps -public final class ConditionalExistenceCollections { - /** - * Elements in this collection only exist if they meet a condition. - * - * @author Adrien Hopkins - * @since 2019-10-17 - * @param type of element in collection - */ - static final class ConditionalExistenceCollection - extends AbstractCollection { - final Collection collection; - final Predicate existenceCondition; - - /** - * Creates the {@code ConditionalExistenceCollection}. - * - * @param collection - * @param existenceCondition - * @since 2019-10-17 - */ - 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 - // 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); - } - - @Override - public boolean remove(final Object o) { - // 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(); - } - - @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 - */ - static final class ConditionalExistenceIterator implements Iterator { - final Iterator iterator; - final Predicate existenceCondition; - E nextElement; - boolean hasNext; - - /** - * Creates the {@code ConditionalExistenceIterator}. - * - * @param iterator - * @param condition - * @since 2019-10-17 - */ - 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. - * - * @since 2019-10-17 - */ - private void getAndSetNextElement() { - do { - if (!this.iterator.hasNext()) { - this.nextElement = null; - this.hasNext = false; - return; - } - this.nextElement = this.iterator.next(); - } while (!this.existenceCondition.test(this.nextElement)); - this.hasNext = true; - } - - @Override - public boolean hasNext() { - return this.hasNext; - } - - @Override - public E next() { - if (this.hasNext()) { - final E next = this.nextElement; - this.getAndSetNextElement(); - return next; - } 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 - */ - static final class ConditionalExistenceMap extends AbstractMap { - Map map; - Predicate> entryExistenceCondition; - - /** - * Creates the {@code ConditionalExistenceMap}. - * - * @param map - * @param entryExistenceCondition - * @since 2019-10-17 - */ - 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); - } - - @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() { - 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 - */ - 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 - * @since 2019-10-17 - */ - 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. - */ - @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 - // 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); - } - - @Override - public boolean remove(final Object o) { - // 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. - * - * @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, - final Predicate existenceCondition) { - return new ConditionalExistenceCollection<>(collection, - existenceCondition); - } - - /** - * 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 - * @return wrapper iterator - * @since 2019-10-17 - */ - 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 - * - * @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, - final Predicate> entryExistenceCondition) { - return new ConditionalExistenceMap<>(map, entryExistenceCondition); - } - - /** - * 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 - * @return wrapper set - * @since 2019-10-17 - */ - public static final Set conditionalExistenceSet(final Set set, - final Predicate existenceCondition) { - return new ConditionalExistenceSet<>(set, existenceCondition); - } -} diff --git a/src/main/java/sevenUnits/math/DecimalComparison.java b/src/main/java/sevenUnits/math/DecimalComparison.java deleted file mode 100644 index 24a9b2f..0000000 --- a/src/main/java/sevenUnits/math/DecimalComparison.java +++ /dev/null @@ -1,256 +0,0 @@ -/** - * 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 sevenUnits.math; - -import java.math.BigDecimal; - -/** - * A class that contains methods to compare float and double values. - * - * @author Adrien Hopkins - * @since 2019-03-18 - * @since v0.2.0 - */ -public final class DecimalComparison { - /** - * The value used for double comparison. If two double values are within this - * value multiplied by the larger value, they are considered equal. - * - * @since 2019-03-18 - * @since v0.2.0 - */ - 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. - * - * @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. - *

- * 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) - *
- * - * @param a first value to test - * @param b second value to test - * @return whether they are equal - * @since 2019-03-18 - * @since v0.2.0 - * @see #hashCode(double) - */ - public static final boolean equals(final double a, final double b) { - 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. - *

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

- * 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) - *
- * - * @param a first value to test - * @param b second value to test - * @return whether they are equal - * @since 2019-03-18 - * @since v0.2.0 - */ - public static final boolean equals(final float a, final float b) { - 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. - *

- * 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) - *
- * - * @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) { - 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. - * - * @param d double to hash - * @return hash code of double - * @since 2019-10-16 - */ - public static final int hash(final double d) { - return Float.hashCode((float) d); - } - - // You may NOT get any DecimalComparison instances - private DecimalComparison() { - throw new AssertionError(); - } - -} diff --git a/src/main/java/sevenUnits/math/ExpressionParser.java b/src/main/java/sevenUnits/math/ExpressionParser.java deleted file mode 100644 index 5c8183b..0000000 --- a/src/main/java/sevenUnits/math/ExpressionParser.java +++ /dev/null @@ -1,735 +0,0 @@ -/** - * 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 sevenUnits.math; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Deque; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.UnaryOperator; - -/** - * An object that can parse expressions with unary or binary operators. - * - * @author Adrien Hopkins - * @param type of object that exists in parsed expressions - * @since 2019-03-14 - * @since v0.2.0 - */ -public final class ExpressionParser { - /** - * A builder that can create {@code ExpressionParser} instances. - * - * @author Adrien Hopkins - * @param type of object that exists in parsed expressions - * @since 2019-03-17 - * @since v0.2.0 - */ - public static final class Builder { - /** - * A function that obtains a parseable object from a string. For example, - * an integer {@code ExpressionParser} would use - * {@code Integer::parseInt}. - * - * @since 2019-03-14 - * @since v0.2.0 - */ - private final Function objectObtainer; - - /** - * The function of the space as an operator (like 3 x y) - * - * @since 2019-03-22 - * @since v0.2.0 - */ - private String spaceFunction = null; - - /** - * A map mapping operator strings to operator functions, for unary - * operators. - * - * @since 2019-03-14 - * @since v0.2.0 - */ - private final Map> unaryOperators; - - /** - * A map mapping operator strings to operator functions, for binary - * operators. - * - * @since 2019-03-14 - * @since v0.2.0 - */ - private final Map> binaryOperators; - - /** - * Creates the {@code Builder}. - * - * @param objectObtainer a function that can turn strings into objects of - * the type handled by the parser. - * @throws NullPointerException if {@code objectObtainer} is null - * @since 2019-03-17 - * @since v0.2.0 - */ - public Builder(final Function objectObtainer) { - this.objectObtainer = Objects.requireNonNull(objectObtainer, - "objectObtainer must not be null."); - this.unaryOperators = new HashMap<>(); - this.binaryOperators = new HashMap<>(); - } - - /** - * Adds a binary operator to the builder. - * - * @param text text used to reference the operator, like '+' - * @param operator operator to add - * @param priority operator's priority, which determines which operators - * are applied first - * @return this builder - * @throws NullPointerException if {@code text} or {@code operator} is - * null - * @since 2019-03-17 - * @since v0.2.0 - */ - public Builder addBinaryOperator(final String text, - final BinaryOperator operator, final int priority) { - Objects.requireNonNull(text, "text must not be null."); - Objects.requireNonNull(operator, "operator must not be null."); - - // Unfortunately, I cannot use a lambda because the - // PriorityBinaryOperator requires arguments. - final PriorityBinaryOperator priorityOperator = new PriorityBinaryOperator<>( - priority) { - @Override - public T apply(final T t, final T u) { - return operator.apply(t, u); - } - - }; - this.binaryOperators.put(text, priorityOperator); - return this; - } - - /** - * Adds a function for spaces. You must use the text of an existing binary - * operator. - * - * @param operator text of operator to use - * @return this builder - * @since 2019-03-22 - * @since v0.2.0 - */ - public Builder addSpaceFunction(final String operator) { - Objects.requireNonNull(operator, "operator must not be null."); - - if (!this.binaryOperators.containsKey(operator)) - throw new IllegalArgumentException(String - .format("Could not find binary operator '%s'", operator)); - - this.spaceFunction = operator; - return this; - } - - /** - * Adds a unary operator to the builder. - * - * @param text text used to reference the operator, like '-' - * @param operator operator to add - * @param priority operator's priority, which determines which operators - * are applied first - * @return this builder - * @throws NullPointerException if {@code text} or {@code operator} is - * null - * @since 2019-03-17 - * @since v0.2.0 - */ - public Builder addUnaryOperator(final String text, - final UnaryOperator operator, final int priority) { - Objects.requireNonNull(text, "text must not be null."); - Objects.requireNonNull(operator, "operator must not be null."); - - // Unfortunately, I cannot use a lambda because the - // PriorityUnaryOperator requires arguments. - final PriorityUnaryOperator priorityOperator = new PriorityUnaryOperator<>( - priority) { - @Override - public T apply(final T t) { - return operator.apply(t); - } - }; - this.unaryOperators.put(text, priorityOperator); - return this; - } - - /** - * @return an {@code ExpressionParser} instance with the properties - * given to this builder - * @since 2019-03-17 - * @since v0.2.0 - */ - public ExpressionParser build() { - return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, - this.binaryOperators, this.spaceFunction); - } - } - - /** - * A binary operator with a priority field that determines which operators - * apply first. - * - * @author Adrien Hopkins - * @param type of operand and result - * @since 2019-03-17 - * @since v0.2.0 - */ - private static abstract class PriorityBinaryOperator - implements BinaryOperator, Comparable> { - /** - * The operator's priority. Higher-priority operators are applied before - * lower-priority operators - * - * @since 2019-03-17 - * @since v0.2.0 - */ - private final int priority; - - /** - * Creates the {@code PriorityBinaryOperator}. - * - * @param priority operator's priority - * @since 2019-03-17 - * @since v0.2.0 - */ - public PriorityBinaryOperator(final int priority) { - this.priority = priority; - } - - /** - * Compares this object to another by priority. - * - *

- * {@inheritDoc} - *

- * - * @since 2019-03-17 - * @since v0.2.0 - */ - @Override - public int compareTo(final PriorityBinaryOperator o) { - if (this.priority < o.priority) - return -1; - else if (this.priority > o.priority) - return 1; - else - return 0; - } - - /** - * @return priority - * @since 2019-03-22 - * @since v0.2.0 - */ - public final int getPriority() { - return this.priority; - } - } - - /** - * A unary operator with a priority field that determines which operators - * apply first. - * - * @author Adrien Hopkins - * @param type of operand and result - * @since 2019-03-17 - * @since v0.2.0 - */ - private static abstract class PriorityUnaryOperator - implements UnaryOperator, Comparable> { - /** - * The operator's priority. Higher-priority operators are applied before - * lower-priority operators - * - * @since 2019-03-17 - * @since v0.2.0 - */ - private final int priority; - - /** - * Creates the {@code PriorityUnaryOperator}. - * - * @param priority operator's priority - * @since 2019-03-17 - * @since v0.2.0 - */ - public PriorityUnaryOperator(final int priority) { - this.priority = priority; - } - - /** - * Compares this object to another by priority. - * - *

- * {@inheritDoc} - *

- * - * @since 2019-03-17 - * @since v0.2.0 - */ - @Override - public int compareTo(final PriorityUnaryOperator o) { - if (this.priority < o.priority) - return -1; - else if (this.priority > o.priority) - return 1; - else - return 0; - } - - /** - * @return priority - * @since 2019-03-22 - * @since v0.2.0 - */ - public final int getPriority() { - return this.priority; - } - } - - /** - * The types of tokens that are available. - * - * @author Adrien Hopkins - * @since 2019-03-14 - * @since v0.2.0 - */ - private static enum TokenType { - OBJECT, UNARY_OPERATOR, BINARY_OPERATOR; - } - - /** - * The opening bracket. - * - * @since 2019-03-22 - * @since v0.2.0 - */ - public static final char OPENING_BRACKET = '('; - - /** - * The closing bracket. - * - * @since 2019-03-22 - * @since v0.2.0 - */ - public static final char CLOSING_BRACKET = ')'; - - /** - * Finds the other bracket in a pair of brackets, given the position of one. - * - * @param string string that contains brackets - * @param bracketPosition position of first bracket - * @return position of matching bracket - * @throws NullPointerException if string is null - * @since 2019-03-22 - * @since v0.2.0 - */ - private static int findBracketPair(final String string, - final int bracketPosition) { - Objects.requireNonNull(string, "string must not be null."); - - final char openingBracket = string.charAt(bracketPosition); - - // figure out what closing bracket to look for - final char closingBracket; - switch (openingBracket) { - case '(': - closingBracket = ')'; - break; - case '[': - closingBracket = ']'; - break; - case '{': - closingBracket = '}'; - break; - default: - throw new IllegalArgumentException( - String.format("Invalid bracket '%s'", openingBracket)); - } - - // level of brackets. every opening bracket increments this; every closing - // bracket decrements it - int bracketLevel = 0; - - // iterate over the string to find the closing bracket - for (int currentPosition = bracketPosition; currentPosition < string - .length(); currentPosition++) { - final char currentCharacter = string.charAt(currentPosition); - - if (currentCharacter == openingBracket) { - bracketLevel++; - } else if (currentCharacter == closingBracket) { - bracketLevel--; - if (bracketLevel == 0) - return currentPosition; - } - } - - throw new IllegalArgumentException("No matching bracket found."); - } - - /** - * A function that obtains a parseable object from a string. For example, an - * integer {@code ExpressionParser} would use {@code Integer::parseInt}. - * - * @since 2019-03-14 - * @since v0.2.0 - */ - private final Function objectObtainer; - - /** - * A map mapping operator strings to operator functions, for unary operators. - * - * @since 2019-03-14 - * @since v0.2.0 - */ - private final Map> unaryOperators; - - /** - * A map mapping operator strings to operator functions, for binary - * operators. - * - * @since 2019-03-14 - * @since v0.2.0 - */ - private final Map> binaryOperators; - - /** - * The operator for space, or null if spaces have no function. - * - * @since 2019-03-22 - * @since v0.2.0 - */ - private final String spaceOperator; - - /** - * Creates the {@code ExpressionParser}. - * - * @param objectObtainer function to get objects from strings - * @param unaryOperators unary operators available to the parser - * @param binaryOperators binary operators available to the parser - * @param spaceOperator operator used by spaces - * @since 2019-03-14 - * @since v0.2.0 - */ - private ExpressionParser(final Function objectObtainer, - final Map> unaryOperators, - final Map> binaryOperators, - final String spaceOperator) { - this.objectObtainer = objectObtainer; - this.unaryOperators = unaryOperators; - this.binaryOperators = binaryOperators; - this.spaceOperator = spaceOperator; - } - - /** - * Converts a given mathematical expression to reverse Polish notation - * (operators after operands). - *

- * For example,
- * {@code 2 * (3 + 4)}
- * becomes
- * {@code 2 3 4 + *}. - * - * @param expression expression - * @return expression in RPN - * @since 2019-03-17 - * @since v0.2.0 - */ - private String convertExpressionToReversePolish(final String expression) { - Objects.requireNonNull(expression, "expression must not be null."); - - final List components = new ArrayList<>(); - - // the part of the expression remaining to parse - String partialExpression = expression; - - // find and deal with brackets - while (partialExpression.indexOf(OPENING_BRACKET) != -1) { - final int openingBracketPosition = partialExpression - .indexOf(OPENING_BRACKET); - final int closingBracketPosition = findBracketPair(partialExpression, - openingBracketPosition); - - // check for function - if (openingBracketPosition > 0 - && partialExpression.charAt(openingBracketPosition - 1) != ' ') { - // function like sin(2) or tempF(32) - // find the position of the last space - int spacePosition = openingBracketPosition; - while (spacePosition >= 0 - && partialExpression.charAt(spacePosition) != ' ') { - spacePosition--; - } - // then split the function into pre-function and function, using the - // space position - components.addAll(Arrays.asList(partialExpression - .substring(0, spacePosition + 1).split(" "))); - components.add(partialExpression.substring(spacePosition + 1, - closingBracketPosition + 1)); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); - } else { - // normal brackets like (1 + 2) * (3 / 5) - components.addAll(Arrays.asList(partialExpression - .substring(0, openingBracketPosition).split(" "))); - components.add(this.convertExpressionToReversePolish( - partialExpression.substring(openingBracketPosition + 1, - closingBracketPosition))); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); - } - } - - // add everything else - components.addAll(Arrays.asList(partialExpression.split(" "))); - - // remove empty entries - while (components.contains("")) { - components.remove(""); - } - - // deal with space multiplication (x y) - if (this.spaceOperator != null) { - for (int i = 0; i < components.size() - 1; i++) { - if (this.getTokenType(components.get(i)) == TokenType.OBJECT && this - .getTokenType(components.get(i + 1)) == TokenType.OBJECT) { - components.add(++i, this.spaceOperator); - } - } - } - - // turn the expression into reverse Polish - while (true) { - final int highestPriorityOperatorPosition = this - .findHighestPriorityOperatorPosition(components); - if (highestPriorityOperatorPosition == -1) { - break; - } - - // swap components based on what kind of operator there is - // 1 + 2 becomes 2 1 + - // - 1 becomes 1 - - switch (this - .getTokenType(components.get(highestPriorityOperatorPosition))) { - case UNARY_OPERATOR: - final String unaryOperator = components - .remove(highestPriorityOperatorPosition); - final String operand = components - .remove(highestPriorityOperatorPosition); - components.add(highestPriorityOperatorPosition, - operand + " " + unaryOperator); - break; - case BINARY_OPERATOR: - final String binaryOperator = components - .remove(highestPriorityOperatorPosition); - final String operand1 = components - .remove(highestPriorityOperatorPosition - 1); - final String operand2 = components - .remove(highestPriorityOperatorPosition - 1); - components.add(highestPriorityOperatorPosition - 1, - operand2 + " " + operand1 + " " + binaryOperator); - break; - default: - throw new AssertionError("Expected operator, found non-operator."); - } - } - - // join all of the components together, then ensure there is only one - // space in a row - String expressionRPN = String.join(" ", components).replaceAll(" +", " "); - - while (expressionRPN.charAt(0) == ' ') { - expressionRPN = expressionRPN.substring(1); - } - while (expressionRPN.charAt(expressionRPN.length() - 1) == ' ') { - expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1); - } - return expressionRPN; - } - - /** - * Finds the position of the highest-priority operator in a list - * - * @param components components to test - * @param blacklist positions of operators that should be ignored - * @return position of highest priority, or -1 if the list contains no - * operators - * @throws NullPointerException if components is null - * @since 2019-03-22 - * @since v0.2.0 - */ - private int findHighestPriorityOperatorPosition( - final List components) { - Objects.requireNonNull(components, "components must not be null."); - // find highest priority - int maxPriority = Integer.MIN_VALUE; - int maxPriorityPosition = -1; - - // go over components one by one - // if it is an operator, test its priority to see if it's max - // if it is, update maxPriority and maxPriorityPosition - for (int i = 0; i < components.size(); i++) { - - switch (this.getTokenType(components.get(i))) { - case UNARY_OPERATOR: - final PriorityUnaryOperator unaryOperator = this.unaryOperators - .get(components.get(i)); - final int unaryPriority = unaryOperator.getPriority(); - - if (unaryPriority > maxPriority) { - maxPriority = unaryPriority; - maxPriorityPosition = i; - } - break; - case BINARY_OPERATOR: - final PriorityBinaryOperator binaryOperator = this.binaryOperators - .get(components.get(i)); - final int binaryPriority = binaryOperator.getPriority(); - - if (binaryPriority > maxPriority) { - maxPriority = binaryPriority; - maxPriorityPosition = i; - } - break; - default: - break; - } - } - - // max priority position found - return maxPriorityPosition; - } - - /** - * Determines whether an inputted string is an object or an operator - * - * @param token string to input - * @return type of token it is - * @throws NullPointerException if {@code expression} is null - * @since 2019-03-14 - * @since v0.2.0 - */ - private TokenType getTokenType(final String token) { - Objects.requireNonNull(token, "token must not be null."); - - if (this.unaryOperators.containsKey(token)) - return TokenType.UNARY_OPERATOR; - else if (this.binaryOperators.containsKey(token)) - return TokenType.BINARY_OPERATOR; - else - return TokenType.OBJECT; - } - - /** - * Parses an expression. - * - * @param expression expression to parse - * @return result - * @throws NullPointerException if {@code expression} is null - * @since 2019-03-14 - * @since v0.2.0 - */ - public T parseExpression(final String expression) { - return this.parseReversePolishExpression( - this.convertExpressionToReversePolish(expression)); - } - - /** - * Parses an expression expressed in reverse Polish notation. - * - * @param expression expression to parse - * @return result - * @throws NullPointerException if {@code expression} is null - * @since 2019-03-14 - * @since v0.2.0 - */ - private T parseReversePolishExpression(final String expression) { - Objects.requireNonNull(expression, "expression must not be null."); - - final Deque stack = new ArrayDeque<>(); - - // iterate over every item in the expression, then - for (final String item : expression.split(" ")) { - // choose a path based on what kind of thing was just read - switch (this.getTokenType(item)) { - - case BINARY_OPERATOR: - if (stack.size() < 2) - throw new IllegalStateException(String.format( - "Attempted to call binary operator %s with only %d arguments.", - item, stack.size())); - - // get two arguments and operator, then apply! - final T o1 = stack.pop(); - final T o2 = stack.pop(); - final BinaryOperator binaryOperator = this.binaryOperators - .get(item); - - stack.push(binaryOperator.apply(o1, o2)); - break; - - case OBJECT: - // just add it to the stack - stack.push(this.objectObtainer.apply(item)); - break; - - case UNARY_OPERATOR: - if (stack.size() < 1) - throw new IllegalStateException(String.format( - "Attempted to call unary operator %s with only %d arguments.", - item, stack.size())); - - // get one argument and operator, then apply! - final T o = stack.pop(); - final UnaryOperator unaryOperator = this.unaryOperators - .get(item); - - stack.push(unaryOperator.apply(o)); - break; - default: - throw new AssertionError( - String.format("Internal error: Invalid token type %s.", - this.getTokenType(item))); - - } - } - - // return answer, or throw an exception if I can't - if (stack.size() > 1) - throw new IllegalStateException( - "Computation ended up with more than one answer."); - else if (stack.size() == 0) - throw new IllegalStateException( - "Computation ended up without an answer."); - return stack.pop(); - } -} diff --git a/src/main/java/sevenUnits/math/ObjectProduct.java b/src/main/java/sevenUnits/math/ObjectProduct.java deleted file mode 100644 index db95fd4..0000000 --- a/src/main/java/sevenUnits/math/ObjectProduct.java +++ /dev/null @@ -1,284 +0,0 @@ -/** - * Copyright (C) 2018 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 sevenUnits.math; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; - -/** - * An immutable product of multiple objects of a type, such as base units. The objects can be multiplied and - * exponentiated. - * - * @author Adrien Hopkins - * @since 2019-10-16 - */ -public final class ObjectProduct { - /** - * Returns an empty ObjectProduct of a certain type - * - * @param - * type of objects that can be multiplied - * @return empty product - * @since 2019-10-16 - */ - public static final ObjectProduct empty() { - return new ObjectProduct<>(new HashMap<>()); - } - - /** - * Gets an {@code ObjectProduct} from an object-to-integer mapping - * - * @param - * type of object in product - * @param map - * map mapping objects to exponents - * @return object product - * @since 2019-10-16 - */ - public static final ObjectProduct fromExponentMapping(final Map map) { - return new ObjectProduct<>(new HashMap<>(map)); - } - - /** - * Gets an ObjectProduct that has one of the inputted argument, and nothing else. - * - * @param object - * object that will be in the product - * @return product - * @since 2019-10-16 - * @throws NullPointerException - * if object is null - */ - public static final ObjectProduct oneOf(final T object) { - Objects.requireNonNull(object, "object must not be null."); - final Map map = new HashMap<>(); - map.put(object, 1); - return new ObjectProduct<>(map); - } - - /** - * The objects that make up the product, mapped to their exponents. This map treats zero as null, and is immutable. - * - * @since 2019-10-16 - */ - final Map exponents; - - /** - * Creates the {@code ObjectProduct}. - * - * @param exponents - * objects that make up this product - * @since 2019-10-16 - */ - private ObjectProduct(final Map exponents) { - this.exponents = Collections.unmodifiableMap(ConditionalExistenceCollections.conditionalExistenceMap(exponents, - e -> !Integer.valueOf(0).equals(e.getValue()))); - } - - /** - * Calculates the quotient of two products - * - * @param other - * other product - * @return quotient of two products - * @since 2019-10-16 - * @throws NullPointerException - * if other is null - */ - public ObjectProduct dividedBy(final ObjectProduct other) { - Objects.requireNonNull(other, "other must not be null."); - // get a list of all objects in both sets - final Set objects = new HashSet<>(); - objects.addAll(this.getBaseSet()); - objects.addAll(other.getBaseSet()); - - // get a list of all exponents - final Map map = new HashMap<>(objects.size()); - for (final T key : objects) { - map.put(key, this.getExponent(key) - other.getExponent(key)); - } - - // create the product - return new ObjectProduct<>(map); - } - - // this method relies on the use of ZeroIsNullMap - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (!(obj instanceof ObjectProduct)) - return false; - final ObjectProduct other = (ObjectProduct) obj; - return Objects.equals(this.exponents, other.exponents); - } - - /** - * @return immutable map mapping objects to exponents - * @since 2019-10-16 - */ - public Map exponentMap() { - return this.exponents; - } - - /** - * @return a set of all of the base objects with non-zero exponents that make up this dimension. - * @since 2018-12-12 - * @since v0.1.0 - */ - public final Set getBaseSet() { - final Set dimensions = new HashSet<>(); - - // add all dimensions with a nonzero exponent - zero exponents shouldn't be there in the first place - for (final T dimension : this.exponents.keySet()) { - if (!this.exponents.get(dimension).equals(0)) { - dimensions.add(dimension); - } - } - - return dimensions; - } - - /** - * Gets the exponent for a specific dimension. - * - * @param dimension - * dimension to check - * @return exponent for that dimension - * @since 2018-12-12 - * @since v0.1.0 - */ - public int getExponent(final T dimension) { - return this.exponents.getOrDefault(dimension, 0); - } - - @Override - public int hashCode() { - return Objects.hash(this.exponents); - } - - /** - * @return true if this product is a single object, i.e. it has one exponent of one and no other nonzero exponents - * @since 2019-10-16 - */ - public boolean isSingleObject() { - int oneCount = 0; - boolean twoOrMore = false; // has exponents of 2 or more - for (final T b : this.getBaseSet()) { - if (this.getExponent(b) == 1) { - oneCount++; - } else if (this.getExponent(b) != 0) { - twoOrMore = true; - } - } - return oneCount == 1 && !twoOrMore; - } - - /** - * Multiplies this product by another - * - * @param other - * other product - * @return product of two products - * @since 2019-10-16 - * @throws NullPointerException - * if other is null - */ - public ObjectProduct times(final ObjectProduct other) { - Objects.requireNonNull(other, "other must not be null."); - // get a list of all objects in both sets - final Set objects = new HashSet<>(); - objects.addAll(this.getBaseSet()); - objects.addAll(other.getBaseSet()); - - // get a list of all exponents - final Map map = new HashMap<>(objects.size()); - for (final T key : objects) { - map.put(key, this.getExponent(key) + other.getExponent(key)); - } - - // create the product - return new ObjectProduct<>(map); - } - - /** - * Returns this product, but to an exponent - * - * @param exponent - * exponent - * @return result of exponentiation - * @since 2019-10-16 - */ - public ObjectProduct toExponent(final int exponent) { - final Map map = new HashMap<>(this.exponents); - for (final T key : this.exponents.keySet()) { - map.put(key, this.getExponent(key) * exponent); - } - return new ObjectProduct<>(map); - } - - /** - * Converts this product to a string using the objects' {@link Object#toString()} method. If objects have a long - * toString representation, it is recommended to use {@link #toString(Function)} instead to shorten the returned - * string. - * - *

- * {@inheritDoc} - */ - @Override - public String toString() { - return this.toString(Object::toString); - } - - /** - * Converts this product to a string. The objects that make up this product are represented by - * {@code objectToString} - * - * @param objectToString - * function to convert objects to strings - * @return string representation of product - * @since 2019-10-16 - */ - public String toString(final Function objectToString) { - final List positiveStringComponents = new ArrayList<>(); - final List negativeStringComponents = new ArrayList<>(); - - // for each base object that makes up this object, add it and its exponent - for (final T object : this.getBaseSet()) { - final int exponent = this.exponents.get(object); - if (exponent > 0) { - positiveStringComponents.add(String.format("%s^%d", objectToString.apply(object), exponent)); - } else if (exponent < 0) { - negativeStringComponents.add(String.format("%s^%d", objectToString.apply(object), -exponent)); - } - } - - final String positiveString = positiveStringComponents.isEmpty() ? "1" - : String.join(" * ", positiveStringComponents); - final String negativeString = negativeStringComponents.isEmpty() ? "" - : " / " + String.join(" * ", negativeStringComponents); - - return positiveString + negativeString; - } -} diff --git a/src/main/java/sevenUnits/math/UncertainDouble.java b/src/main/java/sevenUnits/math/UncertainDouble.java deleted file mode 100644 index b81bb79..0000000 --- a/src/main/java/sevenUnits/math/UncertainDouble.java +++ /dev/null @@ -1,419 +0,0 @@ -/** - * 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 sevenUnits.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.compare(this.value, other.value) != 0) - return false; - if (Double.compare(this.uncertainty, other.uncertainty) != 0) - 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; - result = prime * result + Double.hashCode(this.value); - result = prime * result + Double.hashCode(this.uncertainty); - 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/main/java/sevenUnits/math/package-info.java b/src/main/java/sevenUnits/math/package-info.java deleted file mode 100644 index 62f72cd..0000000 --- a/src/main/java/sevenUnits/math/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 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 . - */ -/** - * Supplementary classes that are not related to units, but are necessary for their function. - * - * @author Adrien Hopkins - * @since 2019-03-14 - * @since v0.2.0 - */ -package sevenUnits.math; \ No newline at end of file diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index 7bacc9d..df457e4 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -19,7 +19,7 @@ package sevenUnits.unit; import java.util.Objects; import java.util.function.DoubleUnaryOperator; -import sevenUnits.math.ObjectProduct; +import sevenUnits.utils.ObjectProduct; /** * A unit that uses functional objects to convert to and from its base. diff --git a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java index 61503e8..2ee9e19 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java @@ -19,7 +19,7 @@ package sevenUnits.unit; import java.util.function.DoubleFunction; import java.util.function.ToDoubleFunction; -import sevenUnits.math.ObjectProduct; +import sevenUnits.utils.ObjectProduct; /** * A unitlike form that converts using two conversion functions. diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 7c46443..25c2e2e 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -18,9 +18,9 @@ package sevenUnits.unit; import java.util.Objects; -import sevenUnits.math.DecimalComparison; -import sevenUnits.math.ObjectProduct; -import sevenUnits.math.UncertainDouble; +import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.ObjectProduct; +import sevenUnits.utils.UncertainDouble; /** * A unit that can be expressed as a product of its base and a number. For diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index 687a8b4..ffb9271 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -19,8 +19,8 @@ package sevenUnits.unit; import java.util.Objects; import java.util.Optional; -import sevenUnits.math.DecimalComparison; -import sevenUnits.math.UncertainDouble; +import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.UncertainDouble; /** * A possibly uncertain value expressed in a linear unit. diff --git a/src/main/java/sevenUnits/unit/MultiUnit.java b/src/main/java/sevenUnits/unit/MultiUnit.java index 014e055..83cdb03 100644 --- a/src/main/java/sevenUnits/unit/MultiUnit.java +++ b/src/main/java/sevenUnits/unit/MultiUnit.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import sevenUnits.math.ObjectProduct; +import sevenUnits.utils.ObjectProduct; /** * A combination of units, like "5 foot + 7 inch". All but the last units should diff --git a/src/main/java/sevenUnits/unit/SI.java b/src/main/java/sevenUnits/unit/SI.java index e0e5a54..278fb5e 100644 --- a/src/main/java/sevenUnits/unit/SI.java +++ b/src/main/java/sevenUnits/unit/SI.java @@ -18,7 +18,7 @@ package sevenUnits.unit; import java.util.Set; -import sevenUnits.math.ObjectProduct; +import sevenUnits.utils.ObjectProduct; /** * All of the units, prefixes and dimensions that are used by the SI, as well as diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 1e875af..8fcacb8 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -23,8 +23,8 @@ import java.util.Objects; import java.util.Set; import java.util.function.DoubleUnaryOperator; -import sevenUnits.math.DecimalComparison; -import sevenUnits.math.ObjectProduct; +import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.ObjectProduct; /** * A unit that is composed of base units. diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 1960229..a40f000 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -44,11 +44,11 @@ import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import sevenUnits.math.ConditionalExistenceCollections; -import sevenUnits.math.DecimalComparison; -import sevenUnits.math.ExpressionParser; -import sevenUnits.math.ObjectProduct; -import sevenUnits.math.UncertainDouble; +import sevenUnits.utils.ConditionalExistenceCollections; +import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.ExpressionParser; +import sevenUnits.utils.ObjectProduct; +import sevenUnits.utils.UncertainDouble; /** * A database of units, prefixes and dimensions, and their names. diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index e4a27bd..308f4b0 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -20,7 +20,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import sevenUnits.math.DecimalComparison; +import sevenUnits.utils.DecimalComparison; /** * A prefix that can be applied to a {@code LinearUnit} to multiply it by some value diff --git a/src/main/java/sevenUnits/unit/Unitlike.java b/src/main/java/sevenUnits/unit/Unitlike.java index fd361b6..d2dcbbb 100644 --- a/src/main/java/sevenUnits/unit/Unitlike.java +++ b/src/main/java/sevenUnits/unit/Unitlike.java @@ -22,7 +22,7 @@ import java.util.Objects; import java.util.function.DoubleFunction; import java.util.function.ToDoubleFunction; -import sevenUnits.math.ObjectProduct; +import sevenUnits.utils.ObjectProduct; /** * An object that can convert a value between multiple forms (instances of the diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java new file mode 100644 index 0000000..2adb579 --- /dev/null +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -0,0 +1,468 @@ +/** + * 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 sevenUnits.utils; + +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +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 + * 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 + * 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. + *

+ * 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 + * @since 2019-10-17 + */ +// TODO add conditional existence Lists and Sorted/Navigable Sets/Maps +public final class ConditionalExistenceCollections { + /** + * Elements in this collection only exist if they meet a condition. + * + * @author Adrien Hopkins + * @since 2019-10-17 + * @param type of element in collection + */ + static final class ConditionalExistenceCollection + extends AbstractCollection { + final Collection collection; + final Predicate existenceCondition; + + /** + * Creates the {@code ConditionalExistenceCollection}. + * + * @param collection + * @param existenceCondition + * @since 2019-10-17 + */ + 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 + // 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); + } + + @Override + public boolean remove(final Object o) { + // 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(); + } + + @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 + */ + static final class ConditionalExistenceIterator implements Iterator { + final Iterator iterator; + final Predicate existenceCondition; + E nextElement; + boolean hasNext; + + /** + * Creates the {@code ConditionalExistenceIterator}. + * + * @param iterator + * @param condition + * @since 2019-10-17 + */ + 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. + * + * @since 2019-10-17 + */ + private void getAndSetNextElement() { + do { + if (!this.iterator.hasNext()) { + this.nextElement = null; + this.hasNext = false; + return; + } + this.nextElement = this.iterator.next(); + } while (!this.existenceCondition.test(this.nextElement)); + this.hasNext = true; + } + + @Override + public boolean hasNext() { + return this.hasNext; + } + + @Override + public E next() { + if (this.hasNext()) { + final E next = this.nextElement; + this.getAndSetNextElement(); + return next; + } 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 + */ + static final class ConditionalExistenceMap extends AbstractMap { + Map map; + Predicate> entryExistenceCondition; + + /** + * Creates the {@code ConditionalExistenceMap}. + * + * @param map + * @param entryExistenceCondition + * @since 2019-10-17 + */ + 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); + } + + @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() { + 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 + */ + 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 + * @since 2019-10-17 + */ + 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. + */ + @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 + // 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); + } + + @Override + public boolean remove(final Object o) { + // 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. + * + * @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, + final Predicate existenceCondition) { + return new ConditionalExistenceCollection<>(collection, + existenceCondition); + } + + /** + * 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 + * @return wrapper iterator + * @since 2019-10-17 + */ + 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 + * + * @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, + final Predicate> entryExistenceCondition) { + return new ConditionalExistenceMap<>(map, entryExistenceCondition); + } + + /** + * 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 + * @return wrapper set + * @since 2019-10-17 + */ + public static final Set conditionalExistenceSet(final Set set, + final Predicate existenceCondition) { + return new ConditionalExistenceSet<>(set, existenceCondition); + } +} diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java new file mode 100644 index 0000000..a5cbbaa --- /dev/null +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -0,0 +1,256 @@ +/** + * 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 sevenUnits.utils; + +import java.math.BigDecimal; + +/** + * A class that contains methods to compare float and double values. + * + * @author Adrien Hopkins + * @since 2019-03-18 + * @since v0.2.0 + */ +public final class DecimalComparison { + /** + * The value used for double comparison. If two double values are within this + * value multiplied by the larger value, they are considered equal. + * + * @since 2019-03-18 + * @since v0.2.0 + */ + 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. + * + * @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. + *

+ * 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) + *
+ * + * @param a first value to test + * @param b second value to test + * @return whether they are equal + * @since 2019-03-18 + * @since v0.2.0 + * @see #hashCode(double) + */ + public static final boolean equals(final double a, final double b) { + 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. + *

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

+ * 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) + *
+ * + * @param a first value to test + * @param b second value to test + * @return whether they are equal + * @since 2019-03-18 + * @since v0.2.0 + */ + public static final boolean equals(final float a, final float b) { + 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. + *

+ * 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) + *
+ * + * @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) { + 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. + * + * @param d double to hash + * @return hash code of double + * @since 2019-10-16 + */ + public static final int hash(final double d) { + return Float.hashCode((float) d); + } + + // You may NOT get any DecimalComparison instances + private DecimalComparison() { + throw new AssertionError(); + } + +} diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java new file mode 100644 index 0000000..1d3d44d --- /dev/null +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -0,0 +1,735 @@ +/** + * 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 sevenUnits.utils; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +/** + * An object that can parse expressions with unary or binary operators. + * + * @author Adrien Hopkins + * @param type of object that exists in parsed expressions + * @since 2019-03-14 + * @since v0.2.0 + */ +public final class ExpressionParser { + /** + * A builder that can create {@code ExpressionParser} instances. + * + * @author Adrien Hopkins + * @param type of object that exists in parsed expressions + * @since 2019-03-17 + * @since v0.2.0 + */ + public static final class Builder { + /** + * A function that obtains a parseable object from a string. For example, + * an integer {@code ExpressionParser} would use + * {@code Integer::parseInt}. + * + * @since 2019-03-14 + * @since v0.2.0 + */ + private final Function objectObtainer; + + /** + * The function of the space as an operator (like 3 x y) + * + * @since 2019-03-22 + * @since v0.2.0 + */ + private String spaceFunction = null; + + /** + * A map mapping operator strings to operator functions, for unary + * operators. + * + * @since 2019-03-14 + * @since v0.2.0 + */ + private final Map> unaryOperators; + + /** + * A map mapping operator strings to operator functions, for binary + * operators. + * + * @since 2019-03-14 + * @since v0.2.0 + */ + private final Map> binaryOperators; + + /** + * Creates the {@code Builder}. + * + * @param objectObtainer a function that can turn strings into objects of + * the type handled by the parser. + * @throws NullPointerException if {@code objectObtainer} is null + * @since 2019-03-17 + * @since v0.2.0 + */ + public Builder(final Function objectObtainer) { + this.objectObtainer = Objects.requireNonNull(objectObtainer, + "objectObtainer must not be null."); + this.unaryOperators = new HashMap<>(); + this.binaryOperators = new HashMap<>(); + } + + /** + * Adds a binary operator to the builder. + * + * @param text text used to reference the operator, like '+' + * @param operator operator to add + * @param priority operator's priority, which determines which operators + * are applied first + * @return this builder + * @throws NullPointerException if {@code text} or {@code operator} is + * null + * @since 2019-03-17 + * @since v0.2.0 + */ + public Builder addBinaryOperator(final String text, + final BinaryOperator operator, final int priority) { + Objects.requireNonNull(text, "text must not be null."); + Objects.requireNonNull(operator, "operator must not be null."); + + // Unfortunately, I cannot use a lambda because the + // PriorityBinaryOperator requires arguments. + final PriorityBinaryOperator priorityOperator = new PriorityBinaryOperator<>( + priority) { + @Override + public T apply(final T t, final T u) { + return operator.apply(t, u); + } + + }; + this.binaryOperators.put(text, priorityOperator); + return this; + } + + /** + * Adds a function for spaces. You must use the text of an existing binary + * operator. + * + * @param operator text of operator to use + * @return this builder + * @since 2019-03-22 + * @since v0.2.0 + */ + public Builder addSpaceFunction(final String operator) { + Objects.requireNonNull(operator, "operator must not be null."); + + if (!this.binaryOperators.containsKey(operator)) + throw new IllegalArgumentException(String + .format("Could not find binary operator '%s'", operator)); + + this.spaceFunction = operator; + return this; + } + + /** + * Adds a unary operator to the builder. + * + * @param text text used to reference the operator, like '-' + * @param operator operator to add + * @param priority operator's priority, which determines which operators + * are applied first + * @return this builder + * @throws NullPointerException if {@code text} or {@code operator} is + * null + * @since 2019-03-17 + * @since v0.2.0 + */ + public Builder addUnaryOperator(final String text, + final UnaryOperator operator, final int priority) { + Objects.requireNonNull(text, "text must not be null."); + Objects.requireNonNull(operator, "operator must not be null."); + + // Unfortunately, I cannot use a lambda because the + // PriorityUnaryOperator requires arguments. + final PriorityUnaryOperator priorityOperator = new PriorityUnaryOperator<>( + priority) { + @Override + public T apply(final T t) { + return operator.apply(t); + } + }; + this.unaryOperators.put(text, priorityOperator); + return this; + } + + /** + * @return an {@code ExpressionParser} instance with the properties + * given to this builder + * @since 2019-03-17 + * @since v0.2.0 + */ + public ExpressionParser build() { + return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, + this.binaryOperators, this.spaceFunction); + } + } + + /** + * A binary operator with a priority field that determines which operators + * apply first. + * + * @author Adrien Hopkins + * @param type of operand and result + * @since 2019-03-17 + * @since v0.2.0 + */ + private static abstract class PriorityBinaryOperator + implements BinaryOperator, Comparable> { + /** + * The operator's priority. Higher-priority operators are applied before + * lower-priority operators + * + * @since 2019-03-17 + * @since v0.2.0 + */ + private final int priority; + + /** + * Creates the {@code PriorityBinaryOperator}. + * + * @param priority operator's priority + * @since 2019-03-17 + * @since v0.2.0 + */ + public PriorityBinaryOperator(final int priority) { + this.priority = priority; + } + + /** + * Compares this object to another by priority. + * + *

+ * {@inheritDoc} + *

+ * + * @since 2019-03-17 + * @since v0.2.0 + */ + @Override + public int compareTo(final PriorityBinaryOperator o) { + if (this.priority < o.priority) + return -1; + else if (this.priority > o.priority) + return 1; + else + return 0; + } + + /** + * @return priority + * @since 2019-03-22 + * @since v0.2.0 + */ + public final int getPriority() { + return this.priority; + } + } + + /** + * A unary operator with a priority field that determines which operators + * apply first. + * + * @author Adrien Hopkins + * @param type of operand and result + * @since 2019-03-17 + * @since v0.2.0 + */ + private static abstract class PriorityUnaryOperator + implements UnaryOperator, Comparable> { + /** + * The operator's priority. Higher-priority operators are applied before + * lower-priority operators + * + * @since 2019-03-17 + * @since v0.2.0 + */ + private final int priority; + + /** + * Creates the {@code PriorityUnaryOperator}. + * + * @param priority operator's priority + * @since 2019-03-17 + * @since v0.2.0 + */ + public PriorityUnaryOperator(final int priority) { + this.priority = priority; + } + + /** + * Compares this object to another by priority. + * + *

+ * {@inheritDoc} + *

+ * + * @since 2019-03-17 + * @since v0.2.0 + */ + @Override + public int compareTo(final PriorityUnaryOperator o) { + if (this.priority < o.priority) + return -1; + else if (this.priority > o.priority) + return 1; + else + return 0; + } + + /** + * @return priority + * @since 2019-03-22 + * @since v0.2.0 + */ + public final int getPriority() { + return this.priority; + } + } + + /** + * The types of tokens that are available. + * + * @author Adrien Hopkins + * @since 2019-03-14 + * @since v0.2.0 + */ + private static enum TokenType { + OBJECT, UNARY_OPERATOR, BINARY_OPERATOR; + } + + /** + * The opening bracket. + * + * @since 2019-03-22 + * @since v0.2.0 + */ + public static final char OPENING_BRACKET = '('; + + /** + * The closing bracket. + * + * @since 2019-03-22 + * @since v0.2.0 + */ + public static final char CLOSING_BRACKET = ')'; + + /** + * Finds the other bracket in a pair of brackets, given the position of one. + * + * @param string string that contains brackets + * @param bracketPosition position of first bracket + * @return position of matching bracket + * @throws NullPointerException if string is null + * @since 2019-03-22 + * @since v0.2.0 + */ + private static int findBracketPair(final String string, + final int bracketPosition) { + Objects.requireNonNull(string, "string must not be null."); + + final char openingBracket = string.charAt(bracketPosition); + + // figure out what closing bracket to look for + final char closingBracket; + switch (openingBracket) { + case '(': + closingBracket = ')'; + break; + case '[': + closingBracket = ']'; + break; + case '{': + closingBracket = '}'; + break; + default: + throw new IllegalArgumentException( + String.format("Invalid bracket '%s'", openingBracket)); + } + + // level of brackets. every opening bracket increments this; every closing + // bracket decrements it + int bracketLevel = 0; + + // iterate over the string to find the closing bracket + for (int currentPosition = bracketPosition; currentPosition < string + .length(); currentPosition++) { + final char currentCharacter = string.charAt(currentPosition); + + if (currentCharacter == openingBracket) { + bracketLevel++; + } else if (currentCharacter == closingBracket) { + bracketLevel--; + if (bracketLevel == 0) + return currentPosition; + } + } + + throw new IllegalArgumentException("No matching bracket found."); + } + + /** + * A function that obtains a parseable object from a string. For example, an + * integer {@code ExpressionParser} would use {@code Integer::parseInt}. + * + * @since 2019-03-14 + * @since v0.2.0 + */ + private final Function objectObtainer; + + /** + * A map mapping operator strings to operator functions, for unary operators. + * + * @since 2019-03-14 + * @since v0.2.0 + */ + private final Map> unaryOperators; + + /** + * A map mapping operator strings to operator functions, for binary + * operators. + * + * @since 2019-03-14 + * @since v0.2.0 + */ + private final Map> binaryOperators; + + /** + * The operator for space, or null if spaces have no function. + * + * @since 2019-03-22 + * @since v0.2.0 + */ + private final String spaceOperator; + + /** + * Creates the {@code ExpressionParser}. + * + * @param objectObtainer function to get objects from strings + * @param unaryOperators unary operators available to the parser + * @param binaryOperators binary operators available to the parser + * @param spaceOperator operator used by spaces + * @since 2019-03-14 + * @since v0.2.0 + */ + private ExpressionParser(final Function objectObtainer, + final Map> unaryOperators, + final Map> binaryOperators, + final String spaceOperator) { + this.objectObtainer = objectObtainer; + this.unaryOperators = unaryOperators; + this.binaryOperators = binaryOperators; + this.spaceOperator = spaceOperator; + } + + /** + * Converts a given mathematical expression to reverse Polish notation + * (operators after operands). + *

+ * For example,
+ * {@code 2 * (3 + 4)}
+ * becomes
+ * {@code 2 3 4 + *}. + * + * @param expression expression + * @return expression in RPN + * @since 2019-03-17 + * @since v0.2.0 + */ + private String convertExpressionToReversePolish(final String expression) { + Objects.requireNonNull(expression, "expression must not be null."); + + final List components = new ArrayList<>(); + + // the part of the expression remaining to parse + String partialExpression = expression; + + // find and deal with brackets + while (partialExpression.indexOf(OPENING_BRACKET) != -1) { + final int openingBracketPosition = partialExpression + .indexOf(OPENING_BRACKET); + final int closingBracketPosition = findBracketPair(partialExpression, + openingBracketPosition); + + // check for function + if (openingBracketPosition > 0 + && partialExpression.charAt(openingBracketPosition - 1) != ' ') { + // function like sin(2) or tempF(32) + // find the position of the last space + int spacePosition = openingBracketPosition; + while (spacePosition >= 0 + && partialExpression.charAt(spacePosition) != ' ') { + spacePosition--; + } + // then split the function into pre-function and function, using the + // space position + components.addAll(Arrays.asList(partialExpression + .substring(0, spacePosition + 1).split(" "))); + components.add(partialExpression.substring(spacePosition + 1, + closingBracketPosition + 1)); + partialExpression = partialExpression + .substring(closingBracketPosition + 1); + } else { + // normal brackets like (1 + 2) * (3 / 5) + components.addAll(Arrays.asList(partialExpression + .substring(0, openingBracketPosition).split(" "))); + components.add(this.convertExpressionToReversePolish( + partialExpression.substring(openingBracketPosition + 1, + closingBracketPosition))); + partialExpression = partialExpression + .substring(closingBracketPosition + 1); + } + } + + // add everything else + components.addAll(Arrays.asList(partialExpression.split(" "))); + + // remove empty entries + while (components.contains("")) { + components.remove(""); + } + + // deal with space multiplication (x y) + if (this.spaceOperator != null) { + for (int i = 0; i < components.size() - 1; i++) { + if (this.getTokenType(components.get(i)) == TokenType.OBJECT && this + .getTokenType(components.get(i + 1)) == TokenType.OBJECT) { + components.add(++i, this.spaceOperator); + } + } + } + + // turn the expression into reverse Polish + while (true) { + final int highestPriorityOperatorPosition = this + .findHighestPriorityOperatorPosition(components); + if (highestPriorityOperatorPosition == -1) { + break; + } + + // swap components based on what kind of operator there is + // 1 + 2 becomes 2 1 + + // - 1 becomes 1 - + switch (this + .getTokenType(components.get(highestPriorityOperatorPosition))) { + case UNARY_OPERATOR: + final String unaryOperator = components + .remove(highestPriorityOperatorPosition); + final String operand = components + .remove(highestPriorityOperatorPosition); + components.add(highestPriorityOperatorPosition, + operand + " " + unaryOperator); + break; + case BINARY_OPERATOR: + final String binaryOperator = components + .remove(highestPriorityOperatorPosition); + final String operand1 = components + .remove(highestPriorityOperatorPosition - 1); + final String operand2 = components + .remove(highestPriorityOperatorPosition - 1); + components.add(highestPriorityOperatorPosition - 1, + operand2 + " " + operand1 + " " + binaryOperator); + break; + default: + throw new AssertionError("Expected operator, found non-operator."); + } + } + + // join all of the components together, then ensure there is only one + // space in a row + String expressionRPN = String.join(" ", components).replaceAll(" +", " "); + + while (expressionRPN.charAt(0) == ' ') { + expressionRPN = expressionRPN.substring(1); + } + while (expressionRPN.charAt(expressionRPN.length() - 1) == ' ') { + expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1); + } + return expressionRPN; + } + + /** + * Finds the position of the highest-priority operator in a list + * + * @param components components to test + * @param blacklist positions of operators that should be ignored + * @return position of highest priority, or -1 if the list contains no + * operators + * @throws NullPointerException if components is null + * @since 2019-03-22 + * @since v0.2.0 + */ + private int findHighestPriorityOperatorPosition( + final List components) { + Objects.requireNonNull(components, "components must not be null."); + // find highest priority + int maxPriority = Integer.MIN_VALUE; + int maxPriorityPosition = -1; + + // go over components one by one + // if it is an operator, test its priority to see if it's max + // if it is, update maxPriority and maxPriorityPosition + for (int i = 0; i < components.size(); i++) { + + switch (this.getTokenType(components.get(i))) { + case UNARY_OPERATOR: + final PriorityUnaryOperator unaryOperator = this.unaryOperators + .get(components.get(i)); + final int unaryPriority = unaryOperator.getPriority(); + + if (unaryPriority > maxPriority) { + maxPriority = unaryPriority; + maxPriorityPosition = i; + } + break; + case BINARY_OPERATOR: + final PriorityBinaryOperator binaryOperator = this.binaryOperators + .get(components.get(i)); + final int binaryPriority = binaryOperator.getPriority(); + + if (binaryPriority > maxPriority) { + maxPriority = binaryPriority; + maxPriorityPosition = i; + } + break; + default: + break; + } + } + + // max priority position found + return maxPriorityPosition; + } + + /** + * Determines whether an inputted string is an object or an operator + * + * @param token string to input + * @return type of token it is + * @throws NullPointerException if {@code expression} is null + * @since 2019-03-14 + * @since v0.2.0 + */ + private TokenType getTokenType(final String token) { + Objects.requireNonNull(token, "token must not be null."); + + if (this.unaryOperators.containsKey(token)) + return TokenType.UNARY_OPERATOR; + else if (this.binaryOperators.containsKey(token)) + return TokenType.BINARY_OPERATOR; + else + return TokenType.OBJECT; + } + + /** + * Parses an expression. + * + * @param expression expression to parse + * @return result + * @throws NullPointerException if {@code expression} is null + * @since 2019-03-14 + * @since v0.2.0 + */ + public T parseExpression(final String expression) { + return this.parseReversePolishExpression( + this.convertExpressionToReversePolish(expression)); + } + + /** + * Parses an expression expressed in reverse Polish notation. + * + * @param expression expression to parse + * @return result + * @throws NullPointerException if {@code expression} is null + * @since 2019-03-14 + * @since v0.2.0 + */ + private T parseReversePolishExpression(final String expression) { + Objects.requireNonNull(expression, "expression must not be null."); + + final Deque stack = new ArrayDeque<>(); + + // iterate over every item in the expression, then + for (final String item : expression.split(" ")) { + // choose a path based on what kind of thing was just read + switch (this.getTokenType(item)) { + + case BINARY_OPERATOR: + if (stack.size() < 2) + throw new IllegalStateException(String.format( + "Attempted to call binary operator %s with only %d arguments.", + item, stack.size())); + + // get two arguments and operator, then apply! + final T o1 = stack.pop(); + final T o2 = stack.pop(); + final BinaryOperator binaryOperator = this.binaryOperators + .get(item); + + stack.push(binaryOperator.apply(o1, o2)); + break; + + case OBJECT: + // just add it to the stack + stack.push(this.objectObtainer.apply(item)); + break; + + case UNARY_OPERATOR: + if (stack.size() < 1) + throw new IllegalStateException(String.format( + "Attempted to call unary operator %s with only %d arguments.", + item, stack.size())); + + // get one argument and operator, then apply! + final T o = stack.pop(); + final UnaryOperator unaryOperator = this.unaryOperators + .get(item); + + stack.push(unaryOperator.apply(o)); + break; + default: + throw new AssertionError( + String.format("Internal error: Invalid token type %s.", + this.getTokenType(item))); + + } + } + + // return answer, or throw an exception if I can't + if (stack.size() > 1) + throw new IllegalStateException( + "Computation ended up with more than one answer."); + else if (stack.size() == 0) + throw new IllegalStateException( + "Computation ended up without an answer."); + return stack.pop(); + } +} diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java new file mode 100644 index 0000000..1dacb7d --- /dev/null +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -0,0 +1,284 @@ +/** + * Copyright (C) 2018 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 sevenUnits.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +/** + * An immutable product of multiple objects of a type, such as base units. The objects can be multiplied and + * exponentiated. + * + * @author Adrien Hopkins + * @since 2019-10-16 + */ +public final class ObjectProduct { + /** + * Returns an empty ObjectProduct of a certain type + * + * @param + * type of objects that can be multiplied + * @return empty product + * @since 2019-10-16 + */ + public static final ObjectProduct empty() { + return new ObjectProduct<>(new HashMap<>()); + } + + /** + * Gets an {@code ObjectProduct} from an object-to-integer mapping + * + * @param + * type of object in product + * @param map + * map mapping objects to exponents + * @return object product + * @since 2019-10-16 + */ + public static final ObjectProduct fromExponentMapping(final Map map) { + return new ObjectProduct<>(new HashMap<>(map)); + } + + /** + * Gets an ObjectProduct that has one of the inputted argument, and nothing else. + * + * @param object + * object that will be in the product + * @return product + * @since 2019-10-16 + * @throws NullPointerException + * if object is null + */ + public static final ObjectProduct oneOf(final T object) { + Objects.requireNonNull(object, "object must not be null."); + final Map map = new HashMap<>(); + map.put(object, 1); + return new ObjectProduct<>(map); + } + + /** + * The objects that make up the product, mapped to their exponents. This map treats zero as null, and is immutable. + * + * @since 2019-10-16 + */ + final Map exponents; + + /** + * Creates the {@code ObjectProduct}. + * + * @param exponents + * objects that make up this product + * @since 2019-10-16 + */ + private ObjectProduct(final Map exponents) { + this.exponents = Collections.unmodifiableMap(ConditionalExistenceCollections.conditionalExistenceMap(exponents, + e -> !Integer.valueOf(0).equals(e.getValue()))); + } + + /** + * Calculates the quotient of two products + * + * @param other + * other product + * @return quotient of two products + * @since 2019-10-16 + * @throws NullPointerException + * if other is null + */ + public ObjectProduct dividedBy(final ObjectProduct other) { + Objects.requireNonNull(other, "other must not be null."); + // get a list of all objects in both sets + final Set objects = new HashSet<>(); + objects.addAll(this.getBaseSet()); + objects.addAll(other.getBaseSet()); + + // get a list of all exponents + final Map map = new HashMap<>(objects.size()); + for (final T key : objects) { + map.put(key, this.getExponent(key) - other.getExponent(key)); + } + + // create the product + return new ObjectProduct<>(map); + } + + // this method relies on the use of ZeroIsNullMap + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (!(obj instanceof ObjectProduct)) + return false; + final ObjectProduct other = (ObjectProduct) obj; + return Objects.equals(this.exponents, other.exponents); + } + + /** + * @return immutable map mapping objects to exponents + * @since 2019-10-16 + */ + public Map exponentMap() { + return this.exponents; + } + + /** + * @return a set of all of the base objects with non-zero exponents that make up this dimension. + * @since 2018-12-12 + * @since v0.1.0 + */ + public final Set getBaseSet() { + final Set dimensions = new HashSet<>(); + + // add all dimensions with a nonzero exponent - zero exponents shouldn't be there in the first place + for (final T dimension : this.exponents.keySet()) { + if (!this.exponents.get(dimension).equals(0)) { + dimensions.add(dimension); + } + } + + return dimensions; + } + + /** + * Gets the exponent for a specific dimension. + * + * @param dimension + * dimension to check + * @return exponent for that dimension + * @since 2018-12-12 + * @since v0.1.0 + */ + public int getExponent(final T dimension) { + return this.exponents.getOrDefault(dimension, 0); + } + + @Override + public int hashCode() { + return Objects.hash(this.exponents); + } + + /** + * @return true if this product is a single object, i.e. it has one exponent of one and no other nonzero exponents + * @since 2019-10-16 + */ + public boolean isSingleObject() { + int oneCount = 0; + boolean twoOrMore = false; // has exponents of 2 or more + for (final T b : this.getBaseSet()) { + if (this.getExponent(b) == 1) { + oneCount++; + } else if (this.getExponent(b) != 0) { + twoOrMore = true; + } + } + return oneCount == 1 && !twoOrMore; + } + + /** + * Multiplies this product by another + * + * @param other + * other product + * @return product of two products + * @since 2019-10-16 + * @throws NullPointerException + * if other is null + */ + public ObjectProduct times(final ObjectProduct other) { + Objects.requireNonNull(other, "other must not be null."); + // get a list of all objects in both sets + final Set objects = new HashSet<>(); + objects.addAll(this.getBaseSet()); + objects.addAll(other.getBaseSet()); + + // get a list of all exponents + final Map map = new HashMap<>(objects.size()); + for (final T key : objects) { + map.put(key, this.getExponent(key) + other.getExponent(key)); + } + + // create the product + return new ObjectProduct<>(map); + } + + /** + * Returns this product, but to an exponent + * + * @param exponent + * exponent + * @return result of exponentiation + * @since 2019-10-16 + */ + public ObjectProduct toExponent(final int exponent) { + final Map map = new HashMap<>(this.exponents); + for (final T key : this.exponents.keySet()) { + map.put(key, this.getExponent(key) * exponent); + } + return new ObjectProduct<>(map); + } + + /** + * Converts this product to a string using the objects' {@link Object#toString()} method. If objects have a long + * toString representation, it is recommended to use {@link #toString(Function)} instead to shorten the returned + * string. + * + *

+ * {@inheritDoc} + */ + @Override + public String toString() { + return this.toString(Object::toString); + } + + /** + * Converts this product to a string. The objects that make up this product are represented by + * {@code objectToString} + * + * @param objectToString + * function to convert objects to strings + * @return string representation of product + * @since 2019-10-16 + */ + public String toString(final Function objectToString) { + final List positiveStringComponents = new ArrayList<>(); + final List negativeStringComponents = new ArrayList<>(); + + // for each base object that makes up this object, add it and its exponent + for (final T object : this.getBaseSet()) { + final int exponent = this.exponents.get(object); + if (exponent > 0) { + positiveStringComponents.add(String.format("%s^%d", objectToString.apply(object), exponent)); + } else if (exponent < 0) { + negativeStringComponents.add(String.format("%s^%d", objectToString.apply(object), -exponent)); + } + } + + final String positiveString = positiveStringComponents.isEmpty() ? "1" + : String.join(" * ", positiveStringComponents); + final String negativeString = negativeStringComponents.isEmpty() ? "" + : " / " + String.join(" * ", negativeStringComponents); + + return positiveString + negativeString; + } +} diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java new file mode 100644 index 0000000..8fe4b31 --- /dev/null +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -0,0 +1,419 @@ +/** + * 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 sevenUnits.utils; + +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.compare(this.value, other.value) != 0) + return false; + if (Double.compare(this.uncertainty, other.uncertainty) != 0) + 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; + result = prime * result + Double.hashCode(this.value); + result = prime * result + Double.hashCode(this.uncertainty); + 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/main/java/sevenUnits/utils/package-info.java b/src/main/java/sevenUnits/utils/package-info.java new file mode 100644 index 0000000..350c62d --- /dev/null +++ b/src/main/java/sevenUnits/utils/package-info.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2018-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 . + */ +/** + * Supplementary classes that are not related to units, but are necessary for + * their function. + * + * @author Adrien Hopkins + * @since 2019-03-14 + * @since v0.2.0 + */ +package sevenUnits.utils; \ No newline at end of file diff --git a/src/test/java/sevenUnits/math/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/math/ConditionalExistenceCollectionsTest.java deleted file mode 100644 index a35dc5a..0000000 --- a/src/test/java/sevenUnits/math/ConditionalExistenceCollectionsTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * 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 sevenUnits.math; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; - -import org.junit.jupiter.api.Test; - -import sevenUnits.math.ConditionalExistenceCollections; -import sevenUnits.math.ConditionalExistenceCollections.ConditionalExistenceIterator; - -/** - * Tests the {@link #ConditionalExistenceCollections}. - * - * @author Adrien Hopkins - * @since 2019-10-16 - */ -class ConditionalExistenceCollectionsTest { - - /** - * The returned iterator ignores elements that don't start with "a". - * - * @return test iterator - * @since 2019-10-17 - */ - ConditionalExistenceIterator getTestIterator() { - final List items = Arrays.asList("aa", "ab", "ba"); - final Iterator it = items.iterator(); - final ConditionalExistenceIterator cit = (ConditionalExistenceIterator) ConditionalExistenceCollections - .conditionalExistenceIterator(it, s -> s.startsWith("a")); - return cit; - } - - /** - * The returned map ignores mappings where the value is zero. - * - * @return map to be used for test data - * @since 2019-10-16 - */ - Map getTestMap() { - final Map map = new HashMap<>(); - map.put("one", 1); - map.put("two", 2); - map.put("zero", 0); - map.put("ten", 10); - final Map conditionalMap = ConditionalExistenceCollections.conditionalExistenceMap(map, - e -> !Integer.valueOf(0).equals(e.getValue())); - return conditionalMap; - } - - /** - * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsKey(java.lang.Object)}. - */ - @Test - void testContainsKeyObject() { - final Map map = this.getTestMap(); - assertTrue(map.containsKey("one")); - assertTrue(map.containsKey("ten")); - assertFalse(map.containsKey("five")); - assertFalse(map.containsKey("zero")); - } - - /** - * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsValue(java.lang.Object)}. - */ - @Test - void testContainsValueObject() { - final Map map = this.getTestMap(); - assertTrue(map.containsValue(1)); - assertTrue(map.containsValue(10)); - assertFalse(map.containsValue(5)); - assertFalse(map.containsValue(0)); - } - - /** - * Test method for {@link org.unitConverter.math.ZeroIsNullMap#entrySet()}. - */ - @Test - void testEntrySet() { - final Map map = this.getTestMap(); - for (final Entry e : map.entrySet()) { - assertTrue(e.getValue() != 0); - } - } - - /** - * Test method for {@link org.unitConverter.math.ZeroIsNullMap#get(java.lang.Object)}. - */ - @Test - void testGetObject() { - final Map map = this.getTestMap(); - assertEquals(1, map.get("one")); - assertEquals(10, map.get("ten")); - assertEquals(null, map.get("five")); - assertEquals(null, map.get("zero")); - } - - @Test - void testIterator() { - final ConditionalExistenceIterator testIterator = this.getTestIterator(); - - assertTrue(testIterator.hasNext); - assertTrue(testIterator.hasNext()); - assertEquals("aa", testIterator.nextElement); - assertEquals("aa", testIterator.next()); - - assertTrue(testIterator.hasNext); - assertTrue(testIterator.hasNext()); - assertEquals("ab", testIterator.nextElement); - assertEquals("ab", testIterator.next()); - - assertFalse(testIterator.hasNext); - assertFalse(testIterator.hasNext()); - assertEquals(null, testIterator.nextElement); - assertThrows(NoSuchElementException.class, testIterator::next); - } - - /** - * Test method for {@link org.unitConverter.math.ZeroIsNullMap#keySet()}. - */ - @Test - void testKeySet() { - final Map map = this.getTestMap(); - assertFalse(map.keySet().contains("zero")); - } - - /** - * Test method for {@link org.unitConverter.math.ZeroIsNullMap#values()}. - */ - @Test - void testValues() { - final Map map = this.getTestMap(); - assertFalse(map.values().contains(0)); - } - -} diff --git a/src/test/java/sevenUnits/math/ExpressionParserTest.java b/src/test/java/sevenUnits/math/ExpressionParserTest.java deleted file mode 100644 index 4801233..0000000 --- a/src/test/java/sevenUnits/math/ExpressionParserTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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 sevenUnits.math; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import sevenUnits.math.ExpressionParser; - -/** - * A test for the {@code ExpressionParser} class. This is NOT part of this program's public API. - * - * @author Adrien Hopkins - * @since 2019-03-22 - * @since v0.2.0 - */ -class ExpressionParserTest { - private static final ExpressionParser numberParser = new ExpressionParser.Builder<>(Integer::parseInt) - .addBinaryOperator("+", (o1, o2) -> o1 + o2, 0).addBinaryOperator("-", (o1, o2) -> o1 - o2, 0) - .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1).addBinaryOperator("/", (o1, o2) -> o1 / o2, 1) - .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build(); - - /** - * Test method for {@link sevenUnits.math.ExpressionParser#parseExpression(java.lang.String)}. - */ - @Test - public void testParseExpression() { - // test parsing of expressions - assertEquals((int) numberParser.parseExpression("1 + 2 ^ 5 * 3"), 97); - assertEquals((int) numberParser.parseExpression("(1 + 2) ^ 5 * 3"), 729); - - // ensure it normally goes left to right - assertEquals((int) numberParser.parseExpression("1 + 2 + 3 + 4"), 10); - assertEquals((int) numberParser.parseExpression("12 - 4 - 3"), 5); - assertEquals((int) numberParser.parseExpression("12 - (4 - 3)"), 11); - assertEquals((int) numberParser.parseExpression("1 / 2 + 3"), 3); - } - -} diff --git a/src/test/java/sevenUnits/math/ObjectProductTest.java b/src/test/java/sevenUnits/math/ObjectProductTest.java deleted file mode 100644 index 1734f26..0000000 --- a/src/test/java/sevenUnits/math/ObjectProductTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (C) 2018 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 sevenUnits.math; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static sevenUnits.unit.SI.Dimensions.AREA; -import static sevenUnits.unit.SI.Dimensions.ENERGY; -import static sevenUnits.unit.SI.Dimensions.LENGTH; -import static sevenUnits.unit.SI.Dimensions.MASS; -import static sevenUnits.unit.SI.Dimensions.MASS_DENSITY; -import static sevenUnits.unit.SI.Dimensions.QUANTITY; -import static sevenUnits.unit.SI.Dimensions.TIME; -import static sevenUnits.unit.SI.Dimensions.VOLUME; - -import org.junit.jupiter.api.Test; - -import sevenUnits.math.ObjectProduct; -import sevenUnits.unit.SI; - -/** - * Tests for {@link ObjectProduct} using BaseDimension as a test object. This is NOT part of this program's public API. - * - * @author Adrien Hopkins - * @since 2018-12-12 - * @since v0.1.0 - */ -class ObjectProductTest { - /** - * Tests {@link UnitDimension#equals} - * - * @since 2018-12-12 - * @since v0.1.0 - */ - @Test - public void testEquals() { - assertEquals(LENGTH, LENGTH); - assertFalse(LENGTH.equals(QUANTITY)); - } - - /** - * Tests {@code UnitDimension}'s exponentiation - * - * @since 2019-01-15 - * @since v0.1.0 - */ - @Test - public void testExponents() { - assertEquals(1, LENGTH.getExponent(SI.BaseDimensions.LENGTH)); - assertEquals(3, VOLUME.getExponent(SI.BaseDimensions.LENGTH)); - } - - /** - * Tests {@code UnitDimension}'s multiplication and division. - * - * @since 2018-12-12 - * @since v0.1.0 - */ - @Test - public void testMultiplicationAndDivision() { - assertEquals(AREA, LENGTH.times(LENGTH)); - assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME)); - assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME)); - assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME)); - } -} diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index d216ae8..bad5a31 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -26,13 +26,13 @@ import java.util.concurrent.ThreadLocalRandom; import org.junit.jupiter.api.Test; -import sevenUnits.math.DecimalComparison; import sevenUnits.unit.LinearUnit; import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.NameSymbol; import sevenUnits.unit.SI; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitValue; +import sevenUnits.utils.DecimalComparison; /** * Testing the various Unit classes. This is NOT part of this program's public diff --git a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java new file mode 100644 index 0000000..46afe77 --- /dev/null +++ b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java @@ -0,0 +1,161 @@ +/** + * 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 sevenUnits.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Test; + +import sevenUnits.utils.ConditionalExistenceCollections; +import sevenUnits.utils.ConditionalExistenceCollections.ConditionalExistenceIterator; + +/** + * Tests the {@link #ConditionalExistenceCollections}. + * + * @author Adrien Hopkins + * @since 2019-10-16 + */ +class ConditionalExistenceCollectionsTest { + + /** + * The returned iterator ignores elements that don't start with "a". + * + * @return test iterator + * @since 2019-10-17 + */ + ConditionalExistenceIterator getTestIterator() { + final List items = Arrays.asList("aa", "ab", "ba"); + final Iterator it = items.iterator(); + final ConditionalExistenceIterator cit = (ConditionalExistenceIterator) ConditionalExistenceCollections + .conditionalExistenceIterator(it, s -> s.startsWith("a")); + return cit; + } + + /** + * The returned map ignores mappings where the value is zero. + * + * @return map to be used for test data + * @since 2019-10-16 + */ + Map getTestMap() { + final Map map = new HashMap<>(); + map.put("one", 1); + map.put("two", 2); + map.put("zero", 0); + map.put("ten", 10); + final Map conditionalMap = ConditionalExistenceCollections.conditionalExistenceMap(map, + e -> !Integer.valueOf(0).equals(e.getValue())); + return conditionalMap; + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsKey(java.lang.Object)}. + */ + @Test + void testContainsKeyObject() { + final Map map = this.getTestMap(); + assertTrue(map.containsKey("one")); + assertTrue(map.containsKey("ten")); + assertFalse(map.containsKey("five")); + assertFalse(map.containsKey("zero")); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsValue(java.lang.Object)}. + */ + @Test + void testContainsValueObject() { + final Map map = this.getTestMap(); + assertTrue(map.containsValue(1)); + assertTrue(map.containsValue(10)); + assertFalse(map.containsValue(5)); + assertFalse(map.containsValue(0)); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#entrySet()}. + */ + @Test + void testEntrySet() { + final Map map = this.getTestMap(); + for (final Entry e : map.entrySet()) { + assertTrue(e.getValue() != 0); + } + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#get(java.lang.Object)}. + */ + @Test + void testGetObject() { + final Map map = this.getTestMap(); + assertEquals(1, map.get("one")); + assertEquals(10, map.get("ten")); + assertEquals(null, map.get("five")); + assertEquals(null, map.get("zero")); + } + + @Test + void testIterator() { + final ConditionalExistenceIterator testIterator = this.getTestIterator(); + + assertTrue(testIterator.hasNext); + assertTrue(testIterator.hasNext()); + assertEquals("aa", testIterator.nextElement); + assertEquals("aa", testIterator.next()); + + assertTrue(testIterator.hasNext); + assertTrue(testIterator.hasNext()); + assertEquals("ab", testIterator.nextElement); + assertEquals("ab", testIterator.next()); + + assertFalse(testIterator.hasNext); + assertFalse(testIterator.hasNext()); + assertEquals(null, testIterator.nextElement); + assertThrows(NoSuchElementException.class, testIterator::next); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#keySet()}. + */ + @Test + void testKeySet() { + final Map map = this.getTestMap(); + assertFalse(map.keySet().contains("zero")); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#values()}. + */ + @Test + void testValues() { + final Map map = this.getTestMap(); + assertFalse(map.values().contains(0)); + } + +} diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java new file mode 100644 index 0000000..29648ee --- /dev/null +++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java @@ -0,0 +1,54 @@ +/** + * 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 sevenUnits.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import sevenUnits.utils.ExpressionParser; + +/** + * A test for the {@code ExpressionParser} class. This is NOT part of this program's public API. + * + * @author Adrien Hopkins + * @since 2019-03-22 + * @since v0.2.0 + */ +class ExpressionParserTest { + private static final ExpressionParser numberParser = new ExpressionParser.Builder<>(Integer::parseInt) + .addBinaryOperator("+", (o1, o2) -> o1 + o2, 0).addBinaryOperator("-", (o1, o2) -> o1 - o2, 0) + .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1).addBinaryOperator("/", (o1, o2) -> o1 / o2, 1) + .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build(); + + /** + * Test method for {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}. + */ + @Test + public void testParseExpression() { + // test parsing of expressions + assertEquals((int) numberParser.parseExpression("1 + 2 ^ 5 * 3"), 97); + assertEquals((int) numberParser.parseExpression("(1 + 2) ^ 5 * 3"), 729); + + // ensure it normally goes left to right + assertEquals((int) numberParser.parseExpression("1 + 2 + 3 + 4"), 10); + assertEquals((int) numberParser.parseExpression("12 - 4 - 3"), 5); + assertEquals((int) numberParser.parseExpression("12 - (4 - 3)"), 11); + assertEquals((int) numberParser.parseExpression("1 / 2 + 3"), 3); + } + +} diff --git a/src/test/java/sevenUnits/utils/ObjectProductTest.java b/src/test/java/sevenUnits/utils/ObjectProductTest.java new file mode 100644 index 0000000..32a8c78 --- /dev/null +++ b/src/test/java/sevenUnits/utils/ObjectProductTest.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2018 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 sevenUnits.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static sevenUnits.unit.SI.Dimensions.AREA; +import static sevenUnits.unit.SI.Dimensions.ENERGY; +import static sevenUnits.unit.SI.Dimensions.LENGTH; +import static sevenUnits.unit.SI.Dimensions.MASS; +import static sevenUnits.unit.SI.Dimensions.MASS_DENSITY; +import static sevenUnits.unit.SI.Dimensions.QUANTITY; +import static sevenUnits.unit.SI.Dimensions.TIME; +import static sevenUnits.unit.SI.Dimensions.VOLUME; + +import org.junit.jupiter.api.Test; + +import sevenUnits.unit.SI; +import sevenUnits.utils.ObjectProduct; + +/** + * Tests for {@link ObjectProduct} using BaseDimension as a test object. This is NOT part of this program's public API. + * + * @author Adrien Hopkins + * @since 2018-12-12 + * @since v0.1.0 + */ +class ObjectProductTest { + /** + * Tests {@link UnitDimension#equals} + * + * @since 2018-12-12 + * @since v0.1.0 + */ + @Test + public void testEquals() { + assertEquals(LENGTH, LENGTH); + assertFalse(LENGTH.equals(QUANTITY)); + } + + /** + * Tests {@code UnitDimension}'s exponentiation + * + * @since 2019-01-15 + * @since v0.1.0 + */ + @Test + public void testExponents() { + assertEquals(1, LENGTH.getExponent(SI.BaseDimensions.LENGTH)); + assertEquals(3, VOLUME.getExponent(SI.BaseDimensions.LENGTH)); + } + + /** + * Tests {@code UnitDimension}'s multiplication and division. + * + * @since 2018-12-12 + * @since v0.1.0 + */ + @Test + public void testMultiplicationAndDivision() { + assertEquals(AREA, LENGTH.times(LENGTH)); + assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME)); + assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME)); + assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME)); + } +} -- cgit v1.2.3 From 1cb69cfdcb18bbafdbc792174697732e7cf359e7 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 26 Aug 2021 07:52:45 -0500 Subject: Added units and dimensions to the design document --- docs/design.org | 23 + .../converterGUI/DefaultPrefixRepetitionRule.java | 12 +- .../sevenUnits/converterGUI/SevenUnitsGUI.java | 32 +- src/main/java/sevenUnits/unit/BritishImperial.java | 22 +- src/main/java/sevenUnits/unit/LinearUnitValue.java | 2 +- src/main/java/sevenUnits/unit/Metric.java | 479 +++++++++++++++++++++ src/main/java/sevenUnits/unit/SI.java | 479 --------------------- src/main/java/sevenUnits/unit/USCustomary.java | 8 +- src/main/java/sevenUnits/unit/Unit.java | 12 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 8 +- src/test/java/sevenUnits/unit/MultiUnitTest.java | 6 +- .../java/sevenUnits/unit/UnitDatabaseTest.java | 16 +- src/test/java/sevenUnits/unit/UnitTest.java | 50 +-- .../java/sevenUnits/utils/ObjectProductTest.java | 22 +- 14 files changed, 597 insertions(+), 574 deletions(-) create mode 100644 src/main/java/sevenUnits/unit/Metric.java delete mode 100644 src/main/java/sevenUnits/unit/SI.java (limited to 'src/test/java/sevenUnits/unit/UnitTest.java') diff --git a/docs/design.org b/docs/design.org index 383a707..41713eb 100644 --- a/docs/design.org +++ b/docs/design.org @@ -11,8 +11,31 @@ * Unit System Design Any code related to the backend unit system is stored in the ~sevenUnits.unit~ package. ** Dimensions + Dimensions represent what a unit is measuring, such as length, time, or energy. Dimensions are represented as an [[*ObjectProduct][ObjectProduct]], where ~BaseDimension~ is a very simple class (its only properties are a name and a symbol) which represents the dimension of a base unit; these base dimensions can be multiplied to create all other Dimensions. ** Unit Classes + Units are internally represented by the abstract class ~Unit~. All units have an [[*ObjectProduct][ObjectProduct]] (referred to as the base) that they are based on, a dimension (ObjectProduct), one or more names and a symbol (these last two bits of data are contained in the ~NameSymbol~ class). The dimension is calculated from the base unit when needed; the variable is just a cache. It has two constructors: a package-private one used to make ~BaseUnit~ instances, and a protected one used to make general units (for other subclasses of ~Unit~). All unit classes are immutable. + + Units also have two conversion functions - one which converts from a value expressed in this unit to its base unit, and another which converts from a value expressed in the base unit to this unit. In ~Unit~, they are defined as two abstract methods. This allows you to convert from any unit to any other (as long as they have the same base, i.e. you aren't converting metres to pounds). To convert from A to B, first convert from A to its base, then convert from the base to B. + + ~BaseUnit~ represents a unit that all other units are defined by. All of the units used by this system are defined by seven SI ~BaseUnit~ instances (metre, second, kilogram, ampere, kelvin, mole, candela; this is what 7Units is named after) and two non-SI ~BaseUnit~ instances (US dollar and bit). Because base units are themselves units (and should be able to be used as units), ~BaseUnit~ is a subclass of ~Unit~, using its own package-private constructor. + + However, most units are instances of ~LinearUnit~, another subclass of ~Unit~. ~LinearUnit~ represents a unit that is /a product of a base unit and a constant called the *conversion factor*/. Most units you've ever used fall under this definition, the only common exceptions are degrees Celsius and Fahrenheit. This simplicity allows the ~LinearUnit~ to do many things: + - It can implement conversion to and from the base as multiplying and dividing respectively by the conversion factor + - You can easily create new units by multiplying or dividing a ~LinearUnit~ by a number (for example, kilometre = metre * 1000). This can be easily implemented as multiplying this unit's conversion factor by the multiplier and returning a new ~LinearUnit~ with that conversion factor factor. + - You can add or subtract two ~LinearUnit~ instances to create a third (as long as they have the same base) by adding or subtracting the conversion factor. + - You can multiply or divide any two ~LinearUnit~ instances to create a third by multiplying or dividing the bases and conversion factors. + - Note that any operations will return a unit without name(s) or a symbol. All unit classes have a ~withName~ method that returns a copy of them with different names and/or a different symbol (all of this info is contained in the ~NameSymbol~ class) + + There are a few more classes which play small roles in the unit system: + - Unitlike :: A class that is like a unit, but its "value" can be any class. The only use of this class right now is to implement ~MultiUnit~, a combination of units (like "foot + inch", commonly used in North America for measuring height); its "value" is a list of numbers. + - FunctionalUnit :: A convenience class that implements the two conversion functions of ~Unit~ using ~DoubleUnaryOperator~ instances. This is used internally to implement degrees Celsius and Fahrenheit. There is also a version of this for ~Unitlike~, ~FunctionalUnitlike~. + - UnitValue :: A value expressed as a certain unit (such as "7 inches"). This class is used by the simple unit converter to represent units. You can convert them between units. There are also versions of this for ~LinearUnit~ and ~Unitlike~. + - Metric :: A static utility class with instances of all of the SI named units, the 9 base dimensions, SI prefixes, some common prefixed units like the kilometre, and a few non-SI units used commonly with them. + - BritishImperial :: A static utility class with instances of common units in the British Imperial system (not to be confused with the US Customary system, which is also called "Imperial"; it has the same unit names but the values of a few units are different). This class and the US Customary is divided into static classes for each dimension, such as ~BritishImperial.Length~. + - USCustomary :: A static utility class with instances of common units in the US Customary system (not to be confused with the British Imperial system; it has the same unit names but the values of a few units are different). ** Prefixes + +** The Unit Database * Utility Classes 7Units has a few general "utility" classes. They aren't directly related to units, but are used in the units system. ** ObjectProduct diff --git a/src/main/java/sevenUnits/converterGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnits/converterGUI/DefaultPrefixRepetitionRule.java index 835651e..6b6abf0 100644 --- a/src/main/java/sevenUnits/converterGUI/DefaultPrefixRepetitionRule.java +++ b/src/main/java/sevenUnits/converterGUI/DefaultPrefixRepetitionRule.java @@ -6,7 +6,7 @@ package sevenUnits.converterGUI; import java.util.List; import java.util.function.Predicate; -import sevenUnits.unit.SI; +import sevenUnits.unit.Metric; import sevenUnits.unit.UnitPrefix; /** @@ -49,7 +49,7 @@ enum DefaultPrefixRepetitionRule implements Predicate> { // if the first prefix is non-metric (including binary prefixes), // assume we are using non-metric prefixes // non-metric prefixes are allowed, but can't be repeated. - if (!SI.DECIMAL_PREFIXES.contains(prefixes.get(0))) + if (!Metric.DECIMAL_PREFIXES.contains(prefixes.get(0))) return NO_REPETITION.test(prefixes); int part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto, @@ -58,7 +58,7 @@ enum DefaultPrefixRepetitionRule implements Predicate> { for (final UnitPrefix prefix : prefixes) { // check that the current prefix is metric and appropriately // magnifying/reducing - if (!SI.DECIMAL_PREFIXES.contains(prefix)) + if (!Metric.DECIMAL_PREFIXES.contains(prefix)) return false; if (magnifying != prefix.getMultiplier() > 1) return false; @@ -72,7 +72,7 @@ enum DefaultPrefixRepetitionRule implements Predicate> { break; case 1: // after a kilo-zetta, only deka/hecto are valid - if (SI.THOUSAND_PREFIXES.contains(prefix)) + if (Metric.THOUSAND_PREFIXES.contains(prefix)) return false; break; case 2: @@ -81,9 +81,9 @@ enum DefaultPrefixRepetitionRule implements Predicate> { } // set part - if (SI.YOTTA.equals(prefix) || SI.YOCTO.equals(prefix)) { + if (Metric.YOTTA.equals(prefix) || Metric.YOCTO.equals(prefix)) { part = 0; - } else if (SI.THOUSAND_PREFIXES.contains(prefix)) { + } else if (Metric.THOUSAND_PREFIXES.contains(prefix)) { part = 1; } else { part = 2; diff --git a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java index e1eb2f4..bfd5974 100644 --- a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java +++ b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java @@ -70,7 +70,7 @@ import sevenUnits.unit.BritishImperial; import sevenUnits.unit.LinearUnit; import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.NameSymbol; -import sevenUnits.unit.SI; +import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; @@ -110,25 +110,25 @@ final class SevenUnitsGUI { * @since v0.2.0 */ private static void addDefaults(final UnitDatabase database) { - database.addUnit("metre", SI.METRE); - database.addUnit("kilogram", SI.KILOGRAM); - database.addUnit("gram", SI.KILOGRAM.dividedBy(1000)); - database.addUnit("second", SI.SECOND); - database.addUnit("ampere", SI.AMPERE); - database.addUnit("kelvin", SI.KELVIN); - database.addUnit("mole", SI.MOLE); - database.addUnit("candela", SI.CANDELA); - database.addUnit("bit", SI.BIT); - database.addUnit("unit", SI.ONE); + database.addUnit("metre", Metric.METRE); + database.addUnit("kilogram", Metric.KILOGRAM); + database.addUnit("gram", Metric.KILOGRAM.dividedBy(1000)); + database.addUnit("second", Metric.SECOND); + database.addUnit("ampere", Metric.AMPERE); + database.addUnit("kelvin", Metric.KELVIN); + database.addUnit("mole", Metric.MOLE); + database.addUnit("candela", Metric.CANDELA); + database.addUnit("bit", Metric.BIT); + database.addUnit("unit", Metric.ONE); // nonlinear units - must be loaded manually - database.addUnit("tempCelsius", SI.CELSIUS); + database.addUnit("tempCelsius", Metric.CELSIUS); database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT); // load initial dimensions - database.addDimension("LENGTH", SI.Dimensions.LENGTH); - database.addDimension("MASS", SI.Dimensions.MASS); - database.addDimension("TIME", SI.Dimensions.TIME); - database.addDimension("TEMPERATURE", SI.Dimensions.TEMPERATURE); + database.addDimension("LENGTH", Metric.Dimensions.LENGTH); + database.addDimension("MASS", Metric.Dimensions.MASS); + database.addDimension("TIME", Metric.Dimensions.TIME); + database.addDimension("TEMPERATURE", Metric.Dimensions.TEMPERATURE); } /** diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index 63da7f0..81a3f2a 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -48,7 +48,7 @@ public final class BritishImperial { /** * According to the International Yard and Pound of 1959, a yard is defined as exactly 0.9144 metres. */ - public static final LinearUnit YARD = SI.METRE.times(0.9144); + public static final LinearUnit YARD = Metric.METRE.times(0.9144); public static final LinearUnit FOOT = YARD.dividedBy(3); public static final LinearUnit INCH = FOOT.dividedBy(12); public static final LinearUnit THOU = INCH.dividedBy(1000); @@ -57,7 +57,7 @@ public final class BritishImperial { public static final LinearUnit MILE = FURLONG.times(8); public static final LinearUnit LEAGUE = MILE.times(3); - public static final LinearUnit NAUTICAL_MILE = SI.METRE.times(1852); + public static final LinearUnit NAUTICAL_MILE = Metric.METRE.times(1852); public static final LinearUnit CABLE = NAUTICAL_MILE.dividedBy(10); public static final LinearUnit FATHOM = CABLE.dividedBy(100); @@ -72,7 +72,7 @@ public final class BritishImperial { * @since 2019-11-08 */ public static final class Mass { - public static final LinearUnit POUND = SI.GRAM.times(453.59237); + public static final LinearUnit POUND = Metric.GRAM.times(453.59237); public static final LinearUnit OUNCE = POUND.dividedBy(16); public static final LinearUnit DRACHM = POUND.dividedBy(256); public static final LinearUnit GRAIN = POUND.dividedBy(7000); @@ -80,7 +80,7 @@ public final class BritishImperial { public static final LinearUnit QUARTER = STONE.times(2); public static final LinearUnit HUNDREDWEIGHT = QUARTER.times(4); public static final LinearUnit LONG_TON = HUNDREDWEIGHT.times(20); - public static final LinearUnit SLUG = SI.KILOGRAM.times(14.59390294); + public static final LinearUnit SLUG = Metric.KILOGRAM.times(14.59390294); } /** @@ -90,7 +90,7 @@ public final class BritishImperial { * @since 2019-11-08 */ public static final class Volume { - public static final LinearUnit FLUID_OUNCE = SI.LITRE.withPrefix(SI.MILLI).times(28.4130625); + public static final LinearUnit FLUID_OUNCE = Metric.LITRE.withPrefix(Metric.MILLI).times(28.4130625); public static final LinearUnit GILL = FLUID_OUNCE.times(5); public static final LinearUnit PINT = FLUID_OUNCE.times(20); public static final LinearUnit QUART = PINT.times(2); @@ -104,13 +104,13 @@ public final class BritishImperial { public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT); } - public static final LinearUnit OUNCE_FORCE = Mass.OUNCE.times(SI.Constants.EARTH_GRAVITY); - public static final LinearUnit POUND_FORCE = Mass.POUND.times(SI.Constants.EARTH_GRAVITY); + public static final LinearUnit OUNCE_FORCE = Mass.OUNCE.times(Metric.Constants.EARTH_GRAVITY); + public static final LinearUnit POUND_FORCE = Mass.POUND.times(Metric.Constants.EARTH_GRAVITY); - public static final LinearUnit BRITISH_THERMAL_UNIT = SI.JOULE.times(1055.06); - public static final LinearUnit CALORIE = SI.JOULE.times(4.184); - public static final LinearUnit KILOCALORIE = SI.JOULE.times(4184); + public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE.times(1055.06); + public static final LinearUnit CALORIE = Metric.JOULE.times(4.184); + public static final LinearUnit KILOCALORIE = Metric.JOULE.times(4184); - public static final Unit FAHRENHEIT = Unit.fromConversionFunctions(SI.KELVIN.getBase(), + public static final Unit FAHRENHEIT = Unit.fromConversionFunctions(Metric.KELVIN.getBase(), tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8); } diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index ffb9271..a36d568 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -32,7 +32,7 @@ import sevenUnits.utils.UncertainDouble; * @since 2020-07-26 */ public final class LinearUnitValue { - public static final LinearUnitValue ONE = getExact(SI.ONE, 1); + public static final LinearUnitValue ONE = getExact(Metric.ONE, 1); /** * Gets an exact {@code LinearUnitValue} diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java new file mode 100644 index 0000000..3c4d291 --- /dev/null +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -0,0 +1,479 @@ +/** + * Copyright (C) 2018 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 sevenUnits.unit; + +import java.util.Set; + +import sevenUnits.utils.ObjectProduct; + +/** + * All of the units, prefixes and dimensions that are used by the SI, as well as + * some outside the SI. + * + *

+ * This class does not include prefixed units. To obtain prefixed units, use + * {@link LinearUnit#withPrefix}: + * + *

+ * LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
+ * 
+ * + * + * @author Adrien Hopkins + * @since 2019-10-16 + */ +public final class Metric { + /// dimensions used by SI units + // base dimensions, as BaseDimensions + public static final class BaseDimensions { + public static final BaseDimension LENGTH = BaseDimension.valueOf("Length", + "L"); + public static final BaseDimension MASS = BaseDimension.valueOf("Mass", + "M"); + public static final BaseDimension TIME = BaseDimension.valueOf("Time", + "T"); + public static final BaseDimension ELECTRIC_CURRENT = BaseDimension + .valueOf("Electric Current", "I"); + public static final BaseDimension TEMPERATURE = BaseDimension + .valueOf("Temperature", "\u0398"); // theta symbol + public static final BaseDimension QUANTITY = BaseDimension + .valueOf("Quantity", "N"); + public static final BaseDimension LUMINOUS_INTENSITY = BaseDimension + .valueOf("Luminous Intensity", "J"); + public static final BaseDimension INFORMATION = BaseDimension + .valueOf("Information", "Info"); // non-SI + public static final BaseDimension CURRENCY = BaseDimension + .valueOf("Currency", "$$"); // non-SI + + // You may NOT get SI.BaseDimensions instances! + private BaseDimensions() { + throw new AssertionError(); + } + } + + /// base units of the SI + // suppressing warnings since these are the same object, but in a different + /// form (class) + @SuppressWarnings("hiding") + public static final class BaseUnits { + public static final BaseUnit METRE = BaseUnit + .valueOf(BaseDimensions.LENGTH, "metre", "m"); + public static final BaseUnit KILOGRAM = BaseUnit + .valueOf(BaseDimensions.MASS, "kilogram", "kg"); + public static final BaseUnit SECOND = BaseUnit + .valueOf(BaseDimensions.TIME, "second", "s"); + public static final BaseUnit AMPERE = BaseUnit + .valueOf(BaseDimensions.ELECTRIC_CURRENT, "ampere", "A"); + public static final BaseUnit KELVIN = BaseUnit + .valueOf(BaseDimensions.TEMPERATURE, "kelvin", "K"); + public static final BaseUnit MOLE = BaseUnit + .valueOf(BaseDimensions.QUANTITY, "mole", "mol"); + public static final BaseUnit CANDELA = BaseUnit + .valueOf(BaseDimensions.LUMINOUS_INTENSITY, "candela", "cd"); + public static final BaseUnit BIT = BaseUnit + .valueOf(BaseDimensions.INFORMATION, "bit", "b"); + public static final BaseUnit DOLLAR = BaseUnit + .valueOf(BaseDimensions.CURRENCY, "dollar", "$"); + + public static final Set BASE_UNITS = Set.of(METRE, KILOGRAM, + SECOND, AMPERE, KELVIN, MOLE, CANDELA, BIT); + + // You may NOT get SI.BaseUnits instances! + private BaseUnits() { + throw new AssertionError(); + } + } + + /** + * Constants that relate to the SI or other systems. + * + * @author Adrien Hopkins + * @since 2019-11-08 + */ + public static final class Constants { + public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND) + .dividedBy(SECOND).times(9.80665); + } + + // dimensions used in the SI, as ObjectProducts + public static final class Dimensions { + public static final ObjectProduct EMPTY = ObjectProduct + .empty(); + public static final ObjectProduct LENGTH = ObjectProduct + .oneOf(BaseDimensions.LENGTH); + public static final ObjectProduct MASS = ObjectProduct + .oneOf(BaseDimensions.MASS); + public static final ObjectProduct TIME = ObjectProduct + .oneOf(BaseDimensions.TIME); + public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct + .oneOf(BaseDimensions.ELECTRIC_CURRENT); + public static final ObjectProduct TEMPERATURE = ObjectProduct + .oneOf(BaseDimensions.TEMPERATURE); + public static final ObjectProduct QUANTITY = ObjectProduct + .oneOf(BaseDimensions.QUANTITY); + public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct + .oneOf(BaseDimensions.LUMINOUS_INTENSITY); + public static final ObjectProduct INFORMATION = ObjectProduct + .oneOf(BaseDimensions.INFORMATION); + public static final ObjectProduct CURRENCY = ObjectProduct + .oneOf(BaseDimensions.CURRENCY); + + // derived dimensions without named SI units + public static final ObjectProduct AREA = LENGTH + .times(LENGTH); + public static final ObjectProduct VOLUME = AREA + .times(LENGTH); + public static final ObjectProduct VELOCITY = LENGTH + .dividedBy(TIME); + public static final ObjectProduct ACCELERATION = VELOCITY + .dividedBy(TIME); + public static final ObjectProduct WAVENUMBER = EMPTY + .dividedBy(LENGTH); + public static final ObjectProduct MASS_DENSITY = MASS + .dividedBy(VOLUME); + public static final ObjectProduct SURFACE_DENSITY = MASS + .dividedBy(AREA); + public static final ObjectProduct SPECIFIC_VOLUME = VOLUME + .dividedBy(MASS); + public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT + .dividedBy(AREA); + public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT + .dividedBy(LENGTH); + public static final ObjectProduct CONCENTRATION = QUANTITY + .dividedBy(VOLUME); + public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION + .times(MASS); + public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY + .dividedBy(AREA); + public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY + .dividedBy(VELOCITY); + public static final ObjectProduct REFRACTIVE_PERMEABILITY = EMPTY + .times(EMPTY); + public static final ObjectProduct ANGLE = LENGTH + .dividedBy(LENGTH); + public static final ObjectProduct SOLID_ANGLE = AREA + .dividedBy(AREA); + + // derived dimensions with named SI units + public static final ObjectProduct FREQUENCY = EMPTY + .dividedBy(TIME); + public static final ObjectProduct FORCE = MASS + .times(ACCELERATION); + public static final ObjectProduct ENERGY = FORCE + .times(LENGTH); + public static final ObjectProduct POWER = ENERGY + .dividedBy(TIME); + public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT + .times(TIME); + public static final ObjectProduct VOLTAGE = ENERGY + .dividedBy(ELECTRIC_CHARGE); + public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE + .dividedBy(VOLTAGE); + public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE + .dividedBy(ELECTRIC_CURRENT); + public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT + .dividedBy(VOLTAGE); + public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE + .times(TIME); + public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX + .dividedBy(AREA); + public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX + .dividedBy(ELECTRIC_CURRENT); + public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY + .times(SOLID_ANGLE); + public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX + .dividedBy(AREA); + public static final ObjectProduct SPECIFIC_ENERGY = ENERGY + .dividedBy(MASS); + public static final ObjectProduct CATALYTIC_ACTIVITY = QUANTITY + .dividedBy(TIME); + + // You may NOT get SI.Dimension instances! + private Dimensions() { + throw new AssertionError(); + } + } + + /// The units of the SI + public static final LinearUnit ONE = LinearUnit + .valueOf(ObjectProduct.empty(), 1); + + public static final LinearUnit METRE = BaseUnits.METRE.asLinearUnit() + .withName(NameSymbol.of("metre", "m", "meter")); + public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit() + .withName(NameSymbol.of("kilogram", "kg")); + public static final LinearUnit SECOND = BaseUnits.SECOND.asLinearUnit() + .withName(NameSymbol.of("second", "s", "sec")); + public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit() + .withName(NameSymbol.of("ampere", "A")); + public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit() + .withName(NameSymbol.of("kelvin", "K")); + public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit() + .withName(NameSymbol.of("mole", "mol")); + public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit() + .withName(NameSymbol.of("candela", "cd")); + public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit() + .withName(NameSymbol.of("bit", "b")); + public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit() + .withName(NameSymbol.of("dollar", "$")); + // Non-base units + public static final LinearUnit RADIAN = METRE.dividedBy(METRE) + .withName(NameSymbol.of("radian", "rad")); + + public static final LinearUnit STERADIAN = RADIAN.times(RADIAN) + .withName(NameSymbol.of("steradian", "sr")); + public static final LinearUnit HERTZ = ONE.dividedBy(SECOND) + .withName(NameSymbol.of("hertz", "Hz")); + // for periodic phenomena + public static final LinearUnit NEWTON = KILOGRAM.times(METRE) + .dividedBy(SECOND.times(SECOND)) + .withName(NameSymbol.of("newton", "N")); + public static final LinearUnit PASCAL = NEWTON.dividedBy(METRE.times(METRE)) + .withName(NameSymbol.of("pascal", "Pa")); + public static final LinearUnit JOULE = NEWTON.times(METRE) + .withName(NameSymbol.of("joule", "J")); + public static final LinearUnit WATT = JOULE.dividedBy(SECOND) + .withName(NameSymbol.of("watt", "W")); + public static final LinearUnit COULOMB = AMPERE.times(SECOND) + .withName(NameSymbol.of("coulomb", "C")); + public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB) + .withName(NameSymbol.of("volt", "V")); + public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT) + .withName(NameSymbol.of("farad", "F")); + public static final LinearUnit OHM = VOLT.dividedBy(AMPERE) + .withName(NameSymbol.of("ohm", "\u03A9")); // omega + public static final LinearUnit SIEMENS = ONE.dividedBy(OHM) + .withName(NameSymbol.of("siemens", "S")); + public static final LinearUnit WEBER = VOLT.times(SECOND) + .withName(NameSymbol.of("weber", "Wb")); + public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE)) + .withName(NameSymbol.of("tesla", "T")); + public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE) + .withName(NameSymbol.of("henry", "H")); + public static final LinearUnit LUMEN = CANDELA.times(STERADIAN) + .withName(NameSymbol.of("lumen", "lm")); + public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE)) + .withName(NameSymbol.of("lux", "lx")); + public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND) + .withName(NameSymbol.of("bequerel", "Bq")); + // for activity referred to a nucleotide + public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM) + .withName(NameSymbol.of("grey", "Gy")); + // for absorbed dose + public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM) + .withName(NameSymbol.of("sievert", "Sv")); + // for dose equivalent + public static final LinearUnit KATAL = MOLE.dividedBy(SECOND) + .withName(NameSymbol.of("katal", "kat")); + // common derived units included for convenience + public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000) + .withName(NameSymbol.of("gram", "g")); + + public static final LinearUnit SQUARE_METRE = METRE.toExponent(2) + .withName(NameSymbol.of("square metre", "m^2", "square meter", + "metre squared", "meter squared")); + public static final LinearUnit CUBIC_METRE = METRE.toExponent(3) + .withName(NameSymbol.of("cubic metre", "m^3", "cubic meter", + "metre cubed", "meter cubed")); + public static final LinearUnit METRE_PER_SECOND = METRE.dividedBy(SECOND) + .withName( + NameSymbol.of("metre per second", "m/s", "meter per second")); + // Non-SI units included for convenience + public static final Unit CELSIUS = Unit + .fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15, + tempC -> tempC + 273.15) + .withName(NameSymbol.of("degree Celsius", "\u00B0C")); + + public static final LinearUnit MINUTE = SECOND.times(60) + .withName(NameSymbol.of("minute", "min")); + public static final LinearUnit HOUR = MINUTE.times(60) + .withName(NameSymbol.of("hour", "h", "hr")); + public static final LinearUnit DAY = HOUR.times(60) + .withName(NameSymbol.of("day", "d")); + public static final LinearUnit KILOMETRE_PER_HOUR = METRE.times(1000) + .dividedBy(HOUR).withName(NameSymbol.of("kilometre per hour", "km/h", + "kilometer per hour")); + public static final LinearUnit DEGREE = RADIAN.times(360 / (2 * Math.PI)) + .withName(NameSymbol.of("degree", "\u00B0", "deg")); + public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60) + .withName(NameSymbol.of("arcminute", "arcmin")); + public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60) + .withName(NameSymbol.of("arcsecond", "arcsec")); + public static final LinearUnit ASTRONOMICAL_UNIT = METRE + .times(149597870700.0) + .withName(NameSymbol.of("astronomical unit", "au")); + public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT + .dividedBy(ARCSECOND).withName(NameSymbol.of("parsec", "pc")); + public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0) + .withName(NameSymbol.of("hectare", "ha")); + public static final LinearUnit LITRE = METRE.times(METRE).times(METRE) + .dividedBy(1000.0).withName(NameSymbol.of("litre", "L", "l", "liter")); + public static final LinearUnit TONNE = KILOGRAM.times(1000.0) + .withName(NameSymbol.of("tonne", "t", "metric ton")); + public static final LinearUnit DALTON = KILOGRAM.times(1.660539040e-27) + .withName(NameSymbol.of("dalton", "Da", "atomic unit", "u")); // approximate + // value + public static final LinearUnit ELECTRONVOLT = JOULE.times(1.602176634e-19) + .withName(NameSymbol.of("electron volt", "eV")); + public static final LinearUnit BYTE = BIT.times(8) + .withName(NameSymbol.of("byte", "B")); + public static final Unit NEPER = Unit.fromConversionFunctions(ONE.getBase(), + pr -> 0.5 * Math.log(pr), Np -> Math.exp(2 * Np)) + .withName(NameSymbol.of("neper", "Np")); + public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(), + pr -> Math.log10(pr), dB -> Math.pow(10, dB)) + .withName(NameSymbol.of("bel", "B")); + public static final Unit DECIBEL = Unit + .fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr), + dB -> Math.pow(10, dB / 10)) + .withName(NameSymbol.of("decibel", "dB")); + + /// The prefixes of the SI + // expanding decimal prefixes + public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3) + .withName(NameSymbol.of("kilo", "k", "K")); + public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6) + .withName(NameSymbol.of("mega", "M")); + public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9) + .withName(NameSymbol.of("giga", "G")); + public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12) + .withName(NameSymbol.of("tera", "T")); + public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15) + .withName(NameSymbol.of("peta", "P")); + public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18) + .withName(NameSymbol.of("exa", "E")); + public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21) + .withName(NameSymbol.of("zetta", "Z")); + public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24) + .withName(NameSymbol.of("yotta", "Y")); + + // contracting decimal prefixes + public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3) + .withName(NameSymbol.of("milli", "m")); + public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6) + .withName(NameSymbol.of("micro", "\u03BC", "u")); // mu + public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9) + .withName(NameSymbol.of("nano", "n")); + public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12) + .withName(NameSymbol.of("pico", "p")); + public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15) + .withName(NameSymbol.of("femto", "f")); + public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18) + .withName(NameSymbol.of("atto", "a")); + public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21) + .withName(NameSymbol.of("zepto", "z")); + public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24) + .withName(NameSymbol.of("yocto", "y")); + + // prefixes that don't match the pattern of thousands + public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1) + .withName(NameSymbol.of("deka", "da", "deca", "D")); + public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2) + .withName(NameSymbol.of("hecto", "h", "H", "hekto")); + public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1) + .withName(NameSymbol.of("deci", "d")); + public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2) + .withName(NameSymbol.of("centi", "c")); + public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024) + .withName(NameSymbol.of("kibi", "Ki")); + public static final UnitPrefix MEBI = KIBI.times(1024) + .withName(NameSymbol.of("mebi", "Mi")); + public static final UnitPrefix GIBI = MEBI.times(1024) + .withName(NameSymbol.of("gibi", "Gi")); + public static final UnitPrefix TEBI = GIBI.times(1024) + .withName(NameSymbol.of("tebi", "Ti")); + public static final UnitPrefix PEBI = TEBI.times(1024) + .withName(NameSymbol.of("pebi", "Pi")); + public static final UnitPrefix EXBI = PEBI.times(1024) + .withName(NameSymbol.of("exbi", "Ei")); + + // a few prefixed units + public static final LinearUnit MICROMETRE = Metric.METRE.withPrefix(Metric.MICRO); + public static final LinearUnit MILLIMETRE = Metric.METRE.withPrefix(Metric.MILLI); + public static final LinearUnit KILOMETRE = Metric.METRE.withPrefix(Metric.KILO); + public static final LinearUnit MEGAMETRE = Metric.METRE.withPrefix(Metric.MEGA); + + public static final LinearUnit MICROLITRE = Metric.LITRE.withPrefix(Metric.MICRO); + public static final LinearUnit MILLILITRE = Metric.LITRE.withPrefix(Metric.MILLI); + public static final LinearUnit KILOLITRE = Metric.LITRE.withPrefix(Metric.KILO); + public static final LinearUnit MEGALITRE = Metric.LITRE.withPrefix(Metric.MEGA); + + public static final LinearUnit MICROSECOND = Metric.SECOND.withPrefix(Metric.MICRO); + public static final LinearUnit MILLISECOND = Metric.SECOND.withPrefix(Metric.MILLI); + public static final LinearUnit KILOSECOND = Metric.SECOND.withPrefix(Metric.KILO); + public static final LinearUnit MEGASECOND = Metric.SECOND.withPrefix(Metric.MEGA); + + public static final LinearUnit MICROGRAM = Metric.GRAM.withPrefix(Metric.MICRO); + public static final LinearUnit MILLIGRAM = Metric.GRAM.withPrefix(Metric.MILLI); + public static final LinearUnit MEGAGRAM = Metric.GRAM.withPrefix(Metric.MEGA); + + public static final LinearUnit MICRONEWTON = Metric.NEWTON.withPrefix(Metric.MICRO); + public static final LinearUnit MILLINEWTON = Metric.NEWTON.withPrefix(Metric.MILLI); + public static final LinearUnit KILONEWTON = Metric.NEWTON.withPrefix(Metric.KILO); + public static final LinearUnit MEGANEWTON = Metric.NEWTON.withPrefix(Metric.MEGA); + + public static final LinearUnit MICROJOULE = Metric.JOULE.withPrefix(Metric.MICRO); + public static final LinearUnit MILLIJOULE = Metric.JOULE.withPrefix(Metric.MILLI); + public static final LinearUnit KILOJOULE = Metric.JOULE.withPrefix(Metric.KILO); + public static final LinearUnit MEGAJOULE = Metric.JOULE.withPrefix(Metric.MEGA); + + public static final LinearUnit MICROWATT = Metric.WATT.withPrefix(Metric.MICRO); + public static final LinearUnit MILLIWATT = Metric.WATT.withPrefix(Metric.MILLI); + public static final LinearUnit KILOWATT = Metric.WATT.withPrefix(Metric.KILO); + public static final LinearUnit MEGAWATT = Metric.WATT.withPrefix(Metric.MEGA); + + public static final LinearUnit MICROCOULOMB = Metric.COULOMB + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLICOULOMB = Metric.COULOMB + .withPrefix(Metric.MILLI); + public static final LinearUnit KILOCOULOMB = Metric.COULOMB.withPrefix(Metric.KILO); + public static final LinearUnit MEGACOULOMB = Metric.COULOMB.withPrefix(Metric.MEGA); + + public static final LinearUnit MICROAMPERE = Metric.AMPERE.withPrefix(Metric.MICRO); + public static final LinearUnit MILLIAMPERE = Metric.AMPERE.withPrefix(Metric.MILLI); + + public static final LinearUnit MICROVOLT = Metric.VOLT.withPrefix(Metric.MICRO); + public static final LinearUnit MILLIVOLT = Metric.VOLT.withPrefix(Metric.MILLI); + public static final LinearUnit KILOVOLT = Metric.VOLT.withPrefix(Metric.KILO); + public static final LinearUnit MEGAVOLT = Metric.VOLT.withPrefix(Metric.MEGA); + + public static final LinearUnit KILOOHM = Metric.OHM.withPrefix(Metric.KILO); + public static final LinearUnit MEGAOHM = Metric.OHM.withPrefix(Metric.MEGA); + + // sets of prefixes + public static final Set ALL_PREFIXES = Set.of(DEKA, HECTO, KILO, + MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO, + NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO, KIBI, MEBI, GIBI, TEBI, PEBI, + EXBI); + + public static final Set DECIMAL_PREFIXES = Set.of(DEKA, HECTO, + KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, + MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); + public static final Set THOUSAND_PREFIXES = Set.of(KILO, MEGA, + GIGA, TERA, PETA, EXA, ZETTA, YOTTA, MILLI, MICRO, NANO, PICO, FEMTO, + ATTO, ZEPTO, YOCTO); + public static final Set MAGNIFYING_PREFIXES = Set.of(DEKA, HECTO, + KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, KIBI, MEBI, GIBI, + TEBI, PEBI, EXBI); + public static final Set REDUCING_PREFIXES = Set.of(DECI, CENTI, + MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); + + // You may NOT get SI instances! + private Metric() { + throw new AssertionError(); + } +} diff --git a/src/main/java/sevenUnits/unit/SI.java b/src/main/java/sevenUnits/unit/SI.java deleted file mode 100644 index 278fb5e..0000000 --- a/src/main/java/sevenUnits/unit/SI.java +++ /dev/null @@ -1,479 +0,0 @@ -/** - * Copyright (C) 2018 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 sevenUnits.unit; - -import java.util.Set; - -import sevenUnits.utils.ObjectProduct; - -/** - * All of the units, prefixes and dimensions that are used by the SI, as well as - * some outside the SI. - * - *

- * This class does not include prefixed units. To obtain prefixed units, use - * {@link LinearUnit#withPrefix}: - * - *

- * LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
- * 
- * - * - * @author Adrien Hopkins - * @since 2019-10-16 - */ -public final class SI { - /// dimensions used by SI units - // base dimensions, as BaseDimensions - public static final class BaseDimensions { - public static final BaseDimension LENGTH = BaseDimension.valueOf("Length", - "L"); - public static final BaseDimension MASS = BaseDimension.valueOf("Mass", - "M"); - public static final BaseDimension TIME = BaseDimension.valueOf("Time", - "T"); - public static final BaseDimension ELECTRIC_CURRENT = BaseDimension - .valueOf("Electric Current", "I"); - public static final BaseDimension TEMPERATURE = BaseDimension - .valueOf("Temperature", "\u0398"); // theta symbol - public static final BaseDimension QUANTITY = BaseDimension - .valueOf("Quantity", "N"); - public static final BaseDimension LUMINOUS_INTENSITY = BaseDimension - .valueOf("Luminous Intensity", "J"); - public static final BaseDimension INFORMATION = BaseDimension - .valueOf("Information", "Info"); // non-SI - public static final BaseDimension CURRENCY = BaseDimension - .valueOf("Currency", "$$"); // non-SI - - // You may NOT get SI.BaseDimensions instances! - private BaseDimensions() { - throw new AssertionError(); - } - } - - /// base units of the SI - // suppressing warnings since these are the same object, but in a different - /// form (class) - @SuppressWarnings("hiding") - public static final class BaseUnits { - public static final BaseUnit METRE = BaseUnit - .valueOf(BaseDimensions.LENGTH, "metre", "m"); - public static final BaseUnit KILOGRAM = BaseUnit - .valueOf(BaseDimensions.MASS, "kilogram", "kg"); - public static final BaseUnit SECOND = BaseUnit - .valueOf(BaseDimensions.TIME, "second", "s"); - public static final BaseUnit AMPERE = BaseUnit - .valueOf(BaseDimensions.ELECTRIC_CURRENT, "ampere", "A"); - public static final BaseUnit KELVIN = BaseUnit - .valueOf(BaseDimensions.TEMPERATURE, "kelvin", "K"); - public static final BaseUnit MOLE = BaseUnit - .valueOf(BaseDimensions.QUANTITY, "mole", "mol"); - public static final BaseUnit CANDELA = BaseUnit - .valueOf(BaseDimensions.LUMINOUS_INTENSITY, "candela", "cd"); - public static final BaseUnit BIT = BaseUnit - .valueOf(BaseDimensions.INFORMATION, "bit", "b"); - public static final BaseUnit DOLLAR = BaseUnit - .valueOf(BaseDimensions.CURRENCY, "dollar", "$"); - - public static final Set BASE_UNITS = Set.of(METRE, KILOGRAM, - SECOND, AMPERE, KELVIN, MOLE, CANDELA, BIT); - - // You may NOT get SI.BaseUnits instances! - private BaseUnits() { - throw new AssertionError(); - } - } - - /** - * Constants that relate to the SI or other systems. - * - * @author Adrien Hopkins - * @since 2019-11-08 - */ - public static final class Constants { - public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND) - .dividedBy(SECOND).times(9.80665); - } - - // dimensions used in the SI, as ObjectProducts - public static final class Dimensions { - public static final ObjectProduct EMPTY = ObjectProduct - .empty(); - public static final ObjectProduct LENGTH = ObjectProduct - .oneOf(BaseDimensions.LENGTH); - public static final ObjectProduct MASS = ObjectProduct - .oneOf(BaseDimensions.MASS); - public static final ObjectProduct TIME = ObjectProduct - .oneOf(BaseDimensions.TIME); - public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct - .oneOf(BaseDimensions.ELECTRIC_CURRENT); - public static final ObjectProduct TEMPERATURE = ObjectProduct - .oneOf(BaseDimensions.TEMPERATURE); - public static final ObjectProduct QUANTITY = ObjectProduct - .oneOf(BaseDimensions.QUANTITY); - public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct - .oneOf(BaseDimensions.LUMINOUS_INTENSITY); - public static final ObjectProduct INFORMATION = ObjectProduct - .oneOf(BaseDimensions.INFORMATION); - public static final ObjectProduct CURRENCY = ObjectProduct - .oneOf(BaseDimensions.CURRENCY); - - // derived dimensions without named SI units - public static final ObjectProduct AREA = LENGTH - .times(LENGTH); - public static final ObjectProduct VOLUME = AREA - .times(LENGTH); - public static final ObjectProduct VELOCITY = LENGTH - .dividedBy(TIME); - public static final ObjectProduct ACCELERATION = VELOCITY - .dividedBy(TIME); - public static final ObjectProduct WAVENUMBER = EMPTY - .dividedBy(LENGTH); - public static final ObjectProduct MASS_DENSITY = MASS - .dividedBy(VOLUME); - public static final ObjectProduct SURFACE_DENSITY = MASS - .dividedBy(AREA); - public static final ObjectProduct SPECIFIC_VOLUME = VOLUME - .dividedBy(MASS); - public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT - .dividedBy(AREA); - public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT - .dividedBy(LENGTH); - public static final ObjectProduct CONCENTRATION = QUANTITY - .dividedBy(VOLUME); - public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION - .times(MASS); - public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY - .dividedBy(AREA); - public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY - .dividedBy(VELOCITY); - public static final ObjectProduct REFRACTIVE_PERMEABILITY = EMPTY - .times(EMPTY); - public static final ObjectProduct ANGLE = LENGTH - .dividedBy(LENGTH); - public static final ObjectProduct SOLID_ANGLE = AREA - .dividedBy(AREA); - - // derived dimensions with named SI units - public static final ObjectProduct FREQUENCY = EMPTY - .dividedBy(TIME); - public static final ObjectProduct FORCE = MASS - .times(ACCELERATION); - public static final ObjectProduct ENERGY = FORCE - .times(LENGTH); - public static final ObjectProduct POWER = ENERGY - .dividedBy(TIME); - public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT - .times(TIME); - public static final ObjectProduct VOLTAGE = ENERGY - .dividedBy(ELECTRIC_CHARGE); - public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE - .dividedBy(VOLTAGE); - public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE - .dividedBy(ELECTRIC_CURRENT); - public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT - .dividedBy(VOLTAGE); - public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE - .times(TIME); - public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX - .dividedBy(AREA); - public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX - .dividedBy(ELECTRIC_CURRENT); - public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY - .times(SOLID_ANGLE); - public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX - .dividedBy(AREA); - public static final ObjectProduct SPECIFIC_ENERGY = ENERGY - .dividedBy(MASS); - public static final ObjectProduct CATALYTIC_ACTIVITY = QUANTITY - .dividedBy(TIME); - - // You may NOT get SI.Dimension instances! - private Dimensions() { - throw new AssertionError(); - } - } - - /// The units of the SI - public static final LinearUnit ONE = LinearUnit - .valueOf(ObjectProduct.empty(), 1); - - public static final LinearUnit METRE = BaseUnits.METRE.asLinearUnit() - .withName(NameSymbol.of("metre", "m", "meter")); - public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit() - .withName(NameSymbol.of("kilogram", "kg")); - public static final LinearUnit SECOND = BaseUnits.SECOND.asLinearUnit() - .withName(NameSymbol.of("second", "s", "sec")); - public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit() - .withName(NameSymbol.of("ampere", "A")); - public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit() - .withName(NameSymbol.of("kelvin", "K")); - public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit() - .withName(NameSymbol.of("mole", "mol")); - public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit() - .withName(NameSymbol.of("candela", "cd")); - public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit() - .withName(NameSymbol.of("bit", "b")); - public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit() - .withName(NameSymbol.of("dollar", "$")); - // Non-base units - public static final LinearUnit RADIAN = METRE.dividedBy(METRE) - .withName(NameSymbol.of("radian", "rad")); - - public static final LinearUnit STERADIAN = RADIAN.times(RADIAN) - .withName(NameSymbol.of("steradian", "sr")); - public static final LinearUnit HERTZ = ONE.dividedBy(SECOND) - .withName(NameSymbol.of("hertz", "Hz")); - // for periodic phenomena - public static final LinearUnit NEWTON = KILOGRAM.times(METRE) - .dividedBy(SECOND.times(SECOND)) - .withName(NameSymbol.of("newton", "N")); - public static final LinearUnit PASCAL = NEWTON.dividedBy(METRE.times(METRE)) - .withName(NameSymbol.of("pascal", "Pa")); - public static final LinearUnit JOULE = NEWTON.times(METRE) - .withName(NameSymbol.of("joule", "J")); - public static final LinearUnit WATT = JOULE.dividedBy(SECOND) - .withName(NameSymbol.of("watt", "W")); - public static final LinearUnit COULOMB = AMPERE.times(SECOND) - .withName(NameSymbol.of("coulomb", "C")); - public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB) - .withName(NameSymbol.of("volt", "V")); - public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT) - .withName(NameSymbol.of("farad", "F")); - public static final LinearUnit OHM = VOLT.dividedBy(AMPERE) - .withName(NameSymbol.of("ohm", "\u03A9")); // omega - public static final LinearUnit SIEMENS = ONE.dividedBy(OHM) - .withName(NameSymbol.of("siemens", "S")); - public static final LinearUnit WEBER = VOLT.times(SECOND) - .withName(NameSymbol.of("weber", "Wb")); - public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE)) - .withName(NameSymbol.of("tesla", "T")); - public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE) - .withName(NameSymbol.of("henry", "H")); - public static final LinearUnit LUMEN = CANDELA.times(STERADIAN) - .withName(NameSymbol.of("lumen", "lm")); - public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE)) - .withName(NameSymbol.of("lux", "lx")); - public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND) - .withName(NameSymbol.of("bequerel", "Bq")); - // for activity referred to a nucleotide - public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM) - .withName(NameSymbol.of("grey", "Gy")); - // for absorbed dose - public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM) - .withName(NameSymbol.of("sievert", "Sv")); - // for dose equivalent - public static final LinearUnit KATAL = MOLE.dividedBy(SECOND) - .withName(NameSymbol.of("katal", "kat")); - // common derived units included for convenience - public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000) - .withName(NameSymbol.of("gram", "g")); - - public static final LinearUnit SQUARE_METRE = METRE.toExponent(2) - .withName(NameSymbol.of("square metre", "m^2", "square meter", - "metre squared", "meter squared")); - public static final LinearUnit CUBIC_METRE = METRE.toExponent(3) - .withName(NameSymbol.of("cubic metre", "m^3", "cubic meter", - "metre cubed", "meter cubed")); - public static final LinearUnit METRE_PER_SECOND = METRE.dividedBy(SECOND) - .withName( - NameSymbol.of("metre per second", "m/s", "meter per second")); - // Non-SI units included for convenience - public static final Unit CELSIUS = Unit - .fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15, - tempC -> tempC + 273.15) - .withName(NameSymbol.of("degree Celsius", "\u00B0C")); - - public static final LinearUnit MINUTE = SECOND.times(60) - .withName(NameSymbol.of("minute", "min")); - public static final LinearUnit HOUR = MINUTE.times(60) - .withName(NameSymbol.of("hour", "h", "hr")); - public static final LinearUnit DAY = HOUR.times(60) - .withName(NameSymbol.of("day", "d")); - public static final LinearUnit KILOMETRE_PER_HOUR = METRE.times(1000) - .dividedBy(HOUR).withName(NameSymbol.of("kilometre per hour", "km/h", - "kilometer per hour")); - public static final LinearUnit DEGREE = RADIAN.times(360 / (2 * Math.PI)) - .withName(NameSymbol.of("degree", "\u00B0", "deg")); - public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60) - .withName(NameSymbol.of("arcminute", "arcmin")); - public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60) - .withName(NameSymbol.of("arcsecond", "arcsec")); - public static final LinearUnit ASTRONOMICAL_UNIT = METRE - .times(149597870700.0) - .withName(NameSymbol.of("astronomical unit", "au")); - public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT - .dividedBy(ARCSECOND).withName(NameSymbol.of("parsec", "pc")); - public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0) - .withName(NameSymbol.of("hectare", "ha")); - public static final LinearUnit LITRE = METRE.times(METRE).times(METRE) - .dividedBy(1000.0).withName(NameSymbol.of("litre", "L", "l", "liter")); - public static final LinearUnit TONNE = KILOGRAM.times(1000.0) - .withName(NameSymbol.of("tonne", "t", "metric ton")); - public static final LinearUnit DALTON = KILOGRAM.times(1.660539040e-27) - .withName(NameSymbol.of("dalton", "Da", "atomic unit", "u")); // approximate - // value - public static final LinearUnit ELECTRONVOLT = JOULE.times(1.602176634e-19) - .withName(NameSymbol.of("electron volt", "eV")); - public static final LinearUnit BYTE = BIT.times(8) - .withName(NameSymbol.of("byte", "B")); - public static final Unit NEPER = Unit.fromConversionFunctions(ONE.getBase(), - pr -> 0.5 * Math.log(pr), Np -> Math.exp(2 * Np)) - .withName(NameSymbol.of("neper", "Np")); - public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(), - pr -> Math.log10(pr), dB -> Math.pow(10, dB)) - .withName(NameSymbol.of("bel", "B")); - public static final Unit DECIBEL = Unit - .fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr), - dB -> Math.pow(10, dB / 10)) - .withName(NameSymbol.of("decibel", "dB")); - - /// The prefixes of the SI - // expanding decimal prefixes - public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3) - .withName(NameSymbol.of("kilo", "k", "K")); - public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6) - .withName(NameSymbol.of("mega", "M")); - public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9) - .withName(NameSymbol.of("giga", "G")); - public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12) - .withName(NameSymbol.of("tera", "T")); - public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15) - .withName(NameSymbol.of("peta", "P")); - public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18) - .withName(NameSymbol.of("exa", "E")); - public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21) - .withName(NameSymbol.of("zetta", "Z")); - public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24) - .withName(NameSymbol.of("yotta", "Y")); - - // contracting decimal prefixes - public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3) - .withName(NameSymbol.of("milli", "m")); - public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6) - .withName(NameSymbol.of("micro", "\u03BC", "u")); // mu - public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9) - .withName(NameSymbol.of("nano", "n")); - public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12) - .withName(NameSymbol.of("pico", "p")); - public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15) - .withName(NameSymbol.of("femto", "f")); - public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18) - .withName(NameSymbol.of("atto", "a")); - public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21) - .withName(NameSymbol.of("zepto", "z")); - public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24) - .withName(NameSymbol.of("yocto", "y")); - - // prefixes that don't match the pattern of thousands - public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1) - .withName(NameSymbol.of("deka", "da", "deca", "D")); - public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2) - .withName(NameSymbol.of("hecto", "h", "H", "hekto")); - public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1) - .withName(NameSymbol.of("deci", "d")); - public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2) - .withName(NameSymbol.of("centi", "c")); - public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024) - .withName(NameSymbol.of("kibi", "Ki")); - public static final UnitPrefix MEBI = KIBI.times(1024) - .withName(NameSymbol.of("mebi", "Mi")); - public static final UnitPrefix GIBI = MEBI.times(1024) - .withName(NameSymbol.of("gibi", "Gi")); - public static final UnitPrefix TEBI = GIBI.times(1024) - .withName(NameSymbol.of("tebi", "Ti")); - public static final UnitPrefix PEBI = TEBI.times(1024) - .withName(NameSymbol.of("pebi", "Pi")); - public static final UnitPrefix EXBI = PEBI.times(1024) - .withName(NameSymbol.of("exbi", "Ei")); - - // a few prefixed units - public static final LinearUnit MICROMETRE = SI.METRE.withPrefix(SI.MICRO); - public static final LinearUnit MILLIMETRE = SI.METRE.withPrefix(SI.MILLI); - public static final LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO); - public static final LinearUnit MEGAMETRE = SI.METRE.withPrefix(SI.MEGA); - - public static final LinearUnit MICROLITRE = SI.LITRE.withPrefix(SI.MICRO); - public static final LinearUnit MILLILITRE = SI.LITRE.withPrefix(SI.MILLI); - public static final LinearUnit KILOLITRE = SI.LITRE.withPrefix(SI.KILO); - public static final LinearUnit MEGALITRE = SI.LITRE.withPrefix(SI.MEGA); - - public static final LinearUnit MICROSECOND = SI.SECOND.withPrefix(SI.MICRO); - public static final LinearUnit MILLISECOND = SI.SECOND.withPrefix(SI.MILLI); - public static final LinearUnit KILOSECOND = SI.SECOND.withPrefix(SI.KILO); - public static final LinearUnit MEGASECOND = SI.SECOND.withPrefix(SI.MEGA); - - public static final LinearUnit MICROGRAM = SI.GRAM.withPrefix(SI.MICRO); - public static final LinearUnit MILLIGRAM = SI.GRAM.withPrefix(SI.MILLI); - public static final LinearUnit MEGAGRAM = SI.GRAM.withPrefix(SI.MEGA); - - public static final LinearUnit MICRONEWTON = SI.NEWTON.withPrefix(SI.MICRO); - public static final LinearUnit MILLINEWTON = SI.NEWTON.withPrefix(SI.MILLI); - public static final LinearUnit KILONEWTON = SI.NEWTON.withPrefix(SI.KILO); - public static final LinearUnit MEGANEWTON = SI.NEWTON.withPrefix(SI.MEGA); - - public static final LinearUnit MICROJOULE = SI.JOULE.withPrefix(SI.MICRO); - public static final LinearUnit MILLIJOULE = SI.JOULE.withPrefix(SI.MILLI); - public static final LinearUnit KILOJOULE = SI.JOULE.withPrefix(SI.KILO); - public static final LinearUnit MEGAJOULE = SI.JOULE.withPrefix(SI.MEGA); - - public static final LinearUnit MICROWATT = SI.WATT.withPrefix(SI.MICRO); - public static final LinearUnit MILLIWATT = SI.WATT.withPrefix(SI.MILLI); - public static final LinearUnit KILOWATT = SI.WATT.withPrefix(SI.KILO); - public static final LinearUnit MEGAWATT = SI.WATT.withPrefix(SI.MEGA); - - public static final LinearUnit MICROCOULOMB = SI.COULOMB - .withPrefix(SI.MICRO); - public static final LinearUnit MILLICOULOMB = SI.COULOMB - .withPrefix(SI.MILLI); - public static final LinearUnit KILOCOULOMB = SI.COULOMB.withPrefix(SI.KILO); - public static final LinearUnit MEGACOULOMB = SI.COULOMB.withPrefix(SI.MEGA); - - public static final LinearUnit MICROAMPERE = SI.AMPERE.withPrefix(SI.MICRO); - public static final LinearUnit MILLIAMPERE = SI.AMPERE.withPrefix(SI.MILLI); - - public static final LinearUnit MICROVOLT = SI.VOLT.withPrefix(SI.MICRO); - public static final LinearUnit MILLIVOLT = SI.VOLT.withPrefix(SI.MILLI); - public static final LinearUnit KILOVOLT = SI.VOLT.withPrefix(SI.KILO); - public static final LinearUnit MEGAVOLT = SI.VOLT.withPrefix(SI.MEGA); - - public static final LinearUnit KILOOHM = SI.OHM.withPrefix(SI.KILO); - public static final LinearUnit MEGAOHM = SI.OHM.withPrefix(SI.MEGA); - - // sets of prefixes - public static final Set ALL_PREFIXES = Set.of(DEKA, HECTO, KILO, - MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO, - NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO, KIBI, MEBI, GIBI, TEBI, PEBI, - EXBI); - - public static final Set DECIMAL_PREFIXES = Set.of(DEKA, HECTO, - KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, - MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); - public static final Set THOUSAND_PREFIXES = Set.of(KILO, MEGA, - GIGA, TERA, PETA, EXA, ZETTA, YOTTA, MILLI, MICRO, NANO, PICO, FEMTO, - ATTO, ZEPTO, YOCTO); - public static final Set MAGNIFYING_PREFIXES = Set.of(DEKA, HECTO, - KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, KIBI, MEBI, GIBI, - TEBI, PEBI, EXBI); - public static final Set REDUCING_PREFIXES = Set.of(DECI, CENTI, - MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); - - // You may NOT get SI instances! - private SI() { - throw new AssertionError(); - } -} diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java index 76356c0..459071f 100644 --- a/src/main/java/sevenUnits/unit/USCustomary.java +++ b/src/main/java/sevenUnits/unit/USCustomary.java @@ -52,7 +52,7 @@ public final class USCustomary { public static final LinearUnit YARD = BritishImperial.Length.YARD; public static final LinearUnit MILE = BritishImperial.Length.MILE; - public static final LinearUnit SURVEY_FOOT = SI.METRE.times(1200.0 / 3937.0); + public static final LinearUnit SURVEY_FOOT = Metric.METRE.times(1200.0 / 3937.0); public static final LinearUnit SURVEY_LINK = SURVEY_FOOT.times(33.0 / 50.0); public static final LinearUnit SURVEY_ROD = SURVEY_FOOT.times(16.5); public static final LinearUnit SURVEY_CHAIN = SURVEY_ROD.times(4); @@ -97,7 +97,7 @@ public final class USCustomary { public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3); public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT); - public static final LinearUnit MINIM = SI.LITRE.withPrefix(SI.MICRO).times(61.611519921875); + public static final LinearUnit MINIM = Metric.LITRE.withPrefix(Metric.MICRO).times(61.611519921875); public static final LinearUnit FLUID_DRAM = MINIM.times(60); public static final LinearUnit TEASPOON = MINIM.times(80); public static final LinearUnit TABLESPOON = TEASPOON.times(3); @@ -112,7 +112,7 @@ public final class USCustomary { public static final LinearUnit OIL_BARREL = GALLON.times(42); public static final LinearUnit HOGSHEAD = GALLON.times(63); - public static final LinearUnit DRY_PINT = SI.LITRE.times(0.5506104713575); + public static final LinearUnit DRY_PINT = Metric.LITRE.times(0.5506104713575); public static final LinearUnit DRY_QUART = DRY_PINT.times(2); public static final LinearUnit DRY_GALLON = DRY_QUART.times(4); public static final LinearUnit PECK = DRY_GALLON.times(2); @@ -128,7 +128,7 @@ public final class USCustomary { public static final LinearUnit KILOCALORIE = BritishImperial.KILOCALORIE; public static final LinearUnit FOOT_POUND = POUND_FORCE.times(Length.FOOT); - public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE).dividedBy(SI.MINUTE).times(33000); + public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE).dividedBy(Metric.MINUTE).times(33000); public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE.dividedBy(Length.INCH.toExponent(2)); public static final Unit FAHRENHEIT = BritishImperial.FAHRENHEIT; diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 8fcacb8..58b4e10 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -117,7 +117,7 @@ public abstract class Unit implements Nameable { * @since 2019-10-16 * @throws NullPointerException if unitBase or ns is null */ - Unit(ObjectProduct unitBase, NameSymbol ns) { + protected Unit(ObjectProduct unitBase, NameSymbol ns) { this.unitBase = Objects.requireNonNull(unitBase, "unitBase may not be null"); this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); @@ -192,7 +192,7 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in base unit * @return value expressed in this unit * @since 2018-12-22 @@ -208,7 +208,7 @@ public abstract class Unit implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -235,7 +235,7 @@ public abstract class Unit implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unitlike form to convert to * @param value value to convert * @param type of value to convert to @@ -270,7 +270,7 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in this unit * @return value expressed in base unit * @since 2018-12-22 @@ -342,7 +342,7 @@ public abstract class Unit implements Nameable { // second condition - check that for (final BaseUnit b : linear.getBase().getBaseSet()) { - if (!SI.BaseUnits.BASE_UNITS.contains(b)) + if (!Metric.BaseUnits.BASE_UNITS.contains(b)) return false; } diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index a40f000..b45d9cf 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1116,7 +1116,7 @@ public final class UnitDatabase { private static final LinearUnit exponentiateUnits(final LinearUnit base, final LinearUnit exponentUnit) { // exponent function - first check if o2 is a number, - if (exponentUnit.getBase().equals(SI.ONE.getBase())) { + if (exponentUnit.getBase().equals(Metric.ONE.getBase())) { // then check if it is an integer, final double exponent = exponentUnit.getConversionFactor(); if (DecimalComparison.equals(exponent % 1, 0)) @@ -1142,7 +1142,7 @@ public final class UnitDatabase { private static final LinearUnitValue exponentiateUnitValues( final LinearUnitValue base, final LinearUnitValue exponentValue) { // exponent function - first check if o2 is a number, - if (exponentValue.canConvertTo(SI.ONE)) { + if (exponentValue.canConvertTo(Metric.ONE)) { // then check if it is an integer, final double exponent = exponentValue.getValueExact(); if (DecimalComparison.equals(exponent % 1, 0)) @@ -1676,7 +1676,7 @@ public final class UnitDatabase { final BigDecimal number = new BigDecimal(name); final double uncertainty = Math.pow(10, -number.scale()); - return LinearUnitValue.of(SI.ONE, + return LinearUnitValue.of(Metric.ONE, UncertainDouble.of(number.doubleValue(), uncertainty)); } catch (final NumberFormatException e) { return LinearUnitValue.getExact(this.getLinearUnit(name), 1); @@ -1786,7 +1786,7 @@ public final class UnitDatabase { public Unit getUnit(final String name) { try { final double value = Double.parseDouble(name); - return SI.ONE.times(value); + return Metric.ONE.times(value); } catch (final NumberFormatException e) { final Unit unit = this.units.get(name); if (unit == null) diff --git a/src/test/java/sevenUnits/unit/MultiUnitTest.java b/src/test/java/sevenUnits/unit/MultiUnitTest.java index 82722af..d632118 100644 --- a/src/test/java/sevenUnits/unit/MultiUnitTest.java +++ b/src/test/java/sevenUnits/unit/MultiUnitTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test; import sevenUnits.unit.BritishImperial; import sevenUnits.unit.MultiUnit; -import sevenUnits.unit.SI; +import sevenUnits.unit.Metric; /** * Tests related to the {@code MultiUnit}. @@ -42,7 +42,7 @@ class MultiUnitTest { final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT, BritishImperial.Length.INCH); - assertEquals(1702.0, footInch.convertTo(SI.METRE.withPrefix(SI.MILLI), + assertEquals(1702.0, footInch.convertTo(Metric.METRE.withPrefix(Metric.MILLI), Arrays.asList(5.0, 7.0)), 1.0); for (int i = 0; i < 1000; i++) { @@ -50,7 +50,7 @@ class MultiUnitTest { final double inches = rng.nextDouble() * 12; final double millimetres = feet * 304.8 + inches * 25.4; - final List feetAndInches = SI.METRE.withPrefix(SI.MILLI) + final List feetAndInches = Metric.METRE.withPrefix(Metric.MILLI) .convertTo(footInch, millimetres); assertEquals(feet, feetAndInches.get(0), 1e-10); assertEquals(inches, feetAndInches.get(1), 1e-10); diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index c706b5c..1d5e503 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -33,7 +33,7 @@ import org.junit.jupiter.api.Test; import sevenUnits.unit.LinearUnit; import sevenUnits.unit.NameSymbol; -import sevenUnits.unit.SI; +import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; @@ -48,18 +48,18 @@ import sevenUnits.unit.UnitPrefix; */ class UnitDatabaseTest { // some linear units and one nonlinear - private static final Unit U = SI.METRE; - private static final Unit V = SI.KILOGRAM; - private static final Unit W = SI.SECOND; + private static final Unit U = Metric.METRE; + private static final Unit V = Metric.KILOGRAM; + private static final Unit W = Metric.SECOND; // used for testing expressions // J = U^2 * V / W^2 - private static final LinearUnit J = SI.KILOGRAM.times(SI.METRE.toExponent(2)) - .dividedBy(SI.SECOND.toExponent(2)); - private static final LinearUnit K = SI.KELVIN; + private static final LinearUnit J = Metric.KILOGRAM.times(Metric.METRE.toExponent(2)) + .dividedBy(Metric.SECOND.toExponent(2)); + private static final LinearUnit K = Metric.KELVIN; private static final Unit NONLINEAR = Unit - .fromConversionFunctions(SI.METRE.getBase(), o -> o + 1, o -> o - 1); + .fromConversionFunctions(Metric.METRE.getBase(), o -> o + 1, o -> o - 1); // make the prefix values prime so I can tell which multiplications were made private static final UnitPrefix A = UnitPrefix.valueOf(2) diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index bad5a31..a980054 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.Test; import sevenUnits.unit.LinearUnit; import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.NameSymbol; -import sevenUnits.unit.SI; +import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitValue; import sevenUnits.utils.DecimalComparison; @@ -48,19 +48,19 @@ class UnitTest { @Test public void testAdditionAndSubtraction() { - final LinearUnit inch = SI.METRE.times(0.0254) + final LinearUnit inch = Metric.METRE.times(0.0254) .withName(NameSymbol.of("inch", "in")); - final LinearUnit foot = SI.METRE.times(0.3048) + final LinearUnit foot = Metric.METRE.times(0.3048) .withName(NameSymbol.of("foot", "ft")); - assertEquals(inch.plus(foot), SI.METRE.times(0.3302)); - assertEquals(foot.minus(inch), SI.METRE.times(0.2794)); + assertEquals(inch.plus(foot), Metric.METRE.times(0.3302)); + assertEquals(foot.minus(inch), Metric.METRE.times(0.2794)); // test with LinearUnitValue - final LinearUnitValue value1 = LinearUnitValue.getExact(SI.METRE, 15); + final LinearUnitValue value1 = LinearUnitValue.getExact(Metric.METRE, 15); final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120); - final LinearUnitValue value3 = LinearUnitValue.getExact(SI.METRE, 0.5); - final LinearUnitValue value4 = LinearUnitValue.getExact(SI.KILOGRAM, 60); + final LinearUnitValue value3 = LinearUnitValue.getExact(Metric.METRE, 0.5); + final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM, 60); // make sure addition is done correctly assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001); @@ -70,8 +70,8 @@ class UnitTest { // make sure addition uses the correct unit, and is still associative // (ignoring floating-point rounding errors) - assertEquals(SI.METRE, value1.plus(value2).getUnit()); - assertEquals(SI.METRE, value1.plus(value2).plus(value3).getUnit()); + assertEquals(Metric.METRE, value1.plus(value2).getUnit()); + assertEquals(Metric.METRE, value1.plus(value2).plus(value3).getUnit()); assertEquals(foot, value2.plus(value1).getUnit()); assertTrue(value1.plus(value2).equals(value2.plus(value1), true)); @@ -81,7 +81,7 @@ class UnitTest { @Test public void testConversion() { - final LinearUnit metre = SI.METRE; + final LinearUnit metre = Metric.METRE; final Unit inch = metre.times(0.0254); final UnitValue value = UnitValue.of(inch, 75); @@ -97,7 +97,7 @@ class UnitTest { final double expected = testValue * conversionFactor; // test - final Unit unit = SI.METRE.times(conversionFactor); + final Unit unit = Metric.METRE.times(conversionFactor); final double actual = unit.convertToBase(testValue); assertEquals(actual, expected, @@ -107,17 +107,17 @@ class UnitTest { @Test public void testEquals() { - final LinearUnit metre = SI.METRE; - final Unit meter = SI.BaseUnits.METRE.asLinearUnit(); + final LinearUnit metre = Metric.METRE; + final Unit meter = Metric.BaseUnits.METRE.asLinearUnit(); assertEquals(metre, meter); } @Test public void testIsMetric() { - final Unit metre = SI.METRE; - final Unit megasecond = SI.SECOND.withPrefix(SI.MEGA); - final Unit hour = SI.HOUR; + final Unit metre = Metric.METRE; + final Unit megasecond = Metric.SECOND.withPrefix(Metric.MEGA); + final Unit hour = Metric.HOUR; assertTrue(metre.isMetric()); assertTrue(megasecond.isMetric()); @@ -127,26 +127,26 @@ class UnitTest { @Test public void testMultiplicationAndDivision() { // test unit-times-unit multiplication - final LinearUnit generatedJoule = SI.KILOGRAM - .times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2)); - final LinearUnit actualJoule = SI.JOULE; + final LinearUnit generatedJoule = Metric.KILOGRAM + .times(Metric.METRE.toExponent(2)).dividedBy(Metric.SECOND.toExponent(2)); + final LinearUnit actualJoule = Metric.JOULE; assertEquals(generatedJoule, actualJoule); // test multiplication by conversion factors - final LinearUnit kilometre = SI.METRE.times(1000); - final LinearUnit hour = SI.SECOND.times(3600); + final LinearUnit kilometre = Metric.METRE.times(1000); + final LinearUnit hour = Metric.SECOND.times(3600); final LinearUnit generatedKPH = kilometre.dividedBy(hour); - final LinearUnit actualKPH = SI.METRE.dividedBy(SI.SECOND).dividedBy(3.6); + final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND).dividedBy(3.6); assertEquals(generatedKPH, actualKPH); } @Test public void testPrefixes() { - final LinearUnit generatedKilometre = SI.METRE.withPrefix(SI.KILO); - final LinearUnit actualKilometre = SI.METRE.times(1000); + final LinearUnit generatedKilometre = Metric.METRE.withPrefix(Metric.KILO); + final LinearUnit actualKilometre = Metric.METRE.times(1000); assertEquals(generatedKilometre, actualKilometre); } diff --git a/src/test/java/sevenUnits/utils/ObjectProductTest.java b/src/test/java/sevenUnits/utils/ObjectProductTest.java index 32a8c78..13fd7ec 100644 --- a/src/test/java/sevenUnits/utils/ObjectProductTest.java +++ b/src/test/java/sevenUnits/utils/ObjectProductTest.java @@ -18,18 +18,18 @@ package sevenUnits.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static sevenUnits.unit.SI.Dimensions.AREA; -import static sevenUnits.unit.SI.Dimensions.ENERGY; -import static sevenUnits.unit.SI.Dimensions.LENGTH; -import static sevenUnits.unit.SI.Dimensions.MASS; -import static sevenUnits.unit.SI.Dimensions.MASS_DENSITY; -import static sevenUnits.unit.SI.Dimensions.QUANTITY; -import static sevenUnits.unit.SI.Dimensions.TIME; -import static sevenUnits.unit.SI.Dimensions.VOLUME; +import static sevenUnits.unit.Metric.Dimensions.AREA; +import static sevenUnits.unit.Metric.Dimensions.ENERGY; +import static sevenUnits.unit.Metric.Dimensions.LENGTH; +import static sevenUnits.unit.Metric.Dimensions.MASS; +import static sevenUnits.unit.Metric.Dimensions.MASS_DENSITY; +import static sevenUnits.unit.Metric.Dimensions.QUANTITY; +import static sevenUnits.unit.Metric.Dimensions.TIME; +import static sevenUnits.unit.Metric.Dimensions.VOLUME; import org.junit.jupiter.api.Test; -import sevenUnits.unit.SI; +import sevenUnits.unit.Metric; import sevenUnits.utils.ObjectProduct; /** @@ -60,8 +60,8 @@ class ObjectProductTest { */ @Test public void testExponents() { - assertEquals(1, LENGTH.getExponent(SI.BaseDimensions.LENGTH)); - assertEquals(3, VOLUME.getExponent(SI.BaseDimensions.LENGTH)); + assertEquals(1, LENGTH.getExponent(Metric.BaseDimensions.LENGTH)); + assertEquals(3, VOLUME.getExponent(Metric.BaseDimensions.LENGTH)); } /** -- cgit v1.2.3