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.OrekitInternalError;
22 import org.orekit.errors.OrekitMessages;
23
24 import java.io.IOException;
25 import java.util.Arrays;
26
27 /** Formatter for long integers with low overhead.
28 * <p>
29 * This class is intended to be used when formatting large amounts of data with
30 * fixed formats like, for example, large ephemeris or measurement files.
31 * </p>
32 * <p>
33 * Building the formatter is done once, and the formatter
34 * {@link #appendTo(Appendable, long)} or {@link #toString(long)} methods can be
35 * called hundreds of thousands of times, without incurring the overhead that
36 * would occur with {@code String.format()}. Some tests showed this formatter is
37 * about 10 times faster than {@code String.format()} with {@code %{width}d} format.
38 * </p>
39 * <p>
40 * Instances of this class are immutable
41 * </p>
42 * @author Luc Maisonobe
43 * @since 13.0.3
44 */
45 public class FastLongFormatter {
46
47 /** Digits. */
48 private static final char[] DIGITS = {
49 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
50 };
51
52 /** Number of characters to output. */
53 private final int width;
54
55 /** Zero padding indicator. */
56 private final boolean zeroPadding;
57
58 /** Indicator to forbid width overstep. */
59 private final boolean forbidWidthOverstep;
60
61 /** Size of the conversion array. */
62 private final int size;
63
64 /** Simple constructor.
65 * <p>
66 * This constructor is equivalent to either {@link java.util.Formatter Formatter}
67 * integer format {@code %{width}d} or {@code %0{width}d}
68 * </p>
69 * @param width number of characters to output
70 * @param zeroPadding if true, the result is left padded with '0' until it matches width
71 * @param forbidWidthOverstep if true, the width is a hard limit which cannot be overstepped
72 * @since 14.0
73 */
74 public FastLongFormatter(final int width, final boolean zeroPadding, final boolean forbidWidthOverstep) {
75 this.width = width;
76 this.zeroPadding = zeroPadding;
77 this.forbidWidthOverstep = forbidWidthOverstep;
78 this.size = FastMath.max(width, 20);
79 }
80
81 /** Get the width.
82 * @return width
83 */
84 public int getWidth() {
85 return width;
86 }
87
88 /** Check if left padding uses '0' characters.
89 * @return true if left padding uses '0' characters
90 */
91 public boolean hasZeroPadding() {
92 return zeroPadding;
93 }
94
95 /** Append one formatted value to an {@code Appendable}.
96 * @param appendable to append value to
97 * @param value value to format
98 * @exception IOException if an I/O error occurs
99 */
100 public void appendTo(final Appendable appendable, final long value) throws IOException {
101
102 // initialize conversion loop
103 final char[] digits = new char[size];
104 int index = 0;
105 long remaining;
106 if (value == Long.MIN_VALUE) {
107 // special case for value -9223372036854775808L that has no representable opposite
108 digits[0] = '8';
109 index = 1;
110 remaining = 922337203685477580L;
111 } else {
112 remaining = FastMath.abs(value);
113 }
114
115 // convert to decimal string
116 do {
117 digits[index++] = DIGITS[(int) (remaining % 10L)];
118 remaining /= 10L;
119 } while (remaining > 0L);
120
121 // manage sign and padding
122 if (zeroPadding) {
123 if (value < 0L) {
124 // zero padding a negative value occurs between the minus sign and the most significant digit
125 if (index < width - 1) {
126 Arrays.fill(digits, index, width - 1, '0');
127 index = width - 1;
128 }
129 digits[index++] = '-';
130 }
131 else {
132 if (index < width) {
133 Arrays.fill(digits, index, width, '0');
134 index = width;
135 }
136 }
137 } else {
138 if (value < 0L) {
139 // space padding a negative value is before minus sign
140 digits[index++] = '-';
141 }
142 if (index < width) {
143 Arrays.fill(digits, index, width, ' ');
144 index = width;
145 }
146 }
147
148 if (forbidWidthOverstep && index > width) {
149 final StringBuilder buffer = new StringBuilder(index);
150 for (int i = index - 1; i >= 0; --i) {
151 buffer.append(digits[i]);
152 }
153 throw new OrekitException(OrekitMessages.WIDTH_EXCEEDED, buffer.toString(), width);
154 }
155
156 // fill up string
157 while (index > 0) {
158 appendable.append(digits[--index]);
159 }
160
161 }
162
163 /** Format one value.
164 * @param value value to format
165 * @return formatted string
166 */
167 public String toString(final long value) {
168 try {
169 final StringBuilder builder = new StringBuilder();
170 appendTo(builder, value);
171 return builder.toString();
172 } catch (IOException ioe) {
173 // this should never happen
174 throw new OrekitInternalError(ioe);
175 }
176 }
177
178 }