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