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 }