/**
* 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() {
}
}