From 6b20644bae3f961fb21fffc94f1abe2b938b07cc Mon Sep 17 00:00:00 2001 From: Alex Lohr Date: Mon, 17 Feb 2025 12:13:49 +0100 Subject: [PATCH 1/2] new exercise: complex-numbers --- config.json | 8 + .../.docs/instructions.append.md | 21 ++ .../complex-numbers/.docs/instructions.md | 100 ++++++ exercises/practice/complex-numbers/.eslintrc | 14 + .../complex-numbers/.meta/config.json | 28 ++ .../complex-numbers/.meta/proof.ci.wat | 199 ++++++++++++ .../practice/complex-numbers/.meta/tests.toml | 130 ++++++++ exercises/practice/complex-numbers/.npmrc | 1 + exercises/practice/complex-numbers/LICENSE | 21 ++ .../practice/complex-numbers/babel.config.js | 4 + .../complex-numbers/complex-numbers.spec.js | 288 ++++++++++++++++++ .../complex-numbers/complex-numbers.wat | 93 ++++++ .../practice/complex-numbers/package.json | 35 +++ 13 files changed, 942 insertions(+) create mode 100644 exercises/practice/complex-numbers/.docs/instructions.append.md create mode 100644 exercises/practice/complex-numbers/.docs/instructions.md create mode 100644 exercises/practice/complex-numbers/.eslintrc create mode 100644 exercises/practice/complex-numbers/.meta/config.json create mode 100644 exercises/practice/complex-numbers/.meta/proof.ci.wat create mode 100644 exercises/practice/complex-numbers/.meta/tests.toml create mode 100644 exercises/practice/complex-numbers/.npmrc create mode 100644 exercises/practice/complex-numbers/LICENSE create mode 100644 exercises/practice/complex-numbers/babel.config.js create mode 100644 exercises/practice/complex-numbers/complex-numbers.spec.js create mode 100644 exercises/practice/complex-numbers/complex-numbers.wat create mode 100644 exercises/practice/complex-numbers/package.json diff --git a/config.json b/config.json index 1cbb851..3200197 100644 --- a/config.json +++ b/config.json @@ -143,6 +143,14 @@ "prerequisites": [], "difficulty": 3 }, + { + "slug": "complex-numbers", + "name": "Complex Numbers", + "uuid": "5591f4e1-6f72-415e-a789-12eebd712f9a", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "darts", "name": "Darts", diff --git a/exercises/practice/complex-numbers/.docs/instructions.append.md b/exercises/practice/complex-numbers/.docs/instructions.append.md new file mode 100644 index 0000000..a90fb62 --- /dev/null +++ b/exercises/practice/complex-numbers/.docs/instructions.append.md @@ -0,0 +1,21 @@ +# Instruction append + +The exponentiation of complex numbers requires an exponential function, a sine and a cosine function, none of which are available in WebAssembly. Fortunately, one can use a [Taylor Series](https://en.wikipedia.org/wiki/Taylor_series) to calculate these. You should at least have 25+ steps of precision to align with the expected values. + +## Exponential function + +``` +exp(n) = 1 + n + n ^ 2 / 2! + n ^ 3 / 3! + n ^ 4 / 4! + ... + n ^ x / x! +``` + +## Sine function + +``` +sin(n) = n - n ^ 3 / 3! + n ^ 5 / 5! - n ^ 7 / 7! + ... - n ^ x / x! +``` + +## Cosine function + +``` +cos(n) = 1 - n ^ 2 / 2! + n ^ 4 / 4! - n ^ 6 / 6! + ... - n ^ x / x! +``` diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md new file mode 100644 index 0000000..2b8a7a4 --- /dev/null +++ b/exercises/practice/complex-numbers/.docs/instructions.md @@ -0,0 +1,100 @@ +# Instructions + +A **complex number** is expressed in the form `z = a + b * i`, where: + +- `a` is the **real part** (a real number), + +- `b` is the **imaginary part** (also a real number), and + +- `i` is the **imaginary unit** satisfying `i^2 = -1`. + +## Operations on Complex Numbers + +### Conjugate + +The conjugate of the complex number `z = a + b * i` is given by: + +```text +zc = a - b * i +``` + +### Absolute Value + +The absolute value (or modulus) of `z` is defined as: + +```text +|z| = sqrt(a^2 + b^2) +``` + +The square of the absolute value is computed as the product of `z` and its conjugate `zc`: + +```text +|z|^2 = z * zc = a^2 + b^2 +``` + +### Addition + +The sum of two complex numbers `z1 = a + b * i` and `z2 = c + d * i` is computed by adding their real and imaginary parts separately: + +```text +z1 + z2 = (a + b * i) + (c + d * i) + = (a + c) + (b + d) * i +``` + +### Subtraction + +The difference of two complex numbers is obtained by subtracting their respective parts: + +```text +z1 - z2 = (a + b * i) - (c + d * i) + = (a - c) + (b - d) * i +``` + +### Multiplication + +The product of two complex numbers is defined as: + +```text +z1 * z2 = (a + b * i) * (c + d * i) + = (a * c - b * d) + (b * c + a * d) * i +``` + +### Reciprocal + +The reciprocal of a non-zero complex number is given by: + +```text +1 / z = 1 / (a + b * i) + = a / (a^2 + b^2) - b / (a^2 + b^2) * i +``` + +### Division + +The division of one complex number by another is given by: + +```text +z1 / z2 = z1 * (1 / z2) + = (a + b * i) / (c + d * i) + = (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i +``` + +### Exponentiation + +Raising _e_ (the base of the natural logarithm) to a complex exponent can be expressed using Euler's formula: + +```text +e^(a + b * i) = e^a * e^(b * i) + = e^a * (cos(b) + i * sin(b)) +``` + +## Implementation Requirements + +Given that you should not use built-in support for complex numbers, implement the following operations: + +- **addition** of two complex numbers +- **subtraction** of two complex numbers +- **multiplication** of two complex numbers +- **division** of two complex numbers +- **conjugate** of a complex number +- **absolute value** of a complex number +- **exponentiation** of _e_ (the base of the natural logarithm) to a complex number diff --git a/exercises/practice/complex-numbers/.eslintrc b/exercises/practice/complex-numbers/.eslintrc new file mode 100644 index 0000000..9b66a7f --- /dev/null +++ b/exercises/practice/complex-numbers/.eslintrc @@ -0,0 +1,14 @@ +{ + "root": true, + "extends": "@exercism/eslint-config-javascript", + "env": { + "jest": true + }, + "overrides": [ + { + "files": ["*.spec.js"], + "excludedFiles": ["custom.spec.js"], + "extends": "@exercism/eslint-config-javascript/maintainers" + } + ] +} diff --git a/exercises/practice/complex-numbers/.meta/config.json b/exercises/practice/complex-numbers/.meta/config.json new file mode 100644 index 0000000..0153788 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/config.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "atk" + ], + "files": { + "solution": [ + "complex-numbers.wat" + ], + "test": [ + "complex-numbers.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ], + "invalidator": [ + "package.json" + ] + }, + "blurb": "Implement complex numbers.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Complex_number", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/practice/complex-numbers/.meta/proof.ci.wat b/exercises/practice/complex-numbers/.meta/proof.ci.wat new file mode 100644 index 0000000..3825656 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/proof.ci.wat @@ -0,0 +1,199 @@ +(module + ;; precision value p => 1/p! + (global $precision f64 (f64.const 26.0)) + + ;; + ;; adds two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex sum + ;; + (func (export "add") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.add (local.get $realA) (local.get $realB)) + (f64.add (local.get $imagA) (local.get $imagB)) + ) + + ;; + ;; subtracts two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex difference + ;; + (func (export "sub") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.sub (local.get $realA) (local.get $realB)) + (f64.sub (local.get $imagA) (local.get $imagB)) + ) + + ;; + ;; multiplicates two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex product + ;; + (func (export "mul") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.sub (f64.mul (local.get $realA) (local.get $realB)) + (f64.mul (local.get $imagA) (local.get $imagB))) + (f64.add (f64.mul (local.get $imagA) (local.get $realB)) + (f64.mul (local.get $imagB) (local.get $realA))) + ) + + ;; + ;; divides two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex quotient + ;; + (func (export "div") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.div (f64.add (f64.mul (local.get $realA) (local.get $realB)) + (f64.mul (local.get $imagA) (local.get $imagB))) + (f64.add (f64.mul (local.get $realB) (local.get $realB)) + (f64.mul (local.get $imagB) (local.get $imagB)))) + (f64.div (f64.sub (f64.mul (local.get $imagA) (local.get $realB)) + (f64.mul (local.get $realA) (local.get $imagB))) + (f64.add (f64.mul (local.get $realB) (local.get $realB)) + (f64.mul (local.get $imagB) (local.get $imagB)))) + ) + + ;; + ;; returns the absolute of a complex number + ;; + ;; @param $real {f64} - the real part of the number + ;; @param $imag {f64} - the imaginary part of the number + ;; + ;; @returns {f64} - the absolute value of the number + ;; + (func (export "abs") (param $real f64) (param $imag f64) (result f64) + (f64.sqrt (f64.add (f64.mul (local.get $real) (local.get $real)) + (f64.mul (local.get $imag) (local.get $imag)))) + ) + + ;; + ;; returns the conjugate of a complex number + ;; + ;; @param $real {f64} - the real part of the number + ;; @param $imag {f64} - the imaginary part of the number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the conjugate of the number + ;; + (func (export "conj") (param $real f64) (param $imag f64) (result f64 f64) + (local.get $real) (f64.sub (f64.const 0.0) (local.get $imag)) + ) + + ;; + ;; exponential function + ;; + ;; @param $num {f64} - the number for which the exponent should be calculated + ;; + ;; @returns {f64} - the exponential e^num + ;; + (func $exp (param $num f64) (result f64) + (local $product f64) + (local $sum f64) + (local $fac f64) + (local $step f64) + (local.set $sum (f64.add (f64.const 1.0) (local.get $num))) + (local.set $product (local.get $num)) + (local.set $step (f64.const 3.0)) + (local.set $fac (f64.const 2.0)) + (loop $series + (local.set $product (f64.mul (local.get $product) (local.get $num))) + (local.set $sum (f64.add (local.get $sum) (f64.div (local.get $product) (local.get $fac)))) + (local.set $fac (f64.mul (local.get $fac) (local.get $step))) + (local.set $step (f64.add (local.get $step) (f64.const 1.0))) + (br_if $series (f64.lt (local.get $step) (global.get $precision)))) + (local.get $sum) + ) + + ;; + ;; radial sine of a number + ;; + ;; @param $num {f64} - the number for which the sine should be calculated + ;; + ;; @returns {f64} - the sine of the number + ;; + (func $sin (param $num f64) (result f64) + (local $sum f64) + (local $square f64) + (local $product f64) + (local $fac f64) + (local $step f64) + (local.set $sum (local.get $num)) + (local.set $square (f64.mul (local.get $num) (local.get $num))) + (local.set $product (local.get $num)) + (local.set $fac (f64.const 6.0)) + (local.set $step (f64.const 4.0)) + (loop $series + (local.set $product (f64.mul (local.get $product) (local.get $square))) + (local.set $sum (f64.add (local.get $sum) (f64.div + (select (local.get $product) (f64.sub (f64.const 0) (local.get $product)) + (i32.and (i32.trunc_f64_u (local.get $step)) (i32.const 2))) + (local.get $fac)))) + (local.set $fac (f64.mul (f64.mul (local.get $fac) (local.get $step)) + (f64.add (local.get $step) (f64.const 1.0)))) + (local.set $step (f64.add (local.get $step) (f64.const 2))) + (br_if $series (f64.lt (local.get $step) (global.get $precision)))) + (local.get $sum) + ) + + ;; + ;; radial cosine of a number + ;; + ;; @param $num {f64} - the number for which the cosine should be calculated + ;; + ;; @returns {f64} - the cosine of the number + ;; + (func $cos (param $num f64) (result f64) + (local $sum f64) + (local $square f64) + (local $product f64) + (local $fac f64) + (local $step f64) + (local.set $sum (f64.const 1)) + (local.set $square (f64.mul (local.get $num) (local.get $num))) + (local.set $product (f64.const 1)) + (local.set $fac (f64.const 2.0)) + (local.set $step (f64.const 3.0)) + (loop $series + (local.set $product (f64.mul (local.get $product) (local.get $square))) + (local.set $sum (f64.add (local.get $sum) (f64.div + (select (f64.sub (f64.const 0) (local.get $product)) (local.get $product) + (i32.and (i32.trunc_f64_u (local.get $step)) (i32.const 2))) + (local.get $fac)))) + (local.set $fac (f64.mul (f64.mul (local.get $fac) (local.get $step)) + (f64.add (local.get $step) (f64.const 1.0)))) + (local.set $step (f64.add (local.get $step) (f64.const 2))) + (br_if $series (f64.lt (local.get $step) (global.get $precision)))) + (local.get $sum) + ) + + ;; + ;; returns the exponentiation of a complex number + ;; + ;; @param $real {f64} - the real part of the number + ;; @param $imag {f64} - the imaginary part of the number + ;; + ;; @returns {(f64,f64}} - exponentiation of the complex number + ;; + (func (export "exp") (param $real f64) (param $imag f64) (result f64 f64) + (local $expReal f64) + (local.set $expReal (call $exp (local.get $real))) + (f64.mul (local.get $expReal) (call $cos (local.get $imag))) + (f64.mul (local.get $expReal) (call $sin (local.get $imag))) + ) +) diff --git a/exercises/practice/complex-numbers/.meta/tests.toml b/exercises/practice/complex-numbers/.meta/tests.toml new file mode 100644 index 0000000..dffb1f2 --- /dev/null +++ b/exercises/practice/complex-numbers/.meta/tests.toml @@ -0,0 +1,130 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[9f98e133-eb7f-45b0-9676-cce001cd6f7a] +description = "Real part -> Real part of a purely real number" + +[07988e20-f287-4bb7-90cf-b32c4bffe0f3] +description = "Real part -> Real part of a purely imaginary number" + +[4a370e86-939e-43de-a895-a00ca32da60a] +description = "Real part -> Real part of a number with real and imaginary part" + +[9b3fddef-4c12-4a99-b8f8-e3a42c7ccef6] +description = "Imaginary part -> Imaginary part of a purely real number" + +[a8dafedd-535a-4ed3-8a39-fda103a2b01e] +description = "Imaginary part -> Imaginary part of a purely imaginary number" + +[0f998f19-69ee-4c64-80ef-01b086feab80] +description = "Imaginary part -> Imaginary part of a number with real and imaginary part" + +[a39b7fd6-6527-492f-8c34-609d2c913879] +description = "Imaginary unit" + +[9a2c8de9-f068-4f6f-b41c-82232cc6c33e] +description = "Arithmetic -> Addition -> Add purely real numbers" + +[657c55e1-b14b-4ba7-bd5c-19db22b7d659] +description = "Arithmetic -> Addition -> Add purely imaginary numbers" + +[4e1395f5-572b-4ce8-bfa9-9a63056888da] +description = "Arithmetic -> Addition -> Add numbers with real and imaginary part" + +[1155dc45-e4f7-44b8-af34-a91aa431475d] +description = "Arithmetic -> Subtraction -> Subtract purely real numbers" + +[f95e9da8-acd5-4da4-ac7c-c861b02f774b] +description = "Arithmetic -> Subtraction -> Subtract purely imaginary numbers" + +[f876feb1-f9d1-4d34-b067-b599a8746400] +description = "Arithmetic -> Subtraction -> Subtract numbers with real and imaginary part" + +[8a0366c0-9e16-431f-9fd7-40ac46ff4ec4] +description = "Arithmetic -> Multiplication -> Multiply purely real numbers" + +[e560ed2b-0b80-4b4f-90f2-63cefc911aaf] +description = "Arithmetic -> Multiplication -> Multiply purely imaginary numbers" + +[4d1d10f0-f8d4-48a0-b1d0-f284ada567e6] +description = "Arithmetic -> Multiplication -> Multiply numbers with real and imaginary part" + +[b0571ddb-9045-412b-9c15-cd1d816d36c1] +description = "Arithmetic -> Division -> Divide purely real numbers" + +[5bb4c7e4-9934-4237-93cc-5780764fdbdd] +description = "Arithmetic -> Division -> Divide purely imaginary numbers" + +[c4e7fef5-64ac-4537-91c2-c6529707701f] +description = "Arithmetic -> Division -> Divide numbers with real and imaginary part" + +[c56a7332-aad2-4437-83a0-b3580ecee843] +description = "Absolute value -> Absolute value of a positive purely real number" + +[cf88d7d3-ee74-4f4e-8a88-a1b0090ecb0c] +description = "Absolute value -> Absolute value of a negative purely real number" + +[bbe26568-86c1-4bb4-ba7a-da5697e2b994] +description = "Absolute value -> Absolute value of a purely imaginary number with positive imaginary part" + +[3b48233d-468e-4276-9f59-70f4ca1f26f3] +description = "Absolute value -> Absolute value of a purely imaginary number with negative imaginary part" + +[fe400a9f-aa22-4b49-af92-51e0f5a2a6d3] +description = "Absolute value -> Absolute value of a number with real and imaginary part" + +[fb2d0792-e55a-4484-9443-df1eddfc84a2] +description = "Complex conjugate -> Conjugate a purely real number" + +[e37fe7ac-a968-4694-a460-66cb605f8691] +description = "Complex conjugate -> Conjugate a purely imaginary number" + +[f7704498-d0be-4192-aaf5-a1f3a7f43e68] +description = "Complex conjugate -> Conjugate a number with real and imaginary part" + +[6d96d4c6-2edb-445b-94a2-7de6d4caaf60] +description = "Complex exponential function -> Euler's identity/formula" + +[2d2c05a0-4038-4427-a24d-72f6624aa45f] +description = "Complex exponential function -> Exponential of 0" + +[ed87f1bd-b187-45d6-8ece-7e331232c809] +description = "Complex exponential function -> Exponential of a purely real number" + +[08eedacc-5a95-44fc-8789-1547b27a8702] +description = "Complex exponential function -> Exponential of a number with real and imaginary part" + +[d2de4375-7537-479a-aa0e-d474f4f09859] +description = "Complex exponential function -> Exponential resulting in a number with real and imaginary part" + +[06d793bf-73bd-4b02-b015-3030b2c952ec] +description = "Operations between real numbers and complex numbers -> Add real number to complex number" + +[d77dbbdf-b8df-43f6-a58d-3acb96765328] +description = "Operations between real numbers and complex numbers -> Add complex number to real number" + +[20432c8e-8960-4c40-ba83-c9d910ff0a0f] +description = "Operations between real numbers and complex numbers -> Subtract real number from complex number" + +[b4b38c85-e1bf-437d-b04d-49bba6e55000] +description = "Operations between real numbers and complex numbers -> Subtract complex number from real number" + +[dabe1c8c-b8f4-44dd-879d-37d77c4d06bd] +description = "Operations between real numbers and complex numbers -> Multiply complex number by real number" + +[6c81b8c8-9851-46f0-9de5-d96d314c3a28] +description = "Operations between real numbers and complex numbers -> Multiply real number by complex number" + +[8a400f75-710e-4d0c-bcb4-5e5a00c78aa0] +description = "Operations between real numbers and complex numbers -> Divide complex number by real number" + +[9a867d1b-d736-4c41-a41e-90bd148e9d5e] +description = "Operations between real numbers and complex numbers -> Divide real number by complex number" diff --git a/exercises/practice/complex-numbers/.npmrc b/exercises/practice/complex-numbers/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/complex-numbers/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/complex-numbers/LICENSE b/exercises/practice/complex-numbers/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/complex-numbers/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/complex-numbers/babel.config.js b/exercises/practice/complex-numbers/babel.config.js new file mode 100644 index 0000000..9c17ba5 --- /dev/null +++ b/exercises/practice/complex-numbers/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; diff --git a/exercises/practice/complex-numbers/complex-numbers.spec.js b/exercises/practice/complex-numbers/complex-numbers.spec.js new file mode 100644 index 0000000..da6dba3 --- /dev/null +++ b/exercises/practice/complex-numbers/complex-numbers.spec.js @@ -0,0 +1,288 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; + +let wasmModule; +let currentInstance; + +beforeAll(async () => { + try { + const watPath = new URL("./complex-numbers.wat", import.meta.url); + const { buffer } = await compileWat(watPath); + wasmModule = await WebAssembly.compile(buffer); + } catch (err) { + console.log(`Error compiling *.wat: \n${err}`); + process.exit(1); + } +}); + +export class ComplexNumber { + constructor(real, imag) { + this.real = real; + this.imag = imag; + } + + add(other) { + return new ComplexNumber(...currentInstance.exports.add(this.real, this.imag, other.real, other.imag)); + } + + sub(other) { + return new ComplexNumber(...currentInstance.exports.sub(this.real, this.imag, other.real, other.imag)); + } + + mul(other) { + return new ComplexNumber(...currentInstance.exports.mul(this.real, this.imag, other.real, other.imag)); + } + + div(other) { + return new ComplexNumber(...currentInstance.exports.div(this.real, this.imag, other.real, other.imag)); + } + + get abs() { + return currentInstance.exports.abs(this.real, this.imag); + } + + get conj() { + return new ComplexNumber(...currentInstance.exports.conj(this.real, this.imag)); + } + + get exp() { + return new ComplexNumber(...currentInstance.exports.exp(this.real, this.imag)); + } + } + +describe('Complex numbers', () => { + beforeEach(async () => { + currentInstance = null; + + if (!wasmModule) { + return Promise.reject(); + } + try { + currentInstance = await new WasmRunner(wasmModule); + return Promise.resolve(); + } catch (err) { + console.log(`Error instantiating WebAssembly module: ${err}`); + return Promise.reject(); + } + }); + + test('Real part of a purely real number', () => { + const expected = 1; + const actual = new ComplexNumber(1, 0).real; + + expect(actual).toEqual(expected); + }); + + xtest('Real part of a purely imaginary number', () => { + const expected = 0; + const actual = new ComplexNumber(0, 1).real; + + expect(actual).toEqual(expected); + }); + + xtest('Real part of a number with real and imaginary part', () => { + const expected = 1; + const actual = new ComplexNumber(1, 2).real; + + expect(actual).toEqual(expected); + }); + + xtest('Imaginary part of a purely real number', () => { + const expected = 0; + const actual = new ComplexNumber(1, 0).imag; + + expect(actual).toEqual(expected); + }); + + xtest('Imaginary part of a purely imaginary number', () => { + const expected = 1; + const actual = new ComplexNumber(0, 1).imag; + + expect(actual).toEqual(expected); + }); + + xtest('Imaginary part of a number with real and imaginary part', () => { + const expected = 2; + const actual = new ComplexNumber(1, 2).imag; + + expect(actual).toEqual(expected); + }); + + xtest('Add purely real numbers', () => { + const expected = new ComplexNumber(3, 0); + const actual = new ComplexNumber(1, 0).add(new ComplexNumber(2, 0)); + + expect(actual).toEqual(expected); + }); + + xtest('Add purely imaginary numbers', () => { + const expected = new ComplexNumber(0, 3); + const actual = new ComplexNumber(0, 1).add(new ComplexNumber(0, 2)); + + expect(actual).toEqual(expected); + }); + + xtest('Add numbers with real and imaginary part', () => { + const expected = new ComplexNumber(4, 6); + const actual = new ComplexNumber(1, 2).add(new ComplexNumber(3, 4)); + + expect(actual).toEqual(expected); + }); + + xtest('Subtract purely real numbers', () => { + const expected = new ComplexNumber(-1, 0); + const actual = new ComplexNumber(1, 0).sub(new ComplexNumber(2, 0)); + + expect(actual).toEqual(expected); + }); + + xtest('Subtract purely imaginary numbers', () => { + const expected = new ComplexNumber(0, -1); + const actual = new ComplexNumber(0, 1).sub(new ComplexNumber(0, 2)); + + expect(actual).toEqual(expected); + }); + + xtest('Subtract numbers with real and imaginary part', () => { + const expected = new ComplexNumber(-2, -2); + const actual = new ComplexNumber(1, 2).sub(new ComplexNumber(3, 4)); + + expect(actual).toEqual(expected); + }); + + xtest('Multiply purely real numbers', () => { + const expected = new ComplexNumber(2, 0); + const actual = new ComplexNumber(1, 0).mul(new ComplexNumber(2, 0)); + + expect(actual).toEqual(expected); + }); + + xtest('Multiply imaginary unit', () => { + const expected = new ComplexNumber(-1, 0); + const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 1)); + + expect(actual).toEqual(expected); + }); + + xtest('Multiply purely imaginary numbers', () => { + const expected = new ComplexNumber(-2, 0); + const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 2)); + + expect(actual).toEqual(expected); + }); + + xtest('Multiply numbers with real and imaginary part', () => { + const expected = new ComplexNumber(-5, 10); + const actual = new ComplexNumber(1, 2).mul(new ComplexNumber(3, 4)); + + expect(actual).toEqual(expected); + }); + + xtest('Divide purely real numbers', () => { + const expected = new ComplexNumber(0.5, 0); + const actual = new ComplexNumber(1, 0).div(new ComplexNumber(2, 0)); + + expect(actual).toEqual(expected); + }); + + xtest('Divide purely imaginary numbers', () => { + const expected = new ComplexNumber(0.5, 0); + const actual = new ComplexNumber(0, 1).div(new ComplexNumber(0, 2)); + + expect(actual).toEqual(expected); + }); + + xtest('Divide numbers with real and imaginary part', () => { + const expected = new ComplexNumber(0.44, 0.08); + const actual = new ComplexNumber(1, 2).div(new ComplexNumber(3, 4)); + + expect(actual).toEqual(expected); + }); + + xtest('Absolute value of a positive purely real number', () => { + const expected = 5; + const actual = new ComplexNumber(5, 0).abs; + + expect(actual).toEqual(expected); + }); + + xtest('Absolute value of a negative purely real number', () => { + const expected = 5; + const actual = new ComplexNumber(-5, 0).abs; + + expect(actual).toEqual(expected); + }); + + xtest('Absolute value of a purely imaginary number with positive imaginary part', () => { + const expected = 5; + const actual = new ComplexNumber(0, 5).abs; + + expect(actual).toEqual(expected); + }); + + xtest('Absolute value of a purely imaginary number with negative imaginary part', () => { + const expected = 5; + const actual = new ComplexNumber(0, -5).abs; + + expect(actual).toEqual(expected); + }); + + xtest('Absolute value of a number with real and imaginary part', () => { + const expected = 5; + const actual = new ComplexNumber(3, 4).abs; + + expect(actual).toEqual(expected); + }); + + xtest('Conjugate a purely real number', () => { + const expected = new ComplexNumber(5, 0); + const actual = new ComplexNumber(5, 0).conj; + + expect(actual).toEqual(expected); + }); + + xtest('Conjugate a purely imaginary number', () => { + const expected = new ComplexNumber(0, -5); + const actual = new ComplexNumber(0, 5).conj; + + expect(actual).toEqual(expected); + }); + + xtest('Conjugate a number with real and imaginary part', () => { + const expected = new ComplexNumber(1, -1); + const actual = new ComplexNumber(1, 1).conj; + + expect(actual).toEqual(expected); + }); + + xtest("Euler's identity/formula", () => { + const expected = new ComplexNumber(-1, 0); + const actual = new ComplexNumber(0, Math.PI).exp; + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Exponential of 0', () => { + const expected = new ComplexNumber(1, 0); + const actual = new ComplexNumber(0, 0).exp; + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Exponential of a purely real number', () => { + const expected = new ComplexNumber(Math.E, 0); + const actual = new ComplexNumber(1, 0).exp; + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Exponential of a number with real and imaginary part', () => { + const expected = new ComplexNumber(-2, 0); + const actual = new ComplexNumber(Math.LN2, Math.PI).exp; + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); +}); \ No newline at end of file diff --git a/exercises/practice/complex-numbers/complex-numbers.wat b/exercises/practice/complex-numbers/complex-numbers.wat new file mode 100644 index 0000000..52aca87 --- /dev/null +++ b/exercises/practice/complex-numbers/complex-numbers.wat @@ -0,0 +1,93 @@ +(module + ;; + ;; adds two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex sum + ;; + (func (export "add") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.const 0.0) (f64.const 0.0) + ) + + ;; + ;; subtracts two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex difference + ;; + (func (export "sub") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.const 0.0) (f64.const 0.0) + ) + + ;; + ;; multiplicates two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex product + ;; + (func (export "mul") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.const 0.0) (f64.const 0.0) + ) + + ;; + ;; divides two complex numbers + ;; + ;; @param $realA {f64} - the real part of the first number + ;; @param $imagA {f64} - the imaginary part of the first number + ;; @param $realB {f64} - the real part of the second number + ;; @param $imagB {f64} - the imaginary part of the second number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the complex quotient + ;; + (func (export "div") (param $realA f64) (param $imagA f64) (param $realB f64) (param $imagB f64) (result f64 f64) + (f64.const 0.0) (f64.const 0.0) + ) + + ;; + ;; returns the absolute of a complex number + ;; + ;; @param $real {f64} - the real part of the number + ;; @param $imag {f64} - the imaginary part of the number + ;; + ;; @returns {f64} - the absolute of the number + ;; + (func (export "abs") (param $real f64) (param $imag f64) (result f64) + (f64.const 0.0) + ) + + ;; + ;; returns the conjugate of a complex number + ;; + ;; @param $real {f64} - the real part of the number + ;; @param $imag {f64} - the imaginary part of the number + ;; + ;; @returns {(f64,f64)} - the real and imaginary parts of the conjugate of the number + ;; + (func (export "conj") (param $real f64) (param $imag f64) (result f64 f64) + (f64.const 0.0) (f64.const 0.0) + ) + + ;; + ;; returns the exponentiation of a complex number + ;; + ;; @param $real {f64} - the real part of the number + ;; @param $imag {f64} - the imaginary part of the number + ;; + ;; @returns {(f64,f64}} - exponentiation of the complex number + ;; + (func (export "exp") (param $real f64) (param $imag f64) (result f64 f64) + (f64.const 0.0) (f64.const 0.0) + ) +) diff --git a/exercises/practice/complex-numbers/package.json b/exercises/practice/complex-numbers/package.json new file mode 100644 index 0000000..e0d64d9 --- /dev/null +++ b/exercises/practice/complex-numbers/package.json @@ -0,0 +1,35 @@ +{ + "name": "@exercism/wasm-complex-numbers", + "description": "Exercism exercises in WebAssembly.", + "author": "Alex Lohr", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/complex-numbers" + }, + "jest": { + "maxWorkers": 1 + }, + "devDependencies": { + "@babel/core": "^7.23.3", + "@exercism/babel-preset-javascript": "^0.4.0", + "@exercism/eslint-config-javascript": "^0.6.0", + "@types/jest": "^29.5.8", + "@types/node": "^20.9.1", + "babel-jest": "^29.7.0", + "core-js": "^3.33.2", + "eslint": "^8.54.0", + "jest": "^29.7.0" + }, + "dependencies": { + "@exercism/wasm-lib": "^0.2.0" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./*", + "watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch ./*", + "lint": "eslint ." + } +} From 3eefa779490ed0147d062757799c8707cb20e7b8 Mon Sep 17 00:00:00 2001 From: Alex Lohr Date: Wed, 19 Feb 2025 23:21:47 +0100 Subject: [PATCH 2/2] address pr comments --- config.json | 2 +- .../practice/complex-numbers/.docs/instructions.append.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index 3200197..d72aa3a 100644 --- a/config.json +++ b/config.json @@ -149,7 +149,7 @@ "uuid": "5591f4e1-6f72-415e-a789-12eebd712f9a", "practices": [], "prerequisites": [], - "difficulty": 4 + "difficulty": 8 }, { "slug": "darts", diff --git a/exercises/practice/complex-numbers/.docs/instructions.append.md b/exercises/practice/complex-numbers/.docs/instructions.append.md index a90fb62..11b151d 100644 --- a/exercises/practice/complex-numbers/.docs/instructions.append.md +++ b/exercises/practice/complex-numbers/.docs/instructions.append.md @@ -1,21 +1,21 @@ # Instruction append -The exponentiation of complex numbers requires an exponential function, a sine and a cosine function, none of which are available in WebAssembly. Fortunately, one can use a [Taylor Series](https://en.wikipedia.org/wiki/Taylor_series) to calculate these. You should at least have 25+ steps of precision to align with the expected values. +The exponentiation of complex numbers requires an exponential function, a sine and a cosine function, none of which are available in WebAssembly. Fortunately, one can use a [Taylor Series](https://en.wikipedia.org/wiki/Taylor_series) to calculate these. You should at least have `1/26!` precision to align with the expected values. ## Exponential function ``` -exp(n) = 1 + n + n ^ 2 / 2! + n ^ 3 / 3! + n ^ 4 / 4! + ... + n ^ x / x! +exp(x) ≃ 1 + x + x ^ 2 / 2! + x ^ 3 / 3! + x ^ 4 / 4! + ... + x ^ n / n! ``` ## Sine function ``` -sin(n) = n - n ^ 3 / 3! + n ^ 5 / 5! - n ^ 7 / 7! + ... - n ^ x / x! +sin(x) ≃ x - x ^ 3 / 3! + x ^ 5 / 5! - x ^ 7 / 7! + ... - x ^ n / n! ``` ## Cosine function ``` -cos(n) = 1 - n ^ 2 / 2! + n ^ 4 / 4! - n ^ 6 / 6! + ... - n ^ x / x! +cos(x) ≃ 1 - x ^ 2 / 2! + x ^ 4 / 4! - x ^ 6 / 6! + ... - x ^ n / n! ```