-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This staticmethod constructor allows us to create a complex instance from two Reals. Also add a test suite for complex and fix division to raise exception on zero-divison. The acton.guide has been updated with a page on complex numbers.
- Loading branch information
Showing
5 changed files
with
299 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
# Complex Numbers | ||
|
||
Complex numbers in Acton provide support for mathematical operations involving both real and imaginary components. Complex numbers implement the `Number` protocol and include operations for arithmetic, comparison, and hashing. Complex also implement the `Div[Eq]`, `Eq` and `Hashable` protocols. | ||
|
||
## Construction | ||
|
||
Complex numbers can be created using the `from_real_imag` constructor method: | ||
|
||
```python | ||
# Create a complex number with real part 3.0 and imaginary part 4.0 | ||
c = complex.from_real_imag(3.0, 4.0) | ||
``` | ||
|
||
## Properties | ||
|
||
Complex numbers have two main properties accessible through methods: | ||
|
||
- `real()`: Returns the real part as a float | ||
- `imag()`: Returns the imaginary part as a float | ||
|
||
```python | ||
c = complex.from_real_imag(3.0, 4.0) | ||
r = c.real() # 3.0 | ||
i = c.imag() # 4.0 | ||
``` | ||
|
||
## Arithmetic Operations | ||
|
||
Complex numbers support standard arithmetic operations: | ||
|
||
### Addition and Subtraction | ||
```python | ||
a = complex.from_real_imag(1.0, 2.0) | ||
b = complex.from_real_imag(3.0, 4.0) | ||
sum = a + b # 4.0 + 6.0i | ||
diff = b - a # 2.0 + 2.0i | ||
``` | ||
|
||
### Multiplication | ||
Complex multiplication follows the rule (a + bi)(c + di) = (ac - bd) + (ad + bc)i | ||
```python | ||
# (1 + 2i)(3 + 4i) = (1×3 - 2×4) + (1×4 + 2×3)i = -5 + 10i | ||
prod = a * b # -5.0 + 10.0i | ||
``` | ||
|
||
### Division | ||
Complex division is performed by multiplying both numerator and denominator by the complex conjugate of the denominator: | ||
```python | ||
# (1 + 2i)/(1 + i) = (1 + 2i)(1 - i)/(1 + i)(1 - i) = (3 + i)/2 | ||
quotient = a / b | ||
``` | ||
|
||
### Power Operation | ||
```python | ||
c = complex.from_real_imag(1.0, 1.0) | ||
squared = c ** 2 # 0.0 + 2.0i | ||
``` | ||
|
||
## Special Operations | ||
|
||
### Complex Conjugate | ||
The complex conjugate of a + bi is a - bi: | ||
```python | ||
c = complex.from_real_imag(1.0, 2.0) | ||
conj = c.conjugate() # 1.0 - 2.0i | ||
``` | ||
|
||
### Absolute Value (Magnitude) | ||
The absolute value or magnitude of a complex number is the square root of (a² + b²): | ||
```python | ||
c = complex.from_real_imag(3.0, 4.0) | ||
magnitude = abs(c) # 5.0 | ||
``` | ||
|
||
## Comparison and Hashing | ||
|
||
Complex numbers can be compared for equality and can be used as dictionary keys: | ||
|
||
```python | ||
a = complex.from_real_imag(1.0, 2.0) | ||
b = complex.from_real_imag(1.0, 2.0) | ||
c = complex.from_real_imag(2.0, 1.0) | ||
|
||
a == b # True | ||
a != c # True | ||
|
||
# Can be used as dictionary keys | ||
d = {a: "value"} | ||
``` | ||
|
||
## Edge Cases and Limitations | ||
|
||
### Division by Zero | ||
Attempting to divide by zero raises a `ZeroDivisionError`: | ||
```python | ||
zero = complex.from_real_imag(0.0, 0.0) | ||
# a / zero # Raises ZeroDivisionError | ||
``` | ||
|
||
### Numerical Limits | ||
Complex numbers use floating-point arithmetic and are subject to the same limitations: | ||
- Very large numbers may result in infinity | ||
- Very small numbers may underflow to zero | ||
- Floating-point arithmetic may introduce small rounding errors | ||
|
||
## Protocols | ||
|
||
Complex numbers implement multiple protocols that define their behavior: | ||
|
||
### Number Protocol | ||
The `Number` protocol provides: | ||
- Basic arithmetic operations (+, -, *) | ||
- Power operation (**) | ||
- Negation (-x) | ||
- Properties for real and imaginary parts | ||
- Absolute value (magnitude) | ||
- Complex conjugate | ||
|
||
### Div[complex] Protocol | ||
The `Div[complex]` protocol adds: | ||
- Division operation (/) | ||
- In-place division operation (/=) | ||
|
||
### Eq Protocol | ||
The `Eq` protocol provides: | ||
- Equality comparison (==) | ||
- Inequality comparison (!=) | ||
|
||
### Hashable Protocol | ||
The `Hashable` protocol (which extends `Eq`) enables: | ||
- Hash computation via `__hash__` | ||
- Use as dictionary keys or set elements | ||
|
||
## Best Practices | ||
|
||
1. Use appropriate tolerance when comparing results of complex arithmetic due to floating-point rounding: | ||
```python | ||
if abs(result.real() - expected_real) < 1e-10 and abs(result.imag() - expected_imag) < 1e-10: | ||
# Numbers are equal within tolerance | ||
``` | ||
|
||
2. Handle potential exceptions when performing division: | ||
```python | ||
try: | ||
result = a / b | ||
except ZeroDivisionError: | ||
# Handle division by zero | ||
``` | ||
|
||
3. Consider numerical stability when working with very large or very small numbers: | ||
```python | ||
# Check for overflow/underflow | ||
if result.real() == float('inf') or result.imag() == float('inf'): | ||
# Handle overflow | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
def test_complex() -> bool: | ||
# Test basic construction | ||
c1 = complex.from_real_imag(3.0, 4.0) | ||
if c1.real() != 3.0 or c1.imag() != 4.0: | ||
print("Basic construction failed - expected 3.0+4.0i, got:", c1.real(), "+", c1.imag(), "i") | ||
return False | ||
|
||
# Test zero components | ||
c2 = complex.from_real_imag(0.0, 0.0) | ||
if c2.real() != 0.0 or c2.imag() != 0.0: | ||
print("Zero construction failed - expected 0.0+0.0i, got:", c2.real(), "+", c2.imag(), "i") | ||
return False | ||
|
||
# Test negative components | ||
c3 = complex.from_real_imag(-1.0, -1.0) | ||
if c3.real() != -1.0 or c3.imag() != -1.0: | ||
print("Negative construction failed - expected -1.0-1.0i, got:", c3.real(), "+", c3.imag(), "i") | ||
return False | ||
|
||
# Test addition | ||
a = complex.from_real_imag(1.0, 2.0) | ||
b = complex.from_real_imag(3.0, 4.0) | ||
sum = a + b | ||
if sum.real() != 4.0 or sum.imag() != 6.0: | ||
print("Addition failed - expected 4.0+6.0i, got:", sum.real(), "+", sum.imag(), "i") | ||
return False | ||
|
||
# Test subtraction | ||
diff = b - a | ||
if diff.real() != 2.0 or diff.imag() != 2.0: | ||
print("Subtraction failed - expected 2.0+2.0i, got:", diff.real(), "+", diff.imag(), "i") | ||
return False | ||
|
||
# Test multiplication | ||
# (1 + 2i)(3 + 4i) = (1×3 - 2×4) + (1×4 + 2×3)i = -5 + 10i | ||
prod = a * b | ||
if prod.real() != -5.0 or prod.imag() != 10.0: | ||
print("Multiplication failed - expected -5.0+10.0i, got:", prod.real(), "+", prod.imag(), "i") | ||
return False | ||
|
||
# Test division | ||
# (1 + 2i)/(1 + i) = (1 + 2i)(1 - i)/(1 + i)(1 - i) = (1 - i + 2i - 2i²)/(1 - i²) = (3 + i)/2 | ||
num = complex.from_real_imag(1.0, 2.0) | ||
den = complex.from_real_imag(1.0, 1.0) | ||
quot = num / den | ||
if abs(quot.real() - 1.5) > 1e-10 or abs(quot.imag() - 0.5) > 1e-10: | ||
print("Division failed - expected 1.5+0.5i, got:", quot.real(), "+", quot.imag(), "i") | ||
return False | ||
|
||
# Test in-place division | ||
dividend = complex.from_real_imag(1.0, 2.0) | ||
divisor = complex.from_real_imag(1.0, 1.0) | ||
dividend /= divisor | ||
if abs(dividend.real() - 1.5) > 1e-10 or abs(dividend.imag() - 0.5) > 1e-10: | ||
print("In-place division failed - expected 1.5+0.5i, got:", dividend.real(), "+", dividend.imag(), "i") | ||
return False | ||
|
||
# Test conjugate | ||
conj = a.conjugate() | ||
if conj.real() != 1.0 or conj.imag() != -2.0: | ||
print("Conjugate failed - expected 1.0-2.0i, got:", conj.real(), "+", conj.imag(), "i") | ||
return False | ||
|
||
# Test absolute value | ||
# |1 + 2i| = √(1² + 2²) = √5 | ||
abs_val = a.__abs__() | ||
if abs(abs_val - 2.236067977499790) > 1e-10: | ||
print("Absolute value failed - expected ≈2.236067977499790, got:", abs_val) | ||
return False | ||
|
||
# Test power operation | ||
# (1 + i)² = 1 + 2i - 1 = 2i | ||
c = complex.from_real_imag(1.0, 1.0) | ||
# TODO: this depends on __fromatom__ being implemented | ||
#d = c ** 2 | ||
#if abs(d.real()) > 1e-10 or abs(d.imag() - 2.0) > 1e-10: | ||
# print("Power operation failed - expected 0.0+2.0i, got:", d.real(), "+", d.imag(), "i") | ||
# return False | ||
|
||
# Test equality and hash consistency | ||
c1 = complex.from_real_imag(1.0, 2.0) | ||
c2 = complex.from_real_imag(1.0, 2.0) | ||
c3 = complex.from_real_imag(2.0, 1.0) | ||
|
||
if c1 != c2: | ||
print("Equality failed - identical complex numbers not equal") | ||
return False | ||
|
||
if c1 == c3: | ||
print("Equality failed - different complex numbers compared equal") | ||
return False | ||
|
||
if hash(c1) != hash(c2): | ||
print("Hash consistency failed - equal numbers have different hashes") | ||
return False | ||
|
||
# Test division by zero | ||
try: | ||
zero = complex.from_real_imag(0.0, 0.0) | ||
bad = a / zero | ||
print("Division by zero didn't raise expected exception") | ||
return False | ||
except ZeroDivisionError: | ||
pass # Expected behavior | ||
|
||
# Test very large numbers | ||
large = 1e308 | ||
big = complex.from_real_imag(large, large) | ||
overflow = big * big | ||
if not (overflow.real() == float('inf') or overflow.imag() == float('inf')): | ||
print("Large number multiplication failed to handle overflow correctly") | ||
return False | ||
|
||
# Test very small numbers | ||
small = 1e-308 | ||
tiny = complex.from_real_imag(small, small) | ||
underflow = tiny * tiny | ||
if not (underflow.real() == 0.0 or abs(underflow.real()) < 1e-307): | ||
print("Small number multiplication failed to handle underflow correctly") | ||
return False | ||
|
||
return True | ||
|
||
actor main(env): | ||
if test_complex(): | ||
print("All complex number tests passed!") | ||
env.exit(0) | ||
env.exit(1) |