1   /* Copyright 2022-2026 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  
19  import org.hipparchus.util.FastMath;
20  import org.hipparchus.util.Precision;
21  
22  import java.io.IOException;
23  import java.util.Locale;
24  
25  /** Formatter for double numbers in scientific format with low overhead.
26   * <p>
27   * This class is intended to be used when formatting large amounts of data with
28   * fixed scientific formats like, for example, large ephemeris or measurement files.
29   * </p>
30   * <p>
31   * Building the formatter is done once, and the formatter
32   * {@link #appendTo(Appendable, double)} or {@link #toString(double)} methods can
33   * be called hundreds of thousands of times, without incurring the overhead that
34   * would occur with {@code String.format()}. Some tests showed this formatter is
35   * about 6-7 times faster than {@code String.format()} with
36   * {@code %{width}.{%precision}e} format.
37   * </p>
38   * <p>
39   * Instances of this class are immutable
40   * </p>
41   * @author Luc Maisonobe
42   * @since 14.0
43   */
44  public class FastScientificFormatter extends FastDoubleFormatter {
45  
46      /** Scaling formatters. */
47      private static final ScalingFormatter[] SCALING = new ScalingFormatter[(0x1 << 11) - 1];
48  
49      static {
50          SCALING[0] = new SubNormalNumberFormatter();
51          for (int i = 1; i < SCALING.length; ++i) {
52              final double d = Double.longBitsToDouble(((long) i) << 52);
53              SCALING[i] = new NormalNumberFormatter((int) FastMath.floor(FastMath.log10(d)));
54          }
55      }
56  
57      /** Formatter for mantissa when exponent fits in 2 digits. */
58      private final FastDecimalFormatter twoDigitsFormatter;
59  
60      /** Formatter for mantissa when exponent fits in 3 digits. */
61      private final FastDecimalFormatter threeDigitsFormatter;
62  
63      /** Simple constructor.
64       * <p>
65       * This constructor is equivalent to {@link java.util.Formatter Formatter}
66       * float format {@code %{width}.{width-7}e}
67       * </p>
68       * @param width number of characters to output
69       */
70      public FastScientificFormatter(final int width) {
71          super(width);
72          twoDigitsFormatter   = new FastDecimalFormatter(width - 4, width - 7);
73          threeDigitsFormatter = new FastDecimalFormatter(width - 5, width - 8);
74      }
75  
76      /** {@inheritDoc} */
77      @Override
78      protected void appendRegularValueTo(final Appendable appendable, final double value) throws IOException {
79  
80          // extract the binary exponent, with a special case for exact 0
81          final int exponent = value == 0.0 ?
82                               1023 :
83                               (int) ((Double.doubleToRawLongBits(value) & 0x7ff0000000000000L) >>> 52);
84  
85          // select the scaling formatter for the correct range
86          ScalingFormatter scaling = SCALING[exponent];
87          if (scaling.outOfRange(value)) {
88              // number is too large, we need to change the formatter
89              scaling = SCALING[exponent + 1];
90          }
91  
92          // format number
93          scaling.appendTo(appendable, value, this);
94  
95      }
96  
97      /** Scaling formatter. */
98      private interface ScalingFormatter {
99  
100         /** Check if a value exceeds formatter range.
101          * @param value value to check
102          * @return true if value exceeds formatter range
103          */
104         boolean outOfRange(double value);
105 
106         /** Append one formatted value to an {@code Appendable}.
107          * @param appendable to append value to
108          * @param value value to format
109          * @param scFormatter calling scientific formatter
110          * @exception IOException if an I/O error occurs
111          */
112         void appendTo(Appendable appendable, double value, FastScientificFormatter scFormatter)
113             throws IOException;
114 
115     }
116 
117     /** Formatter for subnormal numbers. */
118     private static final class SubNormalNumberFormatter implements ScalingFormatter {
119 
120         /** {@inheritDoc} */
121         @Override
122         public boolean outOfRange(final double value) {
123             return value >= Precision.SAFE_MIN;
124         }
125 
126         /** {@inheritDoc} */
127         @Override
128         public void appendTo(final Appendable appendable, final double value,
129                              final FastScientificFormatter scFormatter)
130             throws IOException {
131             appendable.append(scFormatter.toString(value * 1.0e200).replace("e-1", "e-3"));
132         }
133 
134     }
135 
136     /** Formatter for normal numbers. */
137     private static final class NormalNumberFormatter implements ScalingFormatter {
138 
139         /** Exponent part. */
140         private final String exponent;
141 
142         /** Indicator for two digits exponents. */
143         private final boolean twoDigits;
144 
145         /** Scaling factor to retrieve a number between 1.0 (included) and 10.0 (excluded). */
146         private final double factor;
147 
148         /** Simple constructor.
149          * @param n decimal exponent n
150          */
151         NormalNumberFormatter(final int n) {
152             this.twoDigits = FastMath.abs(n) < 100;
153             this.exponent  = String.format(Locale.US, twoDigits ? "e%+03d" : "e%+04d", n);
154             this.factor    = FastMath.pow(10.0, -n);
155         }
156 
157         /** {@inheritDoc} */
158         @Override
159         public boolean outOfRange(final double value) {
160             return FastMath.abs(value * factor) >= 10.0;
161         }
162 
163         /** {@inheritDoc} */
164         @Override
165         public void appendTo(final Appendable appendable, final double value,
166                              final FastScientificFormatter scFormatter)
167             throws IOException {
168             final FastDecimalFormatter formatter = twoDigits ?
169                                                    scFormatter.twoDigitsFormatter :
170                                                    scFormatter.threeDigitsFormatter;
171             formatter.appendRegularValueTo(appendable, value * factor);
172             appendable.append(exponent);
173         }
174 
175     }
176 
177 }