/** * 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 * 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 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; /** * A static utility class that can be used to make display rules for the * presenter. * * @since v0.4.0 * @since 2022-04-18 */ public final class StandardDisplayRules { /** * A rule that rounds to a fixed number of decimal places. * * @since v0.4.0 * @since 2022-04-18 */ public static final class FixedDecimals implements Function { 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) { 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 "Round to " + this.decimalPlaces + " decimal places"; } } /** * A rule that rounds to a fixed number of significant digits. * * @since v0.4.0 * @since 2022-04-18 */ public static final class FixedPrecision implements Function { 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"; } } /** * 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. * * @since v0.4.0 * @since 2022-04-18 */ public static final class UncertaintyBased implements Function { 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 v0.4.0 * @since 2022-04-18 */ public static final FixedDecimals fixedDecimals(int decimalPlaces) { return new FixedDecimals(decimalPlaces); } /** * @param significantFigures significant figures to round to * @return a rounding rule that rounds to a fixed number of significant * figures * @since v0.4.0 * @since 2022-04-18 */ public static final FixedPrecision fixedPrecision(int significantFigures) { return new FixedPrecision(significantFigures); } /** * Gets one of the standard rules from its string representation. * * @param ruleToString string representation of the display rule * @return display rule * @throws IllegalArgumentException if the provided string is not that of a * standard rule. * @since v0.4.0 * @since 2021-12-24 */ public static final Function getStandardRule( String ruleToString) { 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."); } /** * @return an UncertainDouble-based rounding rule * @since v0.4.0 * @since 2022-04-18 */ public static final UncertaintyBased uncertaintyBased() { return UNCERTAINTY_BASED_ROUNDING_RULE; } private StandardDisplayRules() { } }