DoubleValuedIIRVTerm.java

  1. /* Copyright 2024-2025 The Johns Hopkins University Applied Physics Laboratory
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.files.iirv.terms.base;

  18. import org.hipparchus.util.ArithmeticUtils;
  19. import org.hipparchus.util.FastMath;
  20. import org.orekit.errors.OrekitIllegalArgumentException;
  21. import org.orekit.errors.OrekitMessages;
  22. import org.orekit.files.iirv.terms.IIRVTermUtils;

  23. import java.util.Locale;
  24. import java.util.regex.Pattern;

  25. /**
  26.  * Term in an IIRV Vector representing a double value.
  27.  *
  28.  * @author Nick LaFarge
  29.  * @since 13.0
  30.  */
  31. public class DoubleValuedIIRVTerm extends IIRVVectorTerm<Double> {

  32.     /** Space pattern. */
  33.     private static final Pattern SPACE_PATTERN = Pattern.compile(" ");

  34.     /**
  35.      * Number of characters before the end of the encoded String the decimal place is assumed to occur.
  36.      * <p>
  37.      * For example, for {@code nCharsAfterDecimalPlace=2} and {@code length=4}, then 12.34
  38.      * is encoded to "1234".
  39.      */
  40.     private final int nCharsAfterDecimalPlace;

  41.     /** True if negative values are permitted, false if the value is positive. */
  42.     private final boolean isSigned;

  43.     /**
  44.      * Constructs an IIRV Vector Term represented by a double. This representation is used for any numeric terms
  45.      * in the IIRV Vector that contain a decimal point.
  46.      *
  47.      * @param pattern                 Regular expression pattern that validates the term
  48.      * @param value                   Value of the term, expressed as a double
  49.      * @param length                  Length of the term, measured in number of characters in the String representation
  50.      * @param nCharsAfterDecimalPlace Number of characters before the end of the encoded String the decimal place is
  51.      *                                assumed to occur.
  52.      * @param isSigned                True if negative values are permitted, false if the value is positive
  53.      */
  54.     public DoubleValuedIIRVTerm(final String pattern, final double value, final int length, final int nCharsAfterDecimalPlace, final boolean isSigned) {
  55.         super(pattern, value, length);
  56.         this.nCharsAfterDecimalPlace = nCharsAfterDecimalPlace;
  57.         this.isSigned = isSigned;

  58.         // Validate input data
  59.         validateString(toEncodedString(value));
  60.         validateOverflow(this.value());
  61.     }

  62.     /**
  63.      * Constructs an IIRV Vector Term represented by a double from a given String. This representation is used for any
  64.      * numeric terms in the IIRV Vector that contain a decimal point.
  65.      *
  66.      * @param pattern                 Regular expression pattern that validates the term
  67.      * @param value                   Value of the term, expressed as a String
  68.      * @param length                  Length of the term, measured in number of characters in the String representation
  69.      * @param nCharsAfterDecimalPlace Number of characters before the end of {@code value} the decimal place is
  70.      *                                assumed to occur.
  71.      * @param isSigned                True if negative values are permitted, false if the value is positive
  72.      */
  73.     public DoubleValuedIIRVTerm(final String pattern, final String value, final int length, final int nCharsAfterDecimalPlace, final boolean isSigned) {
  74.         super(pattern, DoubleValuedIIRVTerm.computeValueFromString(value, nCharsAfterDecimalPlace), length);
  75.         this.nCharsAfterDecimalPlace = nCharsAfterDecimalPlace;
  76.         this.isSigned = isSigned;

  77.         // Validate input data
  78.         validateString(value);
  79.         validateOverflow(this.value());
  80.     }

  81.     /**
  82.      * Compute the double value of the term from a given String.
  83.      *
  84.      * @param value                   String value to convert to a double
  85.      * @param nCharsAfterDecimalPlace Number of characters before the end of {@code value} the decimal place is
  86.      *                                assumed to occur.
  87.      * @return Double value corresponding to the {@code value} String argument
  88.      */
  89.     public static double computeValueFromString(final String value, final int nCharsAfterDecimalPlace) {
  90.         try {
  91.             String intStr = value;

  92.             // Remove spaces (for positive values)
  93.             intStr = SPACE_PATTERN.matcher(intStr).replaceAll("");

  94.             // Return if there are no characters after the decimal place
  95.             if (nCharsAfterDecimalPlace == 0) {
  96.                 return Integer.parseInt(intStr);
  97.             }

  98.             // Get the sign: negative if the first character is '-'
  99.             final int sign = intStr.charAt(0) == '-' ? -1 : 1;

  100.             // Get value before/after the decimal place
  101.             final int beforeDecimalPlace = Integer.parseInt(intStr.substring(0, intStr.length() - nCharsAfterDecimalPlace));
  102.             final int afterDecimalPlace = Integer.parseInt(intStr.substring(intStr.length() - nCharsAfterDecimalPlace));

  103.             // Turn into a double by dividing the n numbers that appear after the decimal places by 10^n
  104.             final double unsignedValue = FastMath.abs(beforeDecimalPlace) +
  105.                                          afterDecimalPlace / (double) ArithmeticUtils.pow(10, nCharsAfterDecimalPlace);

  106.             // Return the resulting double with the correct sign
  107.             return sign * unsignedValue;
  108.         } catch (NumberFormatException e) {
  109.             throw new OrekitIllegalArgumentException(OrekitMessages.IIRV_INVALID_TERM_VALUE, value);
  110.         }
  111.     }

  112.     /** {@inheritDoc} */
  113.     @Override
  114.     public String toEncodedString(final Double termValue) {
  115.         // Reserve one character for the sign (if applicable)
  116.         final int signAdjustedStringLength = isSigned ? length() - 1 : length();

  117.         // Round the number to the specified number of decimal places
  118.         final double p = ArithmeticUtils.pow(10, nCharsAfterDecimalPlace); // beware, this *must* be a double
  119.         final double roundedNum = FastMath.round(termValue * p) / p;

  120.         // Format the absolute value of the rounded number with specified integer and decimal lengths
  121.         String formattedStr = String.format(Locale.US,
  122.                                             "%0" + signAdjustedStringLength + "." + nCharsAfterDecimalPlace + "f",
  123.                                             FastMath.abs(roundedNum));

  124.         // Remove the decimal point
  125.         formattedStr = formattedStr.replace(".", "");

  126.         // Add leading zeros for cases where the number has less than the max number of integer digits
  127.         if (formattedStr.length() < signAdjustedStringLength) {
  128.             formattedStr = IIRVTermUtils.addPadding(formattedStr, '0', signAdjustedStringLength, true);
  129.         }

  130.         // If the resulting String is all zero, then always use a positive sign to avoid encoding negative zero
  131.         final int numNonzeroCharacters = formattedStr.replace("0", "").length();

  132.         // Sign character ("" for unsigned number)
  133.         String signCharacter = "";
  134.         if (isSigned) {
  135.             if (termValue >= 0 || numNonzeroCharacters == 0) {
  136.                 signCharacter = " ";
  137.             } else {
  138.                 signCharacter = "-";
  139.             }
  140.         }

  141.         return signCharacter + formattedStr;
  142.     }

  143.     /**
  144.      * Validate that there is a sufficient number of characters available in the encoded String representation to
  145.      * represent the value of the given double value.
  146.      *
  147.      * @param value Double value to check against the String encoding parameters
  148.      */
  149.     void validateOverflow(final double value) {
  150.         // Compute the number of characters, excluding the sign character and all characters after the decimal place
  151.         int n = length() - nCharsAfterDecimalPlace;
  152.         if (isSigned) {
  153.             n--;
  154.         }

  155.         // If the value is greater than the maximum possible value, throw an error
  156.         final double maxPossibleValue = ArithmeticUtils.pow(10, n);
  157.         if (FastMath.abs(value) >= maxPossibleValue) {
  158.             throw new OrekitIllegalArgumentException(OrekitMessages.IIRV_VALUE_TOO_LARGE, FastMath.abs(value), maxPossibleValue);
  159.         }
  160.     }
  161. }