├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── LICENCE ├── README.md ├── gleam.toml ├── manifest.toml ├── src ├── gleam_community │ └── maths.gleam └── maths.mjs └── test ├── gleam_community ├── arithmetics_test.gleam ├── combinatorics_test.gleam ├── conversion_test.gleam ├── elementary_test.gleam ├── metrics_test.gleam ├── piecewise_test.gleam ├── predicates_test.gleam ├── sequences_test.gleam └── special_test.gleam └── gleam_community_maths_test.gleam /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: ["v*"] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - uses: erlef/setup-beam@v1 15 | with: 16 | otp-version: "27.0" 17 | gleam-version: "1.8.0" 18 | 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: "20.x" 22 | 23 | - run: | 24 | version="v$(cat gleam.toml | grep -m 1 "version" | sed -r "s/version *= *\"([[:digit:].]+)\"/\1/")" 25 | if [ "$version" != "${{ github.ref_name }}" ]; then 26 | echo "tag '${{ github.ref_name }}' does not match the version in gleam.toml" 27 | echo "expected a tag name 'v$version'" 28 | exit 1 29 | fi 30 | name: check version 31 | 32 | - run: gleam format --check 33 | 34 | - run: gleam test --target erlang 35 | - run: gleam test --target javascript 36 | 37 | - run: gleam publish -y 38 | env: 39 | HEXPM_USER: ${{ secrets.HEX_USERNAME }} 40 | HEXPM_PASS: ${{ secrets.HEX_PASSWORD }} 41 | 42 | - uses: softprops/action-gh-release@v1 43 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["**"] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: erlef/setup-beam@v1 21 | with: 22 | otp-version: "27.0" 23 | gleam-version: "1.8.0" 24 | 25 | - uses: actions/setup-node@v3 26 | with: 27 | node-version: "20.x" 28 | 29 | - run: gleam format --check 30 | 31 | - run: gleam test --target erlang 32 | - run: gleam test --target javascript 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | build 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @NicklasXYZ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting one of the organizers or moderators of the gleam-community 59 | organization. All complaints will be reviewed and investigated and will result in a 60 | response that is deemed necessary and appropriate to the circumstances. The project team 61 | is obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is a direct decendant of the [Gleam Code of Conduct][gleam coc], 71 | which is itself a decendant of the [Contributor Covenant (v1.4)][cc]. 72 | 73 | [gleam coc]: https://github.com/gleam-lang/gleam/blob/f793b5d28a3102276a8b861c7e16a19c5231426e/CODE_OF_CONDUCT.md 74 | [cc]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2023 Gleam Community Contributors 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gleam-community/maths 2 | 3 | [![Package Version](https://img.shields.io/hexpm/v/gleam_community_maths)](https://hex.pm/packages/gleam_community_maths) 4 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_community_maths/) 5 | 6 | A basic mathematics library that contains some of the most fundamental mathematics functions and utilities. 7 | 8 | The library supports both targets: Erlang and JavaScript. 9 | 10 | ## Quickstart 11 | 12 | ```gleam 13 | import gleam/float 14 | import gleam/list 15 | import gleam/yielder 16 | import gleam_community/maths 17 | import gleeunit/should 18 | 19 | pub fn example() { 20 | // Evaluate the sine function 21 | let result = maths.sin(maths.pi()) 22 | // Set the relative and absolute tolerance 23 | let assert Ok(absolute_tol) = float.power(10.0, -6.0) 24 | let relative_tol = 0.0 25 | // Check that the value is very close to 0.0 26 | // That is, if 'result' is within +/- 10^(-6) 27 | maths.is_close(result, 0.0, relative_tol, absolute_tol) 28 | |> should.be_true() 29 | 30 | // Find the greatest common divisor 31 | maths.gcd(54, 24) 32 | |> should.equal(6) 33 | 34 | // Determine if 999983 is a prime number 35 | maths.is_prime(999_983) 36 | |> should.equal(True) 37 | 38 | // Find the minimum and maximum of a list 39 | maths.extrema([10.0, 3.0, 50.0, 20.0, 3.0], float.compare) 40 | |> should.equal(Ok(#(3.0, 50.0))) 41 | 42 | // Determine if a number is fractional 43 | maths.is_fractional(0.3333) 44 | |> should.equal(True) 45 | 46 | // Generate all k = 2 combinations of [1, 2, 3] 47 | let assert Ok(combinations) = maths.list_combination([1, 2, 3], 2) 48 | combinations 49 | |> yielder.to_list() 50 | |> should.equal([[1, 2], [1, 3], [2, 3]]) 51 | 52 | // Compute the Cosine Similarity between two (orthogonal) "vectors" 53 | maths.cosine_similarity([#(-1.0, 1.0), #(1.0, 1.0), #(0.0, -1.0)]) 54 | |> should.equal(Ok(0.0)) 55 | 56 | // Generate a list of 3 logarithmically-spaced points over a specified 57 | // interval, i.e., [10^1, 10^3] 58 | let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, 10.0) 59 | let pairs = logspace |> list.zip([10.0, 100.0, 1000.0]) 60 | pairs 61 | |> list.all(fn(x) { x.0 == x.1 }) 62 | |> should.be_true() 63 | } 64 | 65 | ``` 66 | 67 | ## Installation 68 | 69 | `gleam_community` packages are published to [hex.pm](https://hex.pm/packages/gleam_community_maths) 70 | with the prefix `gleam_community_`. You can add them to your Gleam projects directly: 71 | 72 | ```sh 73 | gleam add gleam_community_maths 74 | ``` 75 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "gleam_community_maths" 2 | version = "2.0.0" 3 | 4 | licences = ["Apache-2.0"] 5 | description = "A basic maths library" 6 | repository = { type = "github", user = "gleam-community", repo = "maths" } 7 | gleam = ">= 0.32.0" 8 | 9 | [dependencies] 10 | gleam_stdlib = "~> 0.38" 11 | gleam_yielder = ">= 1.1.0 and < 2.0.0" 12 | 13 | [dev-dependencies] 14 | gleeunit = "~> 1.0" 15 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" }, 6 | { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 7 | { name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" }, 8 | ] 9 | 10 | [requirements] 11 | gleam_stdlib = { version = "~> 0.38" } 12 | gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" } 13 | gleeunit = { version = "~> 1.0" } 14 | -------------------------------------------------------------------------------- /src/maths.mjs: -------------------------------------------------------------------------------- 1 | export function sin(float) { 2 | return Math.sin(float) 3 | } 4 | 5 | export function pi() { 6 | return Math.PI 7 | } 8 | 9 | export function acos(float) { 10 | return Math.acos(float) 11 | } 12 | 13 | export function acosh(float) { 14 | return Math.acosh(float) 15 | } 16 | 17 | export function asin(float) { 18 | return Math.asin(float) 19 | } 20 | 21 | export function asinh(float) { 22 | return Math.asinh(float) 23 | } 24 | 25 | export function atan(float) { 26 | return Math.atan(float) 27 | } 28 | 29 | export function tan(float) { 30 | return Math.tan(float) 31 | } 32 | 33 | export function atan2(floaty, floatx) { 34 | return Math.atan2(floaty, floatx) 35 | } 36 | 37 | export function atanh(float) { 38 | return Math.atanh(float) 39 | } 40 | 41 | export function cos(float) { 42 | return Math.cos(float) 43 | } 44 | 45 | export function cosh(float) { 46 | return Math.cosh(float) 47 | } 48 | 49 | export function exponential(float) { 50 | return Math.exp(float) 51 | } 52 | 53 | export function ceiling(float) { 54 | return Math.ceil(float) 55 | } 56 | 57 | export function floor(float) { 58 | return Math.floor(float) 59 | } 60 | 61 | export function power(base, exponent) { 62 | return Math.pow(base, exponent) 63 | } 64 | 65 | export function logarithm(float) { 66 | return Math.log(float) 67 | } 68 | 69 | export function logarithm_10(float) { 70 | return Math.log10(float) 71 | } 72 | 73 | export function logarithm_2(float) { 74 | return Math.log2(float) 75 | } 76 | 77 | export function sinh(float) { 78 | return Math.sinh(float) 79 | } 80 | 81 | export function tanh(float) { 82 | return Math.tanh(float) 83 | } 84 | 85 | export function sign(float) { 86 | return Math.sign(float) 87 | } 88 | 89 | export function truncate(float) { 90 | return Math.trunc(float) 91 | } 92 | 93 | export function to_int(float) { 94 | return Math.trunc(float) 95 | } 96 | -------------------------------------------------------------------------------- /test/gleam_community/arithmetics_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam_community/maths 3 | import gleeunit/should 4 | 5 | pub fn int_gcd_test() { 6 | maths.gcd(1, 1) 7 | |> should.equal(1) 8 | 9 | maths.gcd(100, 10) 10 | |> should.equal(10) 11 | 12 | maths.gcd(10, 100) 13 | |> should.equal(10) 14 | 15 | maths.gcd(100, -10) 16 | |> should.equal(10) 17 | 18 | maths.gcd(-36, -17) 19 | |> should.equal(1) 20 | 21 | maths.gcd(-30, -42) 22 | |> should.equal(6) 23 | } 24 | 25 | pub fn euclidean_modulo_test() { 26 | // Base Case: Positive x, Positive y 27 | // Note that the truncated, floored, and euclidean 28 | // definitions should agree for this base case 29 | maths.euclidean_modulo(15, 4) 30 | |> should.equal(3) 31 | 32 | // Case: Positive x, Negative y 33 | maths.euclidean_modulo(15, -4) 34 | |> should.equal(3) 35 | 36 | // Case: Negative x, Positive y 37 | maths.euclidean_modulo(-15, 4) 38 | |> should.equal(1) 39 | 40 | // Case: Negative x, Negative y 41 | maths.euclidean_modulo(-15, -4) 42 | |> should.equal(1) 43 | 44 | // Case: Positive x, Zero y 45 | maths.euclidean_modulo(5, 0) 46 | |> should.equal(0) 47 | 48 | // Case: Zero x, Negative y 49 | maths.euclidean_modulo(0, 5) 50 | |> should.equal(0) 51 | } 52 | 53 | pub fn lcm_test() { 54 | maths.lcm(1, 1) 55 | |> should.equal(1) 56 | 57 | maths.lcm(100, 10) 58 | |> should.equal(100) 59 | 60 | maths.lcm(10, 100) 61 | |> should.equal(100) 62 | 63 | maths.lcm(100, -10) 64 | |> should.equal(100) 65 | 66 | maths.lcm(-36, -17) 67 | |> should.equal(612) 68 | 69 | maths.lcm(-30, -42) 70 | |> should.equal(210) 71 | } 72 | 73 | pub fn proper_divisors_test() { 74 | maths.proper_divisors(2) 75 | |> should.equal([1]) 76 | 77 | maths.proper_divisors(6) 78 | |> should.equal([1, 2, 3]) 79 | 80 | maths.proper_divisors(13) 81 | |> should.equal([1]) 82 | 83 | maths.proper_divisors(18) 84 | |> should.equal([1, 2, 3, 6, 9]) 85 | 86 | maths.proper_divisors(8128) 87 | |> should.equal([1, 2, 4, 8, 16, 32, 64, 127, 254, 508, 1016, 2032, 4064]) 88 | } 89 | 90 | pub fn divisors_test() { 91 | maths.divisors(2) 92 | |> should.equal([1, 2]) 93 | 94 | maths.divisors(6) 95 | |> should.equal([1, 2, 3, 6]) 96 | 97 | maths.divisors(13) 98 | |> should.equal([1, 13]) 99 | 100 | maths.divisors(18) 101 | |> should.equal([1, 2, 3, 6, 9, 18]) 102 | 103 | maths.divisors(8128) 104 | |> should.equal([ 105 | 1, 2, 4, 8, 16, 32, 64, 127, 254, 508, 1016, 2032, 4064, 8128, 106 | ]) 107 | } 108 | 109 | pub fn list_cumulative_sum_test() { 110 | // An empty lists returns an empty list 111 | [] 112 | |> maths.cumulative_sum() 113 | |> should.equal([]) 114 | 115 | // Valid input returns a result 116 | [1.0, 2.0, 3.0] 117 | |> maths.cumulative_sum() 118 | |> should.equal([1.0, 3.0, 6.0]) 119 | 120 | [-2.0, 4.0, 6.0] 121 | |> maths.cumulative_sum() 122 | |> should.equal([-2.0, 2.0, 8.0]) 123 | } 124 | 125 | pub fn int_list_cumulative_sum_test() { 126 | // An empty lists returns an empty list 127 | [] 128 | |> maths.int_cumulative_sum() 129 | |> should.equal([]) 130 | 131 | // Valid input returns a result 132 | [1, 2, 3] 133 | |> maths.int_cumulative_sum() 134 | |> should.equal([1, 3, 6]) 135 | 136 | [-2, 4, 6] 137 | |> maths.int_cumulative_sum() 138 | |> should.equal([-2, 2, 8]) 139 | } 140 | 141 | pub fn list_cumulative_product_test() { 142 | // An empty lists returns an empty list 143 | [] 144 | |> maths.cumulative_product() 145 | |> should.equal([]) 146 | 147 | // Valid input returns a result 148 | [1.0, 2.0, 3.0] 149 | |> maths.cumulative_product() 150 | |> should.equal([1.0, 2.0, 6.0]) 151 | 152 | [-2.0, 4.0, 6.0] 153 | |> maths.cumulative_product() 154 | |> should.equal([-2.0, -8.0, -48.0]) 155 | } 156 | 157 | pub fn int_list_cumulative_product_test() { 158 | // An empty lists returns an empty list 159 | [] 160 | |> maths.int_cumulative_product() 161 | |> should.equal([]) 162 | 163 | // Valid input returns a result 164 | [1, 2, 3] 165 | |> maths.int_cumulative_product() 166 | |> should.equal([1, 2, 6]) 167 | 168 | [-2, 4, 6] 169 | |> maths.int_cumulative_product() 170 | |> should.equal([-2, -8, -48]) 171 | } 172 | 173 | pub fn weighted_product_test() { 174 | [] 175 | |> maths.weighted_product() 176 | |> should.equal(Ok(1.0)) 177 | 178 | [#(1.0, -1.0), #(2.0, -1.0), #(3.0, -1.0)] 179 | |> maths.weighted_product() 180 | |> should.equal(Error(Nil)) 181 | 182 | [#(1.0, 0.0), #(2.0, 0.0), #(3.0, 0.0)] 183 | |> maths.weighted_product() 184 | |> should.equal(Ok(1.0)) 185 | 186 | [#(1.0, 1.0), #(2.0, 1.0), #(3.0, 1.0)] 187 | |> maths.weighted_product() 188 | |> should.equal(Ok(6.0)) 189 | 190 | let assert Ok(tolerance) = float.power(10.0, -6.0) 191 | let assert Ok(result) = 192 | [#(9.0, 0.5), #(10.0, 0.5), #(10.0, 0.5)] 193 | |> maths.weighted_product() 194 | result 195 | |> maths.is_close(30.0, 0.0, tolerance) 196 | |> should.be_true() 197 | } 198 | 199 | pub fn weighted_sum_test() { 200 | [] 201 | |> maths.weighted_sum() 202 | |> should.equal(Ok(0.0)) 203 | 204 | [#(1.0, -1.0), #(2.0, -1.0), #(3.0, -1.0)] 205 | |> maths.weighted_sum() 206 | |> should.equal(Error(Nil)) 207 | 208 | [#(1.0, 0.0), #(2.0, 0.0), #(3.0, 0.0)] 209 | |> maths.weighted_sum() 210 | |> should.equal(Ok(0.0)) 211 | 212 | [#(1.0, 1.0), #(2.0, 1.0), #(3.0, 1.0)] 213 | |> maths.weighted_sum() 214 | |> should.equal(Ok(6.0)) 215 | 216 | [#(9.0, 0.5), #(10.0, 0.5), #(10.0, 0.5)] 217 | |> maths.weighted_sum() 218 | |> should.equal(Ok(14.5)) 219 | } 220 | -------------------------------------------------------------------------------- /test/gleam_community/combinatorics_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/list 2 | import gleam/set 3 | import gleam/yielder 4 | import gleam_community/maths 5 | import gleeunit/should 6 | 7 | pub fn int_factorial_test() { 8 | // Invalid input gives an error (factorial of negative number) 9 | maths.factorial(-1) 10 | |> should.be_error() 11 | 12 | // Valid inputs for factorial of small numbers 13 | maths.factorial(0) 14 | |> should.equal(Ok(1)) 15 | 16 | maths.factorial(1) 17 | |> should.equal(Ok(1)) 18 | 19 | maths.factorial(2) 20 | |> should.equal(Ok(2)) 21 | 22 | maths.factorial(3) 23 | |> should.equal(Ok(6)) 24 | 25 | maths.factorial(4) 26 | |> should.equal(Ok(24)) 27 | } 28 | 29 | pub fn int_combination_with_repetitions_test() { 30 | // Invalid input: k < 0 should return an error 31 | maths.combination_with_repetitions(1, -1) 32 | |> should.be_error() 33 | 34 | // Invalid input: n < 0 should return an error 35 | maths.combination_with_repetitions(-1, 1) 36 | |> should.be_error() 37 | 38 | // Valid input: k > n with repetition allowed 39 | maths.combination_with_repetitions(2, 3) 40 | |> should.equal(Ok(4)) 41 | 42 | maths.combination_with_repetitions(4, 0) 43 | |> should.equal(Ok(1)) 44 | 45 | // Valid input: k = n with repetition allows more combinations 46 | maths.combination_with_repetitions(4, 4) 47 | |> should.equal(Ok(35)) 48 | 49 | maths.combination_with_repetitions(4, 2) 50 | |> should.equal(Ok(10)) 51 | 52 | maths.combination_with_repetitions(7, 5) 53 | |> should.equal(Ok(462)) 54 | // NOTE: Tests with the 'combination' function that produce values that exceed 55 | // precision of the JavaScript 'Number' primitive will result in errors 56 | } 57 | 58 | pub fn int_combination_without_repetitions_test() { 59 | // Valid input: k > n without repetition gives 0 combinations 60 | maths.combination(2, 3) 61 | |> should.equal(Ok(0)) 62 | maths.combination(10, 20) 63 | |> should.equal(Ok(0)) 64 | 65 | // Valid input: k = n without repetition gives 1 combination 66 | maths.combination(4, 4) 67 | |> should.equal(Ok(1)) 68 | 69 | // Valid input: zero combinations (k=0) should always yield 1 combination 70 | maths.combination(4, 0) 71 | |> should.equal(Ok(1)) 72 | 73 | // Valid input: k < n without and with repetition 74 | maths.combination(4, 2) 75 | |> should.equal(Ok(6)) 76 | 77 | // Valid input with larger values of n and k 78 | maths.combination(7, 5) 79 | |> should.equal(Ok(21)) 80 | } 81 | 82 | pub fn math_permutation_with_repetitions_test() { 83 | // Invalid input: k < 0 should return an error 84 | maths.permutation_with_repetitions(1, -1) 85 | |> should.be_error() 86 | 87 | // Valid input: k > n with repetition allowed gives non-zero permutations 88 | maths.permutation_with_repetitions(2, 3) 89 | |> should.equal(Ok(8)) 90 | 91 | maths.permutation_with_repetitions(4, 0) 92 | |> should.equal(Ok(1)) 93 | 94 | // Valid input: k = n permutations with repetition 95 | maths.permutation_with_repetitions(4, 4) 96 | |> should.equal(Ok(256)) 97 | 98 | maths.permutation_with_repetitions(4, 2) 99 | |> should.equal(Ok(16)) 100 | 101 | maths.permutation_with_repetitions(6, 2) 102 | |> should.equal(Ok(36)) 103 | 104 | maths.permutation_with_repetitions(6, 3) 105 | |> should.equal(Ok(216)) 106 | } 107 | 108 | pub fn math_permutation_without_repetitions_test() { 109 | // Invalid input: k < 0 should return an error 110 | maths.permutation(1, -1) 111 | |> should.be_error() 112 | 113 | // Invalid input: n < 0 should return an error 114 | maths.permutation(-1, 1) 115 | |> should.be_error() 116 | 117 | // Valid input: k > n without repetition gives 0 permutations 118 | maths.permutation(2, 3) 119 | |> should.equal(Ok(0)) 120 | 121 | // Valid input: k = 0 should always yield 1 permutation 122 | maths.permutation(4, 0) 123 | |> should.equal(Ok(1)) 124 | 125 | // Valid input: k = n permutations without repetition 126 | maths.permutation(4, 4) 127 | |> should.equal(Ok(24)) 128 | 129 | // Valid input: k < n permutations without and with repetition 130 | maths.permutation(4, 2) 131 | |> should.equal(Ok(12)) 132 | 133 | // Valid input with larger values of n and k 134 | maths.permutation(6, 2) 135 | |> should.equal(Ok(30)) 136 | 137 | maths.permutation(6, 3) 138 | |> should.equal(Ok(120)) 139 | } 140 | 141 | pub fn list_cartesian_product_test() { 142 | // An empty list returns an empty list as the Cartesian product 143 | let xset = set.from_list([]) 144 | let yset = set.from_list([]) 145 | let expected_result = set.from_list([]) 146 | xset 147 | |> maths.cartesian_product(yset) 148 | |> should.equal(expected_result) 149 | 150 | // Cartesian product of two sets with the same elements 151 | let xset = set.from_list([1, 2, 3]) 152 | let yset = set.from_list([1, 2, 3]) 153 | let expected_result = 154 | set.from_list([ 155 | #(1, 1), 156 | #(1, 2), 157 | #(1, 3), 158 | #(2, 1), 159 | #(2, 2), 160 | #(2, 3), 161 | #(3, 1), 162 | #(3, 2), 163 | #(3, 3), 164 | ]) 165 | xset 166 | |> maths.cartesian_product(yset) 167 | |> should.equal(expected_result) 168 | 169 | // Cartesian product with floating-point numbers 170 | let xset = set.from_list([1.0, 10.0]) 171 | let yset = set.from_list([1.0, 2.0]) 172 | let expected_result = 173 | set.from_list([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]) 174 | xset 175 | |> maths.cartesian_product(yset) 176 | |> should.equal(expected_result) 177 | 178 | // Cartesian product of sets with different sizes 179 | let xset = set.from_list([1.0, 10.0, 100.0]) 180 | let yset = set.from_list([1.0, 2.0]) 181 | let expected_result = 182 | set.from_list([ 183 | #(1.0, 1.0), 184 | #(1.0, 2.0), 185 | #(10.0, 1.0), 186 | #(10.0, 2.0), 187 | #(100.0, 1.0), 188 | #(100.0, 2.0), 189 | ]) 190 | xset 191 | |> maths.cartesian_product(yset) 192 | |> should.equal(expected_result) 193 | 194 | // Cartesian product with different types (strings) 195 | let xset = set.from_list(["a", "y", "z"]) 196 | let yset = set.from_list(["a", "x"]) 197 | let expected_result = 198 | set.from_list([ 199 | #("a", "a"), 200 | #("a", "x"), 201 | #("y", "a"), 202 | #("y", "x"), 203 | #("z", "a"), 204 | #("z", "x"), 205 | ]) 206 | xset 207 | |> maths.cartesian_product(yset) 208 | |> should.equal(expected_result) 209 | } 210 | 211 | pub fn cartesian_product_mixed_types_test() { 212 | // Cartesian product of two empty sets 213 | set.from_list([]) 214 | |> maths.cartesian_product(set.from_list([])) 215 | |> should.equal(set.from_list([])) 216 | 217 | // Cartesian product of two sets with numeric values 218 | set.from_list([1.0, 10.0]) 219 | |> maths.cartesian_product(set.from_list([1.0, 2.0])) 220 | |> should.equal( 221 | set.from_list([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]), 222 | ) 223 | 224 | // Cartesian product of two sets with different types 225 | set.from_list(["1", "10"]) 226 | |> maths.cartesian_product(set.from_list([1.0, 2.0])) 227 | |> should.equal( 228 | set.from_list([#("1", 1.0), #("1", 2.0), #("10", 1.0), #("10", 2.0)]), 229 | ) 230 | } 231 | 232 | pub fn list_permutation_with_repetitions_test() { 233 | // Invalid input: k < 0 should return an error for an empty list 234 | [] 235 | |> maths.list_permutation_with_repetitions(-1) 236 | |> should.be_error() 237 | 238 | // Valid input: An empty list returns a single empty permutation 239 | let assert Ok(permutations) = 240 | [] 241 | |> maths.list_permutation_with_repetitions(0) 242 | permutations 243 | |> yielder.to_list() 244 | |> should.equal([[]]) 245 | 246 | let assert Ok(permutations) = 247 | ["a"] 248 | |> maths.list_permutation_with_repetitions(1) 249 | permutations 250 | |> yielder.to_list() 251 | |> should.equal([["a"]]) 252 | 253 | // 4-permutations of a single element repeats it 4 times 254 | let assert Ok(permutations) = 255 | ["a"] 256 | |> maths.list_permutation_with_repetitions(4) 257 | permutations 258 | |> yielder.to_list() 259 | |> should.equal([["a", "a", "a", "a"]]) 260 | 261 | // 2-permutations of [1, 2] with repetition 262 | let assert Ok(permutations) = 263 | [1, 2] 264 | |> maths.list_permutation_with_repetitions(2) 265 | permutations 266 | |> yielder.to_list() 267 | |> set.from_list() 268 | |> should.equal(set.from_list([[1, 1], [1, 2], [2, 2], [2, 1]])) 269 | 270 | // 3-permutations of [1, 2, 3] with repetition 271 | let assert Ok(permutations) = 272 | [1, 2, 3] 273 | |> maths.list_permutation_with_repetitions(3) 274 | permutations 275 | |> yielder.to_list() 276 | |> should.equal([ 277 | [1, 1, 1], 278 | [1, 1, 2], 279 | [1, 1, 3], 280 | [1, 2, 1], 281 | [1, 2, 2], 282 | [1, 2, 3], 283 | [1, 3, 1], 284 | [1, 3, 2], 285 | [1, 3, 3], 286 | [2, 1, 1], 287 | [2, 1, 2], 288 | [2, 1, 3], 289 | [2, 2, 1], 290 | [2, 2, 2], 291 | [2, 2, 3], 292 | [2, 3, 1], 293 | [2, 3, 2], 294 | [2, 3, 3], 295 | [3, 1, 1], 296 | [3, 1, 2], 297 | [3, 1, 3], 298 | [3, 2, 1], 299 | [3, 2, 2], 300 | [3, 2, 3], 301 | [3, 3, 1], 302 | [3, 3, 2], 303 | [3, 3, 3], 304 | ]) 305 | 306 | // Repeated elements allow more possibilities when repetition is allowed 307 | let assert Ok(permutations) = 308 | [1.0, 1.0] 309 | |> maths.list_permutation_with_repetitions(2) 310 | permutations 311 | |> yielder.to_list() 312 | |> should.equal([[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]) 313 | 314 | let assert Ok(permutations) = 315 | ["a", "b", "c", "d", "e"] 316 | |> maths.list_permutation_with_repetitions(5) 317 | permutations 318 | |> yielder.to_list() 319 | |> list.length() 320 | |> should.equal(3125) 321 | } 322 | 323 | pub fn list_permutation_without_repetitions_test() { 324 | // Invalid input: k > n should return an error without repetition 325 | [1, 2] 326 | |> maths.list_permutation(3) 327 | |> should.be_error() 328 | 329 | // Singleton list returns a single permutation regardless of repetition settings 330 | let assert Ok(permutations) = 331 | ["a"] 332 | |> maths.list_permutation(1) 333 | permutations 334 | |> yielder.to_list() 335 | |> should.equal([["a"]]) 336 | 337 | // 2-permutations of [1, 2] without repetition 338 | let assert Ok(permutations) = 339 | [1, 2] 340 | |> maths.list_permutation(2) 341 | permutations 342 | |> yielder.to_list() 343 | |> should.equal([[1, 2], [2, 1]]) 344 | 345 | // 2-permutations without repetition 346 | let assert Ok(result) = 347 | [1, 2, 3] 348 | |> maths.list_permutation(2) 349 | result 350 | |> yielder.to_list() 351 | |> should.equal([[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]) 352 | 353 | // 3-permutations of [1, 2, 3] without repetition 354 | let assert Ok(permutations) = 355 | [1, 2, 3] 356 | |> maths.list_permutation(3) 357 | permutations 358 | |> yielder.to_list() 359 | |> should.equal([ 360 | [1, 2, 3], 361 | [1, 3, 2], 362 | [2, 1, 3], 363 | [2, 3, 1], 364 | [3, 1, 2], 365 | [3, 2, 1], 366 | ]) 367 | 368 | // 3-permutations of [1, 2, 3, 4] without repetition 369 | let assert Ok(permutations) = 370 | [1, 2, 3, 4] 371 | |> maths.list_permutation(3) 372 | permutations 373 | |> yielder.to_list() 374 | |> should.equal([ 375 | [1, 2, 3], 376 | [1, 2, 4], 377 | [1, 3, 2], 378 | [1, 3, 4], 379 | [1, 4, 2], 380 | [1, 4, 3], 381 | [2, 1, 3], 382 | [2, 1, 4], 383 | [2, 3, 1], 384 | [2, 3, 4], 385 | [2, 4, 1], 386 | [2, 4, 3], 387 | [3, 1, 2], 388 | [3, 1, 4], 389 | [3, 2, 1], 390 | [3, 2, 4], 391 | [3, 4, 1], 392 | [3, 4, 2], 393 | [4, 1, 2], 394 | [4, 1, 3], 395 | [4, 2, 1], 396 | [4, 2, 3], 397 | [4, 3, 1], 398 | [4, 3, 2], 399 | ]) 400 | 401 | // Repeated elements are treated as distinct in permutations without repetition 402 | let assert Ok(permutations) = 403 | [1.0, 1.0] 404 | |> maths.list_permutation(2) 405 | permutations 406 | |> yielder.to_list() 407 | |> should.equal([[1.0, 1.0], [1.0, 1.0]]) 408 | 409 | // Large inputs: Ensure the correct number of permutations is generated 410 | let assert Ok(permutations) = 411 | ["a", "b", "c", "d", "e"] 412 | |> maths.list_permutation(5) 413 | permutations 414 | |> yielder.to_list() 415 | |> list.length() 416 | |> should.equal(120) 417 | } 418 | 419 | pub fn permutation_alignment_test() { 420 | // Test: Number of generated permutations should match the expected count 421 | // Without repetitions 422 | let arr = ["a", "b", "c", "d", "e", "f"] 423 | let length = list.length(arr) 424 | 425 | let assert Ok(number_of_permutations) = maths.permutation(length, length) 426 | let assert Ok(permutations) = 427 | arr 428 | |> maths.list_permutation(length) 429 | permutations 430 | |> yielder.to_list() 431 | |> list.length() 432 | |> should.equal(number_of_permutations) 433 | 434 | // With repetitions 435 | let arr = ["a", "b", "c", "d", "e", "f"] 436 | let length = list.length(arr) 437 | 438 | let assert Ok(number_of_permutations) = 439 | maths.permutation_with_repetitions(length, length) 440 | let assert Ok(permutations) = 441 | arr 442 | |> maths.list_permutation_with_repetitions(length) 443 | permutations 444 | |> yielder.to_list() 445 | |> list.length() 446 | |> should.equal(number_of_permutations) 447 | } 448 | 449 | pub fn list_combination_with_repetitions_test() { 450 | // Invalid input: k < 0 should return an error for an empty list 451 | [] 452 | |> maths.list_combination_with_repetitions(-1) 453 | |> should.be_error() 454 | 455 | // Valid input: k > n with repetition allowed 456 | let assert Ok(combinations) = 457 | [1, 2] 458 | |> maths.list_combination_with_repetitions(3) 459 | combinations 460 | |> yielder.to_list() 461 | |> should.equal([[1, 1, 1], [1, 1, 2], [1, 2, 2], [2, 2, 2]]) 462 | 463 | let assert Ok(combinations) = 464 | [] 465 | |> maths.list_combination_with_repetitions(0) 466 | combinations 467 | |> yielder.to_list() 468 | |> should.equal([[]]) 469 | 470 | let assert Ok(combinations) = 471 | [1, 2] 472 | |> maths.list_combination_with_repetitions(1) 473 | combinations 474 | |> yielder.to_list() 475 | |> should.equal([[1], [2]]) 476 | 477 | let assert Ok(combinations) = 478 | [1, 2] 479 | |> maths.list_combination_with_repetitions(2) 480 | combinations 481 | |> yielder.to_list() 482 | |> should.equal([[1, 1], [1, 2], [2, 2]]) 483 | 484 | let assert Ok(combinations) = 485 | [1, 2, 3, 4] 486 | |> maths.list_combination_with_repetitions(2) 487 | combinations 488 | |> yielder.to_list() 489 | |> should.equal([ 490 | [1, 1], 491 | [1, 2], 492 | [1, 3], 493 | [1, 4], 494 | [2, 2], 495 | [2, 3], 496 | [2, 4], 497 | [3, 3], 498 | [3, 4], 499 | [4, 4], 500 | ]) 501 | 502 | // 3-combination of [1, 2, 3] with repetition 503 | let assert Ok(combinations) = 504 | [1, 2, 3] 505 | |> maths.list_combination_with_repetitions(3) 506 | combinations 507 | |> yielder.to_list() 508 | |> should.equal([ 509 | [1, 1, 1], 510 | [1, 1, 2], 511 | [1, 1, 3], 512 | [1, 2, 2], 513 | [1, 2, 3], 514 | [1, 3, 3], 515 | [2, 2, 2], 516 | [2, 2, 3], 517 | [2, 3, 3], 518 | [3, 3, 3], 519 | ]) 520 | 521 | // 3-permutations of [1, 2, 3, 4] with repetition 522 | let assert Ok(permutations) = 523 | maths.list_combination_with_repetitions([1, 2, 3, 4], 3) 524 | 525 | permutations 526 | |> yielder.to_list() 527 | |> should.equal([ 528 | [1, 1, 1], 529 | [1, 1, 2], 530 | [1, 1, 3], 531 | [1, 1, 4], 532 | [1, 2, 2], 533 | [1, 2, 3], 534 | [1, 2, 4], 535 | [1, 3, 3], 536 | [1, 3, 4], 537 | [1, 4, 4], 538 | [2, 2, 2], 539 | [2, 2, 3], 540 | [2, 2, 4], 541 | [2, 3, 3], 542 | [2, 3, 4], 543 | [2, 4, 4], 544 | [3, 3, 3], 545 | [3, 3, 4], 546 | [3, 4, 4], 547 | [4, 4, 4], 548 | ]) 549 | 550 | // Repetition creates more possibilities even with identical elements 551 | let assert Ok(combinations) = 552 | [1.0, 1.0] 553 | |> maths.list_combination_with_repetitions(2) 554 | combinations 555 | |> yielder.to_list() 556 | |> should.equal([[1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]) 557 | 558 | let assert Ok(combinations) = 559 | ["a", "b", "c", "d", "e"] 560 | |> maths.list_combination_with_repetitions(5) 561 | combinations 562 | |> yielder.to_list() 563 | |> list.length() 564 | |> should.equal(126) 565 | } 566 | 567 | pub fn list_combination_without_repetitions_test() { 568 | // Invalid input: k > n should return an error without repetition 569 | [1, 2] 570 | |> maths.list_combination(3) 571 | |> should.be_error() 572 | 573 | // Valid input: Empty list should return a single empty combination 574 | let assert Ok(combinations) = 575 | [] 576 | |> maths.list_combination(0) 577 | combinations 578 | |> yielder.to_list() 579 | |> should.equal([[]]) 580 | 581 | // 1-combination of [1, 2] without and with repetition 582 | let assert Ok(combinations) = 583 | [1, 2] 584 | |> maths.list_combination(1) 585 | combinations 586 | |> yielder.to_list() 587 | |> should.equal([[1], [2]]) 588 | 589 | // 2-combination of [1, 2] without and with repetition 590 | let assert Ok(combinations) = 591 | [1, 2] 592 | |> maths.list_combination(2) 593 | combinations 594 | |> yielder.to_list() 595 | |> should.equal([[1, 2]]) 596 | 597 | // 2-combination of [1, 2, 3, 4] without and with repetition 598 | let assert Ok(combinations) = 599 | [1, 2, 3, 4] 600 | |> maths.list_combination(2) 601 | combinations 602 | |> yielder.to_list() 603 | |> should.equal([[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]) 604 | 605 | // 3-combination of [1, 2, 3, 4] without repetition 606 | let assert Ok(combinations) = 607 | [1, 2, 3, 4] 608 | |> maths.list_combination(3) 609 | combinations 610 | |> yielder.to_list() 611 | |> should.equal([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]) 612 | 613 | // Combinations treat repeated elements as distinct in certain scenarios 614 | let assert Ok(combinations) = 615 | [1.0, 1.0] 616 | |> maths.list_combination(2) 617 | combinations 618 | |> yielder.to_list() 619 | |> should.equal([[1.0, 1.0]]) 620 | 621 | // Large input: Ensure correct number of combinations is generated 622 | let assert Ok(combinations) = 623 | ["a", "b", "c", "d", "e"] 624 | |> maths.list_combination(5) 625 | combinations 626 | |> yielder.to_list() 627 | |> list.length() 628 | |> should.equal(1) 629 | } 630 | 631 | pub fn combination_alignment_test() { 632 | // Test: Number of generated combinations should match the expected count 633 | // Without repetitions 634 | let arr = ["a", "b", "c", "d", "e", "f"] 635 | let length = list.length(arr) 636 | 637 | let assert Ok(number_of_combinations) = maths.combination(length, length) 638 | let assert Ok(combinations) = 639 | arr 640 | |> maths.list_combination(length) 641 | combinations 642 | |> yielder.to_list() 643 | |> list.length() 644 | |> should.equal(number_of_combinations) 645 | 646 | // With repetitions 647 | let arr = ["a", "b", "c", "d", "e", "f"] 648 | let length = list.length(arr) 649 | 650 | let assert Ok(number_of_combinations) = 651 | maths.combination_with_repetitions(length, length) 652 | let assert Ok(combinations) = 653 | arr 654 | |> maths.list_combination_with_repetitions(length) 655 | combinations 656 | |> yielder.to_list() 657 | |> list.length() 658 | |> should.equal(number_of_combinations) 659 | } 660 | -------------------------------------------------------------------------------- /test/gleam_community/conversion_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam_community/maths 3 | import gleeunit/should 4 | 5 | pub fn to_degree_test() { 6 | let assert Ok(tol) = float.power(10.0, -6.0) 7 | maths.radians_to_degrees(0.0) 8 | |> maths.is_close(0.0, 0.0, tol) 9 | |> should.be_true() 10 | 11 | maths.radians_to_degrees(2.0 *. maths.pi()) 12 | |> maths.is_close(360.0, 0.0, tol) 13 | |> should.be_true() 14 | } 15 | 16 | pub fn to_radian_test() { 17 | let assert Ok(tol) = float.power(10.0, -6.0) 18 | maths.degrees_to_radians(0.0) 19 | |> maths.is_close(0.0, 0.0, tol) 20 | |> should.be_true() 21 | 22 | maths.degrees_to_radians(360.0) 23 | |> maths.is_close(2.0 *. maths.pi(), 0.0, tol) 24 | |> should.be_true() 25 | } 26 | 27 | pub fn cartesian_to_polar_test() { 28 | let assert Ok(tol) = float.power(10.0, -6.0) 29 | 30 | // Test: Cartesian (1, 0) -> Polar (1, 0) 31 | let #(r, theta) = maths.cartesian_to_polar(1.0, 0.0) 32 | r 33 | |> maths.is_close(1.0, 0.0, tol) 34 | |> should.be_true() 35 | 36 | theta 37 | |> maths.is_close(0.0, 0.0, tol) 38 | |> should.be_true() 39 | 40 | // Test: Cartesian (0, 1) -> Polar (1, pi/2) 41 | let #(r, theta) = maths.cartesian_to_polar(0.0, 1.0) 42 | r 43 | |> maths.is_close(1.0, 0.0, tol) 44 | |> should.be_true() 45 | 46 | theta 47 | |> maths.is_close(maths.pi() /. 2.0, 0.0, tol) 48 | |> should.be_true() 49 | 50 | // Test: Cartesian (-1, 0) -> Polar (1, pi) 51 | let #(r, theta) = maths.cartesian_to_polar(-1.0, 0.0) 52 | r 53 | |> maths.is_close(1.0, 0.0, tol) 54 | |> should.be_true() 55 | 56 | theta 57 | |> maths.is_close(maths.pi(), 0.0, tol) 58 | |> should.be_true() 59 | 60 | // Test: Cartesian (0, -1) -> Polar (1, -pi/2) 61 | let #(r, theta) = maths.cartesian_to_polar(0.0, -1.0) 62 | r 63 | |> maths.is_close(1.0, 0.0, tol) 64 | |> should.be_true() 65 | 66 | theta 67 | |> maths.is_close(-1.0 *. maths.pi() /. 2.0, 0.0, tol) 68 | |> should.be_true() 69 | } 70 | 71 | pub fn polar_to_cartesian_test() { 72 | let assert Ok(tol) = float.power(10.0, -6.0) 73 | 74 | // Test: Polar (1, 0) -> Cartesian (1, 0) 75 | let #(x, y) = maths.polar_to_cartesian(1.0, 0.0) 76 | x 77 | |> maths.is_close(1.0, 0.0, tol) 78 | |> should.be_true() 79 | 80 | y 81 | |> maths.is_close(0.0, 0.0, tol) 82 | |> should.be_true() 83 | 84 | // Test: Polar (1, pi/2) -> Cartesian (0, 1) 85 | let #(x, y) = maths.polar_to_cartesian(1.0, maths.pi() /. 2.0) 86 | x 87 | |> maths.is_close(0.0, 0.0, tol) 88 | |> should.be_true() 89 | 90 | y 91 | |> maths.is_close(1.0, 0.0, tol) 92 | |> should.be_true() 93 | 94 | // Test: Polar (1, pi) -> Cartesian (-1, 0) 95 | let #(x, y) = maths.polar_to_cartesian(1.0, maths.pi()) 96 | x 97 | |> maths.is_close(-1.0, 0.0, tol) 98 | |> should.be_true() 99 | 100 | y 101 | |> maths.is_close(0.0, 0.0, tol) 102 | |> should.be_true() 103 | 104 | // Test: Polar (1, -pi/2) -> Cartesian (0, -1) 105 | let #(x, y) = maths.polar_to_cartesian(1.0, -1.0 *. maths.pi() /. 2.0) 106 | x 107 | |> maths.is_close(0.0, 0.0, tol) 108 | |> should.be_true() 109 | 110 | y 111 | |> maths.is_close(-1.0, 0.0, tol) 112 | |> should.be_true() 113 | } 114 | -------------------------------------------------------------------------------- /test/gleam_community/elementary_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam_community/maths 3 | import gleeunit/should 4 | 5 | pub fn acos_test() { 6 | let assert Ok(tol) = float.power(10.0, -6.0) 7 | // Check that the function agrees, at some arbitrary input 8 | // points, with known function values 9 | let assert Ok(result) = maths.acos(1.0) 10 | result 11 | |> maths.is_close(0.0, 0.0, tol) 12 | |> should.be_true() 13 | 14 | let assert Ok(result) = maths.acos(0.5) 15 | result 16 | |> maths.is_close(1.047197, 0.0, tol) 17 | |> should.be_true() 18 | 19 | // Check that we get an error when the function is evaluated 20 | // outside its domain 21 | maths.acos(1.1) 22 | |> should.be_error() 23 | 24 | maths.acos(-1.1) 25 | |> should.be_error() 26 | } 27 | 28 | pub fn acosh_test() { 29 | let assert Ok(tol) = float.power(10.0, -6.0) 30 | // Check that the function agrees, at some arbitrary input 31 | // points, with known function values 32 | let assert Ok(result) = maths.acosh(1.0) 33 | result 34 | |> maths.is_close(0.0, 0.0, tol) 35 | |> should.be_true() 36 | 37 | // Check that we get an error when the function is evaluated 38 | // outside its domain 39 | maths.acosh(0.0) 40 | |> should.be_error() 41 | } 42 | 43 | pub fn asin_test() { 44 | // Check that the function agrees, at some arbitrary input 45 | // points, with known function values 46 | maths.asin(0.0) 47 | |> should.equal(Ok(0.0)) 48 | 49 | let assert Ok(tol) = float.power(10.0, -6.0) 50 | let assert Ok(result) = maths.asin(0.5) 51 | result 52 | |> maths.is_close(0.523598, 0.0, tol) 53 | |> should.be_true() 54 | 55 | // Check that we get an error when the function is evaluated 56 | // outside its domain 57 | maths.asin(1.1) 58 | |> should.be_error() 59 | 60 | maths.asin(-1.1) 61 | |> should.be_error() 62 | } 63 | 64 | pub fn asinh_test() { 65 | let assert Ok(tol) = float.power(10.0, -6.0) 66 | // Check that the function agrees, at some arbitrary input 67 | // points, with known function values 68 | maths.asinh(0.0) 69 | |> maths.is_close(0.0, 0.0, tol) 70 | |> should.be_true() 71 | 72 | maths.asinh(0.5) 73 | |> maths.is_close(0.481211, 0.0, tol) 74 | |> should.be_true() 75 | } 76 | 77 | pub fn atan_test() { 78 | let assert Ok(tol) = float.power(10.0, -6.0) 79 | // Check that the function agrees, at some arbitrary input 80 | // points, with known function values 81 | maths.atan(0.0) 82 | |> maths.is_close(0.0, 0.0, tol) 83 | |> should.be_true() 84 | 85 | maths.atan(0.5) 86 | |> maths.is_close(0.463647, 0.0, tol) 87 | |> should.be_true() 88 | } 89 | 90 | pub fn math_atan2_test() { 91 | let assert Ok(tol) = float.power(10.0, -6.0) 92 | // Check that the function agrees, at some arbitrary input 93 | // points, with known function values 94 | maths.atan2(0.0, 0.0) 95 | |> maths.is_close(0.0, 0.0, tol) 96 | |> should.be_true() 97 | 98 | maths.atan2(0.0, 1.0) 99 | |> maths.is_close(0.0, 0.0, tol) 100 | |> should.be_true() 101 | 102 | // Check atan2(y=1.0, x=0.5) 103 | // Should be equal to atan(y / x) for any x > 0 and any y 104 | let result = maths.atan(1.0 /. 0.5) 105 | maths.atan2(1.0, 0.5) 106 | |> maths.is_close(result, 0.0, tol) 107 | |> should.be_true() 108 | 109 | // Check atan2(y=2.0, x=-1.5) 110 | // Should be equal to pi + atan(y / x) for any x < 0 and y >= 0 111 | let result = maths.pi() +. maths.atan(2.0 /. -1.5) 112 | maths.atan2(2.0, -1.5) 113 | |> maths.is_close(result, 0.0, tol) 114 | |> should.be_true() 115 | 116 | // Check atan2(y=-2.0, x=-1.5) 117 | // Should be equal to atan(y / x) - pi for any x < 0 and y < 0 118 | let result = maths.atan(-2.0 /. -1.5) -. maths.pi() 119 | maths.atan2(-2.0, -1.5) 120 | |> maths.is_close(result, 0.0, tol) 121 | |> should.be_true() 122 | 123 | // Check atan2(y=1.5, x=0.0) 124 | // Should be equal to pi/2 for x = 0 and any y > 0 125 | let result = maths.pi() /. 2.0 126 | maths.atan2(1.5, 0.0) 127 | |> maths.is_close(result, 0.0, tol) 128 | |> should.be_true() 129 | 130 | // Check atan2(y=-1.5, x=0.0) 131 | // Should be equal to -pi/2 for x = 0 and any y < 0 132 | let result = -1.0 *. maths.pi() /. 2.0 133 | maths.atan2(-1.5, 0.0) 134 | |> maths.is_close(result, 0.0, tol) 135 | |> should.be_true() 136 | } 137 | 138 | pub fn atanh_test() { 139 | let assert Ok(tol) = float.power(10.0, -6.0) 140 | // Check that the function agrees, at some arbitrary input 141 | // points, with known function values 142 | let assert Ok(result) = maths.atanh(0.0) 143 | result 144 | |> maths.is_close(0.0, 0.0, tol) 145 | |> should.be_true() 146 | 147 | let assert Ok(result) = maths.atanh(0.5) 148 | result 149 | |> maths.is_close(0.549306, 0.0, tol) 150 | |> should.be_true() 151 | 152 | // Check that we get an error when the function is evaluated 153 | // outside its domain 154 | maths.atanh(1.0) 155 | |> should.be_error() 156 | 157 | maths.atanh(2.0) 158 | |> should.be_error() 159 | 160 | maths.atanh(1.0) 161 | |> should.be_error() 162 | 163 | maths.atanh(-2.0) 164 | |> should.be_error() 165 | } 166 | 167 | pub fn cos_test() { 168 | let assert Ok(tol) = float.power(10.0, -6.0) 169 | // Check that the function agrees, at some arbitrary input 170 | // points, with known function values 171 | maths.cos(0.0) 172 | |> maths.is_close(1.0, 0.0, tol) 173 | |> should.be_true() 174 | 175 | maths.cos(maths.pi()) 176 | |> maths.is_close(-1.0, 0.0, tol) 177 | |> should.be_true() 178 | 179 | maths.cos(0.5) 180 | |> maths.is_close(0.877582, 0.0, tol) 181 | |> should.be_true() 182 | } 183 | 184 | pub fn cosh_test() { 185 | let assert Ok(tol) = float.power(10.0, -6.0) 186 | // Check that the function agrees, at some arbitrary input 187 | // points, with known function values 188 | maths.cosh(0.0) 189 | |> maths.is_close(1.0, 0.0, tol) 190 | |> should.be_true() 191 | 192 | maths.cosh(0.5) 193 | |> maths.is_close(1.127625, 0.0, tol) 194 | |> should.be_true() 195 | // An (overflow) error might occur when given an input 196 | // value that will result in a too large output value 197 | // e.g. maths.cosh(1000.0) but this is a property of the 198 | // runtime. 199 | } 200 | 201 | pub fn sin_test() { 202 | let assert Ok(tol) = float.power(10.0, -6.0) 203 | // Check that the function agrees, at some arbitrary input 204 | // points, with known function values 205 | maths.sin(0.0) 206 | |> maths.is_close(0.0, 0.0, tol) 207 | |> should.be_true() 208 | 209 | maths.sin(0.5 *. maths.pi()) 210 | |> maths.is_close(1.0, 0.0, tol) 211 | |> should.be_true() 212 | 213 | maths.sin(0.5) 214 | |> maths.is_close(0.479425, 0.0, tol) 215 | |> should.be_true() 216 | } 217 | 218 | pub fn sinh_test() { 219 | let assert Ok(tol) = float.power(10.0, -6.0) 220 | // Check that the function agrees, at some arbitrary input 221 | // points, with known function values 222 | maths.sinh(0.0) 223 | |> maths.is_close(0.0, 0.0, tol) 224 | |> should.be_true() 225 | 226 | maths.sinh(0.5) 227 | |> maths.is_close(0.521095, 0.0, tol) 228 | |> should.be_true() 229 | // An (overflow) error might occur when given an input 230 | // value that will result in a too large output value 231 | // e.g. maths.sinh(1000.0) but this is a property of the 232 | // runtime. 233 | } 234 | 235 | pub fn math_tan_test() { 236 | let assert Ok(tol) = float.power(10.0, -6.0) 237 | // Check that the function agrees, at some arbitrary input 238 | // points, with known function values 239 | maths.tan(0.0) 240 | |> maths.is_close(0.0, 0.0, tol) 241 | |> should.be_true() 242 | 243 | maths.tan(0.5) 244 | |> maths.is_close(0.546302, 0.0, tol) 245 | |> should.be_true() 246 | } 247 | 248 | pub fn math_tanh_test() { 249 | let assert Ok(tol) = float.power(10.0, -6.0) 250 | // Check that the function agrees, at some arbitrary input 251 | // points, with known function values 252 | maths.tanh(0.0) 253 | |> maths.is_close(0.0, 0.0, tol) 254 | |> should.be_true() 255 | 256 | maths.tanh(25.0) 257 | |> maths.is_close(1.0, 0.0, tol) 258 | |> should.be_true() 259 | 260 | maths.tanh(-25.0) 261 | |> maths.is_close(-1.0, 0.0, tol) 262 | |> should.be_true() 263 | 264 | maths.tanh(0.5) 265 | |> maths.is_close(0.462117, 0.0, tol) 266 | |> should.be_true() 267 | } 268 | 269 | pub fn exponential_test() { 270 | let assert Ok(tol) = float.power(10.0, -6.0) 271 | // Check that the function agrees, at some arbitrary input 272 | // points, with known function values 273 | maths.exponential(0.0) 274 | |> maths.is_close(1.0, 0.0, tol) 275 | |> should.be_true() 276 | 277 | maths.exponential(0.5) 278 | |> maths.is_close(1.648721, 0.0, tol) 279 | |> should.be_true() 280 | // An (overflow) error might occur when given an input 281 | // value that will result in a too large output value 282 | // e.g. maths.exponential(1000.0) but this is a property of the 283 | // runtime. 284 | } 285 | 286 | pub fn natural_logarithm_test() { 287 | let assert Ok(tol) = float.power(10.0, -6.0) 288 | // Check that the function agrees, at some arbitrary input 289 | // points, with known function values 290 | let assert Ok(result) = maths.natural_logarithm(1.0) 291 | result 292 | |> maths.is_close(0.0, 0.0, tol) 293 | |> should.be_true() 294 | 295 | let assert Ok(result) = maths.natural_logarithm(0.5) 296 | result 297 | |> maths.is_close(-0.693147, 0.0, tol) 298 | |> should.be_true() 299 | 300 | // Check that we get an error when the function is evaluated 301 | // outside its domain 302 | maths.natural_logarithm(-1.0) 303 | |> should.be_error() 304 | } 305 | 306 | pub fn logarithm_test() { 307 | let assert Ok(tol) = float.power(10.0, -6.0) 308 | 309 | // Check that the function agrees, at some arbitrary input 310 | // points, with known function values 311 | let assert Ok(result) = maths.logarithm(10.0, 10.0) 312 | result 313 | |> maths.is_close(1.0, 0.0, tol) 314 | |> should.be_true() 315 | 316 | let assert Ok(result) = maths.logarithm(10.0, 100.0) 317 | result 318 | |> maths.is_close(0.5, 0.0, tol) 319 | |> should.be_true() 320 | 321 | let assert Ok(result) = maths.logarithm(1.0, 0.25) 322 | result 323 | |> maths.is_close(0.0, 0.0, tol) 324 | |> should.be_true() 325 | // Check that we get an error when the function is evaluated 326 | // outside its domain 327 | maths.logarithm(1.0, 1.0) 328 | |> should.be_error() 329 | 330 | maths.logarithm(10.0, 1.0) 331 | |> should.be_error() 332 | 333 | maths.logarithm(-1.0, 1.0) 334 | |> should.be_error() 335 | 336 | let assert Ok(result) = maths.logarithm(1.0, 10.0) 337 | result 338 | |> maths.is_close(0.0, 0.0, tol) 339 | |> should.be_true() 340 | 341 | let assert Ok(result) = maths.logarithm(maths.e(), maths.e()) 342 | result 343 | |> maths.is_close(1.0, 0.0, tol) 344 | |> should.be_true() 345 | 346 | maths.logarithm(-1.0, 2.0) 347 | |> should.be_error() 348 | } 349 | 350 | pub fn logarithm_2_test() { 351 | let assert Ok(tol) = float.power(10.0, -6.0) 352 | // Check that the function agrees, at some arbitrary input 353 | // points, with known function values 354 | let assert Ok(result) = maths.logarithm_2(1.0) 355 | result 356 | |> maths.is_close(0.0, 0.0, tol) 357 | |> should.be_true() 358 | 359 | let assert Ok(result) = maths.logarithm_2(2.0) 360 | result 361 | |> maths.is_close(1.0, 0.0, tol) 362 | |> should.be_true() 363 | 364 | let assert Ok(result) = maths.logarithm_2(5.0) 365 | result 366 | |> maths.is_close(2.321928, 0.0, tol) 367 | |> should.be_true() 368 | 369 | // Check that we get an error when the function is evaluated 370 | // outside its domain 371 | maths.logarithm_2(-1.0) 372 | |> should.be_error() 373 | } 374 | 375 | pub fn logarithm_10_test() { 376 | let assert Ok(tol) = float.power(10.0, -6.0) 377 | // Check that the function agrees, at some arbitrary input 378 | // points, with known function values 379 | let assert Ok(result) = maths.logarithm_10(1.0) 380 | result 381 | |> maths.is_close(0.0, 0.0, tol) 382 | |> should.be_true() 383 | 384 | let assert Ok(result) = maths.logarithm_10(10.0) 385 | result 386 | |> maths.is_close(1.0, 0.0, tol) 387 | |> should.be_true() 388 | 389 | let assert Ok(result) = maths.logarithm_10(50.0) 390 | result 391 | |> maths.is_close(1.69897, 0.0, tol) 392 | |> should.be_true() 393 | 394 | // Check that we get an error when the function is evaluated 395 | // outside its domain 396 | maths.logarithm_10(-1.0) 397 | |> should.be_error() 398 | } 399 | 400 | pub fn nth_root_test() { 401 | maths.nth_root(9.0, 2) 402 | |> should.equal(Ok(3.0)) 403 | 404 | maths.nth_root(27.0, 3) 405 | |> should.equal(Ok(3.0)) 406 | 407 | maths.nth_root(1.0, 4) 408 | |> should.equal(Ok(1.0)) 409 | 410 | maths.nth_root(256.0, 4) 411 | |> should.equal(Ok(4.0)) 412 | 413 | // An error should be returned as an imaginary number would otherwise 414 | // have to be returned 415 | maths.nth_root(-1.0, 4) 416 | |> should.be_error() 417 | } 418 | 419 | pub fn constants_test() { 420 | let assert Ok(tolerance) = float.power(10.0, -12.0) 421 | 422 | // Test that the constant is approximately equal to 2.7128... 423 | maths.e() 424 | |> maths.is_close(2.7182818284590452353602, 0.0, tolerance) 425 | |> should.be_true() 426 | 427 | // Test that the constant is approximately equal to 2.7128... 428 | maths.pi() 429 | |> maths.is_close(3.14159265359, 0.0, tolerance) 430 | |> should.be_true() 431 | 432 | // Test that the constant is approximately equal to 1.6180... 433 | maths.golden_ratio() 434 | |> maths.is_close(1.618033988749895, 0.0, tolerance) 435 | |> should.be_true() 436 | } 437 | -------------------------------------------------------------------------------- /test/gleam_community/metrics_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam/int 3 | import gleam/list 4 | import gleam/set 5 | import gleam_community/maths 6 | import gleeunit/should 7 | 8 | fn norm_test_cases() { 9 | // Tuples of #(list, norm, expected result) 10 | [ 11 | // An empty list will always return 0.0 12 | #([], 1.0, 0.0), 13 | #([1.0, 1.0, 1.0], 1.0, 3.0), 14 | // Check the special case: 15 | // The pseudo-norm when p = 0 (we count the number of non-zero elements in the given list) 16 | #([1.0, 1.0, 1.0], 0.0, 3.0), 17 | #([1.0, 1.0, 1.0], -1.0, 0.3333333333333333), 18 | #([-1.0, -1.0, -1.0], -1.0, 0.3333333333333333), 19 | // Check if we internally correctly remember to take the absolute 20 | // value of each of the list elements 21 | #([-1.0, -1.0, -1.0], 1.0, 3.0), 22 | #([-1.0, -1.0, -1.0], 0.5, 9.0), 23 | #([0.0, 0.0, 0.0], 1.0, 0.0), 24 | // Check if we internally handle division by zero appropriately 25 | #([0.0], -1.0, 0.0), 26 | #([1.0, 2.0, 3.0, 0.0], -1.0, 0.0), 27 | #([-1.0, -2.0, -3.0], -10.0, 0.9999007044905545), 28 | #([-1.0, -2.0, -3.0], -100.0, 1.0), 29 | // Test the Euclidean norm (p = 2) 30 | #([-1.0, -2.0, -3.0], 2.0, 3.7416573867739413), 31 | ] 32 | } 33 | 34 | pub fn list_norm_test() { 35 | let assert Ok(tol) = float.power(10.0, -6.0) 36 | 37 | norm_test_cases() 38 | |> list.map(fn(tuple) { 39 | let #(arr, p, expected) = tuple 40 | let assert Ok(result) = maths.norm(arr, p) 41 | result |> maths.is_close(expected, 0.0, tol) |> should.be_true() 42 | }) 43 | } 44 | 45 | pub fn list_norm_with_weights_test() { 46 | let assert Ok(tol) = float.power(10.0, -6.0) 47 | 48 | // Check that the weighted version of the norm function aligns with the 49 | // non-weighted version by re-using the test cases for the non-weighted 50 | // version by associating a weight of 1.0 to each elemet of the input lists 51 | norm_test_cases() 52 | |> list.map(fn(tuple) { 53 | let #(arr, p, expected) = tuple 54 | let new_arr = list.map(arr, fn(element) { #(element, 1.0) }) 55 | let assert Ok(result) = maths.norm_with_weights(new_arr, p) 56 | result |> maths.is_close(expected, 0.0, tol) 57 | }) 58 | 59 | // Check that the function agrees, at some additional arbitrary input points 60 | // with known function values 61 | let assert Ok(result) = 62 | [#(1.0, 1.0), #(1.0, 1.0), #(1.0, 1.0)] 63 | |> maths.norm_with_weights(1.0) 64 | result 65 | |> maths.is_close(3.0, 0.0, tol) 66 | |> should.be_true() 67 | 68 | let assert Ok(result) = 69 | [#(1.0, 0.5), #(1.0, 0.5), #(1.0, 0.5)] 70 | |> maths.norm_with_weights(1.0) 71 | result 72 | |> maths.is_close(1.5, 0.0, tol) 73 | |> should.be_true() 74 | 75 | let assert Ok(result) = 76 | [#(1.0, 0.5), #(2.0, 0.5), #(3.0, 0.5)] 77 | |> maths.norm_with_weights(2.0) 78 | result 79 | |> maths.is_close(2.6457513110645907, 0.0, tol) 80 | |> should.be_true() 81 | } 82 | 83 | pub fn list_manhattan_test() { 84 | let assert Ok(tol) = float.power(10.0, -6.0) 85 | 86 | // Try with valid input (same as Minkowski distance with p = 1) 87 | let assert Ok(result) = maths.manhattan_distance([#(0.0, 1.0), #(0.0, 2.0)]) 88 | result 89 | |> maths.is_close(3.0, 0.0, tol) 90 | |> should.be_true() 91 | 92 | maths.manhattan_distance([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)]) 93 | |> should.equal(Ok(9.0)) 94 | 95 | maths.manhattan_distance([#(1.0, 2.0), #(2.0, 3.0)]) 96 | |> should.equal(Ok(2.0)) 97 | 98 | maths.manhattan_distance_with_weights([#(1.0, 2.0, 0.5), #(2.0, 3.0, 1.0)]) 99 | |> should.equal(Ok(1.5)) 100 | 101 | maths.manhattan_distance_with_weights([ 102 | #(1.0, 4.0, 1.0), 103 | #(2.0, 5.0, 1.0), 104 | #(3.0, 6.0, 1.0), 105 | ]) 106 | |> should.equal(Ok(9.0)) 107 | 108 | maths.manhattan_distance_with_weights([ 109 | #(1.0, 4.0, 1.0), 110 | #(2.0, 5.0, 2.0), 111 | #(3.0, 6.0, 3.0), 112 | ]) 113 | |> should.equal(Ok(18.0)) 114 | 115 | maths.manhattan_distance_with_weights([ 116 | #(1.0, 4.0, -7.0), 117 | #(2.0, 5.0, -8.0), 118 | #(3.0, 6.0, -9.0), 119 | ]) 120 | |> should.be_error() 121 | } 122 | 123 | pub fn list_minkowski_test() { 124 | let assert Ok(tol) = float.power(10.0, -6.0) 125 | 126 | // Test order < 1 127 | maths.minkowski_distance([#(0.0, 0.0), #(0.0, 0.0)], -1.0) 128 | |> should.be_error() 129 | 130 | // Check that the function agrees, at some arbitrary input 131 | // points, with known function values 132 | let assert Ok(result) = 133 | maths.minkowski_distance([#(1.0, 1.0), #(1.0, 1.0)], 1.0) 134 | result 135 | |> maths.is_close(0.0, 0.0, tol) 136 | |> should.be_true() 137 | 138 | let assert Ok(result) = 139 | maths.minkowski_distance([#(0.0, 1.0), #(0.0, 1.0)], 10.0) 140 | result 141 | |> maths.is_close(1.0717734625362931, 0.0, tol) 142 | |> should.be_true() 143 | 144 | let assert Ok(result) = 145 | maths.minkowski_distance([#(0.0, 1.0), #(0.0, 1.0)], 100.0) 146 | result 147 | |> maths.is_close(1.0069555500567189, 0.0, tol) 148 | |> should.be_true() 149 | 150 | // Euclidean distance (p = 2) 151 | let assert Ok(result) = 152 | maths.minkowski_distance([#(0.0, 1.0), #(0.0, 2.0)], 2.0) 153 | result 154 | |> maths.is_close(2.23606797749979, 0.0, tol) 155 | |> should.be_true() 156 | 157 | // Manhattan distance (p = 1) 158 | let assert Ok(result) = 159 | maths.minkowski_distance([#(0.0, 1.0), #(0.0, 2.0)], 1.0) 160 | result 161 | |> maths.is_close(3.0, 0.0, tol) 162 | |> should.be_true() 163 | 164 | // Try different valid input 165 | let assert Ok(result) = 166 | maths.minkowski_distance([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)], 4.0) 167 | result 168 | |> maths.is_close(3.9482220388574776, 0.0, tol) 169 | |> should.be_true() 170 | 171 | let assert Ok(result) = 172 | maths.minkowski_distance_with_weights( 173 | [#(1.0, 4.0, 1.0), #(2.0, 5.0, 1.0), #(3.0, 6.0, 1.0)], 174 | 4.0, 175 | ) 176 | result 177 | |> maths.is_close(3.9482220388574776, 0.0, tol) 178 | |> should.be_true() 179 | 180 | let assert Ok(result) = 181 | maths.minkowski_distance_with_weights( 182 | [#(1.0, 4.0, 1.0), #(2.0, 5.0, 2.0), #(3.0, 6.0, 3.0)], 183 | 4.0, 184 | ) 185 | result 186 | |> maths.is_close(4.6952537402198615, 0.0, tol) 187 | |> should.be_true() 188 | 189 | // Try invalid input with weights that are negative 190 | maths.minkowski_distance_with_weights( 191 | [#(1.0, 4.0, -7.0), #(2.0, 5.0, -8.0), #(3.0, 6.0, -9.0)], 192 | 2.0, 193 | ) 194 | |> should.be_error() 195 | } 196 | 197 | pub fn list_euclidean_test() { 198 | let assert Ok(tol) = float.power(10.0, -6.0) 199 | 200 | // Empty lists returns an error 201 | maths.euclidean_distance([]) 202 | |> should.be_error() 203 | 204 | // Try with valid input (same as Minkowski distance with p = 2) 205 | let assert Ok(result) = maths.euclidean_distance([#(0.0, 1.0), #(0.0, 2.0)]) 206 | result 207 | |> maths.is_close(2.23606797749979, 0.0, tol) 208 | |> should.be_true() 209 | 210 | // Try different valid input 211 | let assert Ok(result) = 212 | maths.euclidean_distance([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)]) 213 | result 214 | |> maths.is_close(5.196152422706632, 0.0, tol) 215 | |> should.be_true() 216 | 217 | let assert Ok(result) = 218 | maths.euclidean_distance_with_weights([ 219 | #(1.0, 4.0, 1.0), 220 | #(2.0, 5.0, 1.0), 221 | #(3.0, 6.0, 1.0), 222 | ]) 223 | result 224 | |> maths.is_close(5.196152422706632, 0.0, tol) 225 | |> should.be_true() 226 | 227 | let assert Ok(result) = 228 | maths.euclidean_distance_with_weights([ 229 | #(1.0, 4.0, 1.0), 230 | #(2.0, 5.0, 2.0), 231 | #(3.0, 6.0, 3.0), 232 | ]) 233 | result 234 | |> maths.is_close(7.3484692283495345, 0.0, tol) 235 | |> should.be_true() 236 | 237 | // Try invalid input with weights that are negative 238 | maths.euclidean_distance_with_weights([ 239 | #(1.0, 4.0, -7.0), 240 | #(2.0, 5.0, -8.0), 241 | #(3.0, 6.0, -9.0), 242 | ]) 243 | |> should.be_error() 244 | } 245 | 246 | pub fn moment_test() { 247 | // An empty list returns an error 248 | [] 249 | |> maths.moment(0) 250 | |> should.be_error() 251 | 252 | [1.0, 2.0, 3.0] 253 | |> maths.moment(-1) 254 | |> should.be_error() 255 | 256 | [2.0] 257 | |> maths.moment(0) 258 | |> should.equal(Ok(1.0)) 259 | 260 | [2.0] 261 | |> maths.moment(1) 262 | |> should.equal(Ok(0.0)) 263 | 264 | [2.0] 265 | |> maths.moment(2) 266 | |> should.equal(Ok(0.0)) 267 | 268 | // 0th moment about the mean is 1. per definition 269 | [0.0, 1.0, 2.0, 3.0, 4.0] 270 | |> maths.moment(0) 271 | |> should.equal(Ok(1.0)) 272 | 273 | // 1st moment about the mean is 0. per definition 274 | [0.0, 1.0, 2.0, 3.0, 4.0] 275 | |> maths.moment(1) 276 | |> should.equal(Ok(0.0)) 277 | 278 | // 2nd moment about the mean 279 | [0.0, 1.0, 2.0, 3.0, 4.0] 280 | |> maths.moment(2) 281 | |> should.equal(Ok(2.0)) 282 | } 283 | 284 | pub fn mean_test() { 285 | // An empty list returns an error 286 | [] 287 | |> maths.mean() 288 | |> should.be_error() 289 | 290 | // Valid input returns a result 291 | 292 | [5.0] 293 | |> maths.mean() 294 | |> should.equal(Ok(5.0)) 295 | 296 | [1.0, 2.0, 3.0] 297 | |> maths.mean() 298 | |> should.equal(Ok(2.0)) 299 | 300 | [-1.0, -2.0, -3.0] 301 | |> maths.mean() 302 | |> should.equal(Ok(-2.0)) 303 | } 304 | 305 | pub fn harmonic_mean_test() { 306 | // An empty list returns an error 307 | [] 308 | |> maths.harmonic_mean() 309 | |> should.be_error() 310 | 311 | [1.0, 2.0, 0.0] 312 | |> maths.harmonic_mean() 313 | |> should.equal(Ok(0.0)) 314 | 315 | // List with negative numbers returns an error 316 | [-1.0, -3.0, -6.0] 317 | |> maths.harmonic_mean() 318 | |> should.be_error() 319 | 320 | // Valid input returns a result 321 | [4.0] 322 | |> maths.harmonic_mean() 323 | |> should.equal(Ok(4.0)) 324 | 325 | [1.0, 3.0, 6.0] 326 | |> maths.harmonic_mean() 327 | |> should.equal(Ok(2.0)) 328 | } 329 | 330 | pub fn geometric_mean_test() { 331 | // An empty list returns an error 332 | [] 333 | |> maths.geometric_mean() 334 | |> should.be_error() 335 | 336 | // List with negative numbers returns an error 337 | [-1.0, -3.0, -6.0] 338 | |> maths.geometric_mean() 339 | |> should.be_error() 340 | 341 | // Valid input returns a result 342 | [5.0] 343 | |> maths.geometric_mean() 344 | |> should.equal(Ok(5.0)) 345 | 346 | [1.0, 2.0, 0.0] 347 | |> maths.geometric_mean() 348 | |> should.equal(Ok(0.0)) 349 | 350 | [1.0, 3.0, 9.0] 351 | |> maths.geometric_mean() 352 | |> should.equal(Ok(3.0)) 353 | } 354 | 355 | pub fn median_test() { 356 | // An empty list returns an error 357 | [] 358 | |> maths.median() 359 | |> should.be_error() 360 | 361 | // A single-element list returns the element itself 362 | [42.0] 363 | |> maths.median() 364 | |> should.equal(Ok(42.0)) 365 | 366 | // A two-element list returns the average of the two elements 367 | [10.0, 20.0] 368 | |> maths.median() 369 | |> should.equal(Ok(15.0)) 370 | 371 | // An odd-length list returns the middle element 372 | [1.0, 3.0, 5.0] 373 | |> maths.median() 374 | |> should.equal(Ok(3.0)) 375 | 376 | // An even-length list returns the average of the middle two elements 377 | [1.0, 2.0, 3.0, 4.0] 378 | |> maths.median() 379 | |> should.equal(Ok(2.5)) 380 | 381 | // Make sure an unsorted list is sorted before calculating the median 382 | [9.0, 1.0, 4.0, 2.0, 8.0] 383 | |> maths.median() 384 | |> should.equal(Ok(4.0)) 385 | 386 | [8.0, 2.0, 4.0, 1.0, 9.0] 387 | |> maths.median() 388 | |> should.equal(Ok(4.0)) 389 | 390 | [4.0, 8.0, 9.0, 2.0, 1.0] 391 | |> maths.median() 392 | |> should.equal(Ok(4.0)) 393 | } 394 | 395 | pub fn variance_test() { 396 | // Degrees of freedom 397 | let ddof = 1 398 | 399 | // An empty list returns an error 400 | [] 401 | |> maths.variance(ddof) 402 | |> should.be_error() 403 | 404 | // Valid input returns a result 405 | [1.0, 2.0, 3.0] 406 | |> maths.variance(ddof) 407 | |> should.equal(Ok(1.0)) 408 | } 409 | 410 | pub fn standard_deviation_test() { 411 | // Degrees of freedom 412 | let ddof = 1 413 | 414 | // An empty list returns an error 415 | [] 416 | |> maths.standard_deviation(ddof) 417 | |> should.be_error() 418 | 419 | // Valid input returns a result 420 | [1.0, 2.0, 3.0] 421 | |> maths.standard_deviation(ddof) 422 | |> should.equal(Ok(1.0)) 423 | } 424 | 425 | pub fn kurtosis_test() { 426 | let assert Ok(tol) = float.power(10.0, -6.0) 427 | 428 | // An empty list returns an error 429 | [] 430 | |> maths.kurtosis() 431 | |> should.be_error() 432 | 433 | // To calculate kurtosis at least four values are needed, so 434 | // test that we receive an error with list of size 1, 2, 3, 4. 435 | [1.0] 436 | |> maths.kurtosis() 437 | |> should.be_error() 438 | 439 | [1.0, 2.0] 440 | |> maths.kurtosis() 441 | |> should.be_error() 442 | 443 | [1.0, 2.0, 3.0] 444 | |> maths.kurtosis() 445 | |> should.be_error() 446 | 447 | [1.0, 2.0, 3.0, 4.0] 448 | |> maths.kurtosis() 449 | |> should.equal(Ok(-1.36)) 450 | 451 | // Try with a homogeneous list (variance is zero, so kurtosis is undefined) 452 | [1.0, 1.0, 1.0, 1.0] 453 | |> maths.kurtosis() 454 | |> should.be_error() 455 | 456 | // Try with another non-homogeneous list 457 | let assert Ok(result) = 458 | [1.0, 1.0, 1.0, 2.0] 459 | |> maths.kurtosis() 460 | result 461 | |> maths.is_close(-0.666666666666666, 0.0, tol) 462 | |> should.be_true() 463 | 464 | // Try with a larger input list 465 | let assert Ok(result) = 466 | [6.0, 7.0, 5.0, 7.0, 5.0, 4.0, 4.0, 6.0, 1.0, 3.0, 2.0, 8.0] 467 | |> maths.kurtosis() 468 | result 469 | |> maths.is_close(-0.8548263591730101, 0.0, tol) 470 | |> should.be_true() 471 | } 472 | 473 | pub fn skewness_test() { 474 | let assert Ok(tol) = float.power(10.0, -6.0) 475 | 476 | // An empty list returns an error 477 | [] 478 | |> maths.skewness() 479 | |> should.be_error() 480 | 481 | // To calculate skewness at least three values are needed, so 482 | // test that we receive an error with list of size 1, 2, 3. 483 | [1.0] 484 | |> maths.skewness() 485 | |> should.be_error() 486 | 487 | [1.0, 2.0] 488 | |> maths.skewness() 489 | |> should.be_error() 490 | 491 | // No skewness (zero skewness) 492 | [1.0, 2.0, 3.0] 493 | |> maths.skewness() 494 | |> should.equal(Ok(0.0)) 495 | 496 | [1.0, 2.0, 3.0, 4.0] 497 | |> maths.skewness() 498 | |> should.equal(Ok(0.0)) 499 | 500 | [1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 4.0] 501 | |> maths.skewness() 502 | |> should.equal(Ok(0.6)) 503 | 504 | // Try with a homogeneous list (variance is zero, so skewness is undefined) 505 | [1.0, 1.0, 1.0, 1.0] 506 | |> maths.skewness() 507 | |> should.be_error() 508 | 509 | // Try with another non-homogeneous list 510 | let assert Ok(result) = 511 | [1.0, 1.0, 1.0, 2.0] 512 | |> maths.skewness() 513 | result 514 | |> maths.is_close(1.1547005383792515, 0.0, tol) 515 | |> should.be_true() 516 | 517 | // Try with a larger input list 518 | let assert Ok(result) = 519 | [6.0, 7.0, 5.0, 7.0, 5.0, 4.0, 4.0, 6.0, 1.0, 3.0, 2.0, 8.0] 520 | |> maths.skewness() 521 | result 522 | |> maths.is_close(-0.3078992452957464, 0.0, tol) 523 | |> should.be_true() 524 | } 525 | 526 | pub fn percentile_test() { 527 | // An empty list returns an error 528 | [] 529 | |> maths.percentile(40) 530 | |> should.be_error() 531 | 532 | // Percentile 0 (minimum value) 533 | [15.0, 20.0, 35.0, 40.0, 50.0] 534 | |> maths.percentile(0) 535 | |> should.equal(Ok(15.0)) 536 | 537 | // Calculate 40th percentile 538 | [15.0, 20.0, 35.0, 40.0, 50.0] 539 | |> maths.percentile(40) 540 | |> should.equal(Ok(29.0)) 541 | 542 | // Percentile 100 (maximum value) 543 | [15.0, 20.0, 35.0, 40.0, 50.0] 544 | |> maths.percentile(100) 545 | |> should.equal(Ok(50.0)) 546 | 547 | // Percentile 40 (interpolated value) 548 | [15.0, 20.0, 35.0, 40.0, 50.0] 549 | |> maths.percentile(40) 550 | |> should.equal(Ok(29.0)) 551 | 552 | // Percentile for a single-element list (always returns the element) 553 | [25.0] 554 | |> maths.percentile(0) 555 | |> should.equal(Ok(25.0)) 556 | 557 | [25.0] 558 | |> maths.percentile(50) 559 | |> should.equal(Ok(25.0)) 560 | 561 | [25.0] 562 | |> maths.percentile(100) 563 | |> should.equal(Ok(25.0)) 564 | 565 | // Percentile 50 (median) for an even-length list 566 | [10.0, 20.0, 30.0, 40.0] 567 | |> maths.percentile(50) 568 | // Interpolates between 20.0 and 30.0 569 | |> should.equal(Ok(25.0)) 570 | 571 | // Percentile 50 (median) for an odd-length list 572 | [10.0, 20.0, 30.0, 40.0, 50.0] 573 | |> maths.percentile(50) 574 | // Middle value 575 | |> should.equal(Ok(30.0)) 576 | 577 | // Percentile 25 (lower quartile) 578 | [10.0, 20.0, 30.0, 40.0, 50.0] 579 | |> maths.percentile(25) 580 | |> should.equal(Ok(20.0)) 581 | 582 | // Percentile 75 (upper quartile) 583 | [10.0, 20.0, 30.0, 40.0, 50.0] 584 | |> maths.percentile(75) 585 | |> should.equal(Ok(40.0)) 586 | 587 | // Percentile for a two-element list (interpolation) 588 | [10.0, 20.0] 589 | |> maths.percentile(50) 590 | // Interpolates between 10.0 and 20.0 591 | |> should.equal(Ok(15.0)) 592 | 593 | // Percentile outside valid range (negative percentile) 594 | [10.0, 20.0, 30.0] 595 | |> maths.percentile(-10) 596 | |> should.be_error() 597 | 598 | // Percentile outside valid range (greater than 100) 599 | [10.0, 20.0, 30.0] 600 | |> maths.percentile(110) 601 | |> should.be_error() 602 | 603 | // Percentile 0 and 100 for an unsorted list (valid result after sorting) 604 | [50.0, 20.0, 40.0, 10.0, 30.0] 605 | |> maths.percentile(0) 606 | // Minimum after sorting 607 | |> should.equal(Ok(10.0)) 608 | 609 | [50.0, 20.0, 40.0, 10.0, 30.0] 610 | |> maths.percentile(100) 611 | // Maximum after sorting 612 | |> should.equal(Ok(50.0)) 613 | } 614 | 615 | pub fn zscore_test() { 616 | let assert Ok(tol) = float.power(10.0, -6.0) 617 | 618 | // An empty list returns an error 619 | [] 620 | // Use degrees of freedom = 1 621 | |> maths.zscore(1) 622 | |> should.be_error() 623 | 624 | [1.0, 2.0, 3.0] 625 | // Use degrees of freedom = 1 626 | |> maths.zscore(1) 627 | |> should.equal(Ok([-1.0, 0.0, 1.0])) 628 | 629 | // A single-element list should return an error 630 | [42.0] 631 | |> maths.zscore(1) 632 | |> should.be_error() 633 | 634 | // A typical case with multiple values 635 | [1.0, 2.0, 3.0] 636 | |> maths.zscore(1) 637 | |> should.equal(Ok([-1.0, 0.0, 1.0])) 638 | 639 | // Degrees of freedom = 0 (population standard deviation) 640 | let assert Ok(zscores) = 641 | [1.0, 2.0, 3.0] 642 | |> maths.zscore(0) 643 | 644 | maths.all_close( 645 | list.zip(zscores, [-1.224744871391589, 0.0, 1.224744871391589]), 646 | 0.0, 647 | tol, 648 | ) 649 | |> list.all(fn(x) { x == True }) 650 | |> should.be_true() 651 | 652 | // Handling negative degrees of freedom (invalid input) 653 | [1.0, 2.0, 3.0] 654 | |> maths.zscore(-1) 655 | |> should.be_error() 656 | 657 | // A list with all identical values should return an error 658 | // (stdev = 0, division by zero not allowed) 659 | [5.0, 5.0, 5.0] 660 | |> maths.zscore(1) 661 | |> should.be_error() 662 | 663 | // A list with negative and positive values 664 | [-2.0, 0.0, 2.0] 665 | |> maths.zscore(1) 666 | |> should.equal(Ok([-1.0, 0.0, 1.0])) 667 | 668 | // A list with fractional values 669 | [1.5, 2.5, 3.5] 670 | |> maths.zscore(1) 671 | |> should.equal(Ok([-1.0, 0.0, 1.0])) 672 | } 673 | 674 | pub fn iqr_test() { 675 | // An empty list returns an error 676 | [] 677 | |> maths.interquartile_range() 678 | |> should.be_error() 679 | 680 | // A single-element list returns an error 681 | [42.0] 682 | |> maths.interquartile_range() 683 | |> should.be_error() 684 | 685 | // Try a two-element list 686 | [10.0, 20.0] 687 | |> maths.interquartile_range() 688 | // Q1 = 10.0, Q3 = 20.0, IQR = Q3 - Q1 = 10.0 689 | |> should.equal(Ok(10.0)) 690 | 691 | // A valid input with an odd number of elements 692 | [1.0, 2.0, 3.0, 4.0, 5.0] 693 | |> maths.interquartile_range() 694 | // Q1 = 2.0, Q3 = 5.0, IQR = Q3 - Q1 = 3.0 695 | |> should.equal(Ok(3.0)) 696 | 697 | // // A valid input with an even number of elements 698 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] 699 | |> maths.interquartile_range() 700 | // Q1 = 2.5, Q3 = 5.5, IQR = Q3 - Q1 = 3.0 701 | |> should.equal(Ok(3.0)) 702 | 703 | // Make sure an unsorted list is sorted before calculating the IQR 704 | [9.0, 1.0, 4.0, 2.0, 8.0, 3.0, 7.0] 705 | |> maths.interquartile_range() 706 | // Sorted: [1.0, 2.0, 3.0, 4.0, 7.0, 8.0, 9.0], IQR = 8.0 - 2.0 = 6.0 707 | |> should.equal(Ok(6.0)) 708 | 709 | // A list with duplicates still computes the correct IQR 710 | [1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 5.0] 711 | |> maths.interquartile_range() 712 | // Q1 = 2.0, Q3 = 4.0, IQR = Q3 - Q1 = 2.0 713 | |> should.equal(Ok(2.0)) 714 | 715 | // A list with all identical elements should return IQR = 0.0 716 | [5.0, 5.0, 5.0, 5.0, 5.0] 717 | |> maths.interquartile_range() 718 | // Q1 = 5.0, Q3 = 5.0, IQR = Q3 - Q1 = 0.0 719 | |> should.equal(Ok(0.0)) 720 | } 721 | 722 | pub fn correlation_test() { 723 | // An empty list returns an error 724 | maths.correlation([]) 725 | |> should.be_error() 726 | 727 | // Lists with fewer than 2 elements return an error 728 | maths.correlation([#(1.0, 1.0)]) 729 | |> should.be_error() 730 | 731 | // Perfect positive correlation 732 | let xarr = 733 | list.range(0, 100) 734 | |> list.map(fn(x) { int.to_float(x) }) 735 | let yarr = 736 | list.range(0, 100) 737 | |> list.map(fn(y) { int.to_float(y) }) 738 | list.zip(xarr, yarr) 739 | |> maths.correlation() 740 | |> should.equal(Ok(1.0)) 741 | 742 | // Perfect negative correlation 743 | let xarr = 744 | list.range(0, 100) 745 | |> list.map(fn(x) { -1.0 *. int.to_float(x) }) 746 | let yarr = 747 | list.range(0, 100) 748 | |> list.map(fn(y) { int.to_float(y) }) 749 | list.zip(xarr, yarr) 750 | |> maths.correlation() 751 | |> should.equal(Ok(-1.0)) 752 | 753 | // No correlation (independent variables) 754 | let xarr = [1.0, 2.0, 3.0, 4.0] 755 | let yarr = [5.0, 5.0, 5.0, 5.0] 756 | list.zip(xarr, yarr) 757 | |> maths.correlation() 758 | |> should.equal(Ok(0.0)) 759 | } 760 | 761 | pub fn jaccard_index_test() { 762 | maths.jaccard_index(set.from_list([]), set.from_list([])) 763 | |> should.equal(0.0) 764 | 765 | let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9]) 766 | let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9]) 767 | maths.jaccard_index(set_a, set_b) 768 | |> should.equal(4.0 /. 10.0) 769 | 770 | let set_c = set.from_list([0, 1, 2, 3, 4, 5]) 771 | let set_d = set.from_list([6, 7, 8, 9, 10]) 772 | maths.jaccard_index(set_c, set_d) 773 | |> should.equal(0.0 /. 11.0) 774 | 775 | let set_e = set.from_list(["cat", "dog", "hippo", "monkey"]) 776 | let set_f = set.from_list(["monkey", "rhino", "ostrich", "salmon"]) 777 | maths.jaccard_index(set_e, set_f) 778 | |> should.equal(1.0 /. 7.0) 779 | } 780 | 781 | pub fn sorensen_dice_coefficient_test() { 782 | maths.sorensen_dice_coefficient(set.from_list([]), set.from_list([])) 783 | |> should.equal(0.0) 784 | 785 | let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9]) 786 | let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9]) 787 | maths.sorensen_dice_coefficient(set_a, set_b) 788 | |> should.equal(2.0 *. 4.0 /. { 7.0 +. 7.0 }) 789 | 790 | let set_c = set.from_list([0, 1, 2, 3, 4, 5]) 791 | let set_d = set.from_list([6, 7, 8, 9, 10]) 792 | maths.sorensen_dice_coefficient(set_c, set_d) 793 | |> should.equal(2.0 *. 0.0 /. { 6.0 +. 5.0 }) 794 | 795 | let set_e = set.from_list(["cat", "dog", "hippo", "monkey"]) 796 | let set_f = set.from_list(["monkey", "rhino", "ostrich", "salmon", "spider"]) 797 | maths.sorensen_dice_coefficient(set_e, set_f) 798 | |> should.equal(2.0 *. 1.0 /. { 4.0 +. 5.0 }) 799 | } 800 | 801 | pub fn overlap_coefficient_test() { 802 | maths.overlap_coefficient(set.from_list([]), set.from_list([])) 803 | |> should.equal(0.0) 804 | 805 | let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9]) 806 | let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9]) 807 | maths.overlap_coefficient(set_a, set_b) 808 | |> should.equal(4.0 /. 7.0) 809 | 810 | let set_c = set.from_list([0, 1, 2, 3, 4, 5]) 811 | let set_d = set.from_list([6, 7, 8, 9, 10]) 812 | maths.overlap_coefficient(set_c, set_d) 813 | |> should.equal(0.0 /. 5.0) 814 | 815 | let set_e = set.from_list(["horse", "dog", "hippo", "monkey", "bird"]) 816 | let set_f = set.from_list(["monkey", "bird", "ostrich", "salmon"]) 817 | maths.overlap_coefficient(set_e, set_f) 818 | |> should.equal(2.0 /. 4.0) 819 | } 820 | 821 | pub fn cosine_similarity_test() { 822 | let assert Ok(tol) = float.power(10.0, -6.0) 823 | 824 | // An empty list returns an error 825 | maths.cosine_similarity([]) 826 | |> should.be_error() 827 | 828 | // Two orthogonal vectors (represented by lists) 829 | maths.cosine_similarity([#(-1.0, 1.0), #(1.0, 1.0), #(0.0, -1.0)]) 830 | |> should.equal(Ok(0.0)) 831 | 832 | // Two identical (parallel) vectors (represented by lists) 833 | maths.cosine_similarity([#(1.0, 1.0), #(2.0, 2.0), #(3.0, 3.0)]) 834 | |> should.equal(Ok(1.0)) 835 | 836 | // Two parallel, but oppositely oriented vectors (represented by lists) 837 | maths.cosine_similarity([#(-1.0, 1.0), #(-2.0, 2.0), #(-3.0, 3.0)]) 838 | |> should.equal(Ok(-1.0)) 839 | 840 | // Try with arbitrary valid input 841 | let assert Ok(result) = 842 | maths.cosine_similarity([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)]) 843 | result 844 | |> maths.is_close(0.9746318461970762, 0.0, tol) 845 | |> should.be_true() 846 | 847 | // An empty list returns an error 848 | maths.cosine_similarity_with_weights([]) 849 | |> should.be_error() 850 | 851 | // Try valid input with weights 852 | let assert Ok(result) = 853 | maths.cosine_similarity_with_weights([ 854 | #(1.0, 4.0, 1.0), 855 | #(2.0, 5.0, 1.0), 856 | #(3.0, 6.0, 1.0), 857 | ]) 858 | result 859 | |> maths.is_close(0.9746318461970762, 0.0, tol) 860 | |> should.be_true() 861 | 862 | // Try with different weights 863 | let assert Ok(result) = 864 | maths.cosine_similarity_with_weights([ 865 | #(1.0, 4.0, 1.0), 866 | #(2.0, 5.0, 2.0), 867 | #(3.0, 6.0, 3.0), 868 | ]) 869 | result 870 | |> maths.is_close(0.9855274566525745, 0.0, tol) 871 | |> should.be_true() 872 | 873 | let assert Ok(result) = 874 | maths.cosine_similarity_with_weights([ 875 | #(1.0, 1.0, 2.0), 876 | #(2.0, 2.0, 3.0), 877 | #(3.0, 3.0, 4.0), 878 | ]) 879 | result 880 | |> maths.is_close(1.0, 0.0, tol) 881 | |> should.be_true() 882 | 883 | let assert Ok(result) = 884 | maths.cosine_similarity_with_weights([ 885 | #(-1.0, 1.0, 1.0), 886 | #(-2.0, 2.0, 0.5), 887 | #(-3.0, 3.0, 0.33), 888 | ]) 889 | result 890 | |> maths.is_close(-1.0, 0.0, tol) 891 | |> should.be_true() 892 | 893 | // Try invalid input with weights that are negative 894 | maths.cosine_similarity_with_weights([ 895 | #(1.0, 4.0, -7.0), 896 | #(2.0, 5.0, -8.0), 897 | #(3.0, 6.0, -9.0), 898 | ]) 899 | |> should.be_error() 900 | } 901 | 902 | pub fn chebyshev_distance_test() { 903 | // An empty list returns an error 904 | maths.chebyshev_distance([]) 905 | |> should.be_error() 906 | 907 | // Try different types of valid input 908 | maths.chebyshev_distance([#(1.0, 0.0), #(0.0, 2.0)]) 909 | |> should.equal(Ok(2.0)) 910 | 911 | maths.chebyshev_distance([#(1.0, 2.0), #(0.0, 0.0)]) 912 | |> should.equal(Ok(1.0)) 913 | 914 | maths.chebyshev_distance([#(1.0, -2.0), #(0.0, 0.0)]) 915 | |> should.equal(Ok(3.0)) 916 | 917 | maths.chebyshev_distance([#(-5.0, -1.0), #(-10.0, -12.0), #(-3.0, -3.0)]) 918 | |> should.equal(Ok(4.0)) 919 | 920 | maths.chebyshev_distance([#(1.0, 1.0), #(2.0, 2.0), #(3.0, 3.0)]) 921 | |> should.equal(Ok(0.0)) 922 | 923 | maths.chebyshev_distance_with_weights([ 924 | #(-5.0, -1.0, 0.5), 925 | #(-10.0, -12.0, 1.0), 926 | #(-3.0, -3.0, 1.0), 927 | ]) 928 | |> should.equal(Ok(2.0)) 929 | } 930 | 931 | pub fn canberra_distance_test() { 932 | // An empty list returns an error 933 | maths.canberra_distance([]) 934 | |> should.be_error() 935 | 936 | // Try different types of valid input 937 | maths.canberra_distance([#(0.0, 0.0), #(0.0, 0.0)]) 938 | |> should.equal(Ok(0.0)) 939 | 940 | maths.canberra_distance([#(1.0, -2.0), #(2.0, -1.0)]) 941 | |> should.equal(Ok(2.0)) 942 | 943 | maths.canberra_distance([#(1.0, 0.0), #(0.0, 2.0)]) 944 | |> should.equal(Ok(2.0)) 945 | 946 | maths.canberra_distance([#(1.0, 2.0), #(0.0, 0.0)]) 947 | |> should.equal(Ok(1.0 /. 3.0)) 948 | 949 | maths.canberra_distance_with_weights([#(1.0, 0.0, 1.0), #(0.0, 2.0, 1.0)]) 950 | |> should.equal(Ok(2.0)) 951 | 952 | maths.canberra_distance_with_weights([#(1.0, 0.0, 1.0), #(0.0, 2.0, 0.5)]) 953 | |> should.equal(Ok(1.5)) 954 | 955 | maths.canberra_distance_with_weights([#(1.0, 0.0, 0.5), #(0.0, 2.0, 0.5)]) 956 | |> should.equal(Ok(1.0)) 957 | 958 | // Try invalid input with weights that are negative 959 | maths.canberra_distance_with_weights([ 960 | #(1.0, 4.0, -7.0), 961 | #(2.0, 5.0, -8.0), 962 | #(3.0, 6.0, -9.0), 963 | ]) 964 | |> should.be_error() 965 | } 966 | 967 | pub fn braycurtis_distance_test() { 968 | // An empty list returns an error 969 | maths.braycurtis_distance([]) 970 | |> should.be_error() 971 | 972 | // Try different types of valid input 973 | maths.braycurtis_distance([#(0.0, 0.0), #(0.0, 0.0)]) 974 | |> should.equal(Ok(0.0)) 975 | 976 | maths.braycurtis_distance([#(1.0, -2.0), #(2.0, -1.0)]) 977 | |> should.equal(Ok(3.0)) 978 | 979 | maths.braycurtis_distance([#(1.0, 0.0), #(0.0, 2.0)]) 980 | |> should.equal(Ok(1.0)) 981 | 982 | maths.braycurtis_distance([#(1.0, 3.0), #(2.0, 4.0)]) 983 | |> should.equal(Ok(0.4)) 984 | 985 | maths.braycurtis_distance_with_weights([#(1.0, 3.0, 1.0), #(2.0, 4.0, 1.0)]) 986 | |> should.equal(Ok(0.4)) 987 | 988 | maths.braycurtis_distance_with_weights([#(1.0, 3.0, 0.5), #(2.0, 4.0, 1.0)]) 989 | |> should.equal(Ok(0.375)) 990 | 991 | maths.braycurtis_distance_with_weights([#(1.0, 3.0, 0.25), #(2.0, 4.0, 0.25)]) 992 | |> should.equal(Ok(0.4)) 993 | // Try invalid input with weights that are negative 994 | maths.braycurtis_distance_with_weights([ 995 | #(1.0, 4.0, -7.0), 996 | #(2.0, 5.0, -8.0), 997 | #(3.0, 6.0, -9.0), 998 | ]) 999 | |> should.be_error() 1000 | } 1001 | -------------------------------------------------------------------------------- /test/gleam_community/piecewise_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam/int 3 | import gleam_community/maths 4 | import gleeunit/should 5 | 6 | pub fn ceiling_test() { 7 | // Round 3. digit AFTER decimal point 8 | maths.round_up(12.0654, 3) 9 | |> should.equal(12.066) 10 | 11 | // Round 2. digit AFTER decimal point 12 | maths.round_up(12.0654, 2) 13 | |> should.equal(12.07) 14 | 15 | // Round 1. digit AFTER decimal point 16 | maths.round_up(12.0654, 1) 17 | |> should.equal(12.1) 18 | 19 | // Round 0. digit BEFORE decimal point 20 | maths.round_up(12.0654, 0) 21 | |> should.equal(13.0) 22 | 23 | // Round 1. digit BEFORE decimal point 24 | maths.round_up(12.0654, -1) 25 | |> should.equal(20.0) 26 | 27 | // Round 2. digit BEFORE decimal point 28 | maths.round_up(12.0654, -2) 29 | |> should.equal(100.0) 30 | 31 | // Round 3. digit BEFORE decimal point 32 | maths.round_up(12.0654, -3) 33 | |> should.equal(1000.0) 34 | } 35 | 36 | pub fn floor_test() { 37 | // Round 3. digit AFTER decimal point 38 | maths.round_down(12.0654, 3) 39 | |> should.equal(12.065) 40 | 41 | // Round 2. digit AFTER decimal point 42 | maths.round_down(12.0654, 2) 43 | |> should.equal(12.06) 44 | 45 | // Round 1. digit AFTER decimal point 46 | maths.round_down(12.0654, 1) 47 | |> should.equal(12.0) 48 | 49 | // Round 0. digit BEFORE decimal point 50 | maths.round_down(12.0654, 0) 51 | |> should.equal(12.0) 52 | 53 | // Round 1. digit BEFORE decimal point 54 | maths.round_down(12.0654, -1) 55 | |> should.equal(10.0) 56 | 57 | // Round 2. digit BEFORE decimal point 58 | maths.round_down(12.0654, -2) 59 | |> should.equal(0.0) 60 | 61 | // Round 2. digit BEFORE decimal point 62 | maths.round_down(12.0654, -3) 63 | |> should.equal(0.0) 64 | } 65 | 66 | pub fn truncate_test() { 67 | // Round 3. digit AFTER decimal point 68 | maths.round_to_zero(12.0654, 3) 69 | |> should.equal(12.065) 70 | 71 | // Round 2. digit AFTER decimal point 72 | maths.round_to_zero(12.0654, 2) 73 | |> should.equal(12.06) 74 | 75 | // Round 1. digit AFTER decimal point 76 | maths.round_to_zero(12.0654, 1) 77 | |> should.equal(12.0) 78 | 79 | // Round 0. digit BEFORE decimal point 80 | maths.round_to_zero(12.0654, 0) 81 | |> should.equal(12.0) 82 | 83 | // Round 1. digit BEFORE decimal point 84 | maths.round_to_zero(12.0654, -1) 85 | |> should.equal(10.0) 86 | 87 | // Round 2. digit BEFORE decimal point 88 | maths.round_to_zero(12.0654, -2) 89 | |> should.equal(0.0) 90 | 91 | // Round 2. digit BEFORE decimal point 92 | maths.round_to_zero(12.0654, -3) 93 | |> should.equal(0.0) 94 | } 95 | 96 | pub fn math_round_to_nearest_test() { 97 | // Try with positive values 98 | maths.round_to_nearest(1.5, 0) 99 | |> should.equal(2.0) 100 | 101 | maths.round_to_nearest(1.75, 0) 102 | |> should.equal(2.0) 103 | 104 | maths.round_to_nearest(2.0, 0) 105 | |> should.equal(2.0) 106 | 107 | maths.round_to_nearest(3.5, 0) 108 | |> should.equal(4.0) 109 | 110 | maths.round_to_nearest(4.5, 0) 111 | |> should.equal(4.0) 112 | 113 | // Try with negative values 114 | maths.round_to_nearest(-3.5, 0) 115 | |> should.equal(-4.0) 116 | 117 | maths.round_to_nearest(-4.5, 0) 118 | |> should.equal(-4.0) 119 | 120 | // // Round 3. digit AFTER decimal point 121 | maths.round_to_nearest(12.0654, 3) 122 | |> should.equal(12.065) 123 | 124 | // Round 2. digit AFTER decimal point 125 | maths.round_to_nearest(12.0654, 2) 126 | |> should.equal(12.07) 127 | 128 | // Round 1. digit AFTER decimal point 129 | maths.round_to_nearest(12.0654, 1) 130 | |> should.equal(12.1) 131 | 132 | // Round 0. digit BEFORE decimal point 133 | maths.round_to_nearest(12.0654, 0) 134 | |> should.equal(12.0) 135 | 136 | // Round 1. digit BEFORE decimal point 137 | maths.round_to_nearest(12.0654, -1) 138 | |> should.equal(10.0) 139 | 140 | // Round 2. digit BEFORE decimal point 141 | maths.round_to_nearest(12.0654, -2) 142 | |> should.equal(0.0) 143 | 144 | // Round 3. digit BEFORE decimal point 145 | maths.round_to_nearest(12.0654, -3) 146 | |> should.equal(0.0) 147 | } 148 | 149 | pub fn math_round_up_test() { 150 | // Try with positive values 151 | maths.round_up(0.45, 0) 152 | |> should.equal(1.0) 153 | 154 | maths.round_up(0.5, 0) 155 | |> should.equal(1.0) 156 | 157 | maths.round_up(0.45, 1) 158 | |> should.equal(0.5) 159 | 160 | maths.round_up(0.5, 1) 161 | |> should.equal(0.5) 162 | 163 | maths.round_up(0.455, 2) 164 | |> should.equal(0.46) 165 | 166 | maths.round_up(0.505, 2) 167 | |> should.equal(0.51) 168 | 169 | // Try with negative values 170 | maths.round_up(-0.45, 0) 171 | |> should.equal(-0.0) 172 | 173 | maths.round_up(-0.5, 0) 174 | |> should.equal(-0.0) 175 | 176 | maths.round_up(-0.45, 1) 177 | |> should.equal(-0.4) 178 | 179 | maths.round_up(-0.5, 1) 180 | |> should.equal(-0.5) 181 | 182 | maths.round_up(-0.455, 2) 183 | |> should.equal(-0.45) 184 | 185 | maths.round_up(-0.505, 2) 186 | |> should.equal(-0.5) 187 | } 188 | 189 | pub fn math_round_down_test() { 190 | // Try with positive values 191 | maths.round_down(0.45, 0) 192 | |> should.equal(0.0) 193 | 194 | maths.round_down(0.5, 0) 195 | |> should.equal(0.0) 196 | 197 | maths.round_down(0.45, 1) 198 | |> should.equal(0.4) 199 | 200 | maths.round_down(0.5, 1) 201 | |> should.equal(0.5) 202 | 203 | maths.round_down(0.455, 2) 204 | |> should.equal(0.45) 205 | 206 | maths.round_down(0.505, 2) 207 | |> should.equal(0.5) 208 | 209 | // Try with negative values 210 | maths.round_down(-0.45, 0) 211 | |> should.equal(-1.0) 212 | 213 | maths.round_down(-0.5, 0) 214 | |> should.equal(-1.0) 215 | 216 | maths.round_down(-0.45, 1) 217 | |> should.equal(-0.5) 218 | 219 | maths.round_down(-0.5, 1) 220 | |> should.equal(-0.5) 221 | 222 | maths.round_down(-0.455, 2) 223 | |> should.equal(-0.46) 224 | 225 | maths.round_down(-0.505, 2) 226 | |> should.equal(-0.51) 227 | } 228 | 229 | pub fn math_round_to_zero_test() { 230 | // Note: Rounding mode "RoundToZero" is an alias for the truncate function 231 | // Try with positive values 232 | maths.round_to_zero(0.5, 0) 233 | |> should.equal(0.0) 234 | 235 | maths.round_to_zero(0.75, 0) 236 | |> should.equal(0.0) 237 | 238 | maths.round_to_zero(0.45, 1) 239 | |> should.equal(0.4) 240 | 241 | maths.round_to_zero(0.57, 1) 242 | |> should.equal(0.5) 243 | 244 | maths.round_to_zero(0.4575, 2) 245 | |> should.equal(0.45) 246 | 247 | maths.round_to_zero(0.5075, 2) 248 | |> should.equal(0.5) 249 | 250 | // Try with negative values 251 | maths.round_to_zero(-0.5, 0) 252 | |> should.equal(0.0) 253 | 254 | maths.round_to_zero(-0.75, 0) 255 | |> should.equal(0.0) 256 | 257 | maths.round_to_zero(-0.45, 1) 258 | |> should.equal(-0.4) 259 | 260 | maths.round_to_zero(-0.57, 1) 261 | |> should.equal(-0.5) 262 | 263 | maths.round_to_zero(-0.4575, 2) 264 | |> should.equal(-0.45) 265 | 266 | maths.round_to_zero(-0.5075, 2) 267 | |> should.equal(-0.5) 268 | } 269 | 270 | pub fn math_round_ties_away_test() { 271 | // Try with positive values 272 | maths.round_ties_away(1.4, 0) 273 | |> should.equal(1.0) 274 | 275 | maths.round_ties_away(1.5, 0) 276 | |> should.equal(2.0) 277 | 278 | maths.round_ties_away(2.5, 0) 279 | |> should.equal(3.0) 280 | 281 | // Try with negative values 282 | maths.round_ties_away(-1.4, 0) 283 | |> should.equal(-1.0) 284 | 285 | maths.round_ties_away(-1.5, 0) 286 | |> should.equal(-2.0) 287 | 288 | maths.round_ties_away(-2.0, 0) 289 | |> should.equal(-2.0) 290 | 291 | maths.round_ties_away(-2.5, 0) 292 | |> should.equal(-3.0) 293 | 294 | // Round 3. digit AFTER decimal point 295 | maths.round_ties_away(12.0654, 3) 296 | |> should.equal(12.065) 297 | 298 | // Round 2. digit AFTER decimal point 299 | maths.round_ties_away(12.0654, 2) 300 | |> should.equal(12.07) 301 | 302 | // Round 1. digit AFTER decimal point 303 | maths.round_ties_away(12.0654, 1) 304 | |> should.equal(12.1) 305 | 306 | // Round 0. digit BEFORE decimal point 307 | maths.round_ties_away(12.0654, 0) 308 | |> should.equal(12.0) 309 | 310 | // Round 1. digit BEFORE decimal point 311 | maths.round_ties_away(12.0654, -1) 312 | |> should.equal(10.0) 313 | 314 | // Round 2. digit BEFORE decimal point 315 | maths.round_ties_away(12.0654, -2) 316 | |> should.equal(0.0) 317 | 318 | // Round 2. digit BEFORE decimal point 319 | maths.round_ties_away(12.0654, -3) 320 | |> should.equal(0.0) 321 | } 322 | 323 | pub fn math_round_ties_up_test() { 324 | // Try with positive values 325 | maths.round_ties_up(1.4, 0) 326 | |> should.equal(1.0) 327 | 328 | maths.round_ties_up(1.5, 0) 329 | |> should.equal(2.0) 330 | 331 | maths.round_ties_up(2.5, 0) 332 | |> should.equal(3.0) 333 | 334 | // Try with negative values 335 | maths.round_ties_up(-1.4, 0) 336 | |> should.equal(-1.0) 337 | 338 | maths.round_ties_up(-1.5, 0) 339 | |> should.equal(-1.0) 340 | 341 | maths.round_ties_up(-2.0, 0) 342 | |> should.equal(-2.0) 343 | 344 | maths.round_ties_up(-2.5, 0) 345 | |> should.equal(-2.0) 346 | 347 | // Round 3. digit AFTER decimal point 348 | maths.round_ties_up(12.0654, 3) 349 | |> should.equal(12.065) 350 | 351 | // Round 2. digit AFTER decimal point 352 | maths.round_ties_up(12.0654, 2) 353 | |> should.equal(12.07) 354 | 355 | // Round 1. digit AFTER decimal point 356 | maths.round_ties_up(12.0654, 1) 357 | |> should.equal(12.1) 358 | 359 | // Round 0. digit BEFORE decimal point 360 | maths.round_ties_up(12.0654, 0) 361 | |> should.equal(12.0) 362 | 363 | // Round 1. digit BEFORE decimal point 364 | maths.round_ties_up(12.0654, -1) 365 | |> should.equal(10.0) 366 | 367 | // Round 2. digit BEFORE decimal point 368 | maths.round_ties_up(12.0654, -2) 369 | |> should.equal(0.0) 370 | 371 | // Round 2. digit BEFORE decimal point 372 | maths.round_ties_up(12.0654, -3) 373 | |> should.equal(0.0) 374 | } 375 | 376 | pub fn absolute_difference_test() { 377 | maths.absolute_difference(20.0, 15.0) 378 | |> should.equal(5.0) 379 | 380 | maths.absolute_difference(-20.0, -15.0) 381 | |> should.equal(5.0) 382 | 383 | maths.absolute_difference(20.0, -15.0) 384 | |> should.equal(35.0) 385 | 386 | maths.absolute_difference(-20.0, 15.0) 387 | |> should.equal(35.0) 388 | 389 | maths.absolute_difference(0.0, 0.0) 390 | |> should.equal(0.0) 391 | 392 | maths.absolute_difference(1.0, 2.0) 393 | |> should.equal(1.0) 394 | 395 | maths.absolute_difference(2.0, 1.0) 396 | |> should.equal(1.0) 397 | 398 | maths.absolute_difference(-1.0, 0.0) 399 | |> should.equal(1.0) 400 | 401 | maths.absolute_difference(0.0, -1.0) 402 | |> should.equal(1.0) 403 | 404 | maths.absolute_difference(10.0, 20.0) 405 | |> should.equal(10.0) 406 | 407 | maths.absolute_difference(-10.0, -20.0) 408 | |> should.equal(10.0) 409 | 410 | maths.absolute_difference(-10.5, 10.5) 411 | |> should.equal(21.0) 412 | } 413 | 414 | pub fn int_absolute_difference_test() { 415 | maths.int_absolute_difference(20, 15) 416 | |> should.equal(5) 417 | 418 | maths.int_absolute_difference(-20, -15) 419 | |> should.equal(5) 420 | 421 | maths.int_absolute_difference(20, -15) 422 | |> should.equal(35) 423 | 424 | maths.int_absolute_difference(-20, 15) 425 | |> should.equal(35) 426 | } 427 | 428 | pub fn sign_test() { 429 | maths.sign(100.0) 430 | |> should.equal(1.0) 431 | 432 | maths.sign(0.0) 433 | |> should.equal(0.0) 434 | 435 | maths.sign(-100.0) 436 | |> should.equal(-1.0) 437 | } 438 | 439 | pub fn flip_sign_test() { 440 | maths.flip_sign(100.0) 441 | |> should.equal(-100.0) 442 | 443 | maths.flip_sign(0.0) 444 | |> should.equal(-0.0) 445 | 446 | maths.flip_sign(-100.0) 447 | |> should.equal(100.0) 448 | } 449 | 450 | pub fn copy_sign_test() { 451 | maths.copy_sign(100.0, 10.0) 452 | |> should.equal(100.0) 453 | 454 | maths.copy_sign(-100.0, 10.0) 455 | |> should.equal(100.0) 456 | 457 | maths.copy_sign(100.0, -10.0) 458 | |> should.equal(-100.0) 459 | 460 | maths.copy_sign(-100.0, -10.0) 461 | |> should.equal(-100.0) 462 | } 463 | 464 | pub fn int_sign_test() { 465 | maths.int_sign(100) 466 | |> should.equal(1) 467 | 468 | maths.int_sign(0) 469 | |> should.equal(0) 470 | 471 | maths.int_sign(-100) 472 | |> should.equal(-1) 473 | } 474 | 475 | pub fn int_flip_sign_test() { 476 | maths.int_flip_sign(100) 477 | |> should.equal(-100) 478 | 479 | maths.int_flip_sign(0) 480 | |> should.equal(-0) 481 | 482 | maths.int_flip_sign(-100) 483 | |> should.equal(100) 484 | } 485 | 486 | pub fn int_copy_sign_test() { 487 | maths.int_copy_sign(100, 10) 488 | |> should.equal(100) 489 | 490 | maths.int_copy_sign(-100, 10) 491 | |> should.equal(100) 492 | 493 | maths.int_copy_sign(100, -10) 494 | |> should.equal(-100) 495 | 496 | maths.int_copy_sign(-100, -10) 497 | |> should.equal(-100) 498 | } 499 | 500 | pub fn minmax_test() { 501 | maths.minmax(0.75, 0.5, float.compare) 502 | |> should.equal(#(0.5, 0.75)) 503 | 504 | maths.minmax(0.5, 0.75, float.compare) 505 | |> should.equal(#(0.5, 0.75)) 506 | 507 | maths.minmax(-0.75, 0.5, float.compare) 508 | |> should.equal(#(-0.75, 0.5)) 509 | 510 | maths.minmax(-0.75, 0.5, float.compare) 511 | |> should.equal(#(-0.75, 0.5)) 512 | } 513 | 514 | pub fn int_minmax_test() { 515 | maths.minmax(75, 50, int.compare) 516 | |> should.equal(#(50, 75)) 517 | 518 | maths.minmax(50, 75, int.compare) 519 | |> should.equal(#(50, 75)) 520 | 521 | maths.minmax(-75, 50, int.compare) 522 | |> should.equal(#(-75, 50)) 523 | 524 | maths.minmax(-75, 50, int.compare) 525 | |> should.equal(#(-75, 50)) 526 | } 527 | 528 | pub fn list_minimum_test() { 529 | // An empty lists returns an error 530 | [] 531 | |> maths.list_minimum(float.compare) 532 | |> should.be_error() 533 | 534 | // Valid input returns a result 535 | [4.5, 4.5, 3.5, 2.5, 1.5] 536 | |> maths.list_minimum(float.compare) 537 | |> should.equal(Ok(1.5)) 538 | } 539 | 540 | pub fn int_list_minimum_test() { 541 | // An empty lists returns an error 542 | [] 543 | |> maths.list_minimum(int.compare) 544 | |> should.be_error() 545 | 546 | // Valid input returns a result 547 | [4, 4, 3, 2, 1] 548 | |> maths.list_minimum(int.compare) 549 | |> should.equal(Ok(1)) 550 | } 551 | 552 | pub fn list_maximum_test() { 553 | // An empty lists returns an error 554 | [] 555 | |> maths.list_maximum(float.compare) 556 | |> should.be_error() 557 | 558 | // Valid input returns a result 559 | [4.5, 4.5, 3.5, 2.5, 1.5] 560 | |> maths.list_maximum(float.compare) 561 | |> should.equal(Ok(4.5)) 562 | } 563 | 564 | pub fn int_list_maximum_test() { 565 | // An empty lists returns an error 566 | [] 567 | |> maths.list_maximum(int.compare) 568 | |> should.be_error() 569 | 570 | // Valid input returns a result 571 | [4, 4, 3, 2, 1] 572 | |> maths.list_maximum(int.compare) 573 | |> should.equal(Ok(4)) 574 | } 575 | 576 | pub fn list_arg_maximum_test() { 577 | // An empty lists returns an error 578 | [] 579 | |> maths.arg_maximum(float.compare) 580 | |> should.be_error() 581 | 582 | // Valid input returns a result 583 | [4.5, 4.5, 3.5, 2.5, 1.5] 584 | |> maths.arg_maximum(float.compare) 585 | |> should.equal(Ok([0, 1])) 586 | } 587 | 588 | pub fn int_list_arg_maximum_test() { 589 | // An empty lists returns an error 590 | [] 591 | |> maths.arg_maximum(int.compare) 592 | |> should.be_error() 593 | 594 | // Valid input returns a result 595 | [4, 4, 3, 2, 1] 596 | |> maths.arg_maximum(int.compare) 597 | |> should.equal(Ok([0, 1])) 598 | } 599 | 600 | pub fn list_arg_minimum_test() { 601 | // An empty lists returns an error 602 | [] 603 | |> maths.arg_minimum(float.compare) 604 | |> should.be_error() 605 | 606 | // Valid input returns a result 607 | [4.5, 4.5, 3.5, 2.5, 1.5] 608 | |> maths.arg_minimum(float.compare) 609 | |> should.equal(Ok([4])) 610 | } 611 | 612 | pub fn int_list_arg_minimum_test() { 613 | // An empty lists returns an error 614 | [] 615 | |> maths.arg_minimum(int.compare) 616 | |> should.be_error() 617 | 618 | // Valid input returns a result 619 | [4, 4, 3, 2, 1] 620 | |> maths.arg_minimum(int.compare) 621 | |> should.equal(Ok([4])) 622 | } 623 | 624 | pub fn list_extrema_test() { 625 | // An empty lists returns an error 626 | [] 627 | |> maths.extrema(float.compare) 628 | |> should.be_error() 629 | 630 | // Valid input returns a result 631 | [4.0, 4.0, 3.0, 2.0, 1.0] 632 | |> maths.extrema(float.compare) 633 | |> should.equal(Ok(#(1.0, 4.0))) 634 | 635 | // Valid input returns a result 636 | [1.0, 4.0, 2.0, 5.0, 0.0] 637 | |> maths.extrema(float.compare) 638 | |> should.equal(Ok(#(0.0, 5.0))) 639 | } 640 | 641 | pub fn int_list_extrema_test() { 642 | // An empty lists returns an error 643 | [] 644 | |> maths.extrema(int.compare) 645 | |> should.be_error() 646 | 647 | // Valid input returns a result 648 | [4, 4, 3, 2, 1] 649 | |> maths.extrema(int.compare) 650 | |> should.equal(Ok(#(1, 4))) 651 | 652 | // Valid input returns a result 653 | [1, 4, 2, 5, 0] 654 | |> maths.extrema(int.compare) 655 | |> should.equal(Ok(#(0, 5))) 656 | } 657 | -------------------------------------------------------------------------------- /test/gleam_community/predicates_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/function 2 | import gleam/list 3 | import gleam_community/maths 4 | import gleeunit/should 5 | 6 | pub fn float_is_close_test() { 7 | let val = 99.0 8 | let ref_val = 100.0 9 | // We set 'atol' and 'rtol' such that the values are equivalent 10 | // if 'val' is within 1 percent of 'ref_val' +/- 0.1 11 | let rtol = 0.01 12 | let atol = 0.1 13 | maths.is_close(val, ref_val, rtol, atol) 14 | |> should.be_true() 15 | } 16 | 17 | pub fn float_list_all_close_test() { 18 | let val = 99.0 19 | let ref_val = 100.0 20 | let xarr = list.repeat(val, 42) 21 | let yarr = list.repeat(ref_val, 42) 22 | let arr = list.zip(xarr, yarr) 23 | // We set 'atol' and 'rtol' such that the values are equivalent 24 | // if 'val' is within 1 percent of 'ref_val' +/- 0.1 25 | let rtol = 0.01 26 | let atol = 0.1 27 | maths.all_close(arr, rtol, atol) 28 | |> list.all(function.identity) 29 | |> should.equal(True) 30 | } 31 | 32 | pub fn float_is_fractional_test() { 33 | maths.is_fractional(1.5) 34 | |> should.equal(True) 35 | maths.is_fractional(0.5) 36 | |> should.equal(True) 37 | maths.is_fractional(0.3333) 38 | |> should.equal(True) 39 | maths.is_fractional(0.9999) 40 | |> should.equal(True) 41 | maths.is_fractional(1.0) 42 | |> should.equal(False) 43 | maths.is_fractional(999.0) 44 | |> should.equal(False) 45 | } 46 | 47 | pub fn int_is_power_test() { 48 | maths.is_power(10, 10) 49 | |> should.equal(True) 50 | maths.is_power(11, 10) 51 | |> should.equal(False) 52 | maths.is_power(4, 2) 53 | |> should.equal(True) 54 | maths.is_power(5, 2) 55 | |> should.equal(False) 56 | maths.is_power(27, 3) 57 | |> should.equal(True) 58 | maths.is_power(28, 3) 59 | |> should.equal(False) 60 | maths.is_power(-1, 10) 61 | |> should.equal(False) 62 | maths.is_power(1, -10) 63 | |> should.equal(False) 64 | } 65 | 66 | pub fn int_is_perfect_test() { 67 | maths.is_perfect(6) 68 | |> should.equal(True) 69 | maths.is_perfect(28) 70 | |> should.equal(True) 71 | maths.is_perfect(496) 72 | |> should.equal(True) 73 | maths.is_perfect(1) 74 | |> should.equal(False) 75 | maths.is_perfect(3) 76 | |> should.equal(False) 77 | maths.is_perfect(13) 78 | |> should.equal(False) 79 | } 80 | 81 | pub fn int_is_prime_test() { 82 | // Test a negative integer, i.e., not a natural number 83 | maths.is_prime(-7) 84 | |> should.equal(False) 85 | maths.is_prime(1) 86 | |> should.equal(False) 87 | maths.is_prime(2) 88 | |> should.equal(True) 89 | maths.is_prime(3) 90 | |> should.equal(True) 91 | maths.is_prime(5) 92 | |> should.equal(True) 93 | maths.is_prime(7) 94 | |> should.equal(True) 95 | maths.is_prime(11) 96 | |> should.equal(True) 97 | maths.is_prime(42) 98 | |> should.equal(False) 99 | maths.is_prime(7919) 100 | |> should.equal(True) 101 | // Test 1st Carmichael number 102 | maths.is_prime(561) 103 | |> should.equal(False) 104 | // Test 2nd Carmichael number 105 | maths.is_prime(1105) 106 | |> should.equal(False) 107 | } 108 | 109 | pub fn is_between_test() { 110 | maths.is_between(5.5, 5.0, 6.0) 111 | |> should.equal(True) 112 | maths.is_between(5.0, 5.0, 6.0) 113 | |> should.equal(False) 114 | maths.is_between(6.0, 5.0, 6.0) 115 | |> should.equal(False) 116 | } 117 | 118 | pub fn is_divisible_test() { 119 | maths.is_divisible(10, 2) 120 | |> should.equal(True) 121 | maths.is_divisible(7, 3) 122 | |> should.equal(False) 123 | } 124 | 125 | pub fn is_multiple_test() { 126 | maths.is_multiple(15, 5) 127 | |> should.equal(True) 128 | maths.is_multiple(14, 5) 129 | |> should.equal(False) 130 | } 131 | -------------------------------------------------------------------------------- /test/gleam_community/sequences_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam/list 3 | import gleam/yielder 4 | import gleam_community/maths 5 | import gleeunit/should 6 | 7 | pub fn yield_linear_space_test() { 8 | let assert Ok(tol) = float.power(10.0, -6.0) 9 | 10 | // Check that the function agrees, at some arbitrary input 11 | // points, with known function values 12 | // ---> With endpoint included 13 | let assert Ok(linspace) = maths.yield_linear_space(10.0, 50.0, 5, True) 14 | maths.all_close( 15 | linspace |> yielder.to_list() |> list.zip([10.0, 20.0, 30.0, 40.0, 50.0]), 16 | 0.0, 17 | tol, 18 | ) 19 | |> list.all(fn(x) { x == True }) 20 | |> should.be_true() 21 | 22 | let assert Ok(linspace) = maths.yield_linear_space(10.0, 20.0, 5, True) 23 | maths.all_close( 24 | linspace |> yielder.to_list() |> list.zip([10.0, 12.5, 15.0, 17.5, 20.0]), 25 | 0.0, 26 | tol, 27 | ) 28 | |> list.all(fn(x) { x == True }) 29 | |> should.be_true() 30 | 31 | // Try with negative stop 32 | // ----> Without endpoint included 33 | let assert Ok(linspace) = maths.yield_linear_space(10.0, 50.0, 5, False) 34 | maths.all_close( 35 | linspace |> yielder.to_list() |> list.zip([10.0, 18.0, 26.0, 34.0, 42.0]), 36 | 0.0, 37 | tol, 38 | ) 39 | |> list.all(fn(x) { x == True }) 40 | |> should.be_true() 41 | 42 | let assert Ok(linspace) = maths.yield_linear_space(10.0, 20.0, 5, False) 43 | maths.all_close( 44 | linspace |> yielder.to_list() |> list.zip([10.0, 12.0, 14.0, 16.0, 18.0]), 45 | 0.0, 46 | tol, 47 | ) 48 | |> list.all(fn(x) { x == True }) 49 | |> should.be_true() 50 | 51 | // Try with negative stop 52 | let assert Ok(linspace) = maths.yield_linear_space(10.0, -50.0, 5, False) 53 | maths.all_close( 54 | linspace 55 | |> yielder.to_list() 56 | |> list.zip([10.0, -2.0, -14.0, -26.0, -38.0]), 57 | 0.0, 58 | tol, 59 | ) 60 | |> list.all(fn(x) { x == True }) 61 | |> should.be_true() 62 | 63 | let assert Ok(linspace) = maths.yield_linear_space(10.0, -20.0, 5, True) 64 | maths.all_close( 65 | linspace 66 | |> yielder.to_list() 67 | |> list.zip([10.0, 2.5, -5.0, -12.5, -20.0]), 68 | 0.0, 69 | tol, 70 | ) 71 | |> list.all(fn(x) { x == True }) 72 | |> should.be_true() 73 | 74 | // Try with negative start 75 | let assert Ok(linspace) = maths.yield_linear_space(-10.0, 50.0, 5, False) 76 | maths.all_close( 77 | linspace |> yielder.to_list() |> list.zip([-10.0, 2.0, 14.0, 26.0, 38.0]), 78 | 0.0, 79 | tol, 80 | ) 81 | |> list.all(fn(x) { x == True }) 82 | |> should.be_true() 83 | 84 | let assert Ok(linspace) = maths.yield_linear_space(-10.0, 20.0, 5, True) 85 | maths.all_close( 86 | linspace |> yielder.to_list() |> list.zip([-10.0, -2.5, 5.0, 12.5, 20.0]), 87 | 0.0, 88 | tol, 89 | ) 90 | |> list.all(fn(x) { x == True }) 91 | |> should.be_true() 92 | 93 | // Check that when start == stop and steps > 0, then 94 | // the value (start/stop) is just repeated, since the 95 | // step increment will be 0 96 | let assert Ok(linspace) = maths.yield_linear_space(10.0, 10.0, 5, True) 97 | maths.all_close( 98 | linspace |> yielder.to_list() |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]), 99 | 0.0, 100 | tol, 101 | ) 102 | |> list.all(fn(x) { x == True }) 103 | |> should.be_true() 104 | 105 | let assert Ok(linspace) = maths.yield_linear_space(10.0, 10.0, 5, False) 106 | maths.all_close( 107 | linspace |> yielder.to_list() |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]), 108 | 0.0, 109 | tol, 110 | ) 111 | |> list.all(fn(x) { x == True }) 112 | |> should.be_true() 113 | 114 | // A negative number of points does not work (-5) 115 | maths.yield_linear_space(10.0, 50.0, -5, True) 116 | |> should.be_error() 117 | } 118 | 119 | pub fn list_linear_space_test() { 120 | let assert Ok(tol) = float.power(10.0, -6.0) 121 | 122 | // Check that the function agrees, at some arbitrary input 123 | // points, with known function values 124 | // ---> With endpoint included 125 | let assert Ok(linspace) = maths.linear_space(10.0, 50.0, 5, True) 126 | maths.all_close( 127 | linspace |> list.zip([10.0, 20.0, 30.0, 40.0, 50.0]), 128 | 0.0, 129 | tol, 130 | ) 131 | |> list.all(fn(x) { x == True }) 132 | |> should.be_true() 133 | 134 | let assert Ok(linspace) = maths.linear_space(10.0, 20.0, 5, True) 135 | maths.all_close( 136 | linspace |> list.zip([10.0, 12.5, 15.0, 17.5, 20.0]), 137 | 0.0, 138 | tol, 139 | ) 140 | |> list.all(fn(x) { x == True }) 141 | |> should.be_true() 142 | 143 | // Try with negative stop 144 | // ----> Without endpoint included 145 | let assert Ok(linspace) = maths.linear_space(10.0, 50.0, 5, False) 146 | maths.all_close( 147 | linspace |> list.zip([10.0, 18.0, 26.0, 34.0, 42.0]), 148 | 0.0, 149 | tol, 150 | ) 151 | |> list.all(fn(x) { x == True }) 152 | |> should.be_true() 153 | 154 | let assert Ok(linspace) = maths.linear_space(10.0, 20.0, 5, False) 155 | maths.all_close( 156 | linspace |> list.zip([10.0, 12.0, 14.0, 16.0, 18.0]), 157 | 0.0, 158 | tol, 159 | ) 160 | |> list.all(fn(x) { x == True }) 161 | |> should.be_true() 162 | 163 | // Try with negative stop 164 | let assert Ok(linspace) = maths.linear_space(10.0, -50.0, 5, False) 165 | maths.all_close( 166 | linspace 167 | |> list.zip([10.0, -2.0, -14.0, -26.0, -38.0]), 168 | 0.0, 169 | tol, 170 | ) 171 | |> list.all(fn(x) { x == True }) 172 | |> should.be_true() 173 | 174 | let assert Ok(linspace) = maths.linear_space(10.0, -20.0, 5, True) 175 | maths.all_close( 176 | linspace 177 | |> list.zip([10.0, 2.5, -5.0, -12.5, -20.0]), 178 | 0.0, 179 | tol, 180 | ) 181 | |> list.all(fn(x) { x == True }) 182 | |> should.be_true() 183 | 184 | // Try with negative start 185 | let assert Ok(linspace) = maths.linear_space(-10.0, 50.0, 5, False) 186 | maths.all_close( 187 | linspace |> list.zip([-10.0, 2.0, 14.0, 26.0, 38.0]), 188 | 0.0, 189 | tol, 190 | ) 191 | |> list.all(fn(x) { x == True }) 192 | |> should.be_true() 193 | 194 | let assert Ok(linspace) = maths.linear_space(-10.0, 20.0, 5, True) 195 | maths.all_close( 196 | linspace |> list.zip([-10.0, -2.5, 5.0, 12.5, 20.0]), 197 | 0.0, 198 | tol, 199 | ) 200 | |> list.all(fn(x) { x == True }) 201 | |> should.be_true() 202 | 203 | // Check that when start == stop and steps > 0, then 204 | // the value (start/stop) is just repeated, since the 205 | // step increment will be 0 206 | let assert Ok(linspace) = maths.linear_space(10.0, 10.0, 5, True) 207 | maths.all_close( 208 | linspace |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]), 209 | 0.0, 210 | tol, 211 | ) 212 | |> list.all(fn(x) { x == True }) 213 | |> should.be_true() 214 | 215 | let assert Ok(linspace) = maths.linear_space(10.0, 10.0, 5, False) 216 | maths.all_close( 217 | linspace |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]), 218 | 0.0, 219 | tol, 220 | ) 221 | |> list.all(fn(x) { x == True }) 222 | |> should.be_true() 223 | 224 | // A negative number of points does not work (-5) 225 | maths.linear_space(10.0, 50.0, -5, True) 226 | |> should.be_error() 227 | } 228 | 229 | pub fn yield_logarithmic_space_test() { 230 | let assert Ok(tol) = float.power(10.0, -6.0) 231 | // Check that the function agrees, at some arbitrary input 232 | // points, with known function values 233 | // ---> With endpoint included 234 | // - Positive start, stop 235 | let assert Ok(logspace) = 236 | maths.yield_logarithmic_space(1.0, 3.0, 3, True, 10.0) 237 | 238 | maths.all_close( 239 | logspace |> yielder.to_list() |> list.zip([10.0, 100.0, 1000.0]), 240 | 0.0, 241 | tol, 242 | ) 243 | |> list.all(fn(x) { x == True }) 244 | |> should.be_true() 245 | 246 | // - Positive start, negative stop 247 | let assert Ok(logspace) = 248 | maths.yield_logarithmic_space(1.0, -3.0, 3, True, 10.0) 249 | maths.all_close( 250 | logspace |> yielder.to_list() |> list.zip([10.0, 0.1, 0.001]), 251 | 0.0, 252 | tol, 253 | ) 254 | |> list.all(fn(x) { x == True }) 255 | |> should.be_true() 256 | 257 | // - Positive stop, negative start 258 | let assert Ok(logspace) = 259 | maths.yield_logarithmic_space(-1.0, 3.0, 3, True, 10.0) 260 | maths.all_close( 261 | logspace |> yielder.to_list() |> list.zip([0.1, 10.0, 1000.0]), 262 | 0.0, 263 | tol, 264 | ) 265 | |> list.all(fn(x) { x == True }) 266 | |> should.be_true() 267 | 268 | // ----> Without endpoint included 269 | // - Positive start, stop 270 | let assert Ok(logspace) = 271 | maths.yield_logarithmic_space(1.0, 3.0, 3, False, 10.0) 272 | maths.all_close( 273 | logspace 274 | |> yielder.to_list() 275 | |> list.zip([10.0, 46.41588834, 215.443469]), 276 | 0.0, 277 | tol, 278 | ) 279 | |> list.all(fn(x) { x == True }) 280 | |> should.be_true() 281 | 282 | // Check that when start == stop and steps > 0, then 283 | // the value (start/stop) is just repeated, since the 284 | // step increment will be 0 285 | let assert Ok(logspace) = 286 | maths.yield_logarithmic_space(5.0, 5.0, 5, True, 5.0) 287 | maths.all_close( 288 | logspace 289 | |> yielder.to_list() 290 | |> list.zip([3125.0, 3125.0, 3125.0, 3125.0, 3125.0]), 291 | 0.0, 292 | tol, 293 | ) 294 | |> list.all(fn(x) { x == True }) 295 | |> should.be_true() 296 | let assert Ok(logspace) = 297 | maths.yield_logarithmic_space(5.0, 5.0, 5, False, 5.0) 298 | maths.all_close( 299 | logspace 300 | |> yielder.to_list() 301 | |> list.zip([3125.0, 3125.0, 3125.0, 3125.0, 3125.0]), 302 | 0.0, 303 | tol, 304 | ) 305 | |> list.all(fn(x) { x == True }) 306 | |> should.be_true() 307 | 308 | // A negative number of points does not work (-3) 309 | maths.yield_logarithmic_space(1.0, 3.0, -3, True, 10.0) 310 | |> should.be_error() 311 | 312 | // A negative base does not work (-10) 313 | maths.yield_logarithmic_space(1.0, 3.0, 3, True, -10.0) 314 | |> should.be_error() 315 | } 316 | 317 | pub fn list_logarithmic_space_test() { 318 | let assert Ok(tol) = float.power(10.0, -6.0) 319 | // Check that the function agrees, at some arbitrary input 320 | // points, with known function values 321 | // ---> With endpoint included 322 | // - Positive start, stop 323 | let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, 10.0) 324 | maths.all_close(logspace |> list.zip([10.0, 100.0, 1000.0]), 0.0, tol) 325 | |> list.all(fn(x) { x == True }) 326 | |> should.be_true() 327 | 328 | // - Positive start, negative stop 329 | let assert Ok(logspace) = maths.logarithmic_space(1.0, -3.0, 3, True, 10.0) 330 | maths.all_close(logspace |> list.zip([10.0, 0.1, 0.001]), 0.0, tol) 331 | |> list.all(fn(x) { x == True }) 332 | |> should.be_true() 333 | 334 | // - Positive stop, negative start 335 | let assert Ok(logspace) = maths.logarithmic_space(-1.0, 3.0, 3, True, 10.0) 336 | maths.all_close(logspace |> list.zip([0.1, 10.0, 1000.0]), 0.0, tol) 337 | |> list.all(fn(x) { x == True }) 338 | |> should.be_true() 339 | 340 | // ----> Without endpoint included 341 | // - Positive start, stop 342 | let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, False, 10.0) 343 | maths.all_close( 344 | logspace 345 | |> list.zip([10.0, 46.41588834, 215.443469]), 346 | 0.0, 347 | tol, 348 | ) 349 | |> list.all(fn(x) { x == True }) 350 | |> should.be_true() 351 | 352 | // Check the special case when 'base' is equal to zero 353 | maths.logarithmic_space(-1.0, 3.0, 3, False, 0.0) |> should.be_error() 354 | maths.logarithmic_space(1.0, -3.0, 3, False, 0.0) |> should.be_error() 355 | maths.logarithmic_space(-1.0, -3.0, 3, False, 0.0) |> should.be_error() 356 | 357 | // Check that when start == stop and steps > 0, then 358 | // the value (start/stop) is just repeated, since the 359 | // step increment will be 0 360 | let assert Ok(logspace) = maths.logarithmic_space(5.0, 5.0, 5, True, 5.0) 361 | maths.all_close( 362 | logspace 363 | |> list.zip([3125.0, 3125.0, 3125.0, 3125.0, 3125.0]), 364 | 0.0, 365 | tol, 366 | ) 367 | |> list.all(fn(x) { x == True }) 368 | |> should.be_true() 369 | let assert Ok(logspace) = maths.logarithmic_space(5.0, 5.0, 5, False, 5.0) 370 | maths.all_close( 371 | logspace 372 | |> list.zip([3125.0, 3125.0, 3125.0, 3125.0, 3125.0]), 373 | 0.0, 374 | tol, 375 | ) 376 | |> list.all(fn(x) { x == True }) 377 | |> should.be_true() 378 | 379 | // A negative number of points does not work (-3) 380 | maths.logarithmic_space(1.0, 3.0, -3, True, 10.0) 381 | |> should.be_error() 382 | 383 | // A negative base does not work (-10) 384 | maths.logarithmic_space(1.0, 3.0, 3, True, -10.0) 385 | |> should.be_error() 386 | } 387 | 388 | pub fn yield_geometric_space_test() { 389 | let assert Ok(tol) = float.power(10.0, -6.0) 390 | // Check that the function agrees, at some arbitrary input 391 | // points, with known function values 392 | // ---> With endpoint included 393 | // - Positive start, stop 394 | let assert Ok(logspace) = maths.yield_geometric_space(10.0, 1000.0, 3, True) 395 | maths.all_close( 396 | logspace |> yielder.to_list() |> list.zip([10.0, 100.0, 1000.0]), 397 | 0.0, 398 | tol, 399 | ) 400 | |> list.all(fn(x) { x == True }) 401 | |> should.be_true() 402 | 403 | // - Positive start, negative stop 404 | let assert Ok(logspace) = maths.yield_geometric_space(10.0, 0.001, 3, True) 405 | maths.all_close( 406 | logspace |> yielder.to_list() |> list.zip([10.0, 0.1, 0.001]), 407 | 0.0, 408 | tol, 409 | ) 410 | |> list.all(fn(x) { x == True }) 411 | |> should.be_true() 412 | 413 | // - Positive stop, negative start 414 | let assert Ok(logspace) = maths.yield_geometric_space(0.1, 1000.0, 3, True) 415 | maths.all_close( 416 | logspace |> yielder.to_list() |> list.zip([0.1, 10.0, 1000.0]), 417 | 0.0, 418 | tol, 419 | ) 420 | |> list.all(fn(x) { x == True }) 421 | |> should.be_true() 422 | 423 | // ----> Without endpoint included 424 | // - Positive start, stop 425 | let assert Ok(logspace) = maths.yield_geometric_space(10.0, 1000.0, 3, False) 426 | maths.all_close( 427 | logspace 428 | |> yielder.to_list() 429 | |> list.zip([10.0, 46.41588834, 215.443469]), 430 | 0.0, 431 | tol, 432 | ) 433 | |> list.all(fn(x) { x == True }) 434 | |> should.be_true() 435 | 436 | // Check that when start == stop and steps > 0, then 437 | // the value (start/stop) is just repeated, since the 438 | // step increment will be 0 439 | let assert Ok(logspace) = maths.yield_geometric_space(5.0, 5.0, 5, True) 440 | maths.all_close( 441 | logspace 442 | |> yielder.to_list() 443 | |> list.zip([5.0, 5.0, 5.0, 5.0, 5.0]), 444 | 0.0, 445 | tol, 446 | ) 447 | |> list.all(fn(x) { x == True }) 448 | |> should.be_true() 449 | 450 | let assert Ok(logspace) = maths.yield_geometric_space(5.0, 5.0, 5, False) 451 | maths.all_close( 452 | logspace 453 | |> yielder.to_list() 454 | |> list.zip([5.0, 5.0, 5.0, 5.0, 5.0]), 455 | 0.0, 456 | tol, 457 | ) 458 | |> list.all(fn(x) { x == True }) 459 | |> should.be_true() 460 | 461 | // Test invalid input (start and stop can't be less than or equal to 0.0) 462 | maths.yield_geometric_space(0.0, 1000.0, 3, False) 463 | |> should.be_error() 464 | 465 | maths.yield_geometric_space(-1000.0, 0.0, 3, False) 466 | |> should.be_error() 467 | 468 | // A negative number of points does not work 469 | maths.yield_geometric_space(-1000.0, 0.0, -3, False) 470 | |> should.be_error() 471 | } 472 | 473 | pub fn list_geometric_space_test() { 474 | let assert Ok(tol) = float.power(10.0, -6.0) 475 | // Check that the function agrees, at some arbitrary input 476 | // points, with known function values 477 | // ---> With endpoint included 478 | // - Positive start, stop 479 | let assert Ok(logspace) = maths.geometric_space(10.0, 1000.0, 3, True) 480 | maths.all_close(logspace |> list.zip([10.0, 100.0, 1000.0]), 0.0, tol) 481 | |> list.all(fn(x) { x == True }) 482 | |> should.be_true() 483 | 484 | // - Positive start, negative stop 485 | let assert Ok(logspace) = maths.geometric_space(10.0, 0.001, 3, True) 486 | maths.all_close(logspace |> list.zip([10.0, 0.1, 0.001]), 0.0, tol) 487 | |> list.all(fn(x) { x == True }) 488 | |> should.be_true() 489 | 490 | // - Positive stop, negative start 491 | let assert Ok(logspace) = maths.geometric_space(0.1, 1000.0, 3, True) 492 | maths.all_close(logspace |> list.zip([0.1, 10.0, 1000.0]), 0.0, tol) 493 | |> list.all(fn(x) { x == True }) 494 | |> should.be_true() 495 | 496 | // ----> Without endpoint included 497 | // - Positive start, stop 498 | let assert Ok(logspace) = maths.geometric_space(10.0, 1000.0, 3, False) 499 | maths.all_close( 500 | logspace 501 | |> list.zip([10.0, 46.41588834, 215.443469]), 502 | 0.0, 503 | tol, 504 | ) 505 | |> list.all(fn(x) { x == True }) 506 | |> should.be_true() 507 | 508 | // Check that when start == stop and steps > 0, then 509 | // the value (start/stop) is just repeated, since the 510 | // step increment will be 0 511 | let assert Ok(logspace) = maths.geometric_space(5.0, 5.0, 5, True) 512 | maths.all_close( 513 | logspace 514 | |> list.zip([5.0, 5.0, 5.0, 5.0, 5.0]), 515 | 0.0, 516 | tol, 517 | ) 518 | |> list.all(fn(x) { x == True }) 519 | |> should.be_true() 520 | 521 | let assert Ok(logspace) = maths.geometric_space(5.0, 5.0, 5, False) 522 | maths.all_close( 523 | logspace 524 | |> list.zip([5.0, 5.0, 5.0, 5.0, 5.0]), 525 | 0.0, 526 | tol, 527 | ) 528 | |> list.all(fn(x) { x == True }) 529 | |> should.be_true() 530 | 531 | // Test invalid input (start and stop can't be less than or equal to 0.0) 532 | maths.geometric_space(0.0, 1000.0, 3, False) 533 | |> should.be_error() 534 | 535 | maths.geometric_space(-1000.0, 0.0, 3, False) 536 | |> should.be_error() 537 | 538 | // A negative number of points does not work 539 | maths.geometric_space(-1000.0, 0.0, -3, False) 540 | |> should.be_error() 541 | } 542 | 543 | pub fn list_step_range_test() { 544 | // Positive start, stop, step 545 | maths.step_range(1.0, 5.0, 1.0) 546 | |> should.equal([1.0, 2.0, 3.0, 4.0]) 547 | 548 | maths.step_range(1.0, 5.0, 0.5) 549 | |> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]) 550 | 551 | maths.step_range(1.0, 2.0, 0.25) 552 | |> should.equal([1.0, 1.25, 1.5, 1.75]) 553 | 554 | // Reverse (switch start/stop largest/smallest value) 555 | maths.step_range(5.0, 1.0, 1.0) 556 | |> should.equal([]) 557 | 558 | // Reverse negative step 559 | maths.step_range(5.0, 1.0, -1.0) 560 | |> should.equal([5.0, 4.0, 3.0, 2.0]) 561 | 562 | // Positive start, negative stop, step 563 | maths.step_range(5.0, -1.0, -1.0) 564 | |> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) 565 | 566 | // Negative start, stop, step 567 | maths.step_range(-5.0, -1.0, -1.0) 568 | |> should.equal([]) 569 | 570 | // Negative start, stop, positive step 571 | maths.step_range(-5.0, -1.0, 1.0) 572 | |> should.equal([-5.0, -4.0, -3.0, -2.0]) 573 | } 574 | 575 | pub fn yield_step_range_test() { 576 | // Positive start, stop, step 577 | maths.yield_step_range(1.0, 5.0, 1.0) 578 | |> yielder.to_list() 579 | |> should.equal([1.0, 2.0, 3.0, 4.0]) 580 | 581 | maths.yield_step_range(1.0, 5.0, 0.5) 582 | |> yielder.to_list() 583 | |> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]) 584 | 585 | maths.yield_step_range(1.0, 2.0, 0.25) 586 | |> yielder.to_list() 587 | |> should.equal([1.0, 1.25, 1.5, 1.75]) 588 | 589 | // Reverse (switch start/stop largest/smallest value) 590 | maths.yield_step_range(5.0, 1.0, 1.0) 591 | |> yielder.to_list() 592 | |> should.equal([]) 593 | 594 | // Reverse negative step 595 | maths.yield_step_range(5.0, 1.0, -1.0) 596 | |> yielder.to_list() 597 | |> should.equal([5.0, 4.0, 3.0, 2.0]) 598 | 599 | // Positive start, negative stop, step 600 | maths.yield_step_range(5.0, -1.0, -1.0) 601 | |> yielder.to_list() 602 | |> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) 603 | 604 | // Negative start, stop, step 605 | maths.yield_step_range(-5.0, -1.0, -1.0) 606 | |> yielder.to_list() 607 | |> should.equal([]) 608 | 609 | // Negative start, stop, positive step 610 | maths.yield_step_range(-5.0, -1.0, 1.0) 611 | |> yielder.to_list() 612 | |> should.equal([-5.0, -4.0, -3.0, -2.0]) 613 | } 614 | 615 | pub fn yield_symmetric_space_test() { 616 | let assert Ok(tolerance) = float.power(10.0, -6.0) 617 | 618 | // Check that the function agrees, at some arbitrary input 619 | // points, with known function values 620 | let assert Ok(sym_space) = maths.yield_symmetric_space(0.0, 5.0, 5) 621 | sym_space 622 | |> yielder.to_list() 623 | |> should.equal([-5.0, -2.5, 0.0, 2.5, 5.0]) 624 | 625 | let assert Ok(sym_space) = maths.yield_symmetric_space(0.0, 5.0, 5) 626 | sym_space 627 | |> yielder.to_list() 628 | |> should.equal([-5.0, -2.5, 0.0, 2.5, 5.0]) 629 | 630 | // Negative center 631 | let assert Ok(sym_space) = maths.yield_symmetric_space(-10.0, 5.0, 5) 632 | sym_space 633 | |> yielder.to_list() 634 | |> should.equal([-15.0, -12.5, -10.0, -7.5, -5.0]) 635 | 636 | // Negative Radius (simply reverses the order of the values) 637 | let assert Ok(sym_space) = maths.yield_symmetric_space(0.0, -5.0, 5) 638 | sym_space 639 | |> yielder.to_list() 640 | |> should.equal([5.0, 2.5, 0.0, -2.5, -5.0]) 641 | 642 | // Uneven number of points 643 | let assert Ok(sym_space) = maths.yield_symmetric_space(0.0, 2.0, 4) 644 | maths.all_close( 645 | sym_space 646 | |> yielder.to_list() 647 | |> list.zip([-2.0, -0.6666666666666667, 0.6666666666666665, 2.0]), 648 | 0.0, 649 | tolerance, 650 | ) 651 | |> list.all(fn(x) { x == True }) 652 | |> should.be_true() 653 | 654 | // Check that when radius == 0 and steps > 0, then 655 | // the value center value is just repeated, since the 656 | // step increment will be 0 657 | let assert Ok(sym_space) = maths.yield_symmetric_space(10.0, 0.0, 4) 658 | sym_space 659 | |> yielder.to_list() 660 | |> should.equal([10.0, 10.0, 10.0, 10.0]) 661 | 662 | // A negative number of points does not work (-5) 663 | maths.yield_symmetric_space(0.0, 5.0, -5) 664 | |> should.be_error() 665 | } 666 | 667 | pub fn list_symmetric_space_test() { 668 | let assert Ok(tolerance) = float.power(10.0, -6.0) 669 | 670 | // Check that the function agrees, at some arbitrary input 671 | // points, with known function values 672 | let assert Ok(sym_space) = maths.symmetric_space(0.0, 5.0, 5) 673 | sym_space 674 | |> should.equal([-5.0, -2.5, 0.0, 2.5, 5.0]) 675 | 676 | let assert Ok(sym_space) = maths.symmetric_space(0.0, 5.0, 5) 677 | sym_space 678 | |> should.equal([-5.0, -2.5, 0.0, 2.5, 5.0]) 679 | 680 | // Negative center 681 | let assert Ok(sym_space) = maths.symmetric_space(-10.0, 5.0, 5) 682 | sym_space 683 | |> should.equal([-15.0, -12.5, -10.0, -7.5, -5.0]) 684 | 685 | // Negative Radius (simply reverses the order of the values) 686 | let assert Ok(sym_space) = maths.symmetric_space(0.0, -5.0, 5) 687 | sym_space 688 | |> should.equal([5.0, 2.5, 0.0, -2.5, -5.0]) 689 | 690 | // Uneven number of points 691 | let assert Ok(sym_space) = maths.symmetric_space(0.0, 2.0, 4) 692 | maths.all_close( 693 | sym_space 694 | |> list.zip([-2.0, -0.6666666666666667, 0.6666666666666665, 2.0]), 695 | 0.0, 696 | tolerance, 697 | ) 698 | |> list.all(fn(x) { x == True }) 699 | |> should.be_true() 700 | 701 | // Check that when radius == 0 and steps > 0, then 702 | // the value center value is just repeated, since the 703 | // step increment will be 0 704 | let assert Ok(sym_space) = maths.symmetric_space(10.0, 0.0, 4) 705 | sym_space 706 | |> should.equal([10.0, 10.0, 10.0, 10.0]) 707 | 708 | // A negative number of points does not work (-5) 709 | maths.symmetric_space(0.0, 5.0, -5) 710 | |> should.be_error() 711 | } 712 | -------------------------------------------------------------------------------- /test/gleam_community/special_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam/result 3 | import gleam_community/maths 4 | import gleeunit/should 5 | 6 | pub fn float_beta_function_test() { 7 | let assert Ok(tol) = float.power(10.0, -6.0) 8 | 9 | // Valid input returns a result 10 | maths.beta(-0.5, 0.5) 11 | |> maths.is_close(0.0, 0.0, tol) 12 | |> should.be_true() 13 | 14 | maths.beta(0.5, 0.5) 15 | |> maths.is_close(3.1415926535897927, 0.0, tol) 16 | |> should.be_true() 17 | 18 | maths.beta(0.5, -0.5) 19 | |> maths.is_close(0.0, 0.0, tol) 20 | |> should.be_true() 21 | 22 | maths.beta(5.0, 5.0) 23 | |> maths.is_close(0.0015873015873015873, 0.0, tol) 24 | |> should.be_true() 25 | } 26 | 27 | pub fn float_error_function_test() { 28 | let assert Ok(tol) = float.power(10.0, -6.0) 29 | 30 | // Valid input returns a result 31 | maths.erf(-0.5) 32 | |> maths.is_close(-0.5204998778130465, 0.0, tol) 33 | |> should.be_true() 34 | 35 | maths.erf(0.5) 36 | |> maths.is_close(0.5204998778130465, 0.0, tol) 37 | |> should.be_true() 38 | 39 | maths.erf(1.0) 40 | |> maths.is_close(0.8427007929497148, 0.0, tol) 41 | |> should.be_true() 42 | 43 | maths.erf(2.0) 44 | |> maths.is_close(0.9953222650189527, 0.0, tol) 45 | |> should.be_true() 46 | 47 | maths.erf(10.0) 48 | |> maths.is_close(1.0, 0.0, tol) 49 | |> should.be_true() 50 | } 51 | 52 | pub fn float_gamma_function_test() { 53 | let assert Ok(tol) = float.power(10.0, -6.0) 54 | 55 | // Valid input returns a result 56 | maths.gamma(-0.5) 57 | |> maths.is_close(-3.5449077018110318, 0.0, tol) 58 | |> should.be_true() 59 | 60 | maths.gamma(0.5) 61 | |> maths.is_close(1.7724538509055159, 0.0, tol) 62 | |> should.be_true() 63 | 64 | maths.gamma(1.0) 65 | |> maths.is_close(1.0, 0.0, tol) 66 | |> should.be_true() 67 | 68 | maths.gamma(2.0) 69 | |> maths.is_close(1.0, 0.0, tol) 70 | |> should.be_true() 71 | 72 | maths.gamma(3.0) 73 | |> maths.is_close(2.0, 0.0, tol) 74 | |> should.be_true() 75 | 76 | maths.gamma(10.0) 77 | |> maths.is_close(362_880.0, 0.0, tol) 78 | |> should.be_true() 79 | } 80 | 81 | pub fn float_incomplete_gamma_function_test() { 82 | let assert Ok(tol) = float.power(10.0, -6.0) 83 | 84 | // Invalid input gives an error 85 | // 1st arg is invalid 86 | maths.incomplete_gamma(-1.0, 1.0) 87 | |> should.be_error() 88 | 89 | // 2nd arg is invalid 90 | maths.incomplete_gamma(1.0, -1.0) 91 | |> should.be_error() 92 | 93 | // Valid input returns a result 94 | maths.incomplete_gamma(1.0, 0.0) 95 | |> result.unwrap(-999.0) 96 | |> maths.is_close(0.0, 0.0, tol) 97 | |> should.be_true() 98 | 99 | maths.incomplete_gamma(1.0, 2.0) 100 | |> result.unwrap(-999.0) 101 | |> maths.is_close(0.864664716763387308106, 0.0, tol) 102 | |> should.be_true() 103 | 104 | maths.incomplete_gamma(2.0, 3.0) 105 | |> result.unwrap(-999.0) 106 | |> maths.is_close(0.8008517265285442280826, 0.0, tol) 107 | |> should.be_true() 108 | 109 | maths.incomplete_gamma(3.0, 4.0) 110 | |> result.unwrap(-999.0) 111 | |> maths.is_close(1.523793388892911312363, 0.0, tol) 112 | |> should.be_true() 113 | } 114 | -------------------------------------------------------------------------------- /test/gleam_community_maths_test.gleam: -------------------------------------------------------------------------------- 1 | import gleeunit 2 | 3 | pub fn main() { 4 | gleeunit.main() 5 | } 6 | --------------------------------------------------------------------------------