ComplexFormat.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF 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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */

  21. package org.hipparchus.complex;

  22. import java.text.FieldPosition;
  23. import java.text.NumberFormat;
  24. import java.text.ParsePosition;
  25. import java.util.Locale;

  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.hipparchus.exception.MathIllegalArgumentException;
  28. import org.hipparchus.exception.MathIllegalStateException;
  29. import org.hipparchus.exception.NullArgumentException;
  30. import org.hipparchus.util.CompositeFormat;
  31. import org.hipparchus.util.MathUtils;

  32. /**
  33.  * Formats a Complex number in cartesian format "Re(c) + Im(c)i".  'i' can
  34.  * be replaced with 'j' (or anything else), and the number format for both real
  35.  * and imaginary parts can be configured.
  36.  */
  37. public class ComplexFormat {

  38.      /** The default imaginary character. */
  39.     private static final String DEFAULT_IMAGINARY_CHARACTER = "i";
  40.     /** The notation used to signify the imaginary part of the complex number. */
  41.     private final String imaginaryCharacter;
  42.     /** The format used for the imaginary part. */
  43.     private final NumberFormat imaginaryFormat;
  44.     /** The format used for the real part. */
  45.     private final NumberFormat realFormat;

  46.     /**
  47.      * Create an instance with the default imaginary character, 'i', and the
  48.      * default number format for both real and imaginary parts.
  49.      */
  50.     public ComplexFormat() {
  51.         this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
  52.         this.imaginaryFormat = CompositeFormat.getDefaultNumberFormat();
  53.         this.realFormat = imaginaryFormat;
  54.     }

  55.     /**
  56.      * Create an instance with a custom number format for both real and
  57.      * imaginary parts.
  58.      * @param format the custom format for both real and imaginary parts.
  59.      * @throws NullArgumentException if {@code realFormat} is {@code null}.
  60.      */
  61.     public ComplexFormat(NumberFormat format) throws NullArgumentException {
  62.         MathUtils.checkNotNull(format, LocalizedCoreFormats.IMAGINARY_FORMAT);
  63.         this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
  64.         this.imaginaryFormat = format;
  65.         this.realFormat = format;
  66.     }

  67.     /**
  68.      * Create an instance with a custom number format for the real part and a
  69.      * custom number format for the imaginary part.
  70.      * @param realFormat the custom format for the real part.
  71.      * @param imaginaryFormat the custom format for the imaginary part.
  72.      * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
  73.      * @throws NullArgumentException if {@code realFormat} is {@code null}.
  74.       */
  75.     public ComplexFormat(NumberFormat realFormat, NumberFormat imaginaryFormat)
  76.         throws NullArgumentException {
  77.         MathUtils.checkNotNull(imaginaryFormat, LocalizedCoreFormats.IMAGINARY_FORMAT);
  78.         MathUtils.checkNotNull(realFormat, LocalizedCoreFormats.REAL_FORMAT);

  79.         this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
  80.         this.imaginaryFormat = imaginaryFormat;
  81.         this.realFormat = realFormat;
  82.     }

  83.     /**
  84.      * Create an instance with a custom imaginary character, and the default
  85.      * number format for both real and imaginary parts.
  86.      * @param imaginaryCharacter The custom imaginary character.
  87.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  88.      * {@code null}.
  89.      * @throws MathIllegalArgumentException if {@code imaginaryCharacter} is an
  90.      * empty string.
  91.      */
  92.     public ComplexFormat(String imaginaryCharacter)
  93.         throws MathIllegalArgumentException, NullArgumentException {
  94.         this(imaginaryCharacter, CompositeFormat.getDefaultNumberFormat());
  95.     }

  96.     /**
  97.      * Create an instance with a custom imaginary character, and a custom number
  98.      * format for both real and imaginary parts.
  99.      * @param imaginaryCharacter The custom imaginary character.
  100.      * @param format the custom format for both real and imaginary parts.
  101.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  102.      * {@code null}.
  103.      * @throws MathIllegalArgumentException if {@code imaginaryCharacter} is an
  104.      * empty string.
  105.      * @throws NullArgumentException if {@code format} is {@code null}.
  106.      */
  107.     public ComplexFormat(String imaginaryCharacter, NumberFormat format)
  108.         throws MathIllegalArgumentException, NullArgumentException {
  109.         this(imaginaryCharacter, format, format);
  110.     }

  111.     /**
  112.      * Create an instance with a custom imaginary character, a custom number
  113.      * format for the real part, and a custom number format for the imaginary
  114.      * part.
  115.      *
  116.      * @param imaginaryCharacter The custom imaginary character.
  117.      * @param realFormat the custom format for the real part.
  118.      * @param imaginaryFormat the custom format for the imaginary part.
  119.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  120.      * {@code null}.
  121.      * @throws MathIllegalArgumentException if {@code imaginaryCharacter} is an
  122.      * empty string.
  123.      * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
  124.      * @throws NullArgumentException if {@code realFormat} is {@code null}.
  125.      */
  126.     public ComplexFormat(String imaginaryCharacter,
  127.                          NumberFormat realFormat,
  128.                          NumberFormat imaginaryFormat)
  129.         throws MathIllegalArgumentException, NullArgumentException {
  130.         if (imaginaryCharacter == null) {
  131.             throw new NullArgumentException();
  132.         }
  133.         if (imaginaryCharacter.isEmpty()) {
  134.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NO_DATA);
  135.         }
  136.         MathUtils.checkNotNull(imaginaryFormat, LocalizedCoreFormats.IMAGINARY_FORMAT);
  137.         MathUtils.checkNotNull(realFormat, LocalizedCoreFormats.REAL_FORMAT);

  138.         this.imaginaryCharacter = imaginaryCharacter;
  139.         this.imaginaryFormat = imaginaryFormat;
  140.         this.realFormat = realFormat;
  141.     }

  142.     /**
  143.      * Get the set of locales for which complex formats are available.
  144.      * <p>This is the same set as the {@link NumberFormat} set.</p>
  145.      * @return available complex format locales.
  146.      */
  147.     public static Locale[] getAvailableLocales() {
  148.         return NumberFormat.getAvailableLocales();
  149.     }

  150.     /**
  151.      * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
  152.      *
  153.      * @param c Complex object to format.
  154.      * @return A formatted number in the form "Re(c) + Im(c)i".
  155.      */
  156.     public String format(Complex c) {
  157.         return format(c, new StringBuffer(), new FieldPosition(0)).toString();
  158.     }

  159.     /**
  160.      * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
  161.      *
  162.      * @param c Double object to format.
  163.      * @return A formatted number.
  164.      */
  165.     public String format(Double c) {
  166.         return format(new Complex(c, 0), new StringBuffer(), new FieldPosition(0)).toString();
  167.     }

  168.     /**
  169.      * Formats a {@link Complex} object to produce a string.
  170.      *
  171.      * @param complex the object to format.
  172.      * @param toAppendTo where the text is to be appended
  173.      * @param pos On input: an alignment field, if desired. On output: the
  174.      *            offsets of the alignment field
  175.      * @return the value passed in as toAppendTo.
  176.      */
  177.     public StringBuffer format(Complex complex, StringBuffer toAppendTo,
  178.                                FieldPosition pos) {
  179.         pos.setBeginIndex(0);
  180.         pos.setEndIndex(0);

  181.         // format real
  182.         double re = complex.getReal();
  183.         CompositeFormat.formatDouble(re, getRealFormat(), toAppendTo, pos);

  184.         // format sign and imaginary
  185.         double im = complex.getImaginary();
  186.         StringBuffer imAppendTo;
  187.         if (im < 0.0) {
  188.             toAppendTo.append(" - ");
  189.             imAppendTo = formatImaginary(-im, new StringBuffer(), pos);
  190.             toAppendTo.append(imAppendTo).append(getImaginaryCharacter());
  191.         } else if (im > 0.0 || Double.isNaN(im)) {
  192.             toAppendTo.append(" + ");
  193.             imAppendTo = formatImaginary(im, new StringBuffer(), pos);
  194.             toAppendTo.append(imAppendTo).append(getImaginaryCharacter());
  195.         }

  196.         return toAppendTo;
  197.     }

  198.     /**
  199.      * Format the absolute value of the imaginary part.
  200.      *
  201.      * @param absIm Absolute value of the imaginary part of a complex number.
  202.      * @param toAppendTo where the text is to be appended.
  203.      * @param pos On input: an alignment field, if desired. On output: the
  204.      * offsets of the alignment field.
  205.      * @return the value passed in as toAppendTo.
  206.      */
  207.     private StringBuffer formatImaginary(double absIm,
  208.                                          StringBuffer toAppendTo,
  209.                                          FieldPosition pos) {
  210.         pos.setBeginIndex(0);
  211.         pos.setEndIndex(0);

  212.         CompositeFormat.formatDouble(absIm, getImaginaryFormat(), toAppendTo, pos);
  213.         if ("1".equals(toAppendTo.toString())) {
  214.             // Remove the character "1" if it is the only one.
  215.             toAppendTo.setLength(0);
  216.         }

  217.         return toAppendTo;
  218.     }

  219.     /**
  220.      * Formats a object to produce a string.  {@code obj} must be either a
  221.      * {@link Complex} object or a {@link Number} object.  Any other type of
  222.      * object will result in an {@link IllegalArgumentException} being thrown.
  223.      *
  224.      * @param obj the object to format.
  225.      * @param toAppendTo where the text is to be appended
  226.      * @param pos On input: an alignment field, if desired. On output: the
  227.      *            offsets of the alignment field
  228.      * @return the value passed in as toAppendTo.
  229.      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
  230.      * @throws MathIllegalArgumentException is {@code obj} is not a valid type.
  231.      */
  232.     public StringBuffer format(Object obj, StringBuffer toAppendTo,
  233.                                FieldPosition pos)
  234.         throws MathIllegalArgumentException {

  235.         if (obj instanceof Complex) {
  236.             return format( (Complex)obj, toAppendTo, pos);
  237.         } else if (obj instanceof Number) {
  238.             return format(new Complex(((Number)obj).doubleValue(), 0.0), toAppendTo, pos);
  239.         } else {
  240.             throw new MathIllegalArgumentException(LocalizedCoreFormats.CANNOT_FORMAT_INSTANCE_AS_COMPLEX,
  241.                                                    obj.getClass().getName());
  242.         }

  243.     }

  244.     /**
  245.      * Access the imaginaryCharacter.
  246.      * @return the imaginaryCharacter.
  247.      */
  248.     public String getImaginaryCharacter() {
  249.         return imaginaryCharacter;
  250.     }

  251.     /**
  252.      * Access the imaginaryFormat.
  253.      * @return the imaginaryFormat.
  254.      */
  255.     public NumberFormat getImaginaryFormat() {
  256.         return imaginaryFormat;
  257.     }

  258.     /**
  259.      * Returns the default complex format for the current locale.
  260.      * @return the default complex format.
  261.      * @since 1.4
  262.      */
  263.     public static ComplexFormat getComplexFormat() {
  264.         return getComplexFormat(Locale.getDefault());
  265.     }

  266.     /**
  267.      * Returns the default complex format for the given locale.
  268.      * @param locale the specific locale used by the format.
  269.      * @return the complex format specific to the given locale.
  270.      * @since 1.4
  271.      */
  272.     public static ComplexFormat getComplexFormat(Locale locale) {
  273.         NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
  274.         return new ComplexFormat(f);
  275.     }

  276.     /**
  277.      * Returns the default complex format for the given locale.
  278.      * @param locale the specific locale used by the format.
  279.      * @param imaginaryCharacter Imaginary character.
  280.      * @return the complex format specific to the given locale.
  281.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  282.      * {@code null}.
  283.      * @throws MathIllegalArgumentException if {@code imaginaryCharacter} is an
  284.      * empty string.
  285.      * @since 1.4
  286.      */
  287.     public static ComplexFormat getComplexFormat(String imaginaryCharacter, Locale locale)
  288.         throws MathIllegalArgumentException, NullArgumentException {
  289.         NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
  290.         return new ComplexFormat(imaginaryCharacter, f);
  291.     }

  292.     /**
  293.      * Access the realFormat.
  294.      * @return the realFormat.
  295.      */
  296.     public NumberFormat getRealFormat() {
  297.         return realFormat;
  298.     }

  299.     /**
  300.      * Parses a string to produce a {@link Complex} object.
  301.      *
  302.      * @param source the string to parse.
  303.      * @return the parsed {@link Complex} object.
  304.      * @throws MathIllegalStateException if the beginning of the specified string
  305.      * cannot be parsed.
  306.      */
  307.     public Complex parse(String source) throws MathIllegalStateException {
  308.         ParsePosition parsePosition = new ParsePosition(0);
  309.         Complex result = parse(source, parsePosition);
  310.         if (parsePosition.getIndex() == 0) {
  311.             throw new MathIllegalStateException(LocalizedCoreFormats.CANNOT_PARSE_AS_TYPE,
  312.                                                 source, parsePosition.getErrorIndex(),
  313.                                                 Complex.class);
  314.         }
  315.         return result;
  316.     }

  317.     /**
  318.      * Parses a string to produce a {@link Complex} object.
  319.      *
  320.      * @param source the string to parse
  321.      * @param pos input/ouput parsing parameter.
  322.      * @return the parsed {@link Complex} object.
  323.      */
  324.     public Complex parse(String source, ParsePosition pos) {
  325.         int initialIndex = pos.getIndex();

  326.         // parse whitespace
  327.         CompositeFormat.parseAndIgnoreWhitespace(source, pos);

  328.         // parse real
  329.         Number re = CompositeFormat.parseNumber(source, getRealFormat(), pos);
  330.         if (re == null) {
  331.             // invalid real number
  332.             // set index back to initial, error index should already be set
  333.             pos.setIndex(initialIndex);
  334.             return null;
  335.         }

  336.         // parse sign
  337.         int startIndex = pos.getIndex();
  338.         char c = CompositeFormat.parseNextCharacter(source, pos);
  339.         int sign;
  340.         switch (c) {
  341.         case 0 :
  342.             // no sign
  343.             // return real only complex number
  344.             return new Complex(re.doubleValue(), 0.0);
  345.         case '-' :
  346.             sign = -1;
  347.             break;
  348.         case '+' :
  349.             sign = 1;
  350.             break;
  351.         default :
  352.             // invalid sign
  353.             // set index back to initial, error index should be the last
  354.             // character examined.
  355.             pos.setIndex(initialIndex);
  356.             pos.setErrorIndex(startIndex);
  357.             return null;
  358.         }

  359.         // parse whitespace
  360.         CompositeFormat.parseAndIgnoreWhitespace(source, pos);

  361.         // parse imaginary
  362.         Number im = CompositeFormat.parseNumber(source, getRealFormat(), pos);
  363.         if (im == null) {
  364.             // invalid imaginary number
  365.             // set index back to initial, error index should already be set
  366.             pos.setIndex(initialIndex);
  367.             return null;
  368.         }

  369.         // parse imaginary character
  370.         if (!CompositeFormat.parseFixedstring(source, getImaginaryCharacter(), pos)) {
  371.             return null;
  372.         }

  373.         return new Complex(re.doubleValue(), im.doubleValue() * sign);

  374.     }
  375. }