From 0765ba720223c10acab7e2aae27fb1f6db42e717 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 28 Apr 2025 16:29:33 +0000 Subject: [PATCH] feat: Add return typing strategies and implement sqrt with them (#26) Resolves #25 Reviewed-on: https://code.studioinfinity.org/StudioInfinity/nanomath/pulls/26 Co-authored-by: Glen Whitney Co-committed-by: Glen Whitney --- etc/eslint.config.mjs | 14 + package.json5 | 4 + pnpm-lock.yaml | 479 +++++++++++++++++++++++ src/__test__/numbers.spec.js | 5 + src/boolean/all.js | 1 + src/boolean/type.js | 2 +- src/boolean/utils.js | 7 + src/complex/Complex.js | 2 +- src/complex/__test__/arithmetic.spec.js | 60 ++- src/complex/__test__/type.spec.js | 22 +- src/complex/__test__/utils.spec.js | 51 +++ src/complex/arithmetic.js | 137 +++++-- src/complex/helpers.js | 32 +- src/complex/type.js | 100 +++-- src/complex/utils.js | 51 ++- src/core/Type.js | 37 +- src/core/TypeDispatcher.js | 108 +++-- src/core/TypePatterns.js | 2 +- src/core/__test__/Type.spec.js | 5 +- src/core/__test__/TypeDispatcher.spec.js | 13 +- src/core/config.js | 11 + src/core/type.js | 3 +- src/generic/__test__/arithmetic.spec.js | 15 +- src/generic/__test__/utils.spec.js | 3 + src/generic/all.js | 1 - src/generic/arithmetic.js | 17 +- src/generic/config.js | 5 - src/generic/relational.js | 14 +- src/generic/utils.js | 17 +- src/number/__test__/arithmetic.spec.js | 15 + src/number/__test__/utils.spec.js | 7 + src/number/arithmetic.js | 26 ++ src/number/relational.js | 1 - src/number/utils.js | 7 +- src/package.json | 3 +- 35 files changed, 1125 insertions(+), 152 deletions(-) create mode 100644 etc/eslint.config.mjs create mode 100644 src/boolean/utils.js create mode 100644 src/complex/__test__/utils.spec.js create mode 100644 src/core/config.js delete mode 100644 src/generic/config.js diff --git a/etc/eslint.config.mjs b/etc/eslint.config.mjs new file mode 100644 index 0000000..d00cbda --- /dev/null +++ b/etc/eslint.config.mjs @@ -0,0 +1,14 @@ +import {defineConfig} from 'eslint/config' +import js from '@eslint/js' +import globals from 'globals' + +export default defineConfig([{ + files: ['**/*.js'], + plugins: {js}, + languageOptions: {globals: {...globals.mocha, ...globals.node}}, + extends: ['js/recommended'], + rules: { + 'no-unused-vars': ['error', {argsIgnorePattern: '^_'}], + 'no-empty': ['error', {allowEmptyCatch: true}], + } +}]) diff --git a/package.json5 b/package.json5 index 50e6463..82af637 100644 --- a/package.json5 +++ b/package.json5 @@ -4,6 +4,7 @@ description: 'Sequel to mathjs as prototype for mathjs overhaul', scripts: { test: 'mocha ./**/*.spec.js', + lint: 'eslint -c etc/eslint.config.mjs', }, keywords: [ 'math', @@ -16,6 +17,9 @@ url: 'https://code.studioinfinity.org/glen/nanomath.git', }, devDependencies: { + '@eslint/js': '^9.25.1', + eslint: '^9.25.1', + globals: '^16.0.0', mocha: '^11.1.0', }, dependencies: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2704e5b..d898d03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,12 +12,79 @@ importers: specifier: ^2.1.3 version: 2.1.3 devDependencies: + '@eslint/js': + specifier: ^9.25.1 + version: 9.25.1 + eslint: + specifier: ^9.25.1 + version: 9.25.1 + globals: + specifier: ^16.0.0 + version: 16.0.0 mocha: specifier: ^11.1.0 version: 11.1.0 packages: + '@eslint-community/eslint-utils@4.6.1': + resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.1': + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.25.1': + resolution: {integrity: sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -26,6 +93,25 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -63,6 +149,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -73,6 +162,10 @@ packages: browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} @@ -96,6 +189,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -113,6 +209,9 @@ packages: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} @@ -134,6 +233,61 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.25.1: + resolution: {integrity: sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -142,10 +296,17 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -163,10 +324,22 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.0.0: + resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==} + engines: {node: '>=18'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -175,6 +348,18 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -213,10 +398,29 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -224,6 +428,9 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -244,10 +451,17 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -259,6 +473,10 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -275,6 +493,14 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -286,6 +512,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -336,11 +566,22 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + workerpool@6.5.1: resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} @@ -374,6 +615,63 @@ packages: snapshots: + '@eslint-community/eslint-utils@4.6.1(eslint@9.25.1)': + dependencies: + eslint: 9.25.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0(supports-color@8.1.1) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.1': {} + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0(supports-color@8.1.1) + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.25.1': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -386,6 +684,23 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@types/estree@1.0.7': {} + + '@types/json-schema@7.0.15': {} + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-colors@4.1.3: {} ansi-regex@5.0.1: {} @@ -411,6 +726,11 @@ snapshots: binary-extensions@2.3.0: {} + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -421,6 +741,8 @@ snapshots: browser-stdout@1.3.1: {} + callsites@3.1.0: {} + camelcase@6.3.0: {} chalk@4.1.2: @@ -452,6 +774,8 @@ snapshots: color-name@1.1.4: {} + concat-map@0.0.1: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -466,6 +790,8 @@ snapshots: decamelize@4.0.0: {} + deep-is@0.1.4: {} + diff@5.2.0: {} eastasianwidth@0.2.0: {} @@ -478,6 +804,83 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.25.1: + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.13.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.25.1 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0(supports-color@8.1.1) + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -487,8 +890,15 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + flat@5.0.2: {} + flatted@3.3.3: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -503,6 +913,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -512,10 +926,23 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + globals@14.0.0: {} + + globals@16.0.0: {} + has-flag@4.0.0: {} he@1.2.0: {} + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -546,10 +973,27 @@ snapshots: dependencies: argparse: 2.0.1 + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 + lodash.merge@4.6.2: {} + log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -557,6 +1001,10 @@ snapshots: lru-cache@10.4.3: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 @@ -592,8 +1040,19 @@ snapshots: ms@2.1.3: {} + natural-compare@1.4.0: {} + normalize-path@3.0.0: {} + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -604,6 +1063,10 @@ snapshots: package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -615,6 +1078,10 @@ snapshots: picomatch@2.3.1: {} + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -625,6 +1092,8 @@ snapshots: require-directory@2.1.1: {} + resolve-from@4.0.0: {} + safe-buffer@5.2.1: {} serialize-javascript@6.0.2: @@ -673,10 +1142,20 @@ snapshots: dependencies: is-number: 7.0.0 + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + which@2.0.2: dependencies: isexe: 2.0.0 + word-wrap@1.2.5: {} + workerpool@6.5.1: {} wrap-ansi@7.0.0: diff --git a/src/__test__/numbers.spec.js b/src/__test__/numbers.spec.js index 95f3658..f1a8602 100644 --- a/src/__test__/numbers.spec.js +++ b/src/__test__/numbers.spec.js @@ -10,4 +10,9 @@ describe('the numbers-only bundle', () => { assert.strictEqual(math.isnan(-16.5), 0) assert.strictEqual(math.isnan(NaN), 1) }) + it('takes sqrt with NaN for negative', () => { + assert.strictEqual(math.sqrt(25), 5) + assert(math.isnan(math.sqrt(-25))) + assert(math.isnan(math.sqrt(NaN))) + }) }) diff --git a/src/boolean/all.js b/src/boolean/all.js index 443a8fa..be70fad 100644 --- a/src/boolean/all.js +++ b/src/boolean/all.js @@ -1,2 +1,3 @@ export * as typeDefinition from './BooleanT.js' export * as type from './type.js' +export * as utilities from './utils.js' diff --git a/src/boolean/type.js b/src/boolean/type.js index 63b2ae7..b9f3287 100644 --- a/src/boolean/type.js +++ b/src/boolean/type.js @@ -1,6 +1,6 @@ import {BooleanT} from './BooleanT.js' import {match} from '#core/TypePatterns.js' -import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js' +import {Returns, TypeOfTypes, Undefined} from '#core/Type.js' import {NumberT} from '#number/NumberT.js' const bool = f => Returns(BooleanT, f) diff --git a/src/boolean/utils.js b/src/boolean/utils.js new file mode 100644 index 0000000..ae18ef6 --- /dev/null +++ b/src/boolean/utils.js @@ -0,0 +1,7 @@ +import {BooleanT} from './BooleanT.js' +import {Returns} from '#core/Type.js' +import {match} from '#core/TypePatterns.js' + +export const clone = match(BooleanT, Returns(BooleanT, p => p)) +export const isnan = match(BooleanT, Returns(BooleanT, () => false)) +export const isfinite = match(BooleanT, Returns(BooleanT, () => true)) diff --git a/src/complex/Complex.js b/src/complex/Complex.js index 9dc0630..2d308d6 100644 --- a/src/complex/Complex.js +++ b/src/complex/Complex.js @@ -11,7 +11,7 @@ function complexSpecialize(ComponentType) { const typeName = `Complex(${ComponentType})` if (ComponentType.concrete) { const fromSpec = [match( - ComponentType, math => r => ({re: r, im: ComponentType.zero}))] + ComponentType, () => r => ({re: r, im: ComponentType.zero}))] for (const {pattern, does} of ComponentType.from) { fromSpec.push(match( this.specialize(pattern.type), diff --git a/src/complex/__test__/arithmetic.spec.js b/src/complex/__test__/arithmetic.spec.js index 966843b..4a54f42 100644 --- a/src/complex/__test__/arithmetic.spec.js +++ b/src/complex/__test__/arithmetic.spec.js @@ -1,9 +1,12 @@ import assert from 'assert' import math from '#nanomath' import {Complex} from '../Complex.js' +import {ReturnTyping} from '#core/Type.js' import {NumberT} from '#number/NumberT.js' const cplx = math.complex +const CplxNum = Complex(NumberT) +const {full} = ReturnTyping describe('complex arithmetic operations', () => { it('computes absquare of complex numbers', () => { @@ -25,23 +28,42 @@ describe('complex arithmetic operations', () => { assert.deepStrictEqual(add(cplx(z,z), 10), cplx(cplx(13, 4), z)) assert.deepStrictEqual( add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4))) + assert.strictEqual(add(z, math.conj(z)), 6) + const addFull = math.add.resolve([CplxNum, CplxNum], full) + assert.deepStrictEqual(addFull(z, math.conj(z)), cplx(6, 0)) + }) + it('adds real and complex numbers', () => { + const z = cplx(3, 4) + const zp3 = cplx(6,4) + const add = math.add + assert.deepStrictEqual(add(z, 3), zp3) + assert.deepStrictEqual(add(3, z), zp3) + assert.deepStrictEqual(add(3, cplx(z, z)), cplx(zp3, z)) + assert.deepStrictEqual(add(cplx(z, z), 3), cplx(zp3, z)) }) it('conjugates complex numbers', () => { const conj = math.conj const z = cplx(3, 4) assert.deepStrictEqual(conj(z), cplx(3, -4)) assert.deepStrictEqual(conj(cplx(z,z)), cplx(cplx(3, -4), cplx(-3, -4))) + const r = cplx(3, 0) + assert.strictEqual(conj(r), 3) + const conjFull = math.conj.resolve(CplxNum, full) + assert.deepStrictEqual(conjFull(r), cplx(3, -0)) }) it('divides complex numbers', () => { const div = math.divide const z = cplx(3, 4) assert.deepStrictEqual(div(z, cplx(0, 1)), cplx(4, -3)) - assert(math.equal(div(z, z), 1)) + assert.strictEqual(div(z, z), 1) + const divFull = math.divide.resolve([CplxNum, CplxNum], full) + assert.deepStrictEqual(divFull(z, z), math.one(CplxNum)) // should probably have a quaternion example, but it's intricate }) it('inverts complex numbers', () => { const inv = math.invert assert.deepStrictEqual(inv(cplx(0, 1)), cplx(0, -1)) + assert.strictEqual(inv(cplx(2, 0)), 0.5) assert.deepStrictEqual(inv(cplx(3, 4)), cplx(3/25, -4/25)) assert.deepStrictEqual( inv(cplx(cplx(1, 2), cplx(4, 2))), @@ -51,8 +73,10 @@ describe('complex arithmetic operations', () => { const mult = math.multiply const z = cplx(3, 4) assert.deepStrictEqual(mult(z, z), cplx(-7, 24)) - assert(math.equal(mult(z, math.conj(z)), 25)) - const q0 = cplx(cplx(1, 1), math.zero(Complex(NumberT))) + assert.strictEqual(mult(z, math.conj(z)), 25) + const multFull = math.multiply.resolve([CplxNum, CplxNum], full) + assert.deepStrictEqual(multFull(z, math.conj(z)), cplx(25, 0)) + const q0 = cplx(cplx(1, 1), math.zero(CplxNum)) const q1 = cplx(cplx(1, 0.5), cplx(0.5, 0.75)) assert.deepStrictEqual( mult(q0, q1), cplx(cplx(0.5, 1.5), cplx(1.25, 0.25))) @@ -60,6 +84,25 @@ describe('complex arithmetic operations', () => { mult(q0, cplx(cplx(2, 0.1), cplx(1, 0.1))), cplx(cplx(1.9, 2.1), cplx(1.1, -0.9))) }) + it('takes the square roots of complex numbers', () => { + const {sqrt, multiply} = math + const rhalf = Math.sqrt(1 / 2) + assert.deepStrictEqual(sqrt(cplx(1, 0)), 1) + assert.deepStrictEqual(sqrt(cplx(-1, 0)), cplx(0, 1)) + assert(math.equal(sqrt(cplx(0, 1)), cplx(rhalf, rhalf))) + assert(math.equal(sqrt(cplx(0, -1)), cplx(rhalf, -rhalf))) + assert.deepStrictEqual(sqrt(cplx(5, 12)), cplx(3, 2)) + const z = cplx(3, 4) + const rz = sqrt(z) + assert.deepStrictEqual(multiply(rz, rz), z) + // quaternions, too: + assert.deepStrictEqual( + sqrt(cplx(cplx(-46, 20), cplx(12, 16))), + cplx(cplx(2, 5), cplx(3, 4))) + const q = cplx(z, z) + const rq = sqrt(q) + assert(math.equal(multiply(rq, rq), q)) + }) it('subtracts complex numbers', () => { const z = cplx(3, 4) const sub = math.subtract @@ -67,8 +110,15 @@ describe('complex arithmetic operations', () => { assert.deepStrictEqual(sub(z, cplx(true, false)), cplx(2, 4)) assert.deepStrictEqual(sub(cplx(false, true), z), cplx(-3, -3)) assert.deepStrictEqual( - sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)), - cplx(cplx(2.5, 3.5), cplx(0, 0))) + sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)), cplx(2.5, 3.5)) + assert.strictEqual( + sub(cplx(z, z), cplx(cplx(0, 4), z)), 3) + const subFull = math.subtract.resolve([ + Complex(CplxNum), Complex(CplxNum) + ], full) + assert.deepStrictEqual( + subFull(cplx(z, z), cplx(cplx(0.5, 0.5), z)), + cplx(cplx(2.5, 3.5), math.zero(CplxNum))) assert.deepStrictEqual(sub(z, 5), cplx(-2, 4)) assert.deepStrictEqual(sub(true, z), cplx(-2, -4)) assert.deepStrictEqual(sub(cplx(z,z), 10), cplx(cplx(-7, 4), z)) diff --git a/src/complex/__test__/type.spec.js b/src/complex/__test__/type.spec.js index 6ea0875..295d0f3 100644 --- a/src/complex/__test__/type.spec.js +++ b/src/complex/__test__/type.spec.js @@ -1,5 +1,9 @@ import assert from 'assert' import math from '#nanomath' +import {Complex} from '../Complex.js' +import {OneOf, ReturnTyping} from '#core/Type.js' +import {NumberT} from '#number/NumberT.js' + const cplx = math.complex @@ -33,7 +37,21 @@ describe('complex type operations', () => { assert(!assoc(cplx(0, 1), b)) }) it('computes cis of an angle', () => { - assert(math.equal(math.cis(0), 1)) - assert(math.equal(math.cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2))) + const cis = math.cis.resolve(NumberT) + assert.strictEqual(cis.returns, OneOf(Complex(NumberT), NumberT)) + assert.strictEqual(cis(0), 1) + assert.strictEqual(cis(Math.PI), -1) + assert(math.equal(cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2))) + math.config.returnTyping = ReturnTyping.full + const ccis = math.cis.resolve(NumberT) + assert.strictEqual(ccis.returns, Complex(NumberT)) + const one = ccis(0) + assert(one !== 1) + assert(math.equal(one, 1)) + assert(math.equal(ccis(Math.PI), cplx(-1))) + math.config.returnTyping = ReturnTyping.free + assert.strictEqual(math.cis.resolve(NumberT), cis) + assert.strictEqual(math.cis.resolve(NumberT, ReturnTyping.full), ccis) + assert.strictEqual(math.cis(2*Math.PI), 1) }) }) diff --git a/src/complex/__test__/utils.spec.js b/src/complex/__test__/utils.spec.js new file mode 100644 index 0000000..d7f2469 --- /dev/null +++ b/src/complex/__test__/utils.spec.js @@ -0,0 +1,51 @@ +import assert from 'assert' +import math from '#nanomath' + +const cplx = math.complex + +describe('complex utilities', () => { + it('clones a complex', () => { + const z = cplx(3, 4) + const cz = math.clone(z) + assert(cz !== z) + assert.deepStrictEqual(z, cz) + const q = cplx(z, math.add(z, 1)) + const cq = math.clone(q) + assert(cq !== q) + assert.deepStrictEqual(q, cq) + assert(q.re !== cq.re && q.im !== cq.im) + }) + it('checks for nan', () => { + assert(math.isnan(cplx(NaN, NaN))) + assert(!math.isnan(cplx(NaN, 6.28))) + }) + it('tests for finiteness', () => { + const fin = math.isfinite + assert(fin(cplx(3, 4))) + assert(!fin(cplx(2, Infinity))) + assert(!fin(cplx(NaN, 0))) + }) + it('identifies Gaussian integers', () => { + const isInt = math.isInteger + assert(isInt(cplx(3, 4))) + assert(isInt(cplx(-37,0))) + assert(!isInt(cplx(99, -1.000001))) + }) + it('identifies real numbers', () => { + const {isReal} = math + assert(isReal(cplx(5, 0))) + assert(isReal(cplx(5, -1e-17))) + assert(!isReal(cplx(5, 0.000001))) + assert(isReal(cplx(cplx(5, 1e-16), cplx(-0, 0)))) + assert(!isReal(cplx(cplx(5, 2), cplx(0, 0)))) + assert(!isReal(cplx(cplx(5, 0), cplx(0, 0.00002)))) + }) + it('identifies complex numbers that only have a real part', () => { + const noImag = math.nonImaginary + assert(noImag(cplx(5, 0))) + assert(noImag(cplx(5, -1e-17))) + assert(!noImag(cplx(5, 0.000001))) + assert(noImag(cplx(cplx(5, 2), cplx(0, 0)))) + assert(!noImag(cplx(cplx(5, 0), cplx(0, 0.00002)))) + }) +}) diff --git a/src/complex/arithmetic.js b/src/complex/arithmetic.js index d50b993..f839c5b 100644 --- a/src/complex/arithmetic.js +++ b/src/complex/arithmetic.js @@ -1,43 +1,46 @@ import {Complex} from './Complex.js' -import {promoteBinary, promoteUnary} from './helpers.js' +import {maybeComplex, promoteBinary, promoteUnary} from './helpers.js' import {ResolutionError} from '#core/helpers.js' +import {Returns, ReturnTyping} from '#core/Type.js' import {match} from '#core/TypePatterns.js' import {ReturnsAs} from '#generic/helpers.js' -export const absquare = match(Complex, (math, C) => { - const compAbsq = math.absquare.resolve([C.Component]) +const {conservative, full, free} = ReturnTyping + +export const absquare = match(Complex, (math, C, strategy) => { + const compAbsq = math.absquare.resolve(C.Component, full) const R = compAbsq.returns - const add = math.add.resolve([R,R]) + const add = math.add.resolve([R,R], strategy) return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im))) }) export const add = promoteBinary('add') -export const conj = match(Complex, (math, C) => { - const neg = math.negate.resolve(C.Component) - const compConj = math.conj.resolve(C.Component) - const cplx = math.complex.resolve([compConj.returns, neg.returns]) +export const conj = match(Complex, (math, C, strategy) => { + const neg = math.negate.resolve(C.Component, full) + const compConj = math.conj.resolve(C.Component, full) + const cplx = maybeComplex(math, strategy, compConj.returns, neg.returns) return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im))) }) export const divide = [ - match([Complex, T => !T.complex], (math, [C, R]) => { - const div = math.divide.resolve([C.Component, R]) - const cplx = math.complex.resolve([div.returns, div.returns]) + match([Complex, T => !T.complex], (math, [C, R], strategy) => { + const div = math.divide.resolve([C.Component, R], full) + const cplx = maybeComplex(math, strategy, div.returns, div.returns) return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r))) }), - match([Complex, Complex], (math, [W, Z]) => { - const inv = math.invert.resolve(Z) - const mult = math.multiply.resolve([W, inv.returns]) + match([Complex, Complex], (math, [W, Z], strategy) => { + const inv = math.invert.resolve(Z, full) + const mult = math.multiply.resolve([W, inv.returns], strategy) return ReturnsAs(mult, (w, z) => mult(w, inv(z))) }) ] -export const invert = match(Complex, (math, C) => { - const conj = math.conj.resolve(C) - const norm = math.absquare.resolve(C) - const div = math.divide.resolve([C.Component, norm.returns]) - const cplx = math.complex.resolve([div.returns, div.returns]) +export const invert = match(Complex, (math, C, strategy) => { + const conj = math.conj.resolve(C, full) + const norm = math.absquare.resolve(C, full) + const div = math.divide.resolve([C.Component, norm.returns], full) + const cplx = maybeComplex(math, strategy, div.returns, div.returns) return ReturnsAs(cplx, z => { const c = conj(z) const d = norm(z) @@ -47,23 +50,87 @@ export const invert = match(Complex, (math, C) => { // We want this to work for complex numbers, quaternions, octonions, etc // See https://math.ucr.edu/home/baez/octonions/node5.html -export const multiply = match([Complex, Complex], (math, [W, Z]) => { - const conj = math.conj.resolve(W.Component) - if (conj.returns !== W.Component) { - throw new ResolutionError( - `conjugation on ${W.Component} returns other type (${conj.returns})`) - } - const mWZ = math.multiply.resolve([W.Component, Z.Component]) - const mZW = math.multiply.resolve([Z.Component, W.Component]) - const sub = math.subtract.resolve([mWZ.returns, mZW.returns]) - const add = math.add.resolve([mWZ.returns, mZW.returns]) - const cplx = math.complex.resolve([sub.returns, add.returns]) - return ReturnsAs(cplx, (w, z) => { - const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im))) - const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im)) - return cplx(real, imag) +export const multiply = [ + match([T => !T.complex, Complex], (math, [R, C], strategy) => { + const mult = math.multiply.resolve([R, C.Component], full) + const cplx = maybeComplex(math, strategy, mult.returns, mult.returns) + return ReturnsAs(cplx, (r, z) => cplx(mult(r, z.re), mult(r, z.im))) + }), + match([Complex, T => !T.complex], (math, [C, R], strategy) => { + const mult = math.multiply.resolve([R, C], strategy) + return ReturnsAs(mult, (z, r) => mult(r, z)) + }), + match([Complex, Complex], (math, [W, Z], strategy) => { + const conj = math.conj.resolve(W.Component, full) + if (conj.returns !== W.Component) { + throw new ResolutionError( + `conjugation on ${W.Component} returns type (${conj.returns})`) + } + const mWZ = math.multiply.resolve([W.Component, Z.Component], full) + const mZW = math.multiply.resolve([Z.Component, W.Component], full) + const sub = math.subtract.resolve([mWZ.returns, mZW.returns], full) + const add = math.add.resolve([mWZ.returns, mZW.returns], full) + const cplx = maybeComplex(math, strategy, sub.returns, add.returns) + return ReturnsAs(cplx, (w, z) => { + const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im))) + const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im)) + return cplx(real, imag) + }) }) -}) +] export const negate = promoteUnary('negate') + +// Should work for complex, quaternions, octonions, etc., even with +// integer coordinates. +export const sqrt = match(Complex, (math, C, strategy) => { + const re = math.re.resolve(C) + const R = re.returns + const isReal = math.isReal.resolve(C) + // dependencies for the real case: + const zComp = math.zero(C.Component) + const sign = math.sign.resolve(R, conservative) + const oneR = math.one(R) + const zR = math.zero(R) + const addRComp = math.add.resolve([R, C.Component], full) + const sqrtR = math.sqrt.resolve(R, conservative) + const neg = math.negate.resolve(R, conservative) + const cplx = math.complex.resolve([C.Component, C.Component], full) + // additional dependencies for the complex case + const abs = math.abs.resolve(C, full) + if (abs.returns !== R) { + throw new TypeError(`abs on ${C} returns ${abs.returns}, not ${R}`) + } + const addRR = math.add.resolve([R, R], conservative) + const twoR = addRR(oneR, oneR) + const multRR = math.multiply.resolve([R, R], conservative) + const im = math.im.resolve(C, full) + const addRC = math.add.resolve([R, C], full) + const divRR = math.divide.resolve([R, R], conservative) + const divCR = math.divide.resolve([C, R], full) + + // The guts of the computation: + const sqrtImp = Returns(C, z => { + const a = re(z) + if (isReal(z)) { // always a special case + let rp = zComp + let ip = zComp + const sgn = sign(a) + if (sgn === oneR) rp = addRComp(sqrtR(a), rp) + else if (sgn !== zR) ip = addRComp(sqrtR(neg(a)), ip) + return cplx(rp, ip) + } + // Complex case: + // We can write z = a + q where q is pure imaginary. + // Let s = sqrt(2(|z|+a)). Then sqrt(z) = (s/2) + (q/s). + const s = sqrtR(multRR(twoR, addRR(abs(z), a))) + const q = im(z) + return addRC(divRR(s, twoR), divCR(q, s)) + }) + + if (strategy != free) return sqrtImp + const prune = math.pruneImaginary.resolve(C, free) + return ReturnsAs(prune, z => prune(sqrtImp(z))) +}) + export const subtract = promoteBinary('subtract') diff --git a/src/complex/helpers.js b/src/complex/helpers.js index 84e828f..c6c4aac 100644 --- a/src/complex/helpers.js +++ b/src/complex/helpers.js @@ -1,20 +1,38 @@ import {Complex} from './Complex.js' +import {ReturnTyping} from '#core/Type.js' import {match} from '#core/TypePatterns.js' import {ReturnsAs} from '#generic/helpers.js' -export const promoteUnary = name => match( +const {free, full} = ReturnTyping + +export const maybeComplex = (math, strategy, Real, Imag) => { + if (strategy !== free) return math.complex.resolve([Real, Imag], strategy) + const cplx = math.complex.resolve([Real, Imag], full) + const prune = math.pruneImaginary.resolve(cplx.returns, full) + return ReturnsAs(prune, (r, m) => prune(cplx(r, m))) +} + +export const promoteUnary = (name, overrideStrategy) => match( Complex, - (math, C) => { - const compOp = math.resolve(name, C.Component) - const cplx = math.complex.resolve([compOp.returns, compOp.returns]) + (math, C, strategy) => { + const compOp = math.resolve(name, C.Component, full) + if (overrideStrategy) strategy = overrideStrategy + const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns) return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im))) }) +export const promotePredicateAnd = name => match( + Complex, + (math, C, strategy) => { + const compPred = math.resolve(name, C.Component, strategy) + return ReturnsAs(compPred, z => compPred(z.re) && compPred(z.im)) + }) + export const promoteBinary = name => match( [Complex, Complex], - (math, [W, Z]) => { - const compOp = math.resolve(name, [W.Component, Z.Component]) - const cplx = math.complex.resolve([compOp.returns, compOp.returns]) + (math, [W, Z], strategy) => { + const compOp = math.resolve(name, [W.Component, Z.Component], full) + const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns) return ReturnsAs( cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im))) }) diff --git a/src/complex/type.js b/src/complex/type.js index e83aabb..812ee06 100644 --- a/src/complex/type.js +++ b/src/complex/type.js @@ -1,9 +1,11 @@ import {Complex} from './Complex.js' -import {Returns} from "#core/Type.js" +import {OneOf, Returns, ReturnTyping, TypeOfTypes} from "#core/Type.js" import {Any, match} from "#core/TypePatterns.js" import {BooleanT} from '#boolean/BooleanT.js' import {NumberT} from '#number/NumberT.js' +const {free, full} = ReturnTyping + export const complex = [ match(Any, (math, T) => { const z = math.zero(T) @@ -27,29 +29,81 @@ export const complex = [ }) ] -export const arg = match( - Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re))) +export const arg = // [ // enable when we have atan2 in mathjs +// match(Complex, (math, C) => { +// const re = math.re.resolve(C) +// const R = re.returns +// const im = math.im.resolve(C) +// const abs = math.abs.resolve(C) +// const atan2 = math.atan2.resolve([R, R], conservative) +// return Returns(R, z => atan2(abs(im(z)), re(z))) +// }), // note always between 0 and tau/2; need to use in conjunction +// // with a complex unit function that gives you the proper +// // imaginary unit, ±i in the simple complex case, to restore the +// // full circle of values for the direction of a complex number + match(Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re))) +//] /* Returns true if w is z multiplied by a complex unit */ -export const associate = match([Complex, Complex], (math, [W, Z]) => { - if (Z.Component.complex) { - throw new Error( - `The group of units of type ${Z} is not yet implemented`) - } - const eq = math.equal.resolve([W, Z]) - const neg = math.negate.resolve(Z) - const eqN = math.equal.resolve([W, neg.returns]) - const mult = math.multiply.resolve([Z, Z]) - const eqM = math.equal.resolve([W, mult.returns]) - const negM = math.negate.resolve(mult.returns) - const eqNM = math.equal.resolve([W, negM.returns]) - const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component)) - return Returns(BooleanT, (w, z) => { - if (eq(w, z) || eqN(w, neg(z))) return true - const iz = mult(iZ, z) - return eqM(w, iz) || eqNM(w, negM(iz)) - }) +export const associate = match( + [Complex, Complex], + (math, [W, Z]) => { + if (Z.Component.complex) { + throw new Error( + `The group of units of type ${Z} is not yet implemented`) + } + const eq = math.equal.resolve([W, Z], full) + const neg = math.negate.resolve(Z, full) + const eqN = math.equal.resolve([W, neg.returns], full) + const mult = math.multiply.resolve([Z, Z], full) + const eqM = math.equal.resolve([W, mult.returns], full) + const negM = math.negate.resolve(mult.returns, full) + const eqNM = math.equal.resolve([W, negM.returns], full) + const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component)) + return Returns(BooleanT, (w, z) => { + if (eq(w, z) || eqN(w, neg(z))) return true + const iz = mult(iZ, z) + return eqM(w, iz) || eqNM(w, negM(iz)) + }) + }) + +const _cis = t => ({re: Math.cos(t), im: Math.sin(t)}) + +export const cis = match(NumberT, (math, _type, strategy) => { + if (strategy === free) { + const intTest = math.isInteger.resolve(NumberT) + return Returns(OneOf(NumberT, Complex(NumberT)), t => { + let halfCycles = t / Math.PI + if (intTest(halfCycles)) { + halfCycles = Math.round(halfCycles) + return halfCycles % 2 ? -1 : 1 + } + return _cis(t) + }) + } + return Returns(Complex(NumberT), _cis) }) -export const cis = match(NumberT, Returns(Complex(NumberT), t => ({ - re: Math.cos(t), im: Math.sin(t)}))) +// Returns the real part if the complex number supplied has no imaginary +// part, otherwise leaves that complex number alone. +// Since it is explicitly asking for varying type, it ignores strategy. +export const pruneImaginary = match(Complex, (math, C) => { + const outcomes = [C] + let T = C + while (T.complex) { + T = T.Component + outcomes.push(T) + } + const noImag = math.nonImaginary.resolve(C, full) + if (C.Component.complex) { + const compPrune = math.pruneImaginary.resolve(C.Component) + return Returns(OneOf(...outcomes), z => noImag(z) ? compPrune(z.re) : z) + } + return Returns(OneOf(...outcomes), z => noImag(z) ? z.re : z) +}) + +// Returns the type of re(z) +export const Real = match(TypeOfTypes, Returns(TypeOfTypes, t => { + while (t.complex) t = t.Component + return t +})) diff --git a/src/complex/utils.js b/src/complex/utils.js index 2fef03a..04cdd45 100644 --- a/src/complex/utils.js +++ b/src/complex/utils.js @@ -1,9 +1,54 @@ import {Complex} from './Complex.js' +import {promotePredicateAnd, promoteUnary} from './helpers.js' + +import {Returns, ReturnTyping} from '#core/Type.js' import {match} from '#core/TypePatterns.js' import {ReturnsAs} from '#generic/helpers.js' +const {full} = ReturnTyping + +export const clone = promoteUnary('clone', full) +export const isnan = promotePredicateAnd('isnan') +export const isfinite = promotePredicateAnd('isfinite') +// Note: the followig predicate returns true for all Gaussian integers, not +// just so-called rational integers. +export const isInteger = promotePredicateAnd('isInteger') + +// true if the Complex is truly a real number (nonImaginary and its +// real part is a real number) export const isReal = match(Complex, (math, C) => { - const eq = math.equal.resolve([C.Component, C.Component]) - const add = math.add.resolve([C.Component, C.Component]) - return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im))) + const nonImag = math.nonImaginary.resolve(C, full) + const realComp = math.isReal.resolve(C.Component) + return ReturnsAs(realComp, z => nonImag(z) && realComp(z.re)) +}) + +// true if the imaginary part of a Complex is negligible compared to the real +export const nonImaginary = match(Complex, (math, C) => { + const eq = math.equal.resolve([C.Component, C.Component]) + const add = math.add.resolve([C.Component, C.Component], full) + return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im))) +}) + +// Always returns the "true" real part of a complex number z (i.e., the part +// that is actually in the real numbers mathematically). In other words, +// performs z.re recursively until it gets something not Complex. +export const re = match(Complex, (math, C) => { + return Returns(math.Real(C), z => { + let T = C + while (T.complex) { + T = T.Component + z = z.re + } + return z + }) +}) + +// Returns everything but the real part of z. NOTE this is a complex +// number with zero real part, _not_ the coefficient of the imaginary +// component of z. If you want the latter, just use `z.im` + +export const im = match(Complex, (math, C) => { + const imComp = math.im.resolve(C.Component, full) + const cplx = math.complex.resolve([C.Component, C.Component], full) + return ReturnsAs(cplx, z => cplx(imComp(z.re), z.im)) }) diff --git a/src/core/Type.js b/src/core/Type.js index 555b547..398aaaa 100644 --- a/src/core/Type.js +++ b/src/core/Type.js @@ -66,9 +66,30 @@ export const Undefined = new Type( t => typeof t === 'undefined', {zero: undefined, one: undefined, nan: undefined}) export const TypeOfTypes = new Type(t => t instanceof Type) -export const NotAType = new Type(t => true) // Danger, do not merge! +export const NotAType = new Type(() => true) // Danger, do not merge! NotAType._doNotMerge = true +const unionDirectory = new ArrayKeyedMap() // make sure only one of each union +export const OneOf = (...types) => { + const nonType = types.findIndex(T => !(T instanceof Type)) + if (nonType >= 0) { + throw new RangeError( + `OneOf can only take type arguments, not ${types[nonType]}`) + } + const typeSet = new Set(types) // remove duplicates + const typeList = Array.from(typeSet).sort() // canonical order + const generic = typeList.find(T => !T.concrete) + if (generic) { + throw new RangeError(`OneOf can only take concrete types, not ${generic}`) + } + if (!unionDirectory.has(typeList)) { + unionDirectory.set(typeList, new Type( + t => typeList.some(T => T.test(t)), + {typeName: typeList.join('|')})) + } + return unionDirectory.get(typeList) +} + export const Returns = (type, f) => (f.returns = type, f) export const whichType = typs => Returns(TypeOfTypes, item => { @@ -84,3 +105,17 @@ export const whichType = typs => Returns(TypeOfTypes, item => { } throw new TypeError(errorMsg) }) + +// The return typing strategies +// MAKE SURE NONE ARE FALSY, so that code can easily test whether a strategy +// has been specified. +export const ReturnTyping = Object.freeze({ + free: 1, + conservative: 2, + full: 3, + name(strat) { + for (const key in this) { + if (this[key] === strat) return key + } + } +}) diff --git a/src/core/TypeDispatcher.js b/src/core/TypeDispatcher.js index 2b997e6..c56e39d 100644 --- a/src/core/TypeDispatcher.js +++ b/src/core/TypeDispatcher.js @@ -3,7 +3,7 @@ import ArrayKeyedMap from 'array-keyed-map' import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js' import {Implementations, ImplementationsGenerator} from './Implementations.js' import {bootstrapTypes} from './type.js' -import {Returns, whichType, Type} from './Type.js' +import {Returns, ReturnTyping, whichType, Type} from './Type.js' import { matched, needsCollection, Passthru, Matcher, match } from './TypePatterns.js' @@ -155,7 +155,8 @@ export class TypeDispatcher { const types = args.map(thisTypeOf) return this.resolve(key, types)(...args) } - standard.resolve = (types) => this.resolve(key, types) + standard.resolve = + (types, strategy) => this.resolve(key, types, strategy) standard.isDispatcher = true Object.defineProperty(this, key, { enumerable: true, @@ -167,14 +168,18 @@ export class TypeDispatcher { if (typeof tryValue === 'object') { if (!('resolve' in tryValue)) { - tryValue.resolve = types => this.resolve(key, types) + tryValue.resolve = + (types, strat) => this.resolve(key, types, strat) } const get = () => { const keyDeps = this._dependencies.get(key) if (!keyDeps) return tryValue const watch = Array.from(keyDeps.keys().filter( - types => types.length === 0 - || this.resolve(key, types) === tryValue + bhvix => { + if (bhvix.length < 2) return true + const types = bhvix.slice(1) + return this.resolve(key, types) === tryValue + } )) if (watch.length) { return DependencyWatcher(tryValue, key, watch, this) @@ -221,7 +226,7 @@ export class TypeDispatcher { } } - _addToDeps(key, types) { + _addToDeps(key, bhvix) { // Never depend on internal methods: if (key.startsWith('_')) return let depMap = this._dependencies.get(key) @@ -229,11 +234,11 @@ export class TypeDispatcher { depMap = new ArrayKeyedMap() this._dependencies.set(key, depMap) } - if (!depMap.has(types)) depMap.set(types, new Map()) - const depColl = depMap.get(types) - for (const [dkey, types] of this.resolve._genDepsOf) { + if (!depMap.has(bhvix)) depMap.set(bhvix, new Map()) + const depColl = depMap.get(bhvix) + for (const [dkey, bhvix] of this.resolve._genDepsOf) { if (!depColl.has(dkey)) depColl.set(dkey, new ArrayKeyedMap()) - depColl.get(dkey).set(types, true) + depColl.get(dkey).set(bhvix, true) } } @@ -259,28 +264,42 @@ export class TypeDispatcher { return args => extractors.map(f => f(args)) } - resolve(key, types) { + // key is the identifier of the method being looked up + // types is a single argument type or an array of actual argument types + // strategy is a ReturnTyping value specifying how to choose the + // return type for the operation. + resolve(key, types, strategy) { if (!(key in this)) { throw new ReferenceError(`no method or value for key '${key}'`) } if (!Array.isArray(types)) types = [types] + if (!strategy) { + // Avoid recursing on obtaining config + if (key === 'config') strategy = ReturnTyping.free + else strategy = this.config.returnTyping + } + // The "behavior index": the return type strategy followed by the + // types: + const bhvix = [strategy, ...types] const generatingDeps = this.resolve._genDepsOf?.length - if (generatingDeps) this._addToDeps(key, types) + if (generatingDeps) this._addToDeps(key, bhvix) const behave = this._behaviors[key] // Return the cached resolution if it's there - if (behave.has(types)) { - const result = behave.get(types) + if (behave.has(bhvix)) { + const result = behave.get(bhvix) if (result === underResolution) { throw new ResolutionError( - `recursive resolution of ${key} on ${types}`) + `recursive resolution of ${key} on ${types} with return typing ` + + ReturnTyping.name(strategy) + ) } if (generatingDeps && typeof result === 'object' && !(result instanceof Type) ) { - return DependencyRecorder(result, key, this, types) + return DependencyRecorder(result, key, this, bhvix) } return result } @@ -315,12 +334,12 @@ export class TypeDispatcher { } // If this key is producing a non-function value, we're done if (!isPlainFunction(item)) { - behave.set(types, item) + behave.set(bhvix, item) if (generatingDeps && typeof item === 'object' && !(item instanceof Type) ) { - return DependencyRecorder(item, key, this, types) + return DependencyRecorder(item, key, this, bhvix) } return item } @@ -334,17 +353,19 @@ export class TypeDispatcher { let theBehavior = () => undefined let finalBehavior - this.resolve._genDepsOf.push([key, types]) - behave.set(types, underResolution) + this.resolve._genDepsOf.push([key, bhvix]) + behave.set(bhvix, underResolution) try { // Used to make sure not to return without popping _genDepsOf if (!('returns' in item)) { // looks like a factory try { theBehavior = item( DependencyRecorder(this, '', this, []), - matched(template, this)) + matched(template, this), + strategy) } catch (e) { e.message = `Error in factory for ${key} on ${types} ` + + `with return typing ${ReturnTyping.name(strategy)} ` + `(match data ${template}): ${e.message}` throw e } @@ -355,7 +376,8 @@ export class TypeDispatcher { const returning = theBehavior.returns if (!returning) { throw new TypeError( - `No return type specified for ${key} on ${types}`) + `No return type specified for ${key} on ${types} with` + + ` return typing ${ReturnTyping.name(strategy)}`) } if (needsCollection(template)) { // have to wrap the behavior to collect the actual arguments @@ -369,7 +391,7 @@ export class TypeDispatcher { } finally { this.resolve._genDepsOf.pop() // OK, now it's safe to return } - behave.set(types, finalBehavior) + behave.set(bhvix, finalBehavior) finalBehavior.template = template return finalBehavior } @@ -380,8 +402,8 @@ export class TypeDispatcher { _invalidate(depColl) { if (!depColl) return for (const [key, typeMap] of depColl) { - for (const types of typeMap.keys()) { - this._behaviors[key].delete(types) + for (const bhvix of typeMap.keys()) { + this._behaviors[key].delete(bhvix) } } } @@ -389,25 +411,26 @@ export class TypeDispatcher { _disengageFallback(key) { // We need to find all of the behaviors that currently rely on the // fallback, invalidate their dependencies, and remove them. - const fallTypes = [] + const fallIxes = [] const behs = this._behaviors[key] const imps = this._implementations[key] const deps = this._dependencies.get(key) - for (const types of behs.keys()) { + for (const bhvix of behs.keys()) { let fallsback = true for (const [pattern] of imps) { + const types = bhvix.length ? bhvix.slice(1) : bhvix const [finalIndex] = pattern.match(types) if (finalIndex === types.length) { fallsback = false break } } - if (fallsback) fallTypes.push(types) + if (fallsback) fallIxes.push(bhvix) } - for (const types of fallTypes) { - const depColl = deps?.get(types) + for (const bhvix of fallIxes) { + const depColl = deps?.get(bhvix) if (depColl?.size) this._invalidate(depColl) - behs.delete(types) + behs.delete(bhvix) } } @@ -415,20 +438,21 @@ export class TypeDispatcher { // like disengageFallback, just we have the offending pattern: const behs = this._behaviors[key] const deps = this._dependencies.get(key) - const patTypes = behs.keys().filter(types => { + const patIxes = behs.keys().filter(bhvix => { + const types = bhvix.length ? bhvix.slice(1) : bhvix const [finalIndex] = pattern.match(types) return finalIndex === types.length }) - for (const types of patTypes) { - const depColl = deps?.get(types) + for (const bhvix of patIxes) { + const depColl = deps?.get(bhvix) if (depColl?.size) this._invalidate(depColl) - behs.delete(types) + behs.delete(bhvix) } } } // Proxy that traps accesses and records dependencies on them -const DependencyRecorder = (object, path, repo, types) => new Proxy(object, { +const DependencyRecorder = (object, path, repo, bhvix) => new Proxy(object, { get(target, prop, receiver) { const result = Reflect.get(target, prop, receiver) // pass internal methods through, as well as resolve calls, @@ -443,27 +467,27 @@ const DependencyRecorder = (object, path, repo, types) => new Proxy(object, { // OK, it's not a method on a TypeDispatcher, it's some other kind of // value. So first record the dependency on prop at this path. const newPath = path ? [path, prop].join('.') : prop - repo._addToDeps(newPath, types) + repo._addToDeps(newPath, bhvix) // Now, if the result is an object, we may need to record further // dependencies on its properties (e.g. math.config.predictable) // So proxy the return value, except for types, which must maintain // strict referential identity: if (typeof result === 'object' && !(result instanceof Type)) { - return DependencyRecorder(result, newPath, repo, types) + return DependencyRecorder(result, newPath, repo, bhvix) } else return result } }) // The flip side: proxy that traps setting properties and invalidates things // that depend on them: -const DependencyWatcher = (object, path, typesList, repo) => new Proxy(object, { +const DependencyWatcher = (object, path, ixList, repo) => new Proxy(object, { set(target, prop, value, receiver) { // First see if this setting has any dependencies: const newPath = [path, prop].join('.') const depPerTypes = repo._dependencies.get(newPath) if (depPerTypes && Reflect.get(target, prop, receiver) !== value) { - for (const types of typesList) { - repo._invalidate(depPerTypes.get(types)) + for (const bhvix of ixList) { + repo._invalidate(depPerTypes.get(bhvix)) } } // Now we can just perform the setting @@ -474,7 +498,7 @@ const DependencyWatcher = (object, path, typesList, repo) => new Proxy(object, { const result = Reflect.get(target, prop, receiver) if (typeof result === 'object' && !(result instanceof Type)) { const newPath = [path, prop].join('.') - return DependencyWatcher(result, newPath, typesList, repo) + return DependencyWatcher(result, newPath, ixList, repo) } return result } diff --git a/src/core/TypePatterns.js b/src/core/TypePatterns.js index 14f41c0..0705894 100644 --- a/src/core/TypePatterns.js +++ b/src/core/TypePatterns.js @@ -2,7 +2,7 @@ import {Type, Undefined} from './Type.js' import {isPlainFunction} from './helpers.js' export class TypePattern { - match(typeSequence, options={}) { + match(_typeSequence, _options={}) { throw new Error('Specific TypePatterns must implement match') } sampleTypes() { diff --git a/src/core/__test__/Type.spec.js b/src/core/__test__/Type.spec.js index 84efb23..4570333 100644 --- a/src/core/__test__/Type.spec.js +++ b/src/core/__test__/Type.spec.js @@ -2,7 +2,7 @@ import assert from 'assert' import math from '#nanomath' import {NumberT} from '#number/NumberT.js' -import {Returns} from '../Type.js' +import {Returns, ReturnTyping} from '../Type.js' import {isPlainFunction} from '../helpers.js' describe('Core types', () => { @@ -40,4 +40,7 @@ describe('Core types', () => { assert(isPlainFunction(labeledF)) }) + it('provides return typing strategies', () => { + assert.strictEqual(ReturnTyping.name(ReturnTyping.full), 'full') + }) }) diff --git a/src/core/__test__/TypeDispatcher.spec.js b/src/core/__test__/TypeDispatcher.spec.js index c843e59..e8e1cf6 100644 --- a/src/core/__test__/TypeDispatcher.spec.js +++ b/src/core/__test__/TypeDispatcher.spec.js @@ -6,7 +6,7 @@ import * as numbers from '#number/all.js' import {NumberT} from '#number/NumberT.js' import {ResolutionError} from "#core/helpers.js" import {match, Any} from "#core/TypePatterns.js" -import {Returns, NotAType} from "#core/Type.js" +import {NotAType, Returns, ReturnTyping} from "#core/Type.js" import {plain} from "#number/helpers.js" describe('TypeDispatcher', () => { @@ -57,12 +57,15 @@ describe('TypeDispatcher', () => { it('detects dependencies on conversion operations', () => { const bgn = new TypeDispatcher(booleans, generics, numbers) const {BooleanT, NumberT} = bgn.types - assert(!bgn._behaviors.negate.has([BooleanT])) + assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT])) assert.strictEqual(bgn.negate(true), -1) - assert(bgn._behaviors.negate.has([BooleanT])) - const deps = bgn._dependencies.negate + assert(bgn._behaviors.negate.has([ReturnTyping.free, BooleanT])) + const deps = bgn._dependencies + .get('number') + .get([ReturnTyping.free, BooleanT]) + assert(deps.has('negate')) bgn.merge({number: match([BooleanT], Returns(NumberT, b => b ? 2 : 0))}) - assert(!bgn._behaviors.negate.has([BooleanT])) + assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT])) assert.strictEqual(bgn.negate(true), -2) }) it('disallows merging NotAType', () => { diff --git a/src/core/config.js b/src/core/config.js new file mode 100644 index 0000000..3c26db5 --- /dev/null +++ b/src/core/config.js @@ -0,0 +1,11 @@ +import {ImplementationsGenerator} from './Implementations.js' +import {ReturnTyping} from './Type.js' +import {match, Passthru} from './TypePatterns.js' + +export const config = new ImplementationsGenerator(() => match(Passthru, { + // default comparison tolerances: + relTol: 1e-12, + absTol: 1e-15, + // Strategy for choosing operation return types: + returnTyping: ReturnTyping.free, +})) diff --git a/src/core/type.js b/src/core/type.js index 7452dd4..e5f4ecf 100644 --- a/src/core/type.js +++ b/src/core/type.js @@ -1,3 +1,4 @@ +import {config} from './config.js' import {ImplementationsGenerator} from './Implementations.js' import {Type, TypeOfTypes, Undefined, whichType} from './Type.js' import {match, Passthru} from './TypePatterns.js' @@ -22,5 +23,5 @@ export const types = new ImplementationsGenerator(() => match(Passthru, {})) // an explicitly ordered export of implementations for this sake: export const bootstrapTypes = { - types, Type, Undefined, TypeOfTypes, typeOf + types, config, Type, Undefined, TypeOfTypes, typeOf } diff --git a/src/generic/__test__/arithmetic.spec.js b/src/generic/__test__/arithmetic.spec.js index 6ccb17c..e4a71c0 100644 --- a/src/generic/__test__/arithmetic.spec.js +++ b/src/generic/__test__/arithmetic.spec.js @@ -1,11 +1,18 @@ import assert from 'assert' import math from '#nanomath' +import {ReturnTyping} from '#core/Type.js' + +const {Complex, NumberT} = math.types describe('generic arithmetic', () => { it('squares anything', () => { - assert.strictEqual(math.square(7), 49) - assert.strictEqual( - math.square.resolve([math.types.NumberT]).returns, - math.types.NumberT) + const sq = math.square + assert.strictEqual(sq(7), 49) + assert.strictEqual(math.square.resolve([NumberT]).returns, NumberT) + assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24)) + const eyes = math.complex(0, 2) + assert.strictEqual(sq(eyes), -4) + const sqFull = math.square.resolve(Complex(NumberT), ReturnTyping.full) + assert.deepStrictEqual(sqFull(eyes), math.complex(-4, 0)) }) }) diff --git a/src/generic/__test__/utils.spec.js b/src/generic/__test__/utils.spec.js index f8b6d53..41efea2 100644 --- a/src/generic/__test__/utils.spec.js +++ b/src/generic/__test__/utils.spec.js @@ -22,4 +22,7 @@ describe('generic utility functions', () => { assert(isReal(math.complex(-3.25, 4e-16))) assert(!isReal(math.complex(3, 4))) }) + it('tests for no imaginary part', () => { + assert(math.nonImaginary(true)) + }) }) diff --git a/src/generic/all.js b/src/generic/all.js index 4dea4d2..89d8802 100644 --- a/src/generic/all.js +++ b/src/generic/all.js @@ -1,4 +1,3 @@ export * as arithmetic from './arithmetic.js' -export * as configuration from './config.js' export * as relational from './relational.js' export * as utilities from './utils.js' diff --git a/src/generic/arithmetic.js b/src/generic/arithmetic.js index cf67fdc..d561912 100644 --- a/src/generic/arithmetic.js +++ b/src/generic/arithmetic.js @@ -1,8 +1,15 @@ -import {Returns} from '#core/Type.js' +import {ReturnsAs} from './helpers.js' + +import {Returns, ReturnTyping} from '#core/Type.js' import {match, Any} from '#core/TypePatterns.js' -export const conj = match(Any, (_math, T) => Returns(T, a => a)) -export const square = match(Any, (math, T) => { - const mult = math.multiply.resolve([T, T]) - return Returns(mult.returns, a => mult(a, a)) +export const abs = match(Any, (math, T) => { + const absq = math.absquare.resolve(T) + const sqrt = math.sqrt.resolve(absq.returns, ReturnTyping.conservative) + return ReturnsAs(sqrt, t => sqrt(absq(t))) +}) +export const conj = match(Any, (_math, T) => Returns(T, t => t)) +export const square = match(Any, (math, T, strategy) => { + const mult = math.multiply.resolve([T, T], strategy) + return Returns(mult.returns, t => mult(t, t)) }) diff --git a/src/generic/config.js b/src/generic/config.js deleted file mode 100644 index 72f8ced..0000000 --- a/src/generic/config.js +++ /dev/null @@ -1,5 +0,0 @@ -import {ImplementationsGenerator} from '#core/Implementations.js' -import {match, Passthru} from '#core/TypePatterns.js' - -export const config = new ImplementationsGenerator( - () => match(Passthru, {relTol: 1e-12, absTol: 1e-15})) diff --git a/src/generic/relational.js b/src/generic/relational.js index bd23f92..2d70a1e 100644 --- a/src/generic/relational.js +++ b/src/generic/relational.js @@ -1,8 +1,10 @@ import {ReturnsAs} from './helpers.js' -import {Returns} from '#core/Type.js' +import {Returns, ReturnTyping} from '#core/Type.js' import {Any, Passthru, match, matched} from '#core/TypePatterns.js' import {boolnum} from '#number/helpers.js' +const {full} = ReturnTyping + export const equal = match([Any, Any], (math, [T, U]) => { // Finding the correct signature of `indistinguishable` to use for // testing (approximate) equality is tricky, because T or U might @@ -12,7 +14,7 @@ export const equal = match([Any, Any], (math, [T, U]) => { // the matching type, and then we look up with tolerances. let exactChecker try { - exactChecker = math.indistinguishable.resolve([T, U]) + exactChecker = math.indistinguishable.resolve([T, U], full) } catch { // can't compare, so no way they can be equal return boolnum(() => false)(math) } @@ -25,7 +27,7 @@ export const equal = match([Any, Any], (math, [T, U]) => { const {relTol, absTol} = typeConfig const RT = math.typeOf(relTol) const AT = math.typeOf(absTol) - const approx = math.indistinguishable.resolve([T, U, RT, AT]) + const approx = math.indistinguishable.resolve([T, U, RT, AT], full) return ReturnsAs( approx, (t, u) => approx(t, u, relTol, absTol)) } catch {} // fall through to case with no tolerances @@ -86,6 +88,12 @@ export const larger = match([Any, Any], (math, [T, U]) => { return boolnum((t, u) => !eq(t, u) && bigger(t, u))(math) }) +export const isPositive = match(Any, (math, T) => { + const zero = math.zero(T) + const larger = math.larger.resolve([T, T]) + return boolnum(t => larger(t, zero)) +}) + export const largerEq = match([Any, Any], (math, [T, U]) => { const eq = math.equal.resolve([T, U]) const bigger = math.exceeds.resolve([T, U]) diff --git a/src/generic/utils.js b/src/generic/utils.js index d696d6c..716609e 100644 --- a/src/generic/utils.js +++ b/src/generic/utils.js @@ -1,10 +1,15 @@ import {ReturnsAs} from './helpers.js' import {ResolutionError} from '#core/helpers.js' -import {Passthru, match} from '#core/TypePatterns.js' +import {Returns} from '#core/Type.js' +import {Any, Passthru, match} from '#core/TypePatterns.js' import {boolnum} from '#number/helpers.js' -// Most types are real. Have to make sure to redefine on all non-real types -export const isReal = match(Passthru, boolnum(() => true)) +// Most types are real, so we just define them that way generically. +// We have to make sure to redefine isReal on all non-real types. +// We use Any here so that it will match before a specific type matches +// with conversion; such a match runs the risk of not producing the correct +// result, or worse, leading to a resolution loop. +export const isReal = match(Any, boolnum(() => true)) export const isZero = match(Passthru, (math, [T]) => { if (!T) { // called with no arguments throw new ResolutionError('isZero() requires one argument') @@ -13,3 +18,9 @@ export const isZero = match(Passthru, (math, [T]) => { const eq = math.equal.resolve([T, T]) return ReturnsAs(eq, x => eq(z, x)) }) +export const nonImaginary = match(Passthru, boolnum(() => true)) +export const re = match(Any, (_math, T) => Returns(T, t => t)) +export const im = match(Any, (math, T) => { + const z = math.zero(T) + return Returns(T, () => z) +}) diff --git a/src/number/__test__/arithmetic.spec.js b/src/number/__test__/arithmetic.spec.js index 7d368cb..6d718ef 100644 --- a/src/number/__test__/arithmetic.spec.js +++ b/src/number/__test__/arithmetic.spec.js @@ -1,5 +1,6 @@ import assert from 'assert' import math from '#nanomath' +import {ReturnTyping} from '#core/Type.js' describe('number arithmetic', () => { it('supports basic operations', () => { @@ -14,4 +15,18 @@ describe('number arithmetic', () => { assert.strictEqual(math.subtract(4, 2), 2) assert.strictEqual(math.quotient(7, 3), 2) }) + it('takes square root of numbers appropriately', () => { + assert(isNaN(math.sqrt(NaN))) + assert.strictEqual(math.sqrt(4), 2) + assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2)) + math.config.returnTyping = ReturnTyping.conservative + assert(isNaN(math.sqrt(NaN))) + assert.strictEqual(math.sqrt(4), 2) + assert(isNaN(math.sqrt(-4))) + math.config.returnTyping = ReturnTyping.full + assert(isNaN(math.sqrt(NaN))) + assert.deepStrictEqual(math.sqrt(4), math.complex(2, 0)) + assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2)) + math.config.returnTyping = ReturnTyping.free + }) }) diff --git a/src/number/__test__/utils.spec.js b/src/number/__test__/utils.spec.js index 054a30a..99e11b0 100644 --- a/src/number/__test__/utils.spec.js +++ b/src/number/__test__/utils.spec.js @@ -10,4 +10,11 @@ describe('number utilities', () => { assert.strictEqual(math.isnan(Infinity), false) assert.strictEqual(math.isnan(43), false) }) + it('tests if a number is an integer', () => { + assert(math.isInteger(7)) + assert(math.isInteger(7+5e-16)) + assert(!math.isInteger(7.000001)) + assert(!math.isInteger(-Infinity)) + assert(!math.isInteger(NaN)) + }) }) diff --git a/src/number/arithmetic.js b/src/number/arithmetic.js index 662a157..0b5c66f 100644 --- a/src/number/arithmetic.js +++ b/src/number/arithmetic.js @@ -1,4 +1,10 @@ import {plain} from './helpers.js' +import {NumberT} from './NumberT.js' +import {OneOf, Returns, ReturnTyping} from '#core/Type.js' +import {match} from '#core/TypePatterns.js' +import {Complex} from '#complex/Complex.js' + +const {conservative, full} = ReturnTyping export const abs = plain(Math.abs) export const absquare = plain(a => a*a) @@ -18,5 +24,25 @@ export const cbrt = plain(a => { export const invert = plain(a => 1/a) export const multiply = plain((a, b) => a * b) export const negate = plain(a => -a) + +export const sqrt = match(NumberT, (math, _N, strategy) => { + if (!math.types.Complex || strategy === conservative) { + return Returns(NumberT, Math.sqrt) + } + const cplx = math.complex.resolve([NumberT, NumberT], full) + if (strategy === full) { + const cnan = math.nan(Complex(NumberT)) + return Returns(Complex(NumberT), a => { + if (isNaN(a)) return cnan + return a >= 0 ? cplx(Math.sqrt(a), 0) : cplx(0, Math.sqrt(-a)) + }) + } + // strategy === free, return "best" type + return Returns(OneOf(NumberT, Complex(NumberT)), a => { + if (isNaN(a)) return NaN + return a >= 0 ? Math.sqrt(a) : cplx(0, Math.sqrt(-a)) + }) +}) + export const subtract = plain((a, b) => a - b) export const quotient = plain((a,b) => Math.floor(a/b)) diff --git a/src/number/relational.js b/src/number/relational.js index a4820a6..2d2e8df 100644 --- a/src/number/relational.js +++ b/src/number/relational.js @@ -1,4 +1,3 @@ -import {Returns} from '#core/Type.js' import {match, Optional} from '#core/TypePatterns.js' import {boolnum} from './helpers.js' import {NumberT} from './NumberT.js' diff --git a/src/number/utils.js b/src/number/utils.js index 01fdbe4..7d7f31b 100644 --- a/src/number/utils.js +++ b/src/number/utils.js @@ -1,8 +1,13 @@ import {plain, boolnum} from './helpers.js' import {NumberT} from './NumberT.js' -import {Returns} from '#core/Type.js' import {match} from '#core/TypePatterns.js' export const clone = plain(a => a) +export const isfinite = match(NumberT, boolnum(isFinite)) +export const isInteger = match(NumberT, math => { + const finite = math.isfinite.resolve(NumberT) + const eq = math.equal.resolve([NumberT, NumberT]) + return boolnum(a => finite(a) && eq(a, Math.round(a)))(math) +}) export const isnan = match(NumberT, boolnum(isNaN)) diff --git a/src/package.json b/src/package.json index 290fb63..2e7d812 100644 --- a/src/package.json +++ b/src/package.json @@ -1,8 +1,9 @@ { "imports" : { "#nanomath": "./nanomath.js", - "#boolean/*.js": "./boolean/*.js", "#core/*.js": "./core/*.js", + "#boolean/*.js": "./boolean/*.js", + "#complex/*.js": "./complex/*.js", "#generic/*.js": "./generic/*.js", "#number/*.js": "./number/*.js" },