1 /* 2 * #%L 3 * nom.tam FITS library 4 * %% 5 * Copyright (C) 2004 - 2024 nom-tam-fits 6 * %% 7 * This is free and unencumbered software released into the public domain. 8 * 9 * Anyone is free to copy, modify, publish, use, compile, sell, or 10 * distribute this software, either in source code form or as a compiled 11 * binary, for any purpose, commercial or non-commercial, and by any 12 * means. 13 * 14 * In jurisdictions that recognize copyright laws, the author or authors 15 * of this software dedicate any and all copyright interest in the 16 * software to the public domain. We make this dedication for the benefit 17 * of the public at large and to the detriment of our heirs and 18 * successors. We intend this dedication to be an overt act of 19 * relinquishment in perpetuity of all present and future rights to this 20 * software under copyright law. 21 * 22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 26 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 27 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 * OTHER DEALINGS IN THE SOFTWARE. 29 * #L% 30 */ 31 32 package nom.tam.util; 33 34 import java.util.StringTokenizer; 35 import java.util.logging.Logger; 36 37 import nom.tam.fits.FitsFactory; 38 import nom.tam.fits.LongValueException; 39 40 /** 41 * <p> 42 * A no-frills complex value, for representing complex numbers in FITS headers. It is a non-mutable object that is 43 * created with a real and imaginary parts, which can be retrieved thereafter, and provides string formatting that is 44 * suited specifically for representation in FITS headers. 45 * </p> 46 * <p> 47 * Note that binary tables handle complex data differently, with elements of `float[2]` or `double[2]`. 48 * </p> 49 * 50 * @author Attila Kovacs 51 * 52 * @since 1.16 53 */ 54 public class ComplexValue { 55 56 private static final Logger LOG = Logger.getLogger(ComplexValue.class.getName()); 57 58 /** The complex zero **/ 59 public static final ComplexValue ZERO = new ComplexValue(0.0, 0.0); 60 61 /** The complex unity along the real axis, or (1.0, 0.0) **/ 62 public static final ComplexValue ONE = new ComplexValue(1.0, 0.0); 63 64 /** The unity along the imaginary axis <i>i</i>, or (0.0, 1.0) **/ 65 public static final ComplexValue I = new ComplexValue(0.0, 1.0); 66 67 /** The real and imaginary parts */ 68 private double re, im; 69 70 /** 71 * The minimum size string needed to represent a complex value with even just single digits for the real and 72 * imaginary parts. 73 */ 74 private static final int MIN_STRING_LENGTH = 5; // "(#,#)" 75 76 /** 77 * Private constructor 78 */ 79 private ComplexValue() { 80 81 } 82 83 /** 84 * Instantiates a new complex number value with the specified real and imaginary components. 85 * 86 * @param re the real part 87 * @param im thei maginary part 88 */ 89 public ComplexValue(double re, double im) { 90 this(); 91 this.re = re; 92 this.im = im; 93 } 94 95 /** 96 * Returns the real part of this complex value. 97 * 98 * @return the real part 99 * 100 * @see #im() 101 */ 102 public final double re() { 103 return re; 104 } 105 106 /** 107 * Returns the imaginary part of this complex value. 108 * 109 * @return the imaginary part 110 * 111 * @see #re() 112 */ 113 public final double im() { 114 return im; 115 } 116 117 @Override 118 public int hashCode() { 119 return Double.hashCode(re()) ^ Double.hashCode(im()); 120 } 121 122 @Override 123 public boolean equals(Object o) { 124 if (o == this) { 125 return true; 126 } 127 128 if (!(o instanceof ComplexValue)) { 129 return false; 130 } 131 132 ComplexValue z = (ComplexValue) o; 133 return z.re() == re() && z.im() == im(); 134 } 135 136 /** 137 * Checks if the complex value is zero. That is, if both the real or imaginary parts are zero. 138 * 139 * @return <code>true</code>if both the real or imaginary parts are zero. Otherwise <code>false</code>. 140 */ 141 public final boolean isZero() { 142 return re() == 0.0 && im() == 0.0; 143 } 144 145 /** 146 * Checks if the complex value is finite. That is, if neither the real or imaginary parts are NaN or Infinite. 147 * 148 * @return <code>true</code>if neither the real or imaginary parts are NaN or Infinite. Otherwise 149 * <code>false</code>. 150 */ 151 public final boolean isFinite() { 152 return Double.isFinite(re()) && Double.isFinite(im()); 153 } 154 155 @Override 156 public String toString() { 157 return "(" + re() + "," + im() + ")"; 158 } 159 160 /** 161 * Converts this complex value to its string representation with up to the specified number of decimal places 162 * showing after the leading figure, for both the real and imaginary parts. 163 * 164 * @param decimals the maximum number of decimal places to show. 165 * 166 * @return the string representation with the specified precision, which may be used in a FITS header. 167 * 168 * @see FlexFormat 169 */ 170 public String toString(int decimals) { 171 FlexFormat f = new FlexFormat().setPrecision(decimals); 172 return "(" + f.format(re()) + "," + f.format(im()) + ")"; 173 } 174 175 /** 176 * <p> 177 * Instantiates a new complex number value from the string repressentation of it in a FITS header value. By default, 178 * it will parse complex numbers as a comma-separated pair of real values enclosed in a bracket, such as 179 * <code>(1.0, -2.0)</code>, or standard real values, such as <code>123.456</code> or <code>123</code> (as real-only 180 * values). There can be any number of spaces around the brackets, number components or the comma. 181 * </p> 182 * <p> 183 * If {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>true</code>, the parsing becomes more 184 * tolerant, working around missing closing brackets, different number of comma-separated components, and missing 185 * empty components. So, for example <code>(,-1,abc</code> may be parsed assuming it was meant to be -<i>i</i>. 186 * </p> 187 * 188 * @param text The FITS header value representing the complex number, in brackets with the real 189 * and imaginary pars separated by a comma. Additional spaces may surround the 190 * component parts. 191 * 192 * @throws IllegalArgumentException if the supplied string does not appear to be a FITS standard representation of a 193 * complex value. 194 * 195 * @see FitsFactory#setAllowHeaderRepairs(boolean) 196 */ 197 public ComplexValue(String text) throws IllegalArgumentException { 198 this(); 199 200 // Allow the use of 'D' or 'd' to mark the exponent, instead of the standard 'E' or 'e'... 201 text = text.trim().toUpperCase().replace('D', 'E'); 202 203 boolean hasOpeningBracket = text.charAt(0) == '('; 204 boolean hasClosingBracket = text.charAt(text.length() - 1) == ')'; 205 206 if (!(hasOpeningBracket || hasClosingBracket)) { 207 // Use just the real value. 208 re = Double.parseDouble(text); 209 return; 210 } 211 if (!hasOpeningBracket || !hasClosingBracket) { 212 if (!FitsFactory.isAllowHeaderRepairs()) { 213 throw new IllegalArgumentException("Missing bracket around complex value: '" + text 214 + "'\n\n --> Try FitsFactory.setAllowHeaderRepair(true).\n"); 215 } 216 LOG.warning("Ignored missing bracket in '" + text + "'."); 217 } 218 219 int start = hasOpeningBracket ? 1 : 0; 220 int end = hasClosingBracket ? text.length() - 1 : text.length(); 221 StringTokenizer tokens = new StringTokenizer(text.substring(start, end), 222 FitsFactory.isAllowHeaderRepairs() ? ",; \t" : ", "); 223 if (tokens.countTokens() != 2) { 224 if (!FitsFactory.isAllowHeaderRepairs()) { 225 throw new IllegalArgumentException( 226 "Invalid complex value: '" + text + "'\n\n --> Try FitsFactory.setAllowHeaderRepair(true).\n"); 227 } 228 LOG.warning("Ignored wrong number of components (" + tokens.countTokens() + ") in '" + text + "'."); 229 } 230 231 if (tokens.hasMoreTokens()) { 232 re = Double.parseDouble(tokens.nextToken()); 233 } 234 if (tokens.hasMoreTokens()) { 235 im = Double.parseDouble(tokens.nextToken()); 236 } 237 } 238 239 /** 240 * Converts this comlex value to its string representation using up to the specified number of characters only. The 241 * precision may be reduced as necessary to ensure that the representation fits in the allotted space. 242 * 243 * @param maxLength the maximum length of the returned string representation 244 * 245 * @return the string representation, possibly with reduced precision to fit into the alotted 246 * space. 247 * 248 * @throws LongValueException if the space was too short to fit the value even with the minimal (1-digit) precision. 249 */ 250 public String toBoundedString(int maxLength) throws LongValueException { 251 if (maxLength < MIN_STRING_LENGTH) { 252 throw new LongValueException(maxLength, toString()); 253 } 254 255 String s = toString(); 256 if (s.length() <= maxLength) { 257 return s; 258 } 259 260 int decimals = FlexFormat.DOUBLE_DECIMALS; 261 262 s = toString(decimals); 263 while (s.length() > maxLength) { 264 // Assume both real and imaginary parts shorten the same amount... 265 decimals -= (s.length() - maxLength + 1) / 2; 266 267 if (decimals < 0) { 268 throw new LongValueException(maxLength, toString()); 269 } 270 s = toString(decimals); 271 } 272 273 return s; 274 } 275 276 /** 277 * Converts this complex number to an array of 2. 278 * 279 * @return An array of 2 floating point values. 280 * 281 * @since 1.20 282 */ 283 Object toArray() { 284 return new double[] {re, im}; 285 } 286 287 /** 288 * Single-precision complex values. 289 * 290 * @author Attila Kovacs 291 * 292 * @since 1.18 293 */ 294 public static final class Float extends ComplexValue { 295 296 /** 297 * Instantiates a new single-precision complex number value with the specified real and imaginary components. 298 * 299 * @param re the real part 300 * @param im thei maginary part 301 * 302 * @since 1.20 303 */ 304 public Float(float re, float im) { 305 super(re, im); 306 } 307 308 /** 309 * <p> 310 * Instantiates a new single-precision complex number value from the string repressentation of it in a FITS 311 * header value. By default, it will parse complex numbers as a comma-separated pair of real values enclosed in 312 * a bracket, such as <code>(1.0, -2.0)</code>, or standard real values, such as <code>123.456</code> or 313 * <code>123</code> (as real-only values). There can be any number of spaces around the brackets, number 314 * components or the comma. 315 * </p> 316 * 317 * @param str the FITS string representation of the complex value 318 * 319 * @throws IllegalArgumentException if the supplied string does not appear to be a FITS standard representation 320 * of a complex value. 321 * 322 * @since 1.20 323 */ 324 public Float(String str) throws IllegalArgumentException { 325 super(str); 326 } 327 328 @Override 329 Object toArray() { 330 return new float[] {(float) re(), (float) im()}; 331 } 332 } 333 }