-
Notifications
You must be signed in to change notification settings - Fork 0
/
Complex.java
321 lines (278 loc) · 7.61 KB
/
Complex.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/**
* Represents a complex number. This class is immutable. Supports both
* angle/magnitude and real/imaginary representations and automatically converts
* between the two. Minimizes unnecessary calculations. Restricts angle to
* values between negative pi and positive pi , but
* supports any other angle used as an input. Restricts magnitude to positive
* magnitudes but supports any other magnitude input. Zero magnitude defaults
* angle to 0.
*
* @author Samuel Lieberman
*/
public class Complex {
private static final boolean DEBUG = false;
/**
* Sets the coordinate system.
*/
private static ImMath.coordinateSystem coordMode = ImMath.coordinateSystem.CARTISAN;
public static void setCoordMode(ImMath.coordinateSystem coordMode) {
Complex.coordMode = coordMode;
}
/**
* Gets the coordinate system.
* @return the current coordinate system.
*/
public static ImMath.coordinateSystem getCoordMode() {
return coordMode;
}
private double real;
private double imaginary;
private boolean hasReal;
private boolean hasImaginary;
private double magnitude;
private double angle;
private boolean hasMagnitude;
private boolean hasAngle;
/**
* creates a complex number using n1 and n2 as the real and imaginary values
* respectively (in the case of coordMode==CARTISAN) or using n1 and n2 as
* magnitude and angle respectively (in the case of coordMode==POLAR). new
* Complex(x1, x2) works identically to new Complex(x1, x2,
* Complex.getCoordMode());
*
* @param x1 the real value OR the magnitude
* @param x2 the imaginary value OR the angle
*/
public Complex(double x1, double x2) {
this(x1, x2, coordMode);
}
/**
* private static helper class. Always returns a value between lo and hi. If the
* range is the difference between lo and hi, then the end result will always be
* an integer multiple of the range off from the original value x
*
* @param x the number to mod between lo and hi.
* @param lo the minimum value of the result
* @param hi tha maximum value of the result
* @return a value r between lo and hi such that x = (hi-lo)*k + r for some
* integer k
*/
private static double modBetween(double x, double lo, double hi) {
double range = hi-lo;
return ((x - lo)%range + range)%range + lo;
}
/**
* creates a complex number using n1 and n2 as the real and imaginary values
* respectively (in the case of coordMode==CARTISAN) or using n1 and n2 as
* magnitude and angle respectively (in the case of coordMode==POLAR). new
* Complex(x1, x2) works identically to new Complex(x1, x2,
* Complex.getCoordMode());
*
* @param x1 the real value OR the magnitude
* @param x2 the imaginary value OR the angle
*/
public Complex(double x1, double x2, ImMath.coordinateSystem coordMode) {
if (Double.isNaN(x1)) {
throw new IllegalStateException("x1 is NaN");
}
if (Double.isNaN(x2)) {
throw new IllegalStateException("x2 is NaN");
}
hasReal = false;
hasImaginary = false;
hasMagnitude = false;
hasAngle = false;
switch (coordMode) {
case CARTISAN:
real = x1;
imaginary = x2;
hasReal = true;
hasImaginary = true;
break;
case POLAR:
magnitude = Math.abs(x1);
if (magnitude < 0) {
angle = modBetween(x2 + Math.PI, -Math.PI, Math.PI);
}else if (magnitude == 0) {
angle = 0;
}else {
angle = modBetween(x2, -Math.PI, Math.PI);
}
hasMagnitude = true;
hasAngle = true;
break;
default:
throw new UnsupportedOperationException("coordinate system \"" + coordMode + "\" not supported");
}
if (DEBUG) {
checkIllegalState();
}
}
/**
* Use this before setting anything
*
* Checks for illegal states including:
* not enough information to uniquely identify an imaginary number
*/
private void checkIllegalState() {
if ((!hasReal || !hasImaginary) && (!hasMagnitude || !hasAngle)) {
throw new IllegalStateException("too many unknowns");
}
if (hasReal && Double.isNaN(real)) {
throw new IllegalStateException("real is " + real);
}
if (hasImaginary && Double.isNaN(imaginary)) {
throw new IllegalStateException("imaginary is " + imaginary);
}
if (hasMagnitude && Double.isNaN(magnitude)) {
throw new IllegalStateException("magnitude is " + magnitude);
}
if (hasAngle && Double.isNaN(angle)) {
throw new IllegalStateException("angle is " + angle);
}
if (hasAngle && angle < -Math.PI || angle > Math.PI) {
throw new IllegalStateException("invalid angle: \"" + angle + "\"");
}
if (hasMagnitude && magnitude < 0) {
throw new IllegalStateException("invalid magnitude: \"" + magnitude + "\"");
}
}
private void setReal() {
if (DEBUG) {
checkIllegalState();
}
double cos = Math.cos(th());
if (cos == 0) {
real = 0;
}else {
real = r()*cos;
}
hasReal = true;
}
private void setImaginary() {
if (DEBUG) {
checkIllegalState();
}
double sin = Math.sin(th());
if (sin == 0) {
imaginary = 0;
}else {
imaginary = r()*sin;
}
hasImaginary = true;
}
private void setMagnitude() {
if (DEBUG) {
checkIllegalState();
}
magnitude = Math.sqrt(Math.pow(re(), 2) + Math.pow(im(), 2));
hasMagnitude = true;
}
private void setAngle() {
if (DEBUG) {
checkIllegalState();
}
if (Double.isInfinite(im()) && Double.isInfinite(re())) {
throw new RuntimeException("angle is undefined for this complex number since it's real and imaginary values are infinite");
}else {
angle = Math.atan2(im(), re());
}
hasAngle = true;
}
/**
* @return the real value of this complex number
*/
public double re() {
if (DEBUG) {
checkIllegalState();
}
if (!hasReal) {
setReal();
}
if (DEBUG) {
checkIllegalState();
}
return real;
}
/**
* @return the imaginary value of this complex number
*/
public double im() {
if (DEBUG) {
checkIllegalState();
}
if (!hasImaginary) {
setImaginary();
}
if (DEBUG) {
checkIllegalState();
}
return imaginary;
}
/**
* @return the magnitude of this complex number
*/
public double r() {
if (DEBUG) {
checkIllegalState();
}
if (!hasMagnitude) {
setMagnitude();
}
if (DEBUG) {
checkIllegalState();
}
return magnitude;
}
/**
* @return the angle of this complex number
*/
public double th() {
if (DEBUG) {
checkIllegalState();
}
if (!hasAngle) {
setAngle();
}
if (DEBUG) {
checkIllegalState();
}
return angle;
}
/**
* returns true if the real value is infinite, the imaginary value is infinite, or the magnitude is infinite
* @return
*/
public boolean isInfinite() {
return Double.isInfinite(re()) || Double.isInfinite(im()) || Double.isInfinite(r());
}
@Override
public boolean equals(Object arg0) {
if (DEBUG) {
checkIllegalState();
}
if (arg0 instanceof Complex) {
Complex c = (Complex) arg0;
if (hasReal && hasImaginary) {
return real == c.re() && imaginary == c.im();
}else {
return magnitude == c.r() && angle == c.th();
}
}else {
return false;
}
}
public String toString() {
if (DEBUG) {
checkIllegalState();
}
switch (coordMode) {
case CARTISAN:
return re() + " + " + im() + "*i";
case POLAR:
return r() + "*e^(i*" + th() + ")";
default:
throw new UnsupportedOperationException("coordinate system \"" + coordMode + "\" not supported");
}
}
}