001 package edu.nrao.sss.measure; 002 003 import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC; 004 import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS; 005 006 import java.math.BigDecimal; 007 import java.util.Arrays; 008 import java.util.Comparator; 009 010 import javax.xml.bind.annotation.XmlAccessType; 011 import javax.xml.bind.annotation.XmlAccessorType; 012 import javax.xml.bind.annotation.XmlElement; 013 import javax.xml.bind.annotation.XmlType; 014 015 import edu.nrao.sss.math.MathUtil; 016 import edu.nrao.sss.util.StringUtil; 017 018 //TODO work on infinity. Make private static +/-INFINITY BigDecimal variables 019 // Whenever a value becomes infinite, use the private statics 020 021 //TODO the allow-negative-values was kludged in and has not been accounted for 022 // in things like JAXB & maybe Hibernate. We might be saving negative 023 // values and then not allowing them to be reconstituted. 024 025 /** 026 * A measure of frequency. 027 * <p> 028 * <b><u>Note About Accuracy</u></b><br/> 029 * This class originally used java's primitive <tt>double</tt> type 030 * for storage and calculation. Certain transformations, though, 031 * led to results that where not accurate enough for many purposes. 032 * Because of that, the internal references to <tt>double</tt> 033 * have been replaced with references to {@link BigDecimal}.</p> 034 * <p> 035 * <b>Version Info:</b> 036 * <table style="margin-left:2em"> 037 * <tr><td>$Revision: 1816 $</td></tr> 038 * <tr><td>$Date: 2008-12-23 10:21:00 -0700 (Tue, 23 Dec 2008) $</td></tr> 039 * <tr><td>$Author: dharland $</td></tr> 040 * </table></p> 041 * 042 * @author David M. Harland 043 * @since 2006-05-03 044 */ 045 @XmlAccessorType(XmlAccessType.NONE) 046 @XmlType(propOrder={"storedValue","units"}) 047 public class Frequency 048 implements Cloneable, Comparable<Frequency> 049 { 050 051 private static final BigDecimal DEFAULT_VALUE = BigDecimal.ZERO; 052 static final FrequencyUnits DEFAULT_UNITS = FrequencyUnits.GIGAHERTZ; 053 054 //Used by equals, hashCode, and compareTo methods 055 private static FrequencyUnits STD_UNITS = FrequencyUnits.HERTZ; 056 057 private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 058 059 private BigDecimal value; 060 private FrequencyUnits units; 061 062 private boolean allowNegativeValues; 063 064 //=========================================================================== 065 // CONSTRUCTORS 066 //=========================================================================== 067 068 /** Creates a new frequency of zero gigahertz. */ 069 public Frequency() 070 { 071 allowNegativeValues = false; 072 set(DEFAULT_VALUE, DEFAULT_UNITS); 073 } 074 075 /** 076 * Creates a new frequency of {@code gigahertz} gigahertz. 077 * See {@link #setValue(BigDecimal)} for information 078 * about valid parameter values and exceptions that might 079 * be thrown. 080 */ 081 public Frequency(BigDecimal gigahertz) 082 { 083 allowNegativeValues = gigahertz.signum() < 0; 084 set(gigahertz, FrequencyUnits.GIGAHERTZ); 085 } 086 087 /** 088 * Creates a new frequency of {@code gigahertz} gigahertz. 089 * See {@link #setValue(String)} for information 090 * about valid parameter values and exceptions that might 091 * be thrown. 092 */ 093 public Frequency(String gigahertz) 094 { 095 allowNegativeValues = gigahertz.trim().startsWith("-"); 096 set(gigahertz, FrequencyUnits.GIGAHERTZ); 097 } 098 099 /** 100 * Creates a new frequency with the given magnitude and units. 101 * See {@link #set(BigDecimal, FrequencyUnits)} for information 102 * about valid parameter values and exceptions that might 103 * be thrown. 104 */ 105 public Frequency(BigDecimal magnitude, FrequencyUnits units) 106 { 107 allowNegativeValues = magnitude.signum() < 0; 108 set(magnitude, units); 109 } 110 111 /** 112 * Creates a new frequency with the given magnitude and units. 113 * See {@link #set(String, FrequencyUnits)} for information 114 * about valid parameter values and exceptions that might 115 * be thrown. 116 */ 117 public Frequency(String magnitude, FrequencyUnits units) 118 { 119 allowNegativeValues = magnitude.trim().startsWith("-"); 120 set(magnitude, units); 121 } 122 123 /** 124 * Resets this frequency so that it is equal to a frequency created 125 * via the no-argument constructor. 126 */ 127 public void reset() 128 { 129 allowNegativeValues = false; 130 set(DEFAULT_VALUE, DEFAULT_UNITS); 131 } 132 133 //=========================================================================== 134 // GETTING & SETTING THE PROPERTIES 135 //=========================================================================== 136 137 /** 138 * Returns the magnitude of this frequency. 139 * @return the magnitude of this frequency. 140 */ 141 public BigDecimal getValue() 142 { 143 return value; 144 } 145 146 /** 147 * Returns the units of this frequency. 148 * @return the units of this frequency. 149 */ 150 @XmlElement 151 public FrequencyUnits getUnits() 152 { 153 return units; 154 } 155 156 /** 157 * Sets the value and units of this frequency based on {@code frequencyText}. 158 * See {@link #parse(String)} for the expected format of 159 * {@code frequencyText}. 160 * <p> 161 * If the parsing fails, this frequency will be kept in its current 162 * state.</p> 163 * 164 * @param frequencyText 165 * a string that will be converted into a frequency. 166 * 167 * @throws IllegalArgumentException 168 * if {@code frequencyText} is not in the expected form. 169 */ 170 public void set(String frequencyText) 171 { 172 if (frequencyText == null || frequencyText.equals("")) 173 { 174 this.reset(); 175 } 176 else 177 { 178 FrequencyUnits oldUnits = units; 179 BigDecimal oldValue = value; 180 181 try { 182 this.parseFrequency(frequencyText); 183 } 184 catch (Exception ex) { 185 set(oldValue, oldUnits); 186 throw new IllegalArgumentException("Could not parse " + frequencyText, ex); 187 } 188 } 189 } 190 191 /** 192 * Sets the magnitude and units of this frequency. 193 * <p> 194 * See {@link #setValue(BigDecimal)} for more information on legal 195 * values for <tt>value</tt>.</p> 196 * 197 * @param value the new magnitude for this frequency. 198 * @param units the new units for this frequency. 199 */ 200 public final void set(BigDecimal value, FrequencyUnits units) 201 { 202 setValue(value); 203 setUnits(units); 204 } 205 206 /** 207 * Sets the magnitude and units of this frequency. 208 * <p> 209 * See {@link #setValue(String)} for more information on legal 210 * values for <tt>value</tt>.</p> 211 * 212 * @param value the new magnitude for this frequency. 213 * @param units the new units for this frequency. 214 */ 215 public final void set(String value, FrequencyUnits units) 216 { 217 setValue(value); 218 setUnits(units); 219 } 220 221 /** 222 * Sets the magnitude of this frequency to {@code newValue}. 223 * <p> 224 * Note that the <tt>units</tt> of this frequency are unaffected by 225 * this method.</p> 226 * 227 * @param newValue 228 * the new magnitude for this frequency. This value is normally be zero or 229 * positive, but negative values will be accepted if the user has 230 * specifically allowed negative values by calling 231 * {@link #setAllowNegativeValues(boolean)}. It is permissible for this 232 * value to be infinite, but it may not be <i>null</i>. 233 * 234 * @throws NumberFormatException 235 * if {@code newValue} is <i>null</i>, or if it is negative and negative 236 * values have not been expressly allowed. 237 */ 238 public final void setValue(BigDecimal newValue) 239 { 240 if (newValue == null || (!allowNegativeValues && newValue.signum() < 0)) 241 throw new NumberFormatException("newValue=" + newValue + 242 " is not a valid duration. It must be non-null and non-negative."); 243 244 setAndRescale(newValue); 245 } 246 247 private void setAndRescale(BigDecimal newValue) 248 { 249 int precision = newValue.precision(); 250 251 if (precision < PRECISION) 252 { 253 int newScale = 254 (newValue.signum() == 0) ? 1 : PRECISION - precision + newValue.scale(); 255 256 value = newValue.setScale(newScale); 257 } 258 else if (precision > PRECISION) 259 { 260 value = newValue.round(MC_FINAL_CALC); 261 } 262 else 263 { 264 value = newValue; 265 } 266 } 267 268 /** 269 * Sets the magnitude of this frequency to {@code newValue}. 270 * <p> 271 * Note that the <tt>units</tt> of this frequency are unaffected by 272 * this method.</p> 273 * 274 * @param newValue 275 * the new magnitude for this frequency. This value is normally zero or 276 * positive, but negative values will be accepted if the user has 277 * specifically allowed negative values by calling 278 * {@link #allowNegativeValues}. It is permissible for this value to be 279 * infinite, but it may not be <tt>NaN</tt>. The allowable representations 280 * of infinity are <tt>"infinity", "+infinity", </tt>and<tt> 281 * "-infinity"</tt>; these values are not case sensitive. 282 * 283 * @throws NumberFormatException 284 * if {@code newValue} is <i>null</i>, <tt>NaN</tt>, 285 * or if it is negative and negative values have not been expressly allowed. 286 */ 287 public final void setValue(String newValue) 288 { 289 if (newValue == null) 290 throw new NumberFormatException("newValue may not be null."); 291 292 BigDecimal newBD; 293 294 newValue = newValue.trim().toLowerCase(); 295 296 if (newValue.equals("infinity") || 297 newValue.equals("+infinity") || newValue.equals("-infinity")) 298 { 299 newBD = MathUtil.getInfiniteValue(newValue.startsWith("-") ? -1 : +1); 300 } 301 else 302 { 303 newBD = new BigDecimal(newValue); 304 } 305 306 if (!allowNegativeValues && newBD.signum() < 0) 307 throw new NumberFormatException("newValue=" + newValue + 308 " is not a valid frequency. It may not be negative."); 309 310 setAndRescale(newBD); 311 } 312 313 /** 314 * Sets the units of this frequency to {@code newUnits}. 315 * <p> 316 * Note that the <tt>value</tt> of this frequency is unaffected by 317 * this method. Contrast this with {@link #convertTo(FrequencyUnits)}.</p> 318 * 319 * @param newUnits 320 * the new units for this frequency. If {@code newUnits} is <i>null</i> 321 * it will be treated as {@link FrequencyUnits#GIGAHERTZ}. 322 */ 323 public final void setUnits(FrequencyUnits newUnits) 324 { 325 units = (newUnits == null) ? FrequencyUnits.GIGAHERTZ : newUnits; 326 } 327 328 /** 329 * Configure this frequency so that it allows, or disallows, negative 330 * values. By default negative values are <i>not</i> allowed. 331 * 332 * @param allow 333 * <i>true</i> if this frequency should be allowed to hold negative values. 334 * 335 * @throws IllegalStateException 336 * if <tt>allow</tt> is <i>false</i> and this frequency is currently 337 * negative. 338 */ 339 public void setAllowNegativeValues(boolean allow) 340 { 341 if (!allow && value.signum() < 0) 342 throw new IllegalStateException("This frequency is currently " + 343 toString() + ", so you may not disallow negative frequencies."); 344 345 allowNegativeValues = allow; 346 } 347 348 //=========================================================================== 349 // HELPERS FOR PERSISTENCE MECHANISMS 350 //=========================================================================== 351 352 //JAXB was having trouble with the overloaded setValue methods. 353 //These methods work around that trouble. 354 //Also, the set method now allows negative values. 355 @XmlElement(name="value") 356 @SuppressWarnings("unused") 357 private BigDecimal getStoredValue() { return getValue().stripTrailingZeros(); } 358 359 @SuppressWarnings("unused") 360 private void setStoredValue(BigDecimal v) 361 { 362 allowNegativeValues = v.signum() < 0; 363 setValue(v); 364 } 365 366 //=========================================================================== 367 // DERIVED QUERIES 368 //=========================================================================== 369 370 /** 371 * Returns <i>true</i> if this frequency is in its default state, 372 * no matter how it got there. 373 * <p> 374 * A frequency is in its <i>default state</i> if both its value and 375 * its units are the same as those of a frequency newly created via 376 * the {@link #Frequency() no-argument constructor}. 377 * A frequency whose most recent update came via the 378 * {@link #reset() reset} method is also in its default state.</p> 379 * 380 * @return <i>true</i> if this frequency is in its default state. 381 */ 382 public boolean isInDefaultState() 383 { 384 return value.equals(DEFAULT_VALUE) && 385 units.equals(DEFAULT_UNITS); 386 } 387 388 /** 389 * Returns <i>true</i> if the magnitude of this frequency approaches infinity. 390 * @return <i>true</i> if the magnitude of this frequency approaches infinity. 391 */ 392 public boolean isInfinite() 393 { 394 return MathUtil.doubleValueIsInfinite(value); 395 } 396 397 /** 398 * Returns <i>true</i> if this frequency is equal to either the 399 * low or high frequency of {@code range}. 400 * 401 * @param range 402 * a frequency range that may or may not have a low or high frequency equal 403 * to this one. 404 * 405 * @return 406 * <i>true</i> if this frequency is one of the endpoints of {@code range}. 407 * 408 * @since 2008-10-14 409 */ 410 public boolean isEndPointOf(FrequencyRange range) 411 { 412 return compareTo(range.getLowFrequency() ) == 0 || 413 compareTo(range.getHighFrequency()) == 0; 414 } 415 416 //=========================================================================== 417 // CONVERSION TO, AND EXPRESSION IN, OTHER UNITS 418 //=========================================================================== 419 420 /** 421 * Converts this measure of frequency to the new units. 422 * <p> 423 * After this method is complete this frequency will have units of 424 * {@code units} and its <tt>value</tt> will have been converted 425 * accordingly.</p> 426 * 427 * @param newUnits the new units for this frequency. 428 * If {@code newUnits} is <i>null</i> an 429 * {@code IllegalArgumentException} will be thrown. 430 * 431 * @return this frequency. The reason for this return type is to allow 432 * code of this nature: 433 * {@code BigDecimal gigahertz = 434 * myFrequency.convertTo(FrequencyUnits.GIGAHERTZ).getValue();} 435 */ 436 public Frequency convertTo(FrequencyUnits newUnits) 437 { 438 if (newUnits == null) 439 throw new 440 IllegalArgumentException("NULL is not a valid value for newUnits."); 441 442 if (!newUnits.equals(units)) 443 { 444 set(toUnits(newUnits), newUnits); 445 } 446 447 return this; 448 } 449 450 /** 451 * Returns the magnitude of this frequency in {@code otherUnits}. 452 * <p> 453 * Note that this method does not alter the state of this frequency. 454 * Contrast this with {@link #convertTo(FrequencyUnits)}.</p> 455 * 456 * @param otherUnits the units in which to express this frequency's magnitude. 457 * If {@code newUnits} is <i>null</i>, it will be treated as 458 * {@link FrequencyUnits#GIGAHERTZ}. 459 * 460 * @return this frequency's value converted to {@code otherUnits}. 461 */ 462 public BigDecimal toUnits(FrequencyUnits otherUnits) 463 { 464 BigDecimal answer = value; 465 466 if (otherUnits == null) 467 otherUnits = FrequencyUnits.GIGAHERTZ; 468 469 //No conversion for zero, infinite, or if no change of units 470 if (!otherUnits.equals(units) && 471 value.compareTo(BigDecimal.ZERO) != 0.0 && !isInfinite()) 472 { 473 answer = units.convertTo(otherUnits, value); 474 } 475 476 return answer; 477 } 478 479 /** 480 * Converts the value and units of this frequency so that the value 481 * is between one and one thousand. The units will be <tt>10<sup>x</sup></tt> 482 * Hertz, where <tt>x</tt> is evenly divisible by three. 483 * The value might not fall in the range [1.0-1000.0) if the 484 * units are the smallest or largest available. 485 * <p> 486 * If this frequency is exactly zero, the default units, as opposed to the 487 * smallest units, will be used.</p> 488 * 489 * @return this frequency, after normalization. 490 */ 491 public Frequency normalize() 492 { 493 if (value.signum() == 0) 494 { 495 units = FrequencyUnits.getDefault(); 496 } 497 else 498 { 499 int power = (int)Math.floor( 500 Math.log10(toUnits(FrequencyUnits.HERTZ).doubleValue())); 501 502 FrequencyUnits newUnits = FrequencyUnits.getForMultipleOfThreePower(power); 503 504 convertTo(newUnits); 505 } 506 507 return this; 508 } 509 510 //=========================================================================== 511 // ARITHMETIC 512 //=========================================================================== 513 514 /** 515 * Adds {@code other} frequency to this one. 516 * <p> 517 * If all of the items below are true: 518 * <ol> 519 * <li>this frequency is positive</li> 520 * <li><tt>other</tt> is negative</li> 521 * <li>the magnitude of <tt>other</tt> is greater than the magnitude 522 * of this frequency</li> 523 * <li>this frequency does not allow negative values</li> 524 * </ol> 525 * the result of this operation will be a frequency of zero. 526 * Otherwise the result will be the sum of the two frequencies.</p> 527 * 528 * @param other the frequency to be added to this frequency. 529 * 530 * @return this frequency, after the addition. 531 */ 532 public Frequency add(Frequency other) 533 { 534 //TODO when we work on INFINITY, we need to consider other being infinite 535 // and the result being inf, too 536 if (!isInfinite()) 537 { 538 BigDecimal sum = value.add(other.toUnits(this.units)); 539 540 if (!allowNegativeValues && sum.signum() < 0) 541 sum = BigDecimal.ZERO; 542 543 setAndRescale(sum); 544 } 545 return this; 546 } 547 548 /** 549 * Subtracts {@code other} frequency from this one. 550 * <p> 551 * If the result of the subtraction is negative, and if this frequency 552 * does not allow negative values, the result of this operation will 553 * be a frequency of zero.</p> 554 * 555 * @param other the frequency to be subtracted from this frequency. 556 * 557 * @return this frequency, after the subtraction. 558 */ 559 public Frequency subtract(Frequency other) 560 { 561 //TODO when we work on INFINITY, we need to consider other being infinite 562 // and the result being inf, too 563 if (!isInfinite()) 564 { 565 BigDecimal diff = value.subtract(other.toUnits(this.units)); 566 567 if (!allowNegativeValues && diff.signum() < 0) 568 diff = BigDecimal.ZERO; 569 570 setAndRescale(diff); 571 } 572 return this; 573 } 574 575 /** 576 * Multiplies this frequency by {@code multiplier}. 577 * 578 * @param multiplier 579 * the number by which this frequency should be multiplied. 580 * This value must not result in a frequency magnitude that is 581 * negative or <tt>NaN</tt>. 582 * 583 * @return this frequency, after the multiplication. 584 * 585 * @throws ArithmeticException 586 * if the multiplication results in a negative value and this frequency 587 * is not allowed to be negative. 588 * 589 * @throws NumberFormatException 590 * if <tt>multiplier</tt> is <tt>NaN</tt> or is 591 * otherwise rejected by the <tt>BigDecimal</tt> class. 592 * 593 * @throws NullPointerException 594 * if <tt>multiplier</tt> is <i>null</i>. 595 */ 596 public Frequency multiplyBy(String multiplier) 597 { 598 return multiplyBy(new BigDecimal(multiplier)); 599 } 600 601 /** 602 * Multiplies this frequency by {@code multiplier}. 603 * 604 * @param multiplier 605 * the number by which this frequency should be multiplied. 606 * This value must not result in a frequency magnitude that is 607 * negative. 608 * 609 * @return this frequency, after the multiplication. 610 * 611 * @throws ArithmeticException 612 * if the multiplication results in a negative value and this frequency 613 * is not allowed to be negative. 614 * 615 * @throws NullPointerException 616 * if <tt>multiplier</tt> is <i>null</i>. 617 */ 618 public Frequency multiplyBy(BigDecimal multiplier) 619 { 620 if (!isInfinite()) 621 { 622 BigDecimal result = value.multiply(multiplier); 623 624 verifyResult(result); 625 626 setAndRescale(result); 627 } 628 return this; 629 } 630 631 /** 632 * Divides this frequency by {@code divisor}. 633 * 634 * @param divisor 635 * the number by which this frequency should be divided. 636 * This value must not result in a frequency magnitude that is 637 * negative or <tt>NaN</tt>. 638 * 639 * @return this frequency, after the division. 640 * 641 * @throws ArithmeticException 642 * if the division results in a negative value and this frequency 643 * is not allowed to be negative, or if the <tt>divisor</tt> is zero. 644 * 645 * @throws NumberFormatException 646 * if <tt>divisor</tt> is <i>null</i>, <tt>NaN</tt>, or is 647 * otherwise rejected by the <tt>BigDecimal</tt> class. 648 * 649 * @see #dividedBy(Frequency) 650 */ 651 public Frequency divideBy(String divisor) 652 { 653 return divideBy(new BigDecimal(divisor)); 654 } 655 656 /** 657 * Divides this frequency by {@code divisor}. 658 * 659 * @param divisor 660 * the number by which this frequency should be divided. 661 * This value must not result in a frequency magnitude that is 662 * negative or <tt>NaN</tt>. 663 * 664 * @return this frequency, after the division. 665 * 666 * @throws ArithmeticException 667 * if the division results in a negative value and this frequency 668 * is not allowed to be negative, or if the <tt>divisor</tt> is zero. 669 * 670 * @throws NumberFormatException 671 * if <tt>divisor</tt> is <i>null</i>. 672 */ 673 public Frequency divideBy(BigDecimal divisor) 674 { 675 if (!isInfinite()) 676 { 677 BigDecimal result = value.divide(divisor, MC_INTERM_CALCS); 678 679 verifyResult(result); 680 681 setAndRescale(result); 682 } 683 return this; 684 } 685 686 /** 687 * Returns the result of dividing this frequency by {@code other}, 688 * without altering this frequency. 689 * 690 * @param other 691 * the frequency by which this one is divided. This parameter 692 * is the denominator 693 * in the equation <tt>quotient = dividend / divisor</tt>, 694 * this frequency is the numerator, and the quotient is the 695 * value returned. 696 * 697 * @return the result of dividing this frequency by {@code other}. 698 * 699 * @see #divideBy(BigDecimal) 700 * @see #divideBy(String) 701 */ 702 public BigDecimal dividedBy(Frequency other) 703 { 704 if (isInfinite()) 705 return value; 706 else 707 return this.toUnits(STD_UNITS) 708 .divide(other.toUnits(STD_UNITS), MC_INTERM_CALCS); 709 } 710 711 private static final String ARITH_ERR = 712 "Result of operation may not be negative. This frequency was not changed."; 713 714 private void verifyResult(BigDecimal arithmeticResult) 715 { 716 if (!allowNegativeValues && arithmeticResult.signum() < 0) 717 throw new ArithmeticException(ARITH_ERR); 718 } 719 720 //=========================================================================== 721 // ADVANCED QUERIES 722 //=========================================================================== 723 724 /** 725 * Returns the smallest positive difference between this frequency and 726 * {@code range}. 727 * 728 * @param range the range to which a distance is computed. 729 * 730 * @return the smallest positive difference between this frequency and 731 * {@code range}. If {@code range} is <i>null</i>, <i>null</i> 732 * is returned. If {@code range} contains this frequency, 733 * a frequency of zero hertz is returned. 734 */ 735 public Frequency getAbsoluteDistanceFrom(FrequencyRange range) 736 { 737 //TODO need to think about INFINITY in here 738 739 Frequency result; 740 741 if (range == null) 742 { 743 result = null; 744 } 745 else if (range.contains(this)) 746 { 747 result = new Frequency("0.0", STD_UNITS); 748 } 749 else 750 { 751 Frequency temp = range.getLowFrequency(); 752 Frequency lowDiff = 753 (temp.compareTo(this) < 0) ? this.clone().subtract(temp) 754 : temp.clone().subtract(this); 755 756 temp = range.getHighFrequency(); 757 Frequency highDiff = 758 (temp.compareTo(this) < 0) ? this.clone().subtract(temp) 759 : temp.clone().subtract(this); 760 761 result = (lowDiff.compareTo(highDiff) < 0) ? lowDiff : highDiff; 762 } 763 764 return result; 765 } 766 767 //=========================================================================== 768 // 769 //=========================================================================== 770 771 private static final LinearVelocity C = 772 new LinearVelocity(Wave.LIGHT_SPEED_VACUUM_KM_PER_SEC, 773 LinearVelocityUnits.KILOMETERS_PER_SECOND); 774 775 /** 776 * Creates and returns a new wave with the speed of light and this frequency. 777 * @return a new wave with the speed of light and this frequency. 778 */ 779 public Wave makeWave() 780 { 781 return makeWave(C); 782 } 783 784 /** 785 * Creates and returns a new wave with the given speed and this frequency. 786 * @param speedOfWave the speed of the returned wave. 787 * @return a new wave with the given speed and this frequency. 788 */ 789 public Wave makeWave(LinearVelocity speedOfWave) 790 { 791 Wave wave = new Wave(speedOfWave); 792 793 wave.setFrequency(this); 794 795 return wave; 796 } 797 798 //=========================================================================== 799 // PARSING 800 //=========================================================================== 801 802 /** 803 * Returns a new frequency based on {@code frequencyString}. 804 * <p> 805 * <b><u>Valid Formats</u></b><br/> 806 * Let R be the text representation of a real number.<br/> 807 * Let w represent zero or more whitespace characters.<br/> 808 * Let S be a valid {@link FrequencyUnits units} symbol.<br/> 809 * <br/> 810 * <i>Format One</i>: <tt>wRw</tt>. The given number will be defined to be 811 * in units of {@link FrequencyUnits#HERTZ hertz}.<br/> 812 * <br/> 813 * <i>Format Two</i>: <tt>wRwSw</tt>.</p> 814 * 815 * @param frequencyString 816 * a string that will be converted into a frequency. 817 * In addition to the valid formats listed above, this parameter may 818 * use the special values <tt>"infinity"</tt>, <tt>"+infinity"</tt>, 819 * and <tt>"-infinity"</tt>, with or without units, and without 820 * regard to case. 821 * 822 * @return a new frequency. 823 * 824 * @throws IllegalArgumentException if {@code frequencyString} is not in 825 * the expected form. 826 */ 827 public static Frequency parse(String frequencyString) 828 { 829 Frequency newFrequency = new Frequency(); 830 831 if ((frequencyString != null) && !frequencyString.equals("")) 832 { 833 try 834 { 835 //Allow parsing of negative numbers, but if the parsed # is non-negative, 836 //go back to default of disallowing negatives. 837 newFrequency.allowNegativeValues = true; 838 newFrequency.parseFrequency(frequencyString); 839 newFrequency.allowNegativeValues = newFrequency.value.signum() < 0; 840 } 841 catch (Exception ex) 842 { 843 throw new IllegalArgumentException("Could not parse " + frequencyString, ex); 844 } 845 } 846 return newFrequency; 847 } 848 849 /** 850 * If parsing was successful, this frequency's units & value will have been 851 * valued. Otherwise an exception is thrown. 852 */ 853 private void parseFrequency(String frequencyString) 854 { 855 //Quick exit if text represents infinity 856 if (parseInfiniteFrequency(frequencyString)) 857 return; 858 859 //Eliminate whitespace 860 frequencyString = frequencyString.replaceAll("\\s", ""); 861 862 //Assume we have a number followed (optionally) by a symbol 863 units = null; 864 865 int unitsPos = -1; 866 867 //Sort units by length of symbol, longer symbols before shorter. 868 //This helps w/ discovering which unit is contained in distanceString. 869 FrequencyUnits[] sortedUnits = FrequencyUnits.values(); 870 Arrays.sort(sortedUnits, 871 new Comparator<FrequencyUnits>() { 872 public int compare(FrequencyUnits a, FrequencyUnits b) { 873 return b.getSymbol().length() - a.getSymbol().length(); 874 } 875 }); 876 877 //Figure out what kind of units we have 878 for (FrequencyUnits u : sortedUnits) 879 { 880 if (frequencyString.endsWith(u.getSymbol())) 881 { 882 units = u; 883 unitsPos = frequencyString.lastIndexOf(u.getSymbol()); 884 break; 885 } 886 } 887 888 //If unitsPos < 0, we either have no units or garbage. 889 //The BigDecimal constructor will fail if it is garbage. 890 //If we survive that parsing, we will assume default units. 891 String numberString = 892 (unitsPos < 0) ? frequencyString : frequencyString.substring(0, unitsPos); 893 894 setValue(new BigDecimal(numberString)); 895 896 //If we got this far, BigDecimal constructor was successful. 897 //If the units are still null, use kilometers. 898 if (units == null) 899 units = FrequencyUnits.HERTZ; 900 } 901 902 //TODO see if this can be generalized in EnumUtil, perhaps 903 /** Returns <i>true</i> if parsed frequency was infinite. */ 904 private boolean parseInfiniteFrequency(String freqText) 905 { 906 final String origText = freqText; //in case we need to throw exception 907 908 boolean isInfinite; 909 910 final String INF_TEXT = "infinity"; 911 912 char signChar = freqText.charAt(0); 913 boolean negate = (signChar == '-'); 914 boolean hasSignChar = negate || (signChar == '+'); 915 916 //Strip off "+" or "-" 917 if (hasSignChar) 918 freqText = freqText.substring(1); 919 920 int testLength = INF_TEXT.length(); 921 int actualLength = freqText.length(); 922 923 //Might have "infinity" with no units 924 if (actualLength == testLength) 925 { 926 isInfinite = freqText.equalsIgnoreCase(INF_TEXT); 927 928 if (isInfinite) 929 set(MathUtil.getInfiniteValue(negate ? -1 : +1), STD_UNITS); 930 } 931 //Might have "infinity" followed by units 932 else if (actualLength > testLength) 933 { 934 String testString = freqText.substring(0, testLength); 935 936 isInfinite = testString.equalsIgnoreCase(INF_TEXT); 937 938 if (isInfinite) 939 { 940 FrequencyUnits fu = 941 FrequencyUnits.fromString(freqText.substring(testLength, actualLength)); 942 943 if (fu == null) 944 throw new IllegalArgumentException("Could not parse '" + origText + 945 "'. This looked like an infinite frequency but units could not be determined."); 946 947 set(MathUtil.getInfiniteValue(negate ? -1 : +1), fu); 948 } 949 } 950 //String too short to hold "infinity" 951 else //actualLength < testLength 952 { 953 isInfinite = false; 954 } 955 956 return isInfinite; 957 } 958 959 //=========================================================================== 960 // UTILITY METHODS 961 //=========================================================================== 962 963 /** Returns a text representation of this frequency. */ 964 @Override 965 public String toString() 966 { 967 return StringUtil.getInstance() 968 .formatNoScientificNotation(getValue()) + 969 getUnits().getSymbol(); 970 } 971 972 /** 973 * Returns a text representation of this frequency. 974 * 975 * @param minFracDigits the minimum number of places after the decimal point. 976 * 977 * @param maxFracDigits the maximum number of places after the decimal point. 978 * 979 * @return a text representation of this frequency. 980 */ 981 public String toString(int minFracDigits, int maxFracDigits) 982 { 983 return StringUtil.getInstance().formatNoScientificNotation(value, 984 minFracDigits, 985 maxFracDigits) + 986 getUnits().getSymbol(); 987 } 988 989 /** 990 * Returns a text representation of a normalized representation of this 991 * frequency. This method does not modify this frequency; specifically, 992 * it does not normalize this frequency. (See {@link #normalize()} for 993 * the definition of "normalized".) 994 * 995 * @param minFracDigits the minimum number of places after the decimal point. 996 * 997 * @param maxFracDigits the maximum number of places after the decimal point. 998 * 999 * @return a text representation of this frequency. 1000 */ 1001 public String toStringNormalized(int minFracDigits, int maxFracDigits) 1002 { 1003 Frequency clone = this.clone(); 1004 clone.normalize(); 1005 return clone.toString(minFracDigits, maxFracDigits); 1006 } 1007 1008 /** Returns a frequency that is equal to this one. */ 1009 @Override 1010 public Frequency clone() 1011 { 1012 //Since this class has only primitive (& immutable) attributes, 1013 //the clone in Object is all we need. 1014 try 1015 { 1016 return (Frequency)super.clone(); 1017 } 1018 catch (CloneNotSupportedException ex) 1019 { 1020 //We'll never get here, but just in case... 1021 throw new RuntimeException(ex); 1022 } 1023 } 1024 1025 /** Returns <i>true</i> if {@code o} is equal to this frequency. */ 1026 @Override 1027 public boolean equals(Object o) 1028 { 1029 //Quick exit if o is this 1030 if (o == this) 1031 return true; 1032 1033 //Quick exit if o is null 1034 if (o == null) 1035 return false; 1036 1037 //Quick exit if classes are different 1038 if (!o.getClass().equals(this.getClass())) 1039 return false; 1040 1041 Frequency other = (Frequency)o; 1042 1043 //Treat two infinite values of same sign as equal, 1044 //regardless of actual BigDecimal values 1045 if (isInfinite() && other.isInfinite()) 1046 return value.signum() == other.value.signum(); 1047 1048 //Ignore stored units; equality is based purely on magnitude in std units 1049 return compareTo(other) == 0; 1050 } 1051 1052 /** Returns a hash code value for this frequency. */ 1053 @Override 1054 public int hashCode() 1055 { 1056 if (isInfinite()) 1057 return value.signum() > 0 ? "+infinity".hashCode() : "-infinity".hashCode(); 1058 1059 String crude = value.toPlainString() + units.getSymbol(); 1060 return crude.hashCode(); 1061 } 1062 1063 /** Compares this frequency with the {@code otherFreq} for order. */ 1064 public int compareTo(Frequency otherFreq) 1065 { 1066 //Treat two infinite values of same sign as equal, 1067 //regardless of actual BigDecimal values 1068 if (isInfinite() && otherFreq.isInfinite()) 1069 return value.signum() - otherFreq.value.signum(); 1070 1071 //Avoid doing two unit conversions 1072 return value.compareTo(otherFreq.toUnits(units)); 1073 } 1074 1075 //This is here for quick & dirty testing 1076 /*public static void main(String args[]) 1077 { 1078 Frequency f1 = new Frequency(1.2, FrequencyUnits.HERTZ); 1079 System.out.println("f1 = " + f1); 1080 for (FrequencyUnits fu : FrequencyUnits.values()) 1081 { 1082 f1.convertTo(fu); 1083 System.out.println("f1 = " + f1); 1084 } 1085 1086 System.out.println(); 1087 1088 Frequency f2 = new Frequency(12345.678, FrequencyUnits.GIGAHERTZ); 1089 System.out.println("f2 = " + f2); 1090 for (FrequencyUnits fu : FrequencyUnits.values()) 1091 { 1092 f2.convertTo(fu); 1093 System.out.println("f2 = " + f2); 1094 } 1095 }*/ 1096 /* 1097 public static void main(String[] args) 1098 { 1099 Frequency i = new Frequency("infinity "); 1100 Frequency pi = new Frequency("+INFINITY"); 1101 Frequency ni = Frequency.parse("-infinity Hz"); 1102 1103 System.out.println(i.isInfinite()); 1104 System.out.println(pi.isInfinite()); 1105 System.out.println(ni.isInfinite()); 1106 System.out.println(); 1107 1108 System.out.println(i); 1109 System.out.println(pi); 1110 System.out.println(ni); 1111 System.out.println(); 1112 1113 BigDecimal bd1 = new BigDecimal(0.1); 1114 BigDecimal bd2 = new BigDecimal(Double.toString(0.1)); 1115 BigDecimal bd3 = new BigDecimal("0.1"); 1116 1117 System.out.println(bd1.toPlainString()); 1118 System.out.println(bd2.toPlainString()); 1119 System.out.println(bd3.toPlainString()); 1120 System.out.println(); 1121 1122 bd1 = new BigDecimal(28.976); 1123 bd2 = new BigDecimal(Double.toString(28.976)); 1124 bd3 = new BigDecimal("28.976"); 1125 1126 System.out.println(bd1.toPlainString()); 1127 System.out.println(bd2.toPlainString()); 1128 System.out.println(bd3.toPlainString()); 1129 System.out.println(); 1130 1131 System.out.println(bd1.scale() + ", " + bd1.precision()); 1132 System.out.println(bd2.scale() + ", " + bd2.precision()); 1133 System.out.println(bd3.scale() + ", " + bd3.precision()); 1134 System.out.println(); 1135 1136 System.out.println(bd1.doubleValue()); 1137 bd1 = bd1.setScale(3, RoundingMode.HALF_UP); 1138 System.out.println(bd1.toPlainString()); 1139 System.out.println(bd1.doubleValue()); 1140 System.out.println(); 1141 1142 bd1 = new BigDecimal(123.0); 1143 bd2 = new BigDecimal(Double.toString(123.0)); 1144 bd3 = new BigDecimal("123.0"); 1145 1146 System.out.println(bd1.toPlainString()); 1147 System.out.println(bd2.toPlainString()); 1148 System.out.println(bd3.toPlainString()); 1149 System.out.println(); 1150 1151 System.out.println(bd1.scale() + ", " + bd1.precision()); 1152 System.out.println(bd2.scale() + ", " + bd2.precision()); 1153 System.out.println(bd3.scale() + ", " + bd3.precision()); 1154 System.out.println(); 1155 1156 bd1 = new BigDecimal("2.04800000000000000", MathContext.DECIMAL128); 1157 System.out.println(bd1.toPlainString()); 1158 System.out.println(bd1.scale() + ", " + bd1.precision()); 1159 System.out.println(); 1160 1161 bd1 = bd1.stripTrailingZeros(); 1162 System.out.println(bd1.toPlainString()); 1163 System.out.println(bd1.scale() + ", " + bd1.precision()); 1164 System.out.println(); 1165 } 1166 */ 1167 }