summary refs log tree commit diff stats
path: root/lib/pure/complex.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/complex.nim')
-rw-r--r--lib/pure/complex.nim89
1 files changed, 80 insertions, 9 deletions
diff --git a/lib/pure/complex.nim b/lib/pure/complex.nim
index 1e8349ef6..b48811eae 100644
--- a/lib/pure/complex.nim
+++ b/lib/pure/complex.nim
@@ -15,9 +15,6 @@
 runnableExamples:
   from std/math import almostEqual, sqrt
 
-  func almostEqual(a, b: Complex): bool =
-    almostEqual(a.re, b.re) and almostEqual(a.im, b.im)
-
   let
     z1 = complex(1.0, 2.0)
     z2 = complex(3.0, -4.0)
@@ -36,7 +33,7 @@ runnableExamples:
 {.push checks: off, line_dir: off, stack_trace: off, debugger: off.}
 # the user does not want to trace a part of the standard library!
 
-import math
+import std/[math, strformat]
 
 type
   Complex*[T: SomeFloat] = object
@@ -81,7 +78,7 @@ func abs2*[T](z: Complex[T]): T =
   ## that is the squared distance from (0, 0) to `z`.
   ## This is more efficient than `abs(z) ^ 2`.
   result = z.re * z.re + z.im * z.im
-  
+
 func sgn*[T](z: Complex[T]): Complex[T] =
   ## Returns the phase of `z` as a unit complex number,
   ## or 0 if `z` is 0.
@@ -249,10 +246,31 @@ func pow*[T](x, y: Complex[T]): Complex[T] =
     else:
       result.re = 0.0
       result.im = 0.0
-  elif y.re == 1.0 and y.im == 0.0:
-    result = x
-  elif y.re == -1.0 and y.im == 0.0:
-    result = T(1.0) / x
+  elif y.im == 0.0:
+    if y.re == 1.0:
+      result = x
+    elif y.re == -1.0:
+      result = T(1.0) / x
+    elif y.re == 2.0:
+      result = x * x
+    elif y.re == 0.5:
+      result = sqrt(x)
+    elif x.im == 0.0:
+      # Revert to real pow when both base and exponent are real
+      result.re = pow(x.re, y.re)
+      result.im = 0.0
+    else:
+      # Special case when the exponent is real
+      let
+        rho = abs(x)
+        theta = arctan2(x.im, x.re)
+        s = pow(rho, y.re)
+        r = y.re * theta
+      result.re = s * cos(r)
+      result.im = s * sin(r)
+  elif x.im == 0.0 and x.re == E:
+   # Special case Euler's formula
+   result = exp(y)
   else:
     let
       rho = abs(x)
@@ -391,6 +409,24 @@ func rect*[T](r, phi: T): Complex[T] =
   ## * `polar func<#polar,Complex[T]>`_ for the inverse operation
   complex(r * cos(phi), r * sin(phi))
 
+func almostEqual*[T: SomeFloat](x, y: Complex[T]; unitsInLastPlace: Natural = 4): bool =
+  ## Checks if two complex values are almost equal, using the
+  ## [machine epsilon](https://en.wikipedia.org/wiki/Machine_epsilon).
+  ##
+  ## Two complex values are considered almost equal if their real and imaginary
+  ## components are almost equal.
+  ##
+  ## `unitsInLastPlace` is the max number of
+  ## [units in the last place](https://en.wikipedia.org/wiki/Unit_in_the_last_place)
+  ## difference tolerated when comparing two numbers. The larger the value, the
+  ## more error is allowed. A `0` value means that two numbers must be exactly the
+  ## same to be considered equal.
+  ##
+  ## The machine epsilon has to be scaled to the magnitude of the values used
+  ## and multiplied by the desired precision in ULPs unless the difference is
+  ## subnormal.
+  almostEqual(x.re, y.re, unitsInLastPlace = unitsInLastPlace) and
+  almostEqual(x.im, y.im, unitsInLastPlace = unitsInLastPlace)
 
 func `$`*(z: Complex): string =
   ## Returns `z`'s string representation as `"(re, im)"`.
@@ -399,4 +435,39 @@ func `$`*(z: Complex): string =
 
   result = "(" & $z.re & ", " & $z.im & ")"
 
+proc formatValueAsTuple(result: var string; value: Complex; specifier: string) =
+  ## Format implementation for `Complex` representing the value as a (real, imaginary) tuple.
+  result.add "("
+  formatValue(result, value.re, specifier)
+  result.add ", "
+  formatValue(result, value.im, specifier)
+  result.add ")"
+
+proc formatValueAsComplexNumber(result: var string; value: Complex; specifier: string) =
+  ## Format implementation for `Complex` representing the value as a (RE+IMj) number
+  ## By default, the real and imaginary parts are formatted using the general ('g') format
+  let specifier = if specifier.contains({'e', 'E', 'f', 'F', 'g', 'G'}):
+      specifier.replace("j")
+    else:
+      specifier.replace('j', 'g')
+  result.add "("
+  formatValue(result, value.re, specifier)
+  if value.im >= 0 and not specifier.contains({'+', '-'}):
+    result.add "+"
+  formatValue(result, value.im, specifier)
+  result.add "j)"
+
+proc formatValue*(result: var string; value: Complex; specifier: string) =
+  ## Standard format implementation for `Complex`. It makes little
+  ## sense to call this directly, but it is required to exist
+  ## by the `&` macro.
+  ## For complex numbers, we add a specific 'j' specifier, which formats
+  ## the value as (A+Bj) like in mathematics.
+  if specifier.len == 0:
+    result.add $value
+  elif 'j' in specifier:
+    formatValueAsComplexNumber(result, value, specifier)
+  else:
+    formatValueAsTuple(result, value, specifier)
+
 {.pop.}
5 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427