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 }