summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrien Hopkins <ahopk127@my.yorku.ca>2022-04-18 17:01:54 -0500
committerAdrien Hopkins <ahopk127@my.yorku.ca>2022-04-18 17:15:22 -0500
commitf0541a955b6e4b12d808cffec0874f50a004e8b9 (patch)
tree9b7960bd55c3d1d7c8d930802c6a2bcbafc8d49f
parent855cdf83b91bd3061662e563db6656408cc24a12 (diff)
Implemented rounding and duplicate-removal settings into the new GUI
-rw-r--r--CHANGELOG.org2
-rw-r--r--src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java2
-rw-r--r--src/main/java/sevenUnits/unit/LinearUnitValue.java10
-rw-r--r--src/main/java/sevenUnits/unit/UnitDatabase.java6
-rw-r--r--src/main/java/sevenUnits/utils/UncertainDouble.java24
-rw-r--r--src/main/java/sevenUnitsGUI/Presenter.java56
-rw-r--r--src/main/java/sevenUnitsGUI/StandardDisplayRules.java220
-rw-r--r--src/main/java/sevenUnitsGUI/TabbedView.java280
-rw-r--r--src/test/java/sevenUnits/unit/UnitTest.java14
-rw-r--r--src/test/java/sevenUnits/utils/UncertainDoubleTest.java11
-rw-r--r--src/test/java/sevenUnitsGUI/PresenterTest.java14
11 files changed, 518 insertions, 121 deletions
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 61d9333..c164d1f 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -10,6 +10,8 @@
- BaseDimension is now Nameable. As a consequence, its name and symbol return Optional<String> instead of String, even though they will always succeed.
- The UnitDatabase's units, prefixes and dimensions are now always named
- The toString method of the common unit classes is now simpler. Alternate toString functions that describe the full unit are provided.
+ - UncertainDouble and LinearUnitValue accept a RoundingMode in their complicated toString functions.
+ - Rounding rules are now in their own classes
- Tweaked the look of the unit and expression conversion sections of the view
** v0.3.2 - [2021-12-02 Thu]
*** Added
diff --git a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java
index 55e1546..e10bab4 100644
--- a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java
+++ b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java
@@ -488,7 +488,7 @@ final class SevenUnitsGUI {
case SIGNIFICANT_DIGITS:
return this.getRoundedString(value.asUnitValue());
case SCIENTIFIC:
- return value.toString(showUncertainty);
+ return value.toString(showUncertainty, RoundingMode.HALF_EVEN);
default:
throw new AssertionError("Invalid switch condition.");
}
diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java
index a50e1f5..f91d30b 100644
--- a/src/main/java/sevenUnits/unit/LinearUnitValue.java
+++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java
@@ -16,6 +16,7 @@
*/
package sevenUnits.unit;
+import java.math.RoundingMode;
import java.util.Objects;
import java.util.Optional;
@@ -300,7 +301,7 @@ public final class LinearUnitValue {
@Override
public String toString() {
- return this.toString(!this.value.isExact());
+ return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN);
}
/**
@@ -315,7 +316,8 @@ public final class LinearUnitValue {
*
* @since 2020-07-26
*/
- public String toString(final boolean showUncertainty) {
+ public String toString(final boolean showUncertainty,
+ RoundingMode roundingMode) {
final Optional<String> primaryName = this.unit.getPrimaryName();
final Optional<String> symbol = this.unit.getSymbol();
final String chosenName = symbol.orElse(primaryName.orElse(null));
@@ -325,10 +327,10 @@ public final class LinearUnitValue {
// get rounded strings
// if showUncertainty is true, add brackets around the string
final String valueString = (showUncertainty ? "(" : "")
- + this.value.toString(showUncertainty)
+ + this.value.toString(showUncertainty, roundingMode)
+ (showUncertainty ? ")" : "");
final String baseValueString = (showUncertainty ? "(" : "")
- + baseValue.toString(showUncertainty)
+ + baseValue.toString(showUncertainty, roundingMode)
+ (showUncertainty ? ")" : "");
// create string
diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java
index 7b02ac7..a4f0c44 100644
--- a/src/main/java/sevenUnits/unit/UnitDatabase.java
+++ b/src/main/java/sevenUnits/unit/UnitDatabase.java
@@ -19,7 +19,6 @@ package sevenUnits.unit;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.AbstractSet;
@@ -1705,11 +1704,8 @@ public final class UnitDatabase {
LinearUnitValue getLinearUnitValue(final String name) {
try {
// try to parse it as a number - otherwise it is not a number!
- final BigDecimal number = new BigDecimal(name);
-
- final double uncertainty = Math.pow(10, -number.scale());
return LinearUnitValue.of(Metric.ONE,
- UncertainDouble.of(number.doubleValue(), uncertainty));
+ UncertainDouble.fromRoundedString(name));
} catch (final NumberFormatException e) {
return LinearUnitValue.getExact(this.getLinearUnit(name), 1);
}
diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java
index fe41104..ac523b3 100644
--- a/src/main/java/sevenUnits/utils/UncertainDouble.java
+++ b/src/main/java/sevenUnits/utils/UncertainDouble.java
@@ -46,6 +46,21 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
+ "(?:\\s*(?:±|\\+-)\\s*" + NUMBER_REGEX + ")?");
/**
+ * Gets an UncertainDouble from a double string. The uncertainty of the
+ * double will be one of the lowest decimal place of the number. For example,
+ * "12345.678" will become 12345.678 ± 0.001.
+ *
+ * @throws NumberFormatException if the argument is not a number
+ *
+ * @since 2022-04-18
+ */
+ public static final UncertainDouble fromRoundedString(String s) {
+ final BigDecimal value = new BigDecimal(s);
+ final double uncertainty = Math.pow(10, -value.scale());
+ return UncertainDouble.of(value.doubleValue(), uncertainty);
+ }
+
+ /**
* Parses a string in the form of {@link UncertainDouble#toString(boolean)}
* and returns the corresponding {@code UncertainDouble} instance.
* <p>
@@ -348,7 +363,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
*/
@Override
public final String toString() {
- return this.toString(!this.isExact());
+ return this.toString(!this.isExact(), RoundingMode.HALF_EVEN);
}
/**
@@ -379,7 +394,8 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
*
* @since 2020-09-07
*/
- public final String toString(boolean showUncertainty) {
+ public final String toString(boolean showUncertainty,
+ RoundingMode roundingMode) {
String valueString, uncertaintyString;
// generate the string representation of value and uncertainty
@@ -394,9 +410,9 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
final int displayScale = this.getDisplayScale();
final BigDecimal roundedUncertainty = bigUncertainty
- .setScale(displayScale, RoundingMode.HALF_EVEN);
+ .setScale(displayScale, roundingMode);
final BigDecimal roundedValue = bigValue.setScale(displayScale,
- RoundingMode.HALF_EVEN);
+ roundingMode);
valueString = roundedValue.toString();
uncertaintyString = roundedUncertainty.toString();
diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java
index 981af21..85a0ddc 100644
--- a/src/main/java/sevenUnitsGUI/Presenter.java
+++ b/src/main/java/sevenUnitsGUI/Presenter.java
@@ -242,6 +242,9 @@ public final class Presenter {
throw new AssertionError("Loading of metric_exceptions.txt failed.",
e);
}
+
+ // set default settings temporarily
+ this.numberDisplayRule = StandardDisplayRules.uncertaintyBased();
}
/**
@@ -293,9 +296,21 @@ public final class Presenter {
// convert and show output
if (from.getUnit().canConvertTo(to)) {
- final double value = from.asUnitValue().convertTo(to).getValue();
+ final UncertainDouble uncertainValue;
+
+ // uncertainty is meaningless for non-linear units, so we will have
+ // to erase uncertainty information for them
+ if (to instanceof LinearUnit) {
+ final var toLinear = (LinearUnit) to;
+ uncertainValue = from.convertTo(toLinear).getValue();
+ } else {
+ final double value = from.asUnitValue().convertTo(to).getValue();
+ uncertainValue = UncertainDouble.of(value, 0);
+ }
+
final UnitConversionRecord uc = UnitConversionRecord.valueOf(
- fromExpression, toExpression, "", String.valueOf(value));
+ fromExpression, toExpression, "",
+ this.numberDisplayRule.apply(uncertainValue));
xcview.showExpressionConversionOutput(uc);
} else {
this.view.showErrorMessage("Conversion Error",
@@ -324,7 +339,7 @@ public final class Presenter {
final Optional<String> fromUnitOptional = ucview.getFromSelection();
final Optional<String> toUnitOptional = ucview.getToSelection();
- final String valueString = ucview.getInputValue();
+ final String inputValueString = ucview.getInputValue();
// extract values from optionals
final String fromUnitString, toUnitString;
@@ -345,7 +360,7 @@ public final class Presenter {
// convert strings to data, checking if anything is invalid
final Unit fromUnit, toUnit;
- final double value;
+ final UncertainDouble uncertainValue;
if (this.database.containsUnitName(fromUnitString)) {
fromUnit = this.database.getUnit(fromUnitString);
@@ -356,23 +371,42 @@ public final class Presenter {
} else
throw this.viewError("Nonexistent To unit: %s", toUnitString);
try {
- value = Double.parseDouble(valueString);
+ uncertainValue = UncertainDouble
+ .fromRoundedString(inputValueString);
} catch (final NumberFormatException e) {
this.view.showErrorMessage("Value Error",
- "Invalid value " + valueString);
+ "Invalid value " + inputValueString);
return;
}
if (!fromUnit.canConvertTo(toUnit))
throw this.viewError("Could not convert between %s and %s",
fromUnit, toUnit);
-
- // convert!
- final UnitValue initialValue = UnitValue.of(fromUnit, value);
- final UnitValue converted = initialValue.convertTo(toUnit);
+
+ // convert - we will need to erase uncertainty for non-linear units, so
+ // we need to treat linear and non-linear units differently
+ final String outputValueString;
+ if (fromUnit instanceof LinearUnit && toUnit instanceof LinearUnit) {
+ final LinearUnit fromLinear = (LinearUnit) fromUnit;
+ final LinearUnit toLinear = (LinearUnit) toUnit;
+ final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear,
+ uncertainValue);
+ final LinearUnitValue converted = initialValue.convertTo(toLinear);
+
+ outputValueString = this.numberDisplayRule
+ .apply(converted.getValue());
+ } else {
+ final UnitValue initialValue = UnitValue.of(fromUnit,
+ uncertainValue.value());
+ final UnitValue converted = initialValue.convertTo(toUnit);
+
+ outputValueString = this.numberDisplayRule
+ .apply(UncertainDouble.of(converted.getValue(), 0));
+ }
ucview.showUnitConversionOutput(
- UnitConversionRecord.fromValues(initialValue, converted));
+ UnitConversionRecord.valueOf(fromUnitString, toUnitString,
+ inputValueString, outputValueString));
} else
throw new UnsupportedOperationException(
"This function can only be called when the view is a UnitConversionView.");
diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
index f6272c8..0c0ba8e 100644
--- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
+++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021 Adrien Hopkins
+ * Copyright (C) 2022 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
@@ -16,67 +16,192 @@
*/
package sevenUnitsGUI;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
import java.util.function.Function;
+import java.util.regex.Pattern;
import sevenUnits.utils.UncertainDouble;
/**
- * The default rules for displaying numbers.
- *
- * Unless otherwise stated, all of this class's functions throw
- * {@link NullPointerException} when they receive a null parameter.
+ * A static utility class that can be used to make display rules for the
+ * presenter.
*
- * @since 2021-12-24
+ * @since 2022-04-18
*/
-final class StandardDisplayRules {
+public final class StandardDisplayRules {
/**
- * Rounds using UncertainDouble's toString method.
+ * A rule that rounds to a fixed number of decimal places.
+ *
+ * @since 2022-04-18
*/
- private static final Function<UncertainDouble, String> SCIENTIFIC_ROUNDING_RULE = new Function<>() {
+ public static final class FixedDecimals
+ implements Function<UncertainDouble, String> {
+ public static final Pattern TO_STRING_PATTERN = Pattern
+ .compile("Round to (\\d+) decimal places");
+ /**
+ * The number of places to round to.
+ */
+ private final int decimalPlaces;
+
+ /**
+ * @param decimalPlaces
+ * @since 2022-04-18
+ */
+ private FixedDecimals(int decimalPlaces) {
+ this.decimalPlaces = decimalPlaces;
+ }
+
@Override
public String apply(UncertainDouble t) {
- return t.toString(false);
+ final var toRound = new BigDecimal(t.value());
+ return toRound.setScale(this.decimalPlaces, RoundingMode.HALF_EVEN)
+ .toPlainString();
+ }
+
+ /**
+ * @return the number of decimal places this rule rounds to
+ * @since 2022-04-18
+ */
+ public int decimalPlaces() {
+ return this.decimalPlaces;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof FixedDecimals))
+ return false;
+ final FixedDecimals other = (FixedDecimals) obj;
+ if (this.decimalPlaces != other.decimalPlaces)
+ return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 + this.decimalPlaces;
}
@Override
public String toString() {
- return "Scientific Rounding";
+ return "Round to " + this.decimalPlaces + " decimal places";
}
- };
+ }
/**
- * Gets a display rule that rounds numbers to a fixed number of decimal
- * places.
+ * A rule that rounds to a fixed number of significant digits.
*
- * @param decimalPlaces number of decimal places
- * @return display rule
- * @since 2022-04-16
+ * @since 2022-04-18
*/
- public static final Function<UncertainDouble, String> getFixedPlacesRule(
- int decimalPlaces) {
- throw new UnsupportedOperationException("Not implemented yet");
+ public static final class FixedPrecision
+ implements Function<UncertainDouble, String> {
+ public static final Pattern TO_STRING_PATTERN = Pattern
+ .compile("Round to (\\d+) significant figures");
+
+ /**
+ * The number of significant figures to round to.
+ */
+ private final MathContext mathContext;
+
+ /**
+ * @param significantFigures
+ * @since 2022-04-18
+ */
+ private FixedPrecision(int significantFigures) {
+ this.mathContext = new MathContext(significantFigures,
+ RoundingMode.HALF_EVEN);
+ }
+
+ @Override
+ public String apply(UncertainDouble t) {
+ final var toRound = new BigDecimal(t.value());
+ return toRound.round(this.mathContext).toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof FixedPrecision))
+ return false;
+ final FixedPrecision other = (FixedPrecision) obj;
+ if (this.mathContext == null) {
+ if (other.mathContext != null)
+ return false;
+ } else if (!this.mathContext.equals(other.mathContext))
+ return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 127
+ + (this.mathContext == null ? 0 : this.mathContext.hashCode());
+ }
+
+ /**
+ * @return the number of significant figures this rule rounds to
+ * @since 2022-04-18
+ */
+ public int significantFigures() {
+ return this.mathContext.getPrecision();
+ }
+
+ @Override
+ public String toString() {
+ return "Round to " + this.mathContext.getPrecision()
+ + " significant figures";
+ }
}
/**
- * Gets a display rule that rounds numbers to a fixed number of significant
- * figures.
+ * A rounding rule that rounds based on UncertainDouble's toString method.
+ * This means the output will have around as many significant figures as the
+ * input.
*
- * @param significantFigures number of significant figures
- * @return display rule
- * @since 2022-04-16
+ * @since 2022-04-18
+ */
+ public static final class UncertaintyBased
+ implements Function<UncertainDouble, String> {
+ private UncertaintyBased() {}
+
+ @Override
+ public String apply(UncertainDouble t) {
+ return t.toString(false, RoundingMode.HALF_EVEN);
+ }
+
+ @Override
+ public String toString() {
+ return "Uncertainty-Based Rounding";
+ }
+ }
+
+ /**
+ * For now, I want this to be a singleton. I might want to add a parameter
+ * later, so I won't make it an enum.
+ */
+ private static final UncertaintyBased UNCERTAINTY_BASED_ROUNDING_RULE = new UncertaintyBased();
+
+ /**
+ * @param decimalPlaces decimal places to round to
+ * @return a rounding rule that rounds to fixed number of decimal places
+ * @since 2022-04-18
*/
- public static final Function<UncertainDouble, String> getFixedPrecisionRule(
- int significantFigures) {
- throw new UnsupportedOperationException("Not implemented yet");
+ public static final FixedDecimals fixedDecimals(int decimalPlaces) {
+ return new FixedDecimals(decimalPlaces);
}
/**
- * @return a rule that rounds using UncertainDouble's own toString(false)
- * function.
- * @since 2021-12-24
+ * @param significantFigures significant figures to round to
+ * @return a rounding rule that rounds to a fixed number of significant
+ * figures
+ * @since 2022-04-18
*/
- public static final Function<UncertainDouble, String> getScientificRule() {
- return SCIENTIFIC_ROUNDING_RULE;
+ public static final FixedPrecision fixedPrecision(int significantFigures) {
+ return new FixedPrecision(significantFigures);
}
/**
@@ -90,11 +215,32 @@ final class StandardDisplayRules {
*/
public static final Function<UncertainDouble, String> getStandardRule(
String ruleToString) {
- throw new UnsupportedOperationException("Not implemented yet");
+ if (UNCERTAINTY_BASED_ROUNDING_RULE.toString().equals(ruleToString))
+ return UNCERTAINTY_BASED_ROUNDING_RULE;
+
+ // test if it is a fixed-places rule
+ final var placesMatch = FixedDecimals.TO_STRING_PATTERN
+ .matcher(ruleToString);
+ if (placesMatch.matches())
+ return new FixedDecimals(Integer.valueOf(placesMatch.group(1)));
+
+ // test if it is a fixed-sig-fig rule
+ final var sigFigMatch = FixedPrecision.TO_STRING_PATTERN
+ .matcher(ruleToString);
+ if (sigFigMatch.matches())
+ return new FixedPrecision(Integer.valueOf(sigFigMatch.group(1)));
+
+ throw new IllegalArgumentException(
+ "Provided string does not match any given rules.");
}
- private StandardDisplayRules() {
- throw new AssertionError(
- "This is a static utility class, you may not get instances of it.");
+ /**
+ * @return an UncertainDouble-based rounding rule
+ * @since 2022-04-18
+ */
+ public static final UncertaintyBased uncertaintyBased() {
+ return UNCERTAINTY_BASED_ROUNDING_RULE;
}
+
+ private StandardDisplayRules() {}
}
diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java
index d0eb32f..3a951ef 100644
--- a/src/main/java/sevenUnitsGUI/TabbedView.java
+++ b/src/main/java/sevenUnitsGUI/TabbedView.java
@@ -20,16 +20,18 @@ import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
+import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.Set;
+import java.util.function.Function;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
@@ -37,7 +39,6 @@ import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
-import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
@@ -58,6 +59,7 @@ import javax.swing.border.TitledBorder;
import sevenUnits.ProgramInfo;
import sevenUnits.unit.UnitType;
import sevenUnits.utils.NameSymbol;
+import sevenUnits.utils.UncertainDouble;
/**
* A View that separates its functions into multiple tabs
@@ -111,7 +113,99 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
}
- private static final NumberFormat NUMBER_FORMATTER = new DecimalFormat();
+ /**
+ * The standard types of rounding, corresponding to the options on the
+ * TabbedView's settings panel.
+ *
+ * @since 2022-04-18
+ */
+ private static enum StandardRoundingType {
+ /**
+ * Rounds to a fixed number of significant digits. Precision is used,
+ * representing the number of significant digits to round to.
+ */
+ SIGNIFICANT_DIGITS(true) {
+ @Override
+ public Function<UncertainDouble, String> getRuleFromPrecision(
+ int precision) {
+ return StandardDisplayRules.fixedPrecision(precision);
+ }
+ },
+ /**
+ * Rounds to a fixed number of decimal places. Precision is used,
+ * representing the number of decimal places to round to.
+ */
+ DECIMAL_PLACES(true) {
+ @Override
+ public Function<UncertainDouble, String> getRuleFromPrecision(
+ int precision) {
+ return StandardDisplayRules.fixedDecimals(precision);
+ }
+ },
+ /**
+ * Rounds according to UncertainDouble's toString method. The specified
+ * precision is ignored.
+ */
+ UNCERTAINTY(false) {
+ @Override
+ public Function<UncertainDouble, String> getRuleFromPrecision(
+ int precision) {
+ return StandardDisplayRules.uncertaintyBased();
+ }
+ };
+
+ /**
+ * If true, this type of rounding rule requires you to specify a
+ * precision.
+ */
+ private final boolean requiresPrecision;
+
+ /**
+ * @param canCustomizePrecision
+ * @since 2022-04-18
+ */
+ private StandardRoundingType(boolean requiresPrecision) {
+ this.requiresPrecision = requiresPrecision;
+ }
+
+ /**
+ * Gets a rounding rule of this type.
+ *
+ * @param precision the rounding type's precision. If
+ * {@link #requiresPrecision} is false, this field will
+ * be ignored.
+ * @return rounding rule
+ * @since 2022-04-18
+ */
+ public abstract Function<UncertainDouble, String> getRuleFromPrecision(
+ int precision);
+
+ /**
+ * Tries to get this rule without specifying precision.
+ *
+ * @throws UnsupportedOperationException if this rule requires specifying
+ * precision
+ * @since 2022-04-18
+ */
+ public final Function<UncertainDouble, String> getRuleWithoutPrecision() {
+ if (this.requiresPrecision())
+ throw new UnsupportedOperationException("Rounding type " + this
+ + " requires you to specify precision.");
+ else
+ // random number to mess with anyone who lies about whether or not
+ // precision is required
+ return this.getRuleFromPrecision(-623546735);
+ }
+
+ /**
+ * @return whether or not this rounding type requires you to specify an
+ * integer precision
+ * @since 2022-04-18
+ */
+ public boolean requiresPrecision() {
+ return this.requiresPrecision;
+ }
+ }
/**
* Creates a TabbedView.
@@ -137,7 +231,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
/** The combo box that selects dimensions */
private final JComboBox<String> dimensionSelector;
/** The panel for inputting values in the dimension-based converter */
- private final JFormattedTextField valueInput;
+ private final JTextField valueInput;
/** The panel for "From" in the dimension-based converter */
private final SearchBoxList<String> fromSearch;
/** The panel for "To" in the dimension-based converter */
@@ -163,6 +257,10 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
/** The text box for prefix data in the prefix viewer */
private final JTextArea prefixTextBox;
+ // SETTINGS STUFF
+ private StandardRoundingType roundingType;
+ private int precision;
+
/**
* Creates the view and makes it visible to the user
*
@@ -229,7 +327,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
final JLabel valuePrompt = new JLabel("Value to convert: ");
outputPanel.add(valuePrompt, BorderLayout.LINE_START);
- this.valueInput = new JFormattedTextField(NUMBER_FORMATTER);
+ this.valueInput = new JTextField();
outputPanel.add(this.valueInput, BorderLayout.CENTER);
// conversion button
@@ -352,61 +450,89 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
// rounding rule selection
final ButtonGroup roundingRuleButtons = new ButtonGroup();
+ this.roundingType = this.getPresenterRoundingType()
+ .orElseThrow(() -> new AssertionError(
+ "Presenter loaded non-standard rounding rule"));
+ this.precision = this.getPresenterPrecision().orElse(6);
final JLabel roundingRuleLabel = new JLabel("Rounding Rule:");
roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
+ // sigDigSlider needs to be first so that the rounding-type buttons can
+ // show and hide it
+ final JLabel sliderLabel = new JLabel("Precision:");
+ sliderLabel.setVisible(
+ this.roundingType != StandardRoundingType.UNCERTAINTY);
+ roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JSlider sigDigSlider = new JSlider(0, 12);
+ roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ sigDigSlider.setMajorTickSpacing(4);
+ sigDigSlider.setMinorTickSpacing(1);
+ sigDigSlider.setSnapToTicks(true);
+ sigDigSlider.setPaintTicks(true);
+ sigDigSlider.setPaintLabels(true);
+
+ sigDigSlider.setVisible(
+ this.roundingType != StandardRoundingType.UNCERTAINTY);
+ sigDigSlider.setValue(this.precision);
+
+ sigDigSlider.addChangeListener(e -> {
+ this.precision = sigDigSlider.getValue();
+ this.updatePresenterRoundingRule();
+ });
+
+ // significant digit rounding
final JRadioButton fixedPrecision = new JRadioButton(
"Fixed Precision");
-// if (this.presenter.roundingType == RoundingType.SIGNIFICANT_DIGITS) {
-// fixedPrecision.setSelected(true);
-// }
-// fixedPrecision.addActionListener(e -> this.presenter
-// .setRoundingType(RoundingType.SIGNIFICANT_DIGITS));
+ if (this.roundingType == StandardRoundingType.SIGNIFICANT_DIGITS) {
+ fixedPrecision.setSelected(true);
+ }
+ fixedPrecision.addActionListener(e -> {
+ this.roundingType = StandardRoundingType.SIGNIFICANT_DIGITS;
+ sliderLabel.setVisible(true);
+ sigDigSlider.setVisible(true);
+ this.updatePresenterRoundingRule();
+ });
roundingRuleButtons.add(fixedPrecision);
roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
+ // decimal place rounding
final JRadioButton fixedDecimals = new JRadioButton(
"Fixed Decimal Places");
-// if (this.presenter.roundingType == RoundingType.DECIMAL_PLACES) {
-// fixedDecimals.setSelected(true);
-// }
-// fixedDecimals.addActionListener(e -> this.presenter
-// .setRoundingType(RoundingType.DECIMAL_PLACES));
+ if (this.roundingType == StandardRoundingType.DECIMAL_PLACES) {
+ fixedDecimals.setSelected(true);
+ }
+ fixedDecimals.addActionListener(e -> {
+ this.roundingType = StandardRoundingType.DECIMAL_PLACES;
+ sliderLabel.setVisible(true);
+ sigDigSlider.setVisible(true);
+ this.updatePresenterRoundingRule();
+ });
roundingRuleButtons.add(fixedDecimals);
roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2)
.setAnchor(GridBagConstraints.LINE_START).build());
+ // scientific rounding
final JRadioButton relativePrecision = new JRadioButton(
- "Scientific Precision");
-// if (this.presenter.roundingType == RoundingType.SCIENTIFIC) {
-// relativePrecision.setSelected(true);
-// }
-// relativePrecision.addActionListener(
-// e -> this.presenter.setRoundingType(RoundingType.SCIENTIFIC));
+ "Uncertainty-Based Rounding");
+ if (this.roundingType == StandardRoundingType.UNCERTAINTY) {
+ relativePrecision.setSelected(true);
+ }
+ relativePrecision.addActionListener(e -> {
+ this.roundingType = StandardRoundingType.UNCERTAINTY;
+ sliderLabel.setVisible(false);
+ sigDigSlider.setVisible(false);
+ this.updatePresenterRoundingRule();
+ });
roundingRuleButtons.add(relativePrecision);
roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3)
.setAnchor(GridBagConstraints.LINE_START).build());
-
- final JLabel sliderLabel = new JLabel("Precision:");
- roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4)
- .setAnchor(GridBagConstraints.LINE_START).build());
-
- final JSlider sigDigSlider = new JSlider(0, 12);
- roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5)
- .setAnchor(GridBagConstraints.LINE_START).build());
-
- sigDigSlider.setMajorTickSpacing(4);
- sigDigSlider.setMinorTickSpacing(1);
- sigDigSlider.setSnapToTicks(true);
- sigDigSlider.setPaintTicks(true);
- sigDigSlider.setPaintLabels(true);
-// sigDigSlider.setValue(this.presenter.precision);
-
-// sigDigSlider.addChangeListener(
-// e -> this.presenter.setPrecision(sigDigSlider.getValue()));
}
// ============ PREFIX REPETITION SETTINGS ============
@@ -501,17 +627,18 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
miscPanel.setLayout(new GridBagLayout());
final JCheckBox oneWay = new JCheckBox("Convert One Way Only");
-// oneWay.setSelected(this.presenter.oneWay);
-// oneWay.addItemListener(
-// e -> this.presenter.setOneWay(e.getStateChange() == 1));
+ oneWay.setSelected(this.presenter.oneWayConversionEnabled());
+ oneWay.addItemListener(e -> this.presenter.setOneWayConversionEnabled(
+ e.getStateChange() == ItemEvent.SELECTED));
miscPanel.add(oneWay, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
final JCheckBox showAllVariations = new JCheckBox(
"Show Duplicates in \"Convert Units\"");
-// showAllVariations.setSelected(this.presenter.includeDuplicateUnits);
-// showAllVariations.addItemListener(e -> this.presenter
-// .setIncludeDuplicateUnits(e.getStateChange() == 1));
+ showAllVariations.setSelected(this.presenter.duplicateUnitsShown());
+ showAllVariations
+ .addItemListener(e -> this.presenter.setShowDuplicateUnits(
+ e.getStateChange() == ItemEvent.SELECTED));
miscPanel.add(showAllVariations, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
@@ -552,6 +679,43 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
return this.valueInput.getText();
}
+ /**
+ * @return the precision of the presenter's rounding rule, if that is
+ * meaningful
+ * @since 2022-04-18
+ */
+ private OptionalInt getPresenterPrecision() {
+ final var presenterRule = this.presenter.getNumberDisplayRule();
+ if (presenterRule instanceof StandardDisplayRules.FixedDecimals)
+ return OptionalInt
+ .of(((StandardDisplayRules.FixedDecimals) presenterRule)
+ .decimalPlaces());
+ else if (presenterRule instanceof StandardDisplayRules.FixedPrecision)
+ return OptionalInt
+ .of(((StandardDisplayRules.FixedPrecision) presenterRule)
+ .significantFigures());
+ else
+ return OptionalInt.empty();
+ }
+
+ /**
+ * Determines which rounding type the presenter is currently using, if any.
+ *
+ * @since 2022-04-18
+ */
+ private Optional<StandardRoundingType> getPresenterRoundingType() {
+ final var presenterRule = this.presenter.getNumberDisplayRule();
+ if (Objects.equals(presenterRule,
+ StandardDisplayRules.uncertaintyBased()))
+ return Optional.of(StandardRoundingType.UNCERTAINTY);
+ else if (presenterRule instanceof StandardDisplayRules.FixedDecimals)
+ return Optional.of(StandardRoundingType.DECIMAL_PLACES);
+ else if (presenterRule instanceof StandardDisplayRules.FixedPrecision)
+ return Optional.of(StandardRoundingType.SIGNIFICANT_DIGITS);
+ else
+ return Optional.empty();
+ }
+
@Override
public Optional<String> getSelectedDimensionName() {
final String selectedItem = (String) this.dimensionSelector
@@ -644,4 +808,28 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
public void showUnitConversionOutput(UnitConversionRecord uc) {
this.unitOutput.setText(uc.toString());
}
+
+ /**
+ * Sets the presenter's rounding rule to the one specified by the current
+ * settings
+ *
+ * @since 2022-04-18
+ */
+ private void updatePresenterRoundingRule() {
+ final Function<UncertainDouble, String> roundingRule;
+ switch (this.roundingType) {
+ case DECIMAL_PLACES:
+ roundingRule = StandardDisplayRules.fixedDecimals(this.precision);
+ break;
+ case SIGNIFICANT_DIGITS:
+ roundingRule = StandardDisplayRules.fixedPrecision(this.precision);
+ break;
+ case UNCERTAINTY:
+ roundingRule = StandardDisplayRules.uncertaintyBased();
+ break;
+ default:
+ throw new AssertionError();
+ }
+ this.presenter.setNumberDisplayRule(roundingRule);
+ }
}
diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java
index f174e7c..d3699ca 100644
--- a/src/test/java/sevenUnits/unit/UnitTest.java
+++ b/src/test/java/sevenUnits/unit/UnitTest.java
@@ -21,6 +21,7 @@ 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.math.RoundingMode;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
@@ -164,8 +165,9 @@ class UnitTest {
UncertainDouble.of(10, 0.24));
assertEquals("(10.0 ± 0.2) m", value.toString());
- assertEquals("(10.0 ± 0.2) m", value.toString(true));
- assertEquals("10.0 m", value.toString(false));
+ assertEquals("(10.0 ± 0.2) m",
+ value.toString(true, RoundingMode.HALF_EVEN));
+ assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN));
}
/**
@@ -179,8 +181,9 @@ class UnitTest {
UncertainDouble.of(10, 0));
assertEquals("10.0 m", value.toString());
- assertEquals("(10.0 ± 0.0) m", value.toString(true));
- assertEquals("10.0 m", value.toString(false));
+ assertEquals("(10.0 ± 0.0) m",
+ value.toString(true, RoundingMode.HALF_EVEN));
+ assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN));
}
/**
@@ -194,7 +197,8 @@ class UnitTest {
Metric.METRE.withName(NameSymbol.EMPTY),
UncertainDouble.of(10, 0.24));
- assertEquals("10.0 unnamed unit (= 10.0 m)", value.toString(false));
+ assertEquals("10.0 unnamed unit (= 10.0 m)",
+ value.toString(false, RoundingMode.HALF_EVEN));
}
/**
diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
index c891f20..0e18461 100644
--- a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
+++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
@@ -19,6 +19,7 @@ package sevenUnits.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static sevenUnits.utils.UncertainDouble.fromRoundedString;
import static sevenUnits.utils.UncertainDouble.fromString;
import static sevenUnits.utils.UncertainDouble.of;
@@ -66,6 +67,16 @@ class UncertainDoubleTest {
x.toExponentExact(Math.E).value());
}
+ /**
+ * Test for {@link UncertainDouble#fromRoundedString}
+ *
+ * @since 2022-04-18
+ */
+ @Test
+ final void testFromRoundedString() {
+ assertEquals(of(12345.678, 0.001), fromRoundedString("12345.678"));
+ }
+
@Test
final void testFromString() {
// valid strings
diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java
index 8446a90..f52d846 100644
--- a/src/test/java/sevenUnitsGUI/PresenterTest.java
+++ b/src/test/java/sevenUnitsGUI/PresenterTest.java
@@ -60,10 +60,9 @@ public final class PresenterTest {
* @since 2022-04-16
*/
private static final Stream<Function<UncertainDouble, String>> getRoundingRules() {
- final var SCIENTIFIC_ROUNDING = StandardDisplayRules.getScientificRule();
- final var INTEGER_ROUNDING = StandardDisplayRules.getFixedPlacesRule(0);
- final var SIG_FIG_ROUNDING = StandardDisplayRules
- .getFixedPrecisionRule(4);
+ final var SCIENTIFIC_ROUNDING = StandardDisplayRules.uncertaintyBased();
+ final var INTEGER_ROUNDING = StandardDisplayRules.fixedDecimals(0);
+ final var SIG_FIG_ROUNDING = StandardDisplayRules.fixedPrecision(4);
return Stream.of(SCIENTIFIC_ROUNDING, INTEGER_ROUNDING, SIG_FIG_ROUNDING);
}
@@ -264,20 +263,19 @@ public final class PresenterTest {
// set and save custom settings
presenter.setOneWayConversionEnabled(true);
presenter.setShowDuplicateUnits(true);
- presenter.setNumberDisplayRule(
- StandardDisplayRules.getFixedPrecisionRule(11));
+ presenter.setNumberDisplayRule(StandardDisplayRules.fixedPrecision(11));
presenter.saveSettings(TEST_SETTINGS);
// overwrite custom settings
presenter.setOneWayConversionEnabled(false);
presenter.setShowDuplicateUnits(false);
- presenter.setNumberDisplayRule(StandardDisplayRules.getScientificRule());
+ presenter.setNumberDisplayRule(StandardDisplayRules.uncertaintyBased());
// load settings & test that they're the same
presenter.loadSettings(TEST_SETTINGS);
assertTrue(presenter.oneWayConversionEnabled());
assertTrue(presenter.duplicateUnitsShown());
- assertEquals(StandardDisplayRules.getFixedPlacesRule(11),
+ assertEquals(StandardDisplayRules.fixedPrecision(11),
presenter.getNumberDisplayRule());
}