FastLongFormatter.java

  1. /* Copyright 2022-2025 Thales Alenia Space
  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.utils.formatting;

  18. import org.hipparchus.util.FastMath;
  19. import org.orekit.errors.OrekitInternalError;

  20. import java.io.IOException;
  21. import java.util.Arrays;

  22. /** Formatter for long integers with low overhead.
  23.  * <p>
  24.  * This class is intended to be used when formatting large amounts of data with
  25.  * fixed formats like, for example, large ephemeris or measurement files.
  26.  * </p>
  27.  * <p>
  28.  * Building the formatter is done once, and the formatter
  29.  * {@link #appendTo(Appendable, long)} or {@link #toString(long)} methods can be
  30.  * called hundreds of thousands of times, without incurring the overhead that
  31.  * would occur with {@code String.format()}. Some tests showed this formatter is
  32.  * about 10 times faster than {@code String.format()} with {@code %{width}d} format.
  33.  * </p>
  34.  * <p>
  35.  * Instances of this class are immutable
  36.  * </p>
  37.  * @author Luc Maisonobe
  38.  * @since 13.0.3
  39.  */
  40. public class FastLongFormatter {

  41.     /** Digits. */
  42.     private static final char[] DIGITS = {
  43.         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  44.     };

  45.     /** Number of characters to output. */
  46.     private final int width;

  47.     /** Zero padding indicator. */
  48.     private final boolean zeroPadding;

  49.     /** Size of the conversion array. */
  50.     private final int size;

  51.     /** Simple constructor.
  52.      * <p>
  53.      * This constructor is equivalent to either {@link java.util.Formatter Formatter}
  54.      * integer format {@code %{width}d} or {@code %0{width}d}
  55.      * </p>
  56.      * @param width       number of characters to output
  57.      * @param zeroPadding if true, the result is left padded with '0' until it matches width
  58.      */
  59.     public FastLongFormatter(final int width, final boolean zeroPadding) {
  60.         this.width       = width;
  61.         this.zeroPadding = zeroPadding;
  62.         this.size        = FastMath.max(width, 20);
  63.     }

  64.     /** Get the width.
  65.      * @return width
  66.      */
  67.     public int getWidth() {
  68.         return width;
  69.     }

  70.     /** Check if left padding uses '0' characters.
  71.      * @return true if left padding uses '0' characters
  72.      */
  73.     public boolean hasZeroPadding() {
  74.         return zeroPadding;
  75.     }

  76.     /** Append one formatted value to an {@code Appendable}.
  77.      * @param appendable to append value to
  78.      * @param value value to format
  79.      * @exception IOException if an I/O error occurs
  80.      */
  81.     public void appendTo(final Appendable appendable, final long value) throws IOException {

  82.         // initialize conversion loop
  83.         final char[] digits = new char[size];
  84.         int index = 0;
  85.         long remaining;
  86.         if (value == Long.MIN_VALUE) {
  87.             // special case for value -9223372036854775808L that has no representable opposite
  88.             digits[0] = '8';
  89.             index     = 1;
  90.             remaining = 922337203685477580L;
  91.         } else {
  92.             remaining = FastMath.abs(value);
  93.         }

  94.         // convert to decimal string
  95.         do {
  96.             digits[index++] = DIGITS[(int) (remaining % 10L)];
  97.             remaining /= 10L;
  98.         } while (remaining > 0L);

  99.         // manage sign and padding
  100.         if (zeroPadding) {
  101.             if (value < 0L) {
  102.                 // zero padding a negative value occurs between the minus sign and the most significant digit
  103.                 if (index < width - 1) {
  104.                     Arrays.fill(digits, index, width - 1, '0');
  105.                     index = width - 1;
  106.                 }
  107.                 digits[index++] = '-';
  108.             }
  109.             else {
  110.                 if (index < width) {
  111.                     Arrays.fill(digits, index, width, '0');
  112.                     index = width;
  113.                 }
  114.             }
  115.         } else {
  116.             if (value < 0L) {
  117.                 // space padding a negative value is before minus sign
  118.                 digits[index++] = '-';
  119.             }
  120.             if (index < width) {
  121.                 Arrays.fill(digits, index, width, ' ');
  122.                 index = width;
  123.             }
  124.         }

  125.         // fill up string
  126.         while (index > 0) {
  127.             appendable.append(digits[--index]);
  128.         }

  129.     }

  130.     /** Format one value.
  131.      * @param value value to format
  132.      * @return formatted string
  133.      */
  134.     public String toString(final long value) {
  135.         try {
  136.             final StringBuilder builder = new StringBuilder();
  137.             appendTo(builder, value);
  138.             return builder.toString();
  139.         } catch (IOException ioe) {
  140.             // this should never happen
  141.             throw new OrekitInternalError(ioe);
  142.         }
  143.     }

  144. }