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 }