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.orekit.errors.OrekitException;
21  import org.orekit.errors.OrekitMessages;
22  
23  import java.io.IOException;
24  
25  /** Formatter for double numbers with low overhead.
26   * <p>
27   * This class is intended to be used when formatting large amounts of data with
28   * fixed 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 5 times faster than {@code String.format()} with
36   * {@code %{width}.{%precision}f} format.
37   * </p>
38   * <p>
39   * Instances of this class are immutable
40   * </p>
41   * @author Luc Maisonobe
42   * @since 13.0.3
43   */
44  public class FastDecimalFormatter extends FastDoubleFormatter {
45  
46      /** Scaling array for fractional part. */
47      private static final long[] SCALING = new long[19];
48  
49      static {
50          SCALING[0] = 1L;
51          for (int i = 1; i < SCALING.length; ++i) {
52              SCALING[i] = 10L * SCALING[i - 1];
53          }
54      }
55  
56      /** Precision. */
57      private final int precision;
58  
59      /** Scaling. */
60      private final long scaling;
61  
62      /** Formatter for integer part. */
63      private final FastLongFormatter beforeFormatter;
64  
65      /** Formatter for fractional part. */
66      private final FastLongFormatter afterFormatter;
67  
68      /** Simple constructor.
69       * <p>
70       * This constructor is equivalent to {@link java.util.Formatter Formatter}
71       * float format {@code %{width}.{precision}f}
72       * </p>
73       * @param width number of characters to output
74       * @param precision number of decimal precision
75       */
76      public FastDecimalFormatter(final int width, final int precision) {
77  
78          super(width);
79          this.precision = precision;
80          if (width <= 0 || width > SCALING.length || precision < 0 || precision > width) {
81              throw new OrekitException(OrekitMessages.INVALID_FORMAT, width, precision);
82          }
83  
84          this.scaling         = SCALING[precision];
85          this.beforeFormatter = precision == 0 ?
86                                 new FastLongFormatter(width, false) :
87                                 new FastLongFormatter(width - precision - 1, false);
88          this.afterFormatter  = new FastLongFormatter(precision, true);
89      }
90  
91      /** Get the precision.
92       * @return precision
93       */
94      public int getPrecision() {
95          return precision;
96      }
97  
98      /** {@inheritDoc} */
99      @Override
100     protected void appendRegularValueTo(final Appendable appendable, final double value) throws IOException {
101         final double abs = FastMath.abs(value);
102         double before = FastMath.floor(abs);
103         long after = FastMath.round((abs - before) * scaling);
104 
105         if (after >= scaling) {
106             // we have to round up to the next integer
107             before += 1;
108             after = 0L;
109         }
110 
111         // convert to string
112         final double sign = FastMath.copySign(1.0, value);
113         if (sign < 0 && before == 0.0) {
114             // special case for negative values between -0.0 and -1.0
115             for (int i = 0; i < beforeFormatter.getWidth() - 2; ++i) {
116                 appendable.append(' ');
117             }
118             appendable.append("-0");
119         } else {
120             // regular case
121             beforeFormatter.appendTo(appendable, FastMath.round(sign * before));
122         }
123 
124         // fractional part
125         if (scaling > 1) {
126             appendable.append('.');
127             afterFormatter.appendTo(appendable, after);
128         }
129 
130     }
131 
132 }