FastDoubleFormatter.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. /** Formatter for double numbers with low overhead.
  22.  * <p>
  23.  * This class is intended to be used when formatting large amounts of data with
  24.  * fixed formats like, for example, large ephemeris or measurement files.
  25.  * </p>
  26.  * <p>
  27.  * Building the formatter is done once, and the formatter
  28.  * {@link #appendTo(Appendable, double)} or {@link #toString(double)} methods can
  29.  * be called hundreds of thousands of times, without incurring the overhead that
  30.  * would occur with {@code String.format()}. Some tests showed this formatter is
  31.  * about 5 times faster than {@code String.format()} with
  32.  * {@code %{width}.{%precision}f} 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 FastDoubleFormatter {

  41.     /** Scaling array for fractional part. */
  42.     private static final long[] SCALING = new long[19];

  43.     static {
  44.         SCALING[0] = 1L;
  45.         for (int i = 1; i < SCALING.length; ++i) {
  46.             SCALING[i] = 10L * SCALING[i - 1];
  47.         }
  48.     }

  49.     /** Number of characters to output. */
  50.     private final int width;

  51.     /** Precision. */
  52.     private final int precision;

  53.     /** Scaling. */
  54.     private final long scaling;

  55.     /** Formatter for integer part. */
  56.     private final FastLongFormatter beforeFormatter;

  57.     /** Formatter for fractional part. */
  58.     private final FastLongFormatter afterFormatter;

  59.     /** Simple constructor.
  60.      * <p>
  61.      * This constructor is equivalent to {@link java.util.Formatter Formatter}
  62.      * float format {@code %{width}.{precision}f}
  63.      * </p>
  64.      * @param width number of characters to output
  65.      * @param precision number of decimal precision
  66.      */
  67.     public FastDoubleFormatter(final int width, final int precision) {
  68.         this.width           = width;
  69.         this.precision       = precision;
  70.         this.scaling         = SCALING[precision];
  71.         this.beforeFormatter = precision == 0 ?
  72.                                new FastLongFormatter(width, false) :
  73.                                new FastLongFormatter(width - precision - 1, false);
  74.         this.afterFormatter  = new FastLongFormatter(precision, true);
  75.     }

  76.     /** Get the width.
  77.      * @return width
  78.      */
  79.     public int getWidth() {
  80.         return width;
  81.     }

  82.     /** Get the precision.
  83.      * @return precision
  84.      */
  85.     public int getPrecision() {
  86.         return precision;
  87.     }

  88.     /** Append one formatted value to an {@code Appendable}.
  89.      * @param appendable to append value to
  90.      * @param value value to format
  91.      * @exception IOException if an I/O error occurs
  92.      */
  93.     public void appendTo(final Appendable appendable, final double value) throws IOException {

  94.         if (Double.isNaN(value)) {
  95.             // special case for NaN
  96.             for (int i = 0; i < width - 3; ++i) {
  97.                 appendable.append(' ');
  98.             }
  99.             appendable.append("NaN");
  100.         } else {

  101.             if (Double.isInfinite(value)) {
  102.                 // special case for infinities
  103.                 if (FastMath.copySign(1.0, value) < 0) {
  104.                     for (int i = 0; i < width - 9; ++i) {
  105.                         appendable.append(' ');
  106.                     }
  107.                     appendable.append("-Infinity");
  108.                 } else {
  109.                     for (int i = 0; i < width - 8; ++i) {
  110.                         appendable.append(' ');
  111.                     }
  112.                     appendable.append("Infinity");
  113.                 }
  114.             } else {

  115.                 // regular number
  116.                 final double abs    = FastMath.abs(value);
  117.                 double       before = FastMath.floor(abs);
  118.                 long         after  = FastMath.round((abs - before) * scaling);

  119.                 if (after >= scaling) {
  120.                     // we have to round up to the next integer
  121.                     before += 1;
  122.                     after   = 0L;
  123.                 }

  124.                 // convert to string
  125.                 final double sign = FastMath.copySign(1.0, value);
  126.                 if (sign < 0 && before == 0.0) {
  127.                     // special case for negative values between -0.0 and -1.0
  128.                     for (int i = 0; i < beforeFormatter.getWidth() - 2; ++i) {
  129.                         appendable.append(' ');
  130.                     }
  131.                     appendable.append("-0");
  132.                 } else {
  133.                     // regular case
  134.                     beforeFormatter.appendTo(appendable, FastMath.round(sign * before));
  135.                 }

  136.                 // fractional part
  137.                 if (scaling > 1) {
  138.                     appendable.append('.');
  139.                     afterFormatter.appendTo(appendable, after);
  140.                 }

  141.             }
  142.         }

  143.     }

  144.     /** Format one value.
  145.      * @param value value to format
  146.      * @return formatted string
  147.      */
  148.     public String toString(final double value) {
  149.         try {
  150.             final StringBuilder builder = new StringBuilder();
  151.             appendTo(builder, value);
  152.             return builder.toString();
  153.         } catch (IOException ioe) {
  154.             // this should never happen
  155.             throw new OrekitInternalError(ioe);
  156.         }
  157.     }

  158. }