├── .JuliaFormatter.toml ├── .editorconfig ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── 10-bug-report.yml │ ├── 20-feature-request.yml │ ├── 99-general.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── dependabot.yml ├── docscleanup.yml └── workflows │ ├── CompatHelper.yml │ ├── Docs.yml │ ├── Lint.yml │ ├── PreCommitUpdate.yml │ ├── ReusableTest.yml │ ├── TagBot.yml │ └── Test.yml ├── .gitignore ├── .lychee.toml ├── .markdownlint.json ├── .pre-commit-config.yaml ├── .yamllint.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Project.toml ├── README.md ├── docs ├── Project.toml ├── liveserver.jl ├── make.jl ├── setup.jl └── src │ ├── 90-contributing.md │ ├── 91-developer.md │ ├── api.md │ ├── assets │ ├── citations.css │ └── logo.svg │ ├── changelog.md │ ├── index.md │ ├── references.md │ ├── refs.bib │ └── tutorial.md ├── src ├── DedekindCutArithmetic.jl ├── abstract_interface.jl ├── cuts.jl ├── dyadic.jl ├── interval.jl ├── macros.jl ├── promotions.jl └── quantifiers.jl └── test ├── runtests.jl ├── test-cuts.jl ├── test-dyadic.jl ├── test-interval.jl ├── test-library-quality.jl └── test-quantifiers.jl /.JuliaFormatter.toml: -------------------------------------------------------------------------------- 1 | style = "sciml" 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | 11 | [*.jl] 12 | indent_size = 4 13 | 14 | [*.md] 15 | indent_size = 2 16 | 17 | [*.{yml,toml,json}] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributions are welcome! Make sure to check out the [contributor's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing). 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/10-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report related to running the package 3 | title: "[Bug] " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | 11 | Please, before submitting, make sure that: 12 | 13 | - There is not an [existing issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues) with the same question 14 | - You have read the [contributing guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing/) 15 | - You are following the [code of conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md) 16 | The form below should help you in filling out this issue. 17 | - type: textarea 18 | id: description 19 | attributes: 20 | label: Description 21 | description: Describe the bug 22 | validations: 23 | required: true 24 | - type: input 25 | id: pkg-version 26 | attributes: 27 | label: Package Version 28 | description: What version of the package are you running? 29 | validations: 30 | required: true 31 | - type: input 32 | id: version 33 | attributes: 34 | label: Julia Version 35 | description: What version of Julia are you running? 36 | validations: 37 | required: true 38 | - type: textarea 39 | id: reproduction 40 | attributes: 41 | label: Reproduction steps 42 | description: What steps led to the bug happening? Please provide a minimal reproducible example. 43 | validations: 44 | required: true 45 | - type: textarea 46 | id: logs 47 | attributes: 48 | label: Relevant log output 49 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 50 | render: shell 51 | - type: dropdown 52 | id: os 53 | attributes: 54 | label: "Operating System" 55 | description: What is the impacted environment? 56 | multiple: true 57 | options: 58 | - Windows 59 | - Linux 60 | - Mac 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/20-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: Suggest a new feature for the package 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for taking the time to fill out this feature request! 8 | 9 | Please, before submitting, make sure that: 10 | 11 | - There is not an [existing issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues) with the same question 12 | - You have read the [contributing guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing/) 13 | - You are following the [code of conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md) 14 | The form below should help you in filling out this issue. 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: Description 19 | description: Describe the requested feature 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: validation 24 | attributes: 25 | label: Validation and testing 26 | description: How could we verify that the new feature works? What kind of tests can be done? 27 | - type: textarea 28 | id: motivation 29 | attributes: 30 | label: Motivation 31 | description: Explain why this feature is relevant 32 | - type: textarea 33 | id: target 34 | attributes: 35 | label: Target audience 36 | description: Tell more about the users of this feature, or where it could be useful 37 | - type: textarea 38 | id: can-help 39 | attributes: 40 | label: Can you help? 41 | description: Can you help developing this feature? 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/99-general.yml: -------------------------------------------------------------------------------- 1 | name: "General issue" 2 | description: In case none of the others templates apply 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for taking the time to fill out this issue! 8 | 9 | Please, before submitting, make sure that: 10 | 11 | - There is not an [existing issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues) with the same question 12 | - You have read the [contributing guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing/) 13 | - You are following the [code of conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md) 14 | The form below should help you in filling out this issue. 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: Description 19 | description: Describe the issue 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discussions 4 | url: https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions 5 | about: Create and follow discussions here 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## Related issues 9 | 10 | 11 | 12 | 13 | Closes # 14 | 15 | 16 | 19 | 20 | ## Checklist 21 | 22 | 23 | 24 | - [ ] I am following the [contributing guidelines](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/docs/src/90-contributing.md) 25 | - [ ] Tests are passing 26 | - [ ] Lint workflow is passing 27 | - [ ] Docs were updated and workflow is passing 28 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | We take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 4 | 5 | To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/lucaferranti/DedekindCutArithmetic.jl/security/advisories/new) tab. 6 | 7 | We will send a response indicating the next steps in handling your report. After the initial reply to your report, we will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 8 | 9 | This document is adapted from [Electron security policy document](https://github.com/electron/electron/blob/main/SECURITY.md). 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" # Location of package manifests 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /.github/docscleanup.yml: -------------------------------------------------------------------------------- 1 | name: Doc Preview Cleanup 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | 7 | jobs: 8 | doc-preview-cleanup: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout gh-pages branch 12 | uses: actions/checkout@v2 13 | with: 14 | ref: gh-pages 15 | - name: Delete preview and history + push changes 16 | run: | 17 | if [ -d "previews/PR$PRNUM" ]; then 18 | git config user.name "Documenter.jl" 19 | git config user.email "documenter@juliadocs.github.io" 20 | git rm -rf "previews/PR$PRNUM" 21 | git commit -m "delete preview" 22 | git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) 23 | git push --force origin gh-pages-new:gh-pages 24 | fi 25 | env: 26 | PRNUM: ${{ github.event.number }} 27 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | # CompatHelper v3.5.0 2 | name: CompatHelper 3 | 4 | on: 5 | schedule: 6 | - cron: 0 0 * * * # Every day at 00:00 UTC 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | 13 | jobs: 14 | CompatHelper: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check if Julia is already available in the PATH 18 | id: julia_in_path 19 | run: which julia 20 | continue-on-error: true 21 | - name: Install Julia, but only if it is not already available in the PATH 22 | uses: julia-actions/setup-julia@v2 23 | with: 24 | version: "1" 25 | arch: ${{ runner.arch }} 26 | if: steps.julia_in_path.outcome != 'success' 27 | - name: Use Julia cache 28 | uses: julia-actions/cache@v2 29 | - name: "Add the General registry via Git" 30 | run: | 31 | import Pkg 32 | ENV["JULIA_PKG_SERVER"] = "" 33 | Pkg.Registry.add("General") 34 | shell: julia --color=yes {0} 35 | - name: "Install CompatHelper" 36 | run: | 37 | import Pkg 38 | name = "CompatHelper" 39 | uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" 40 | version = "3" 41 | Pkg.add(; name, uuid, version) 42 | shell: julia --color=yes {0} 43 | - name: "Run CompatHelper" 44 | run: | 45 | import CompatHelper 46 | CompatHelper.main() 47 | shell: julia --color=yes {0} 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} 51 | -------------------------------------------------------------------------------- /.github/workflows/Docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "docs/**" 9 | - "src/**" 10 | - "*.toml" 11 | tags: ["*"] 12 | pull_request: 13 | branches: 14 | - main 15 | paths: 16 | - "docs/**" 17 | - "src/**" 18 | - "*.toml" 19 | types: [opened, synchronize, reopened] 20 | 21 | concurrency: 22 | # Skip intermediate builds: always. 23 | # Cancel intermediate builds: only if it is a pull request build. 24 | group: ${{ github.workflow }}-${{ github.ref }} 25 | cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} 26 | 27 | jobs: 28 | docs: 29 | name: Documentation 30 | runs-on: ubuntu-latest 31 | permissions: 32 | contents: write 33 | statuses: write 34 | steps: 35 | - uses: actions/checkout@v5 36 | - uses: julia-actions/setup-julia@v2 37 | with: 38 | version: "1" 39 | - name: Use Julia cache 40 | uses: julia-actions/cache@v2 41 | - name: Instantiate environment with development version of the package 42 | run: julia --project=docs docs/setup.jl 43 | - name: Generate and deploy documentation 44 | run: julia --project=docs docs/make.jl 45 | env: 46 | JULIA_PKG_SERVER: "" 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} 49 | GKSwstype: "100" # https://discourse.julialang.org/t/generation-of-documentation-fails-qt-qpa-xcb-could-not-connect-to-display/60988 50 | -------------------------------------------------------------------------------- /.github/workflows/Lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: ["*"] 8 | pull_request: 9 | 10 | concurrency: 11 | # Skip intermediate builds: always. 12 | # Cancel intermediate builds: only if it is a pull request build. 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} 15 | 16 | jobs: 17 | lint: 18 | name: Linting 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Clone 22 | uses: actions/checkout@v5 23 | - name: Setup Julia 24 | uses: julia-actions/setup-julia@v2 25 | with: 26 | version: "1" 27 | - name: Use Julia cache 28 | uses: julia-actions/cache@v2 29 | - name: Install JuliaFormatter.jl 30 | run: julia -e 'using Pkg; pkg"add JuliaFormatter"' 31 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 32 | run: touch requirements.txt 33 | - name: Setup Python 34 | uses: actions/setup-python@v5 35 | with: 36 | cache: "pip" 37 | python-version: "3.11" 38 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 39 | run: rm requirements.txt 40 | - name: Cache pre-commit 41 | uses: actions/cache@v4 42 | with: 43 | path: ~/.cache/pre-commit 44 | key: ${{ runner.os }}-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} 45 | - name: Install pre-commit 46 | run: pip install pre-commit 47 | - name: Run pre-commit 48 | run: SKIP=no-commit-to-branch pre-commit run -a 49 | link-checker: 50 | name: Link checker 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v5 54 | 55 | - name: Link Checker 56 | id: lychee 57 | uses: lycheeverse/lychee-action@v2 58 | with: 59 | fail: false 60 | args: --config '.lychee.toml' . 61 | -------------------------------------------------------------------------------- /.github/workflows/PreCommitUpdate.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit Update 2 | 3 | on: 4 | schedule: 5 | - cron: "0 7 1/7 * *" # At 7:00 every 7 days 6 | workflow_dispatch: 7 | 8 | jobs: 9 | pre-commit-update: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v5 13 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 14 | run: touch requirements.txt 15 | - name: Setup Python 16 | uses: actions/setup-python@v5 17 | with: 18 | cache: pip 19 | python-version: "3.11" 20 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 21 | run: rm requirements.txt 22 | - name: Install pre-commit 23 | run: pip install pre-commit 24 | - name: Run pre-commit's autoupdate 25 | run: | 26 | # ignore exit code 27 | pre-commit autoupdate || true 28 | - name: Create Pull Request 29 | id: cpr 30 | uses: peter-evans/create-pull-request@v7 31 | with: 32 | commit-message: "chore: :robot: pre-commit update" 33 | title: "[AUTO] pre-commit update" 34 | branch: auto-pre-commit-update 35 | delete-branch: true 36 | labels: chore 37 | - name: Check outputs 38 | run: | 39 | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" 40 | echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" 41 | -------------------------------------------------------------------------------- /.github/workflows/ReusableTest.yml: -------------------------------------------------------------------------------- 1 | name: Reusable test 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | version: 7 | required: false 8 | type: string 9 | default: "1" 10 | os: 11 | required: false 12 | type: string 13 | default: ubuntu-latest 14 | arch: 15 | required: false 16 | type: string 17 | default: x64 18 | allow_failure: 19 | required: false 20 | type: boolean 21 | default: false 22 | run_codecov: 23 | required: false 24 | type: boolean 25 | default: false 26 | secrets: 27 | codecov_token: 28 | required: true 29 | 30 | jobs: 31 | test: 32 | name: Julia ${{ inputs.version }} - ${{ inputs.os }} - ${{ inputs.arch }} - ${{ github.event_name }} 33 | runs-on: ${{ inputs.os }} 34 | continue-on-error: ${{ inputs.allow_failure }} 35 | 36 | steps: 37 | - uses: actions/checkout@v5 38 | - uses: julia-actions/setup-julia@v2 39 | with: 40 | version: ${{ inputs.version }} 41 | arch: ${{ inputs.arch }} 42 | - name: Use Julia cache 43 | uses: julia-actions/cache@v2 44 | - uses: julia-actions/julia-buildpkg@v1 45 | - uses: julia-actions/julia-runtest@v1 46 | - uses: julia-actions/julia-processcoverage@v1 47 | if: ${{ inputs.run_codecov }} 48 | - uses: codecov/codecov-action@v5 49 | if: ${{ inputs.run_codecov }} 50 | with: 51 | file: lcov.info 52 | token: ${{ secrets.codecov_token }} 53 | fail_ci_if_error: false 54 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | workflow_dispatch: 8 | inputs: 9 | lookback: 10 | type: number 11 | default: 3 12 | 13 | permissions: 14 | actions: read 15 | checks: read 16 | contents: write 17 | deployments: read 18 | issues: read 19 | discussions: read 20 | packages: read 21 | pages: read 22 | pull-requests: read 23 | repository-projects: read 24 | security-events: read 25 | statuses: read 26 | 27 | jobs: 28 | TagBot: 29 | if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: JuliaRegistries/TagBot@v1 33 | with: 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | ssh: ${{ secrets.DOCUMENTER_KEY }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: ["*"] 8 | pull_request: 9 | branches: 10 | - main 11 | paths: 12 | - "src/**" 13 | - "test/**" 14 | - "*.toml" 15 | types: [opened, synchronize, reopened] 16 | workflow_dispatch: 17 | 18 | concurrency: 19 | # Skip intermediate builds: always. 20 | # Cancel intermediate builds: only if it is a pull request build. 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} 23 | 24 | jobs: 25 | test: 26 | uses: ./.github/workflows/ReusableTest.yml 27 | with: 28 | os: ${{ matrix.os }} 29 | version: ${{ matrix.version }} 30 | arch: ${{ matrix.arch }} 31 | allow_failure: ${{ matrix.allow_failure }} 32 | run_codecov: ${{ matrix.version == '1' && matrix.os == 'ubuntu-latest' }} 33 | secrets: 34 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | version: 39 | - "lts" 40 | - "1" 41 | os: 42 | - ubuntu-latest 43 | - macOS-latest 44 | - windows-latest 45 | arch: 46 | - x64 47 | allow_failure: [false] 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.jl.*.cov 2 | *.jl.cov 3 | *.jl.mem 4 | *.rej 5 | .DS_Store 6 | .benchmarkci 7 | Manifest.toml 8 | benchmark/*.json 9 | coverage 10 | docs/build/ 11 | env 12 | node_modules 13 | -------------------------------------------------------------------------------- /.lychee.toml: -------------------------------------------------------------------------------- 1 | exclude = [ 2 | "@ref", 3 | "@cite", 4 | "^https://github.com/.*/releases/tag/v.*$", 5 | "^https://doi.org/FIXME$", 6 | "^https://lucaferranti.github.io/DedekindCutArithmetic.jl/stable$", 7 | "zenodo.org/badge/DOI/FIXME$" 8 | ] 9 | 10 | exclude_path = [ 11 | "docs/build" 12 | ] 13 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD007": { 3 | "indent": 2, 4 | "start_indented": false 5 | }, 6 | "MD013": { 7 | "line_length": 1000, 8 | "tables": false 9 | }, 10 | "MD024": { 11 | "siblings_only": true 12 | }, 13 | "MD033": false, 14 | "MD041": false, 15 | "default": true 16 | } 17 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | # Prevent committing .rej files 5 | - id: forbidden-files 6 | name: forbidden files 7 | entry: found Copier update rejection files; review them and remove them 8 | language: fail 9 | files: "\\.rej$" 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v6.0.0 12 | hooks: 13 | - id: check-json 14 | - id: check-toml 15 | - id: check-yaml 16 | - id: end-of-file-fixer 17 | - id: file-contents-sorter 18 | files: .JuliaFormatter.toml 19 | args: [--unique] 20 | - id: mixed-line-ending 21 | args: [--fix=lf] 22 | - id: no-commit-to-branch 23 | - id: pretty-format-json 24 | args: [--autofix, --indent=2] 25 | - id: trailing-whitespace 26 | - id: check-merge-conflict 27 | args: [--assume-in-merge] 28 | - repo: https://github.com/igorshubovych/markdownlint-cli 29 | rev: v0.45.0 30 | hooks: 31 | - id: markdownlint-fix 32 | - repo: https://github.com/citation-file-format/cffconvert 33 | rev: 054bda51dbe278b3e86f27c890e3f3ac877d616c 34 | hooks: 35 | - id: validate-cff 36 | - repo: https://github.com/pre-commit/mirrors-prettier 37 | rev: "v4.0.0-alpha.8" # Use the sha or tag you want to point at 38 | hooks: 39 | - id: prettier 40 | types_or: [yaml, json] 41 | exclude: ".copier-answers.yml" 42 | - repo: https://github.com/adrienverge/yamllint 43 | rev: v1.37.1 44 | hooks: 45 | - id: yamllint 46 | - repo: https://github.com/domluna/JuliaFormatter.jl 47 | rev: v2.1.6 48 | hooks: 49 | - id: julia-formatter 50 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | indentation: { spaces: 2 } 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement: 63 | `luca [dot] ferranti [at] aalto [dot] fi`. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Luca Ferranti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "DedekindCutArithmetic" 2 | uuid = "a9cf20ff-59c8-5762-8ce8-520b700dfeff" 3 | authors = ["Luca Ferranti"] 4 | version = "0.1.1" 5 | 6 | [deps] 7 | ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" 8 | FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" 9 | MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" 10 | 11 | [compat] 12 | Aqua = "0.8.9" 13 | ForwardDiff = "0.10, 1" 14 | FunctionWrappers = "1.1.3" 15 | MacroTools = "0.5.13" 16 | SafeTestsets = "0.1.0" 17 | Test = "1.10" 18 | julia = "1.10" 19 | 20 | [extras] 21 | Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" 22 | SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" 23 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 24 | 25 | [targets] 26 | test = ["Aqua", "SafeTestsets", "Test"] 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 |

DedekindCutArithmetic.jl

5 | 6 | |**Info**|**Documentation**|**Build status**|**Contributing**| 7 | |:------:|:--------------:|:---------------:|:--------------:| 8 | |[![version][ver-img]][ver-url] [![license: MIT][license-img]][license-url]
[![DOI][doi-img]][doi-url]|[![Stable Documentation][stabledoc-img]][stabledoc-url] [![In development documentation][devdoc-img]][devdoc-url]|[![Build Status][ci-img]][ci-url] [![Coverage][cov-img]][cov-url]|[![Contributor Covenant][coc-img]][coc-url] [![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages][colprac-img]][colprac-url]
[![SciML Code Style][style-img]][style-url]| 9 | 10 | A Julia library for exact real arithmetic using [Dedekind cuts](https://en.wikipedia.org/wiki/Dedekind_cut) and [Abstract Stone Duality](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=02c685856371aac16ce81bf7467ffc4d533d48ff). Heavily inspired by the [Marshall](https://github.com/andrejbauer/marshall) programming language. 11 | 12 | ## Table of Content 13 | 14 | - [💾 Installation](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--installation) 15 | - [🌱 Quickstart example](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--quickstart-example) 16 | - [📚 Documentation](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--documentation) 17 | - [🤝 Contributing](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--contributing) 18 | 19 | ## 💾 Installation 20 | 21 | 1. If you haven't already, install Julia. The easiest way is to install [Juliaup](https://github.com/JuliaLang/juliaup#installation). This allows to easily install and manage Julia versions. 22 | 23 | 2. Open the terminal and start a Julia session by typing `julia`. 24 | 25 | 3. Install the library by typing 26 | 27 | ```julia 28 | using Pkg; Pkg.add("DedekindCutArithmetic") 29 | ``` 30 | 31 | 4. The package can now be loaded (in the interactive REPL or in a script file) with the command 32 | 33 | ```julia 34 | using DedekindCutArithmetic 35 | ``` 36 | 37 | 5. That's it, have fun! 38 | 39 | ## 🌱 Quickstart example 40 | 41 | The following snippet shows how to define the square root of a number and the maximum of a function $f: [0, 1] \rightarrow \mathbb{R}$ using Dedekind cuts. It also shows this definition is actually computable and can be used to give a tight rigorous bound on the value. 42 | 43 | ```julia 44 | using DedekindCutArithmetic 45 | 46 | # Textbook example of dedekind cuts, define square-root 47 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a) 48 | 49 | # lazy computation, however it is automatically evaluated to 53 bits of precision if printed in the REPL. 50 | sqrt2 = my_sqrt(2); 51 | 52 | # evaluate to 80 bits precision, this gives an interval with width <2⁻⁸⁰ containing √2 53 | refine!(sqrt2; precision=80) 54 | # [1.4142135623730949, 1.4142135623730951] 55 | 56 | # Define maximum of a function f: [0, 1] → ℝ as a Dedekind cut 57 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1] : f(x) > a), ∀(x ∈ [0, 1] : f(x) < a) 58 | 59 | f = x -> x * (1 - x) 60 | 61 | fmax = my_max(f); 62 | 63 | refine!(fmax) # evaluate to 53 bits of precision by default 64 | # [0.24999999999999992, 0.25000000000000006] 65 | ``` 66 | 67 | ## 📚 Documentation 68 | 69 | - [**STABLE**][stabledoc-url]: Documentation of the latest release 70 | - [**DEV**][devdoc-url]: Documentation of the in-development version on main 71 | 72 | A good starting point is the [beginner tutorial](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/tutorial/) 73 | 74 | ## 🤝 Contributing 75 | 76 | Contributions are welcome! Here is a small decision tree with useful links. More details in the [contributor's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing). 77 | 78 | - There is a [discussion section](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions) on GitHub. You can use the [helpdesk](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/helpdesk) category to ask for help on how to use the software or the [show and tell](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/show-and-tell) category to simply share with the world your work using DedekindCutArithmetic.jl 79 | 80 | - If you find a bug or want to suggest a new feature, [open an issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues). 81 | 82 | - You are also encouraged to send pull requests (PRs). For small changes, it is ok to open a PR directly. For bigger changes, it is advisable to discuss it in an issue first. Before opening a PR, make sure to check the [developer's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/91-developer). 83 | 84 | ## 📜 Copyright 85 | 86 | - Copyright (c) 2024 [Luca Ferranti](https://github.com/lucaferranti), released under MIT license 87 | 88 | [ver-img]: https://juliahub.com/docs/DedekindCutArithmetic/version.svg 89 | [ver-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/releases/latest 90 | 91 | [license-img]: https://img.shields.io/badge/license-MIT-yellow.svg 92 | [license-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/LICENSE 93 | 94 | [doi-img]: https://zenodo.org/badge/876330838.svg 95 | [doi-url]: https://doi.org/10.5281/zenodo.13989059 96 | 97 | [stabledoc-img]: https://img.shields.io/badge/docs-stable-blue.svg 98 | [stabledoc-url]: https://lucaferranti.github.io/DedekindCutArithmetic.jl/stable 99 | 100 | [devdoc-img]: https://img.shields.io/badge/docs-dev-blue.svg 101 | [devdoc-url]: https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev 102 | 103 | [ci-img]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/actions/workflows/Test.yml/badge.svg?branch=main 104 | [ci-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/actions/workflows/Test.yml?query=branch%3Amain 105 | 106 | [cov-img]: https://codecov.io/gh/lucaferranti/DedekindCutArithmetic.jl/branch/main/graph/badge.svg 107 | [cov-url]: https://codecov.io/gh/lucaferranti/DedekindCutArithmetic.jl 108 | 109 | [coc-img]: https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg 110 | [coc-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md 111 | 112 | [colprac-img]: https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet 113 | [colprac-url]: https://github.com/SciML/ColPrac 114 | 115 | [style-img]: https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826 116 | [style-url]: https://github.com/SciML/SciMLStyle 117 | -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | DedekindCutArithmetic = "a9cf20ff-59c8-5762-8ce8-520b700dfeff" 3 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 4 | DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" 5 | IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" 6 | LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" 7 | 8 | [compat] 9 | Documenter = "1" 10 | -------------------------------------------------------------------------------- /docs/liveserver.jl: -------------------------------------------------------------------------------- 1 | import Pkg 2 | 3 | Pkg.activate(@__DIR__) 4 | 5 | using LiveServer 6 | 7 | servedocs(; 8 | # literate_dir = joinpath("docs", "src", "literate"), 9 | # skip_dirs = [ 10 | # joinpath("docs", "src", "notebooks"), 11 | # joinpath("docs", "src", "tutorials"), 12 | # joinpath("docs", "src", "applications"), 13 | # ], 14 | # skip_files = [ 15 | # joinpath("docs", "src", "api", "logical.md"), 16 | # joinpath("docs", "src", "api", "memberships.md"), 17 | # joinpath("docs", "src", "assets", "indigo.css"), 18 | # ], 19 | launch_browser = true, 20 | verbose = true) 21 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | ENV["GKSwstype"] = "100" 2 | 3 | const IS_CI = get(ENV, "CI", "false") == "true" 4 | 5 | import Pkg 6 | Pkg.activate(@__DIR__) 7 | 8 | using Documenter 9 | using DedekindCutArithmetic 10 | 11 | using DocumenterCitations 12 | 13 | bib = CitationBibliography( 14 | joinpath(@__DIR__, "src", "refs.bib"); 15 | style = :numeric 16 | ) 17 | 18 | ############### 19 | # CREATE HTML # 20 | ############### 21 | 22 | makedocs(; 23 | modules = [DedekindCutArithmetic], authors = "Luca Ferranti", 24 | sitename = "DedekindCutArithmetic.jl", 25 | doctest = false, checkdocs = :exports, plugins = [bib], 26 | format = Documenter.HTML(; 27 | prettyurls = IS_CI, collapselevel = 1, 28 | canonical = "https://lucaferranti.github.io/DedekindCutArithmetic.jl", 29 | assets = String["assets/citations.css"] 30 | ), 31 | pages = [ 32 | "Home" => "index.md", 33 | "Tutorial" => "tutorial.md", 34 | "API" => "api.md", 35 | "Contributing" => ["90-contributing.md", "91-developer.md"], 36 | "Release notes" => "changelog.md", 37 | "References" => "references.md" 38 | ]) 39 | 40 | ########## 41 | # DEPLOY # 42 | ########## 43 | 44 | IS_CI && deploydocs(; 45 | repo = "github.com/lucaferranti/DedekindCutArithmetic.jl", push_preview = true) 46 | -------------------------------------------------------------------------------- /docs/setup.jl: -------------------------------------------------------------------------------- 1 | import Pkg 2 | 3 | Pkg.activate(@__DIR__) 4 | Pkg.develop(Pkg.PackageSpec(path = joinpath(@__DIR__, ".."))) 5 | Pkg.instantiate() 6 | -------------------------------------------------------------------------------- /docs/src/90-contributing.md: -------------------------------------------------------------------------------- 1 | # [Contributor's guide](@id contributing) 2 | 3 | First of all, huge thanks for your interest in the package! ✨ 4 | 5 | This page has some tips and guidelines on how to contribute. 6 | 7 | We welcome all kinds of contribution, including, but not limited to code, documentation, examples, bug reports, feature requests etc. 8 | 9 | All interactions should folow the [Code of Conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md). In a nutshell, be polite and respectful. 10 | 11 | !!! tip 12 | Feel free to ping us after a few days if there are no responses. 13 | 14 | ## Discussions 15 | 16 | If you need help using `DedekindCutArithmetic.jl`, you can use the [helpdesk](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/helpdesk) category to ask for help. This is preferable over issues, which are meant for bugs and feature requests, because discussions do not get closed once fixed and remain browsable for others. 17 | 18 | There is also a [show and tell](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/show-and-tell) category to share with the world your work using DedekindCutArithmetic.jl. If your work involves a new application of `DedekindCutArithmetic.jl` and you also want it featured in the Applications section in the documentation, let us know. You will get help with the workflow and setup, but you are expected to do the writing 😃 . 19 | 20 | ## Opening issues 21 | 22 | If you think you found a bug, feel free to open an [issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues). Issues to suggest new features are welcome too. 23 | 24 | ## Opening pull requests 25 | 26 | We follow the [ColPrac guide for collaborative practices](https://github.com/SciML/ColPrac). New contributors should make sure to read that guide before opening their first pull request. 27 | 28 | If you found an issue that interests you, comment on that issue what your plans are. 29 | If the solution to the issue is clear, you can immediately create a pull request. 30 | Otherwise, say what your proposed solution is and wait for a discussion around it. 31 | 32 | For small changes (typos, trivial bug fixes) it is ok to open a PR directly even without an associated issue. For non-trivial changes, please open an issue first. 33 | 34 | More technical details and tips on how work with git, run tests, build documentation etc. can be found in the [developer's guide](91-developer.md). 35 | -------------------------------------------------------------------------------- /docs/src/91-developer.md: -------------------------------------------------------------------------------- 1 | # [Developer documentation](@id dev_docs) 2 | 3 | !!! note "Contributing guidelines" 4 | If you haven't, please read the [Contributing guidelines](90-contributing.md) first. 5 | 6 | If you want to make contributions to this package, then this guide is for you. 7 | 8 | ## First time clone 9 | 10 | !!! tip "If you have writing rights" 11 | If you have writing rights, you don't have to fork. Instead, simply clone and skip ahead. Whenever **upstream** is mentioned, use **origin** instead. 12 | 13 | If this is the first time you work with this repository, follow the instructions below to clone the repository. 14 | 15 | 1. Fork this repository 16 | 2. Clone your fork (this will create a `git remote` called `origin`) 17 | 3. Add the original repository as a remote: 18 | 19 | ```bash 20 | git remote add upstream https://github.com/lucaferranti/DedekindCutArithmetic.jl 21 | ``` 22 | 23 | This will ensure that you have two remotes in your git: `origin` and `upstream`. 24 | You will create branches and push to `origin`, and you will fetch and update your local `main` branch from `upstream`. 25 | 26 | !!! warning "Warning" 27 | From now on, these instructions assume you are in the `DedekindCutArithmetic.jl` folder 28 | 29 | ## Linting and formatting 30 | 31 | Install a plugin on your editor to use [EditorConfig](https://editorconfig.org). 32 | This will ensure that your editor is configured with important formatting settings. 33 | 34 | We use [https://pre-commit.com](https://pre-commit.com) to run the linters and formatters. 35 | In particular, the Julia code is formatted using [JuliaFormatter.jl](https://github.com/domluna/JuliaFormatter.jl), so please install it globally first as follows (the following snippet is copy-pastable into the REPL) 36 | 37 | ```julia-repl 38 | julia> import Pkg; 39 | pkg> Pkg.activate(); 40 | pkg> Pkg.add("JuliaFormatter") 41 | ``` 42 | 43 | To install `pre-commit`, we recommend using [pipx](https://pipx.pypa.io) as follows: 44 | 45 | ```bash 46 | # Install pipx following the link 47 | pipx install pre-commit 48 | ``` 49 | 50 | With `pre-commit` installed, activate it as a pre-commit hook: 51 | 52 | ```bash 53 | pre-commit install 54 | ``` 55 | 56 | To run the linting and formatting manually, enter the command below: 57 | 58 | ```bash 59 | pre-commit run -a 60 | ``` 61 | 62 | **Now, you can only commit if all the pre-commit tests pass**. 63 | 64 | ## Testing 65 | 66 | As with most Julia packages, you can just open Julia in the repository folder, activate the environment, and run `test`: 67 | 68 | ```julia-repl 69 | julia> # press ] 70 | pkg> activate . 71 | pkg> test 72 | ``` 73 | 74 | Each test file is also stand-alone, hence to run a specific file you can `include` that from the REPL 75 | 76 | ```julia-repl 77 | julia> include("test/test-cuts.jl") 78 | ``` 79 | 80 | ## Working on a new issue 81 | 82 | We try to keep a linear history in this repo, so it is important to keep your branches up-to-date. 83 | 84 | 1. Fetch from the remote and fast-forward your local main 85 | 86 | ```bash 87 | git fetch upstream 88 | git switch main 89 | git merge --ff-only upstream/main 90 | ``` 91 | 92 | 2. Branch from `main` to address the issue (see below for naming) 93 | 94 | ```bash 95 | git switch -c 42-add-answer-universe 96 | ``` 97 | 98 | 3. Push the new local branch to your personal remote repository 99 | 100 | ```bash 101 | git push -u origin 42-add-answer-universe 102 | ``` 103 | 104 | 4. Create a pull request to merge your remote branch into the org main. 105 | 106 | ### Branch naming 107 | 108 | - If there is an associated issue, add the issue number. 109 | - If there is no associated issue, **and the changes are small**, add a prefix such as "typo", "hotfix", "small-refactor", according to the type of update. 110 | - If the changes are not small and there is no associated issue, then create the issue first, so we can properly discuss the changes. 111 | - Use dash separated imperative wording related to the issue (e.g., `14-add-tests`, `15-fix-model`, `16-remove-obsolete-files`). 112 | 113 | ### Commit message 114 | 115 | - Use imperative or present tense, for instance: *Add feature* or *Fix bug*. 116 | - Have informative titles. 117 | - When necessary, add a body with details. 118 | - If there are breaking changes, add the information to the commit message. 119 | 120 | ### Before creating a pull request 121 | 122 | !!! tip "Atomic git commits" 123 | Try to create "atomic git commits" (recommended reading: [The Utopic Git History](https://blog.esciencecenter.nl/the-utopic-git-history-d44b81c09593)). 124 | 125 | - Make sure the tests pass. 126 | - Make sure the pre-commit tests pass. 127 | - Fetch any `main` updates from upstream and rebase your branch, if necessary: 128 | 129 | ```bash 130 | git fetch upstream 131 | git rebase upstream/main BRANCH_NAME 132 | ``` 133 | 134 | - Then you can open a pull request and work with the reviewer to address any issues. 135 | 136 | ## Building and viewing the documentation locally 137 | 138 | The first time you want to build the documentation locally, you will need to install all needed dependencies. To do so, run 139 | 140 | ```bash 141 | julia docs/setup.jl 142 | ``` 143 | 144 | This needs to be done the fist time (i.e. if you don't have a `docs/Manifest.toml` file). The setup script needs to be rerun only if some external libraries used in the documentation example change. 145 | 146 | Next, you can build the documentation locally by running 147 | 148 | ```bash 149 | julia docs/liveserver.jl 150 | ``` 151 | 152 | This will open a preview of the documentation in your browser and watch the documentation source files, meaning the preview will automatically update on every documentation file change. 153 | 154 | ## Making a new release 155 | 156 | To create a new release, you can follow these simple steps: 157 | 158 | - Create a branch `release-x.y.z` 159 | - Update `version` in `Project.toml` 160 | - Update the `CHANGELOG.md`: 161 | - Rename the section "Unreleased" to "[x.y.z] - yyyy-mm-dd" (i.e., version under brackets, dash, and date in ISO format) 162 | - Add a new section on top of it named "Unreleased" 163 | - Add a new link in the bottom for version "x.y.z" 164 | - Change the "[unreleased]" link to use the latest version - end of line, `vx.y.z ... HEAD`. 165 | - Create a commit "Release vx.y.z", push, create a PR, wait for it to pass, merge the PR. 166 | - Go back to main screen and click on the latest commit (link: ) 167 | - At the bottom, write `@JuliaRegistrator register` 168 | 169 | After that, you only need to wait and verify: 170 | 171 | - Wait for the bot to comment (should take < 1m) with a link to a PR to the registry 172 | - Follow the link and wait for a comment on the auto-merge 173 | - The comment should said all is well and auto-merge should occur shortly 174 | - After the merge happens, TagBot will trigger and create a new GitHub tag. Check on 175 | - After the release is create, a "docs" GitHub action will start for the tag. 176 | - After it passes, a deploy action will run. 177 | - After that runs, the [stable docs](https://lucaferranti.github.io/DedekindCutArithmetic.jl/stable) should be updated. Check them and look for the version number. 178 | -------------------------------------------------------------------------------- /docs/src/api.md: -------------------------------------------------------------------------------- 1 | # [Reference](@id reference) 2 | 3 | ## Index 4 | 5 | ```@index 6 | Pages = ["api.md"] 7 | ``` 8 | 9 | ```@autodocs 10 | Modules = [DedekindCutArithmetic] 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/src/assets/citations.css: -------------------------------------------------------------------------------- 1 | .citation dl { 2 | display: grid; 3 | grid-template-columns: max-content auto; } 4 | .citation dt { 5 | grid-column-start: 1; } 6 | .citation dd { 7 | grid-column-start: 2; 8 | margin-bottom: 0.75em; } 9 | .citation ul { 10 | padding: 0 0 2.25em 0; 11 | margin: 0; 12 | list-style: none !important;} 13 | .citation ul li { 14 | text-indent: -2.25em; 15 | margin: 0.33em 0.5em 0.5em 2.25em;} 16 | .citation ol li { 17 | padding-left:0.75em;} 18 | -------------------------------------------------------------------------------- /docs/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/src/changelog.md: -------------------------------------------------------------------------------- 1 | ```@meta 2 | EditURL = "https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CHANGELOG.md" 3 | ``` 4 | 5 | ```@meta 6 | EditURL = "https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CHANGELOG.md" 7 | ``` 8 | 9 | # Release Notes 10 | 11 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 12 | 13 | ## [v0.2.0] 14 | 15 | ### Breaking 16 | 17 | - Disallow `DyadicInterval` method on other exact reals ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6)) 18 | - **To upgrade**: replace `DyadicInterval(x)` with `refine!(x)` 19 | - `exact` macro now returns `RationalCaucyCut` instead of `Rational` ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6)) 20 | - **To upgrade**: No change should be needed, all operations should work with the new behavior too. The rational can be obtained using the internal function `parse_decimal`. 21 | 22 | ### Fixed 23 | 24 | - Fixed bug in `exact` string macro to prevent denominator from overflowing ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6)) 25 | 26 | ### Added 27 | 28 | - Division between cuts and composite exact reals ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6)) 29 | 30 | ## [v0.1.1](https://github.com/lucaferranti/DedekindCutArithmetic.jl/releases/tag/v0.1.1) -- 2025-04-13 31 | 32 | ### Added 33 | 34 | - Support `Base.one` and `Base.zero` for cuts ([#5](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/5)) 35 | - Support `^` operation with non-negative integer exponent ([#4](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/4)) 36 | - Support `inv` and division ([#9](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/9)) 37 | - Add `@exact_str` string macro to correctly parse float literals ([#12](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/12)) 38 | - Add non-unicode `@forall` and `@exists` aliases ([#12](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/12)) 39 | 40 | ## [v0.1.0](https://github.com/lucaferranti/DedekindCutArithmetic.jl/releases/tag/v0.1.0) -- 2024-10-25 41 | 42 | Initial release 43 | 44 | ### Added 45 | 46 | - Define basic data structures (`DyadicReal, DyadicInterval, RationalCauchyCut, DedekindCut, UnaryCompositeCut, BinaryCompositeCut`) 47 | - defined addition, subraction and multiplication 48 | - support quantifiers over simple inequality statements 49 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | ```@meta 2 | CurrentModule = DedekindCutArithmetic 3 | ``` 4 | 5 | # DedekindCutArithmetic.jl 6 | 7 | A Julia library for exact real arithmetic using [Dedekind cuts](https://en.wikipedia.org/wiki/Dedekind_cut) and [Abstract Stone Duality](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=02c685856371aac16ce81bf7467ffc4d533d48ff). Heavily inspired by the [Marshall](https://github.com/andrejbauer/marshall) programming language. 8 | 9 | ## 💾 Installation 10 | 11 | 1. If you haven't already, install Julia. The easiest way is to install [Juliaup](https://github.com/JuliaLang/juliaup#installation). This allows to easily install and manage Julia versions. 12 | 13 | 2. Open the terminal and start a Julia session by typing `julia`. 14 | 15 | 3. Install the library by typing 16 | 17 | ```julia 18 | using Pkg; Pkg.add("DedekindCutArithmetic") 19 | ``` 20 | 21 | 4. The package can now be loaded (in the interactive REPL or in a script file) with the command 22 | 23 | ```julia 24 | using DedekindCutArithmetic 25 | ``` 26 | 27 | 5. That's it, have fun! 28 | 29 | ## 🌱 Quickstart example 30 | 31 | The following snippet shows how to define the square root of a number and the maximum of a function $f: [0, 1] \rightarrow \mathbb{R}$ using Dedekind cuts. It also shows this definition is actually computable and can be used to give a tight rigorous bound on the value. 32 | 33 | ```julia 34 | using DedekindCutArithmetic 35 | 36 | # Textbook example of dedekind cuts, define square-root 37 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a) 38 | 39 | # lazy computation, however it is automatically evaluated to 53 bits of precision if printed in the REPL. 40 | sqrt2 = my_sqrt(2); 41 | 42 | # evaluate to 80 bits precision, this gives an interval with width <2⁻⁸⁰ containing √2 43 | refine!(sqrt2; precision=80) 44 | # [1.4142135623730949, 1.4142135623730951] 45 | 46 | # Define maximum of a function f: [0, 1] → ℝ as a Dedekind cut 47 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1] : f(x) > a), ∀(x ∈ [0, 1] : f(x) < a) 48 | 49 | f = x -> x * (1 - x) 50 | 51 | fmax = my_max(f); 52 | 53 | refine!(fmax) # evaluate to 53 bits of precision by default 54 | # [0.24999999999999992, 0.25000000000000006] 55 | ``` 56 | 57 | ## 🤝 Contributing 58 | 59 | Contributions are welcome! Here is a small decision tree with useful links. More details in the [contributor's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing). 60 | 61 | - There is a [discussion section](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions) on GitHub. You can use the [helpdesk](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/helpdesk) category to ask for help on how to use the software or the [show and tell](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/show-and-tell) category to simply share with the world your work using DedekindCutArithmetic.jl 62 | 63 | - If you find a bug or want to suggest a new feature, [open an issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues). 64 | 65 | - You are also encouraged to send pull requests (PRs). For small changes, it is ok to open a PR directly. For bigger changes, it is advisable to discuss it in an issue first. Before opening a PR, make sure to check the [developer's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/91-developer). 66 | 67 | ## Copyright 68 | 69 | - Copyright (c) 2024 [Luca Ferranti](https://github.com/lucaferranti), released under MIT license 70 | -------------------------------------------------------------------------------- /docs/src/references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | ```@bibliography 4 | ``` 5 | -------------------------------------------------------------------------------- /docs/src/refs.bib: -------------------------------------------------------------------------------- 1 | @article{bauer2009dedekind, 2 | title={The Dedekind reals in abstract Stone duality}, 3 | author={Bauer, Andrej and Taylor, Paul}, 4 | journal={Mathematical structures in computer science}, 5 | volume={19}, 6 | number={4}, 7 | pages={757--838}, 8 | year={2009}, 9 | publisher={Cambridge University Press} 10 | } 11 | 12 | @inproceedings{bauer2008efficient, 13 | title={Efficient computation with Dedekind reals}, 14 | author={Bauer, Andrej}, 15 | booktitle={Fifth International Conference on Computability and Complexity in Analysis,(Eds. V Brattka, R Dillhage, T Grubba, A Klutch), Hagen, Germany}, 16 | year={2008}, 17 | organization={Citeseer} 18 | } 19 | 20 | @misc{marshall, 21 | author = {Bauer, Andrej}, 22 | title = {Marshall}, 23 | year = {2012}, 24 | publisher = {GitHub}, 25 | journal = {GitHub repository}, 26 | url = {https://github.com/andrejbauer/marshall}, 27 | } 28 | -------------------------------------------------------------------------------- /docs/src/tutorial.md: -------------------------------------------------------------------------------- 1 | # Basic Tutorial 2 | 3 | This tutorial will guide you through the basic functionalities of `DedekindCutArithmetic.jl`. These examples assume you have loaded the library with 4 | 5 | ```@repl tutorial1 6 | using DedekindCutArithmetic 7 | ``` 8 | 9 | ## Motivation: What is exact real arithmetic? 10 | 11 | Whenever doing computations, we need to face the problem of rounding errors. Standard techniques to cope with it are 12 | 13 | - *floating point arithmetic*: use 64 bits or 32 bits to approximate real numbers. This is what happens by default when you type `0.1` in the julia REPL. It is the fastest of the options, but also loses accuracy the fastest. 14 | - *arbitrary precision floating point arithmetic*: Set an arbitrary (within machine memory limits) precision *a priori* and do all computations using that precision. In Julia, this is achieved using `BigFloat`, which uses the C library MPFR under the hood. By default, it uses 256 bits of precision. This allows more accurate computations, but rounding error will still accumulate and for big enough inputs or long enough computations a significant loss of accuracy may still occur. 15 | - *interval arithmetic*: Use intervals instead of numbers and perform all operations so that the resulting interval contains the true result. This will give a rigorous bound of the error. However, for several factors (directed rounding, dependency problem, wrapping effect) the width of the interval may grow too big and give an uninformative result. 16 | 17 | An alternative approach is *exact real arithmetic*, which sets a target precision and outputs the final result with that precision. The main difference from e.g. MPFR, is that here the precision is dynamic and is increased during computation. Everything has a tradeoff of course, and exact real arithmetic can be slower than MPFR or interval arithmetic, especially for long complex computations. 18 | 19 | The following snippets gives a motivation example for exact real arithmetic. Obviously, ``(1 + a) - a`` should alwyas be ``1``. However, we get a complete wrong answer both with 64 and 256 bits of precision. With interval arithmetic, we get an interval containing the correct answer, but too wide to be informative. With exact real arithmetic we get a sharp bound on the correct value. 20 | 21 | ```@repl tutorial1 22 | a_float = sqrt(2.0^520/3) 23 | res_float = (1 + a_float) - a_float 24 | 25 | a_mpfr = sqrt(big(2)^520/3) 26 | res_mpfr = (1 + a_mpfr) - a_mpfr 27 | 28 | using IntervalArithmetic 29 | 30 | a_interval = sqrt(interval(big(2)^520//3)) 31 | res_interval = (interval(1) + a_interval) - a_interval 32 | 33 | a_era = sqrt(RationalCauchyCut(big(2)^520//3)) 34 | res_era = (1 + a_era) - a_era 35 | ``` 36 | 37 | There are different approaches to exact real arithmetic. This library builds on the theoretical framework based on Dedekind cuts and Abstract stone duality, proposed in [bauer2008efficient, bauer2009dedekind](@cite) and first implemented in [marshall](@cite). Next, the basic functionalities of this library are presented. 38 | 39 | ## Dyadic numbers 40 | 41 | The fundamental building block of our arithmetic is *dyadic numbers*, that is, a number in the form ``\frac{m}{e^{-k}}`` with ``m\in\mathbb{Z},e\in\mathbb{N}``. We will denote the set of dyadic reals as ``\mathbb{D}``. 42 | 43 | These can be built in the libary using [`DyadicReal`](@ref) 44 | 45 | Dyadic reals are closed under addition, subtraction and multiplication. 46 | 47 | ```@repl tutorial1 48 | d1 = DyadicReal(1, 2) # represents 1 ⋅ 2⁻² 49 | d2 = DyadicReal(2) 50 | d1 + d2 51 | d1 - d2 52 | d1 * d2 53 | ``` 54 | 55 | ## Dyadic interval 56 | 57 | There is plenty of real numbers which are not dyadic, for example ``0.1, \sqrt{2},\pi`` and so on so forth. What we will want to do, we will want a [`DyadicInterval`](@ref) ``[a, b]`` with ``a,b`` dyadic reals, which bounds the value we want to approximate. These intervals can be manipulated using interval arithmetic. 58 | 59 | ```@repl tutorial1 60 | i1 = DyadicInterval(1, 2) 61 | i2 = DyadicInterval(DyadicReal(1, 1), DyadicReal(1, 2)) 62 | 63 | i1 + i2 64 | i1 - i2 65 | i1 * i2 66 | ``` 67 | 68 | An important thing to notice is that our library relies on *Kaucher interval arithmetic*, which allows generalized intervals ``[a, b]`` with ``a > b``. 69 | 70 | ```@repl tutorial1 71 | i = DyadicInterval(3, 1) 72 | ``` 73 | 74 | ## Defining cuts 75 | 76 | We finally get to the main data structure of the library: [`DedekindCut`](@ref). The intuitive idea of a dedekind cut to define a real number ``x`` is the following 77 | 78 | 1. Find a set ``L\subset \mathbb{D}`` so that each element in ``L`` is strictly smaller than ``x`` 79 | 2. Find a set ``U \subset \mathbb{D}`` so that each element in ``U`` is strictly bigger than ``x``. 80 | 3. To get better and better approximations of ``x``, keep increasing the upper bound of ``L`` and decreasing the lower bound of ``U``, this will give a dyadic interval that shrinks around ``x``. 81 | 82 | ### Baby example 83 | 84 | Let us first see a very trivial example, to get a taste of how to build dedekind cuts in practice in the library. Suppose we want to define the number ``2`` as a Dedekind cut. This is of course dummy, since that is a dyadic number and we could simply do `DyadicReal(2)` to get the exact value. 85 | 86 | To define a cut, we need to find an expression for the lower set and upper set. In this case, we simply have 87 | 88 | - **Lower set**: ``\{x \in \mathbb{D}: x < 2\}`` 89 | - **Upper set**: ``\{x \in \mathbb{D}: x > 2\}`` 90 | 91 | Dedekind cuts can be built with the [`@cut`](@ref) macro and the syntax is the 92 | 93 | ```julia 94 | @cut var_name ∈ domain, lower_set_expression, upper_set_expression 95 | ``` 96 | 97 | If we have no idea where the the number we are defining lies on the real line, we can use ``\mathbb{R}`` for the domain. Alternatively, if we know that it lies in an interval ``[a, b]``, we can restrict the domain to that interval. This will often lead to faster approximations. Let's see this in practice 98 | 99 | ```@repl tutorial1 100 | my2 = @cut x ∈ ℝ, x < 2, x > 2; 101 | ``` 102 | 103 | By default, this performs a *lazy* computation, that is, nothing has actually been computed so far. We can now get an arbitrary good approximation of ``\sqrt{2}`` using [`refine!`](@ref) function. 104 | 105 | ```@repl tutorial1 106 | refine!(my2) # uses 53 bits of precision by default 107 | ``` 108 | 109 | The number can be queried at different precisions using the `precision` keyword. Refining to a precision `k` will produce a dyadic interval with width smaller than ``2^{-k}``. 110 | 111 | ```@repl tutorial1 112 | refine!(my2; precision=80) 113 | ``` 114 | 115 | It is worth mentioning that for printing, the expression is evaluated to 53 bits of precision (that is, same of a 64-bits float), hence the following in the REPL is not entirely lazy. To have a lazy computation in the REPL, suppress the output with `;` 116 | 117 | ```@repl tutorial1 118 | @cut x ∈ [0, 3], x < 2, x > 2 119 | ``` 120 | 121 | !!! warning "Warning" 122 | Unbounded intervals are not supported yet, infinity is replaced by `big(typemax(Int))` during macro expansion. 123 | 124 | ### Square root as Dedekind cut 125 | 126 | Let's see an example, suppose we want to define ``\sqrt{a}`` for arbitrary dyadic ``a``. We need to find a suitable expression for ``L`` and ``U``. 127 | 128 | - **Lower set**: Since ``\sqrt{a}`` is positive, then all negative ``x`` will be smaller than ``\sqrt{a}`` and hence belong to ``L``. For positive ``x``, we have ``x < \sqrt{a} \leftrightarrow x \cdot x < a``. This gives an expression for the lower set 129 | 130 | ```math 131 | L = \{x \in \mathbb{D} : x < 0 \lor x \cdot x < 0\} 132 | ``` 133 | 134 | - **Upper set**: Similarly, for a number to possibly be greater than ``sqrt{a}``, it will need to be positive. Furthermore for positive ``x`` we have ``x > \sqrt{a} \leftrightarrow x \cdot x > a``, giving the expression 135 | 136 | ```math 137 | L = \{x \in \mathbb{D} : x > 0 \land x \cdot x > 0\} 138 | ``` 139 | 140 | In the library, this can be implemented using the [`@cut`](@ref) macro. We can now define a function that computes the square root of a number using dedekind cuts. 141 | 142 | ```@repl tutorial1 143 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a) 144 | ``` 145 | 146 | We can now use it to compute the ``\sqrt{2}`` to an arbitrary precision 147 | 148 | ```@repl tutorial1 149 | sqrt2 = my_sqrt(2); 150 | isqrt2 = refine!(sqrt2; precision=80) 151 | ``` 152 | 153 | and verify that the desired accuracy is achieved 154 | 155 | ```@repl tutorial1 156 | width(isqrt2) 157 | width(isqrt2) < DyadicReal(1, 80) 158 | ``` 159 | 160 | ## Cuts with quantifiers 161 | 162 | So far we have seen how to define numbers whose cuts can be expressed using propositional logic. We can however do better. Namely, we can use first-order logic, i.e. quantifiers like ``\forall`` and ``\exists`` to define more elaborated cuts. 163 | 164 | Let us now see how to define the maximum of a function with domain ``[0, 1]``. Again, we need to define the lower and upper set. 165 | 166 | **Lower set**: if ``a \in L``, it is smaller than the maximum, i.e. there will be an element in the domain of the function for which ``f(x) > a``, this gives us the expression 167 | 168 | ```math 169 | L = \{a \in \mathbb{D} : \exists x \in [0, 1] : f(x) > a\} 170 | ``` 171 | 172 | **Upper set**: if ``a \in U``, it is greater than the maximum, which means it is also greater than ``f(x)`` for every ``x`` in the domain, this gives us 173 | 174 | ```math 175 | U = \{a \in \mathbb{D} : \forall x \in [0, 1] : f(x) < a\} 176 | ``` 177 | 178 | The `@cut` macro supports parsing quantifiers with a very similar syntax 179 | 180 | ```@repl tutorial1 181 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1] : f(x) > a), ∀(x ∈ [0, 1] : f(x) < a) 182 | ``` 183 | 184 | we can now use that to compute the maximum of ``f(x) = x(1 - x)``, which should be ``\frac{1}{4}`` 185 | 186 | ```@repl tutorial1 187 | my_max(x -> x * (1 - x)) 188 | ``` 189 | 190 | ## Working with float literals 191 | 192 | By default, real numbers on compuers are approximately represented via floating point numbers. 193 | Floats cannot represent all reals exactly and when typing decimals, e.g. `0.1` one does not get the 194 | exact value, but an approximation 195 | 196 | ```@repl tutorial1 197 | a = 0.1 198 | 199 | a == 1//10 200 | 201 | big(a) 202 | ``` 203 | 204 | Unfortunately, the parsing of literals in Julia happens already before building the abstract syntax tree, 205 | hence when processing expressions with macros, the rounding sin has already happened. 206 | 207 | For example, the following would fail to terminate 208 | 209 | ```julia 210 | @∀ x ∈ ℝ: 0.1000000000000000000001 > 0.1 211 | ``` 212 | 213 | because when parsing the literals both produce the same floating point number. 214 | 215 | ```@repl tutorial1 216 | 0.1000000000000000000001 == 0.1 217 | ``` 218 | 219 | When working with rational numbers in decimal form, use the [`@exact_str`](@ref) string macro, which ensures the literal is parsed to the correct rational number. 220 | 221 | ```@repl tutorial1 222 | exact"0.1" 223 | ``` 224 | 225 | ```@repl tutorial1 226 | @∀ x ∈ ℝ: exact"0.1000000000000000000001" > exact"0.1" 227 | ``` 228 | -------------------------------------------------------------------------------- /src/DedekindCutArithmetic.jl: -------------------------------------------------------------------------------- 1 | """ 2 | DedekindCutArithmetic 3 | 4 | Julia library implementing exact real arithmetic using Dedekind cuts and abstract stone duality. 5 | """ 6 | module DedekindCutArithmetic 7 | 8 | using MacroTools 9 | using FunctionWrappers: FunctionWrapper 10 | using ForwardDiff 11 | 12 | export DyadicReal, DyadicInterval, DedekindCut, RationalCauchyCut, BinaryCompositeCut, @cut, 13 | refine!, dual, overlaps, 14 | width, midpoint, radius, thirds, low, high, isforward, isbackward, 15 | exists, forall, @∀, @∃, @forall, @exists, 16 | @exact_str 17 | 18 | const _Real = Union{Integer, AbstractFloat, Rational} 19 | 20 | const DEFAULT_PRECISION = 53 21 | 22 | include("abstract_interface.jl") 23 | include("dyadic.jl") 24 | include("interval.jl") 25 | include("cuts.jl") 26 | include("promotions.jl") 27 | include("quantifiers.jl") 28 | include("macros.jl") 29 | 30 | end 31 | -------------------------------------------------------------------------------- /src/abstract_interface.jl: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Abstract types and fallback methods # 3 | ####################################### 4 | 5 | """ 6 | Abstract type representing a generic real number. 7 | """ 8 | abstract type AbstractDedekindReal <: Real end 9 | 10 | """ 11 | Represents a dyadic number or a dyadic interval. 12 | """ 13 | abstract type AbstractDyadic <: AbstractDedekindReal end 14 | 15 | """ 16 | Abstract type for a generic cut. 17 | """ 18 | abstract type AbstractCut <: AbstractDedekindReal end 19 | 20 | Base.isfinite(::AbstractDedekindReal) = true 21 | Base.isnan(::AbstractDedekindReal) = false 22 | 23 | """ 24 | Refine the given cut to give an approximation with `precision` bits of accuracy. 25 | If the required accuracy cannot be achieved within `max_iter` iterations, return the current estimate 26 | with a warning. 27 | """ 28 | function refine!(::AbstractDedekindReal; precision = DEFAULT_PRECISION, max_iter = 1000) end 29 | 30 | function Base.show(io::IO, ::MIME"text/plain", d::AbstractDedekindReal) 31 | i = refine!(d; precision = 53) 32 | print(io, "[", BigFloat(low(i), RoundDown; precision = 53), 33 | ", ", BigFloat(high(i), RoundUp; precision = 53), "]") 34 | end 35 | -------------------------------------------------------------------------------- /src/cuts.jl: -------------------------------------------------------------------------------- 1 | """ 2 | This represents a dyadic interval [(m-1)/2^e, (m+1)/2^e] approximating a non-dyadic rational number. 3 | 4 | This is a special-case of Dedekind cut representing a rational number for which we do not need iterative refinement 5 | """ 6 | struct RationalCauchyCut <: AbstractCut 7 | num::BigInt 8 | den::BigInt 9 | end 10 | 11 | RationalCauchyCut(x::Rational) = RationalCauchyCut(numerator(x), denominator(x)) 12 | RationalCauchyCut(x::AbstractFloat) = RationalCauchyCut(rationalize(x)) 13 | 14 | """ 15 | Evaluate the Cauchy sequence representing a rational number with `n` bits of precision. 16 | """ 17 | function refine!(d::RationalCauchyCut; precision = DEFAULT_PRECISION, max_iter = 1000) 18 | num = (d.num << precision) ÷ d.den 19 | DyadicInterval(DyadicReal(num - 1, precision), DyadicReal(num + 1, precision)) 20 | end 21 | 22 | for op in (:+, :-, :*, :/) 23 | @eval function Base.$op( 24 | i1::DyadicInterval, c::RationalCauchyCut; precision = DEFAULT_PRECISION) 25 | p = make_prec(precision, i1) 26 | $op(i1, refine!(c; precision = p); precision = p) 27 | end 28 | 29 | @eval function Base.$op( 30 | c::RationalCauchyCut, i2::DyadicInterval; precision = DEFAULT_PRECISION) 31 | p = make_prec(precision, i2) 32 | $op(refine!(c; precision = p), i2; precision = p) 33 | end 34 | end 35 | 36 | """ 37 | Representation of a real number ``x`` as a dedekind cut. 38 | 39 | ### Fields 40 | 41 | - `low` : function approximating the number from below. Evaluates to true for every number ``< x``. 42 | - `high` : function approximating the number from above. Evaluates to true for every number ``> x``. 43 | - `mpa` : Cached most precise approximation computed so far. This is a dyadic interval bound ``x`` which is updated every time the cut is refined to a higher precision. 44 | """ 45 | mutable struct DedekindCut <: AbstractCut 46 | const low::FunctionWrapper{Bool, Tuple{AbstractDedekindReal}} 47 | const high::FunctionWrapper{Bool, Tuple{AbstractDedekindReal}} 48 | mpa::DyadicInterval 49 | end 50 | 51 | function refine!(d::DedekindCut; precision = DEFAULT_PRECISION, max_iter = 1000) 52 | for i in 0:max_iter 53 | width(d.mpa) < DyadicReal(1, precision) && return d.mpa 54 | a1, b1 = thirds(d.mpa) 55 | low_pred = d.low(a1) 56 | high_pred = d.high(b1) 57 | if !low_pred && !high_pred 58 | # TODO: do something smarter here 59 | @warn "cannot refine cut any further, final precision might be lower than desired" 60 | return d.mpa 61 | end 62 | new_low = low_pred ? a1 : d.mpa.lo 63 | new_hi = high_pred ? b1 : d.mpa.hi 64 | d.mpa = DyadicInterval(new_low, new_hi) 65 | end 66 | @warn "Could not reach desired precision within maximum number of iterations, final result may be less accurate than requested" 67 | return d.mpa 68 | end 69 | 70 | """ 71 | Composite cut lazily representing the result of applying an arithmetic unary operation `f` to `child`. 72 | """ 73 | struct UnaryCompositeCut{F} <: AbstractCut 74 | f::F 75 | child::AbstractCut 76 | end 77 | 78 | function refine!(d::UnaryCompositeCut; precision = DEFAULT_PRECISION, max_iter = 1000) 79 | (; f, child) = d 80 | i = refine!(child; precision) 81 | res = f(i; precision) 82 | for _ in 0:max_iter 83 | width(res) < DyadicReal(1, precision) && return res 84 | p = make_prec(precision + 3, i) 85 | i = refine!(child, precision = p) 86 | res = f(i; precision = p) 87 | end 88 | @warn "Could not reach desired precision within maximum number of iterations, final result may be less accurate than requested" 89 | return res 90 | end 91 | 92 | """ 93 | Composite cut lazily representing the result of applying an arithmetic binary operation `f` to `child1` and `child2`. 94 | """ 95 | struct BinaryCompositeCut{F} <: AbstractCut 96 | f::F 97 | child1::AbstractDedekindReal 98 | child2::AbstractDedekindReal 99 | end 100 | 101 | function refine!(d::BinaryCompositeCut; precision = DEFAULT_PRECISION, max_iter = 1000) 102 | (; f, child1, child2) = d 103 | i1, i2 = refine!(child1; precision), refine!(child2; precision) 104 | res = f(i1, i2; precision) 105 | for i in 0:max_iter 106 | width(res) < DyadicReal(1, precision) && return res 107 | p1 = make_prec(precision + 3, i1) 108 | p2 = make_prec(precision + 3, i2) 109 | i1 = refine!(child1, precision = p1) 110 | i2 = refine!(child2, precision = p2) 111 | res = f(i1, i2; precision = max(p1, p2)) 112 | end 113 | @warn "Could not reach desired precision within maximum number of iterations, final result may be less accurate than requested" 114 | return res 115 | end 116 | 117 | ################## 118 | # Real interface # 119 | ################## 120 | 121 | Base.zero(::AbstractCut) = zero(DyadicReal) 122 | Base.zero(::Type{<:AbstractCut}) = zero(DyadicReal) 123 | Base.one(::AbstractCut) = one(DyadicReal) 124 | Base.one(::Type{<:AbstractCut}) = one(DyadicReal) 125 | 126 | ######################### 127 | # Arithmetic operations # 128 | ######################### 129 | 130 | Base.:-(d::AbstractDedekindReal) = UnaryCompositeCut(-, d) 131 | 132 | Base.:+(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(+, d1, d2) 133 | Base.:-(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(-, d1, d2) 134 | Base.:*(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(*, d1, d2) 135 | Base.:/(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(/, d1, d2) 136 | 137 | for op in (:<, :>, :<=, :>=) 138 | @eval function Base.$op(d1::AbstractDedekindReal, d2::AbstractDedekindReal) 139 | p = DEFAULT_PRECISION 140 | i1, i2 = refine!(d1; precision = p), refine!(d2; precision = p) 141 | while overlaps(i1, i2) 142 | p *= 2 143 | i1 = refine!(d1; precision = p) 144 | i2 = refine!(d2; precision = p) 145 | end 146 | return $op(i1, i2) 147 | end 148 | end 149 | 150 | function Base.sqrt(a::AbstractDedekindReal) 151 | fsqrt = isqrt(low(a).m >> low(a).e) 152 | upsqrt = isqrt((high(a).m >> high(a).e) + 1) + 1 153 | i = DyadicInterval(fsqrt, upsqrt) 154 | DedekindCut(x -> x < 0 || x * x < a, x -> x > 0 && x * x > a, i) 155 | end 156 | 157 | function Base.:^(x::AbstractCut, p::Integer) 158 | UnaryCompositeCut((i; precision) -> ^(i, p; precision), x) 159 | end 160 | -------------------------------------------------------------------------------- /src/dyadic.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Represents a dyadic number in the form ``\\frac{m}{2^{-e}}``, with ``d ∈ ℤ`` and ``e ∈ ℕ``. 3 | """ 4 | struct DyadicReal <: AbstractDyadic 5 | m::BigInt 6 | e::Int64 7 | end 8 | 9 | get_mantissa(d::DyadicReal) = d.m 10 | get_exp(d::DyadicReal) = -d.e 11 | 12 | function Base.show(io::IO, ::MIME"text/plain", d::DyadicReal) 13 | print(io, float(d)) 14 | end 15 | 16 | @inline ilog2(x::BigInt) = Base.GMP.MPZ.sizeinbase(x, 2) - 1 17 | 18 | ############### 19 | # Conversions # 20 | ############### 21 | 22 | DyadicReal(n::Integer) = DyadicReal(BigInt(n), 0) 23 | function Base.BigFloat(d::DyadicReal, mode::RoundingMode = RoundNearest; precision = 256) 24 | BigFloat(d.m // (big(1) << d.e), mode; precision) 25 | end 26 | function (::Type{T})( 27 | d::DyadicReal, mode::RoundingMode = RoundNearest) where {T <: AbstractFloat} 28 | T(d.m // (big(1) << d.e), mode) 29 | end 30 | 31 | Rational(d::DyadicReal) = d.m // (big(1) << d.e) 32 | 33 | ############ 34 | # Rounding # 35 | ############ 36 | 37 | Base.round(d::DyadicReal, ::RoundingMode{:Down}) = DyadicReal(d.m >> d.e, 0) 38 | Base.round(d::DyadicReal, ::RoundingMode{:Up}) = DyadicReal(((d.m - 1) >> d.e) + 1, 0) 39 | function Base.round(d::DyadicReal, ::RoundingMode{:Nearest}) 40 | DyadicReal((d.m + big(1) << (d.e - 1)) >> d.e, 0) 41 | end 42 | 43 | Base.round(::Type{BigInt}, d::DyadicReal) = get_mantissa(round(d, RoundNearest)) 44 | Base.floor(::Type{BigInt}, d::DyadicReal) = get_mantissa(round(d, RoundDown)) 45 | Base.ceil(::Type{BigInt}, d::DyadicReal) = get_mantissa(round(d, RoundUp)) 46 | 47 | ###################### 48 | # Inexact operations # 49 | ###################### 50 | 51 | function _inv(d::DyadicReal, ::RoundingMode{:Down}; precision = DEFAULT_PRECISION) 52 | num = (big(1) << (d.e + precision)) ÷ d.m 53 | DyadicReal(num - 1, precision) 54 | end 55 | 56 | function _inv(d::DyadicReal, ::RoundingMode{:Up}; precision = DEFAULT_PRECISION) 57 | num = (big(1) << (d.e + precision)) ÷ d.m 58 | DyadicReal(num + 1, precision) 59 | end 60 | 61 | function _inv(d::DyadicReal, ::RoundingMode{:Nearest}; precision = DEFAULT_PRECISION) 62 | num = (big(1) << (d.e + precision)) ÷ d.m 63 | DyadicReal(num, precision) 64 | end 65 | 66 | function Base.inv( 67 | d::DyadicReal; precision = DEFAULT_PRECISION) 68 | iszero(d) && throw(DomainError(d, "Division by zero")) 69 | RationalCauchyCut(big(1) << d.e, d.m) 70 | # if iszero(abs(d.m) & (abs(d.m) - 1)) 71 | # DyadicReal(big(1) << d.e, ilog2(d.m)) 72 | # else 73 | # _inv(d, r; precision) 74 | # end 75 | end 76 | 77 | function Base.:/( 78 | d1::DyadicReal, d2::DyadicReal; precision = DEFAULT_PRECISION) 79 | iszero(d2) && throw(DomainError(d, "Division by zero")) 80 | RationalCauchyCut((d1.m << d2.e) // (d2.m << d1.e)) 81 | end 82 | 83 | ################# 84 | # Cut interface # 85 | ################# 86 | 87 | width(c::DyadicReal) = zero(c) 88 | midpoint(c::DyadicReal) = c 89 | radius(c::DyadicReal) = zero(c) 90 | low(c::DyadicReal) = c 91 | high(c::DyadicReal) = c 92 | 93 | refine!(x::DyadicReal; kwargs...) = DyadicInterval(x) 94 | 95 | ########################## 96 | # Arithmetic operations # 97 | ########################## 98 | 99 | function Base.:+(d1::DyadicReal, d2::DyadicReal) 100 | m1, e1, m2, e2 = if d1.e <= d2.e 101 | d1.m, d1.e, d2.m, d2.e 102 | else 103 | d2.m, d2.e, d1.m, d1.e 104 | end 105 | DyadicReal(m1 << (e2 - e1) + m2, e2) 106 | end 107 | 108 | Base.:-(d::DyadicReal) = DyadicReal(-d.m, d.e) 109 | 110 | function Base.:-(d1::DyadicReal, d2::DyadicReal) 111 | m1, e1, m2, e2 = if d1.e <= d2.e 112 | d1.m, d1.e, -d2.m, d2.e 113 | else 114 | -d2.m, d2.e, d1.m, d1.e 115 | end 116 | DyadicReal(m1 << (e2 - e1) + m2, e2) 117 | end 118 | 119 | function Base.:*(d1::DyadicReal, d2::DyadicReal) 120 | DyadicReal(d1.m * d2.m, d1.e + d2.e) 121 | end 122 | 123 | Base.:>>(d::DyadicReal, n::Int64) = DyadicReal(d.m, d.e + n) 124 | 125 | function Base.:<<(d::DyadicReal, n::Int64) 126 | return n >= d.e ? DyadicReal(d.m << (n - d.e), 0) : DyadicReal(d.m, d.e - n) 127 | end 128 | 129 | Base.abs(d::DyadicReal) = DyadicReal(abs(d.m), d.e) 130 | 131 | Base.:(==)(d1::DyadicReal, d2::DyadicReal) = (d1.m << d2.e) == (d2.m << d1.e) 132 | 133 | for op in (:<, :>, :<=, :>=) 134 | @eval function Base.$op(d1::DyadicReal, d2::DyadicReal) 135 | $op((d1.m << d2.e), (d2.m << d1.e)) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /src/interval.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Represents an interval with lower and uppper bound expressed as dyadic numbers. 3 | Note this is a generalized interval, that is, the lower bound can be greater than the upper bound. 4 | """ 5 | struct DyadicInterval <: AbstractDyadic 6 | lo::DyadicReal 7 | hi::DyadicReal 8 | end 9 | 10 | DyadicInterval(n1::Integer, n2::Integer) = DyadicInterval(DyadicReal(n1), DyadicReal(n2)) 11 | function DyadicInterval(n::Integer) 12 | x = DyadicReal(n) 13 | DyadicInterval(x, x) 14 | end 15 | DyadicInterval(d::DyadicReal) = DyadicInterval(d, d) 16 | 17 | "Returns true if the lower bound is smaller or equal of the upper bound." 18 | isforward(d::DyadicInterval) = d.lo <= d.hi 19 | 20 | "Returns true if the lower bound is strictly bigger than the upper bound." 21 | isbackward(d::DyadicInterval) = d.hi < d.lo 22 | 23 | "Given the interval ``[a, b]``, return its dual ``[b, a]``." 24 | dual(d::DyadicInterval) = DyadicInterval(d.hi, d.lo) 25 | 26 | Base.in(x::Number, i::DyadicInterval) = min(i.lo, i.hi) <= x <= max(i.lo, i.hi) 27 | 28 | "Whether or not the given dyadic intervals overlap." 29 | overlaps(i1::DyadicInterval, i2::DyadicInterval) = max(i1.lo, i2.lo) <= min(i1.hi, i2.hi) 30 | 31 | "Left endpoint of the dyadic interval." 32 | low(i::DyadicInterval) = i.lo 33 | 34 | "Right endpoint of the dyadic interval." 35 | high(i::DyadicInterval) = i.hi 36 | 37 | """ 38 | Width of the dyadic interval. 39 | 40 | Note this is always positive, also for improper intervals. 41 | """ 42 | width(i::DyadicInterval) = abs(i.hi - i.lo) 43 | 44 | "Half the width of the given interval." 45 | radius(i::DyadicInterval) = width(i) >> 1 46 | 47 | "Midpoint of the dyadic interval" 48 | midpoint(i::DyadicInterval) = (i.lo + i.hi) >> 1 49 | 50 | refine!(i::DyadicInterval; precision = DEFAULT_PRECISION, max_iter = 1000) = i 51 | 52 | "Split the interval in two halves." 53 | function Base.split(i::DyadicInterval) 54 | m = midpoint(i) 55 | DyadicInterval(i.lo, m), DyadicInterval(m, i.hi) 56 | end 57 | 58 | """ 59 | Return two points that divide the interval in three parts with ratio 1/4, 1/2, 1/2 60 | """ 61 | function thirds(i::DyadicInterval) 62 | i1, i2 = split(i) 63 | midpoint(i1), midpoint(i2) 64 | end 65 | 66 | function Base.show(io::IO, ::MIME"text/plain", d::DyadicInterval) 67 | print(io, "[", BigFloat(low(d), RoundDown; precision = 53), 68 | ", ", BigFloat(high(d), RoundUp; precision = 53), "]") 69 | end 70 | 71 | Base.:+(d::DyadicInterval; precision = DEFAULT_PRECISION) = d 72 | Base.:-(d::DyadicInterval; precision = DEFAULT_PRECISION) = DyadicInterval(-d.hi, -d.lo) 73 | 74 | function Base.:+(d1::AbstractDyadic, d2::AbstractDyadic; precision = DEFAULT_PRECISION) 75 | DyadicInterval(low(d1) + low(d2), high(d1) + high(d2)) 76 | end 77 | function Base.:-(d1::AbstractDyadic, d2::AbstractDyadic; precision = DEFAULT_PRECISION) 78 | DyadicInterval(low(d1) - high(d2), high(d1) - low(d2)) 79 | end 80 | 81 | function Base.:*(d1::DyadicInterval, d2::DyadicInterval; precision = DEFAULT_PRECISION) 82 | a, b = low(d1), high(d1) 83 | c, d = low(d2), high(d2) 84 | if a <= 0 && b <= 0 85 | if 0 <= c && 0 <= d 86 | DyadicInterval(a * d, b * c) 87 | elseif d <= 0 <= c 88 | DyadicInterval(b * d, b * c) 89 | elseif c <= 0 <= d 90 | DyadicInterval(a * d, a * c) 91 | else # c, d ≤ 0 92 | DyadicInterval(b * d, a * c) 93 | end 94 | elseif a <= 0 <= b 95 | if 0 <= c && 0 <= d 96 | DyadicInterval(a * d, b * d) 97 | elseif d <= 0 <= c 98 | zero(DyadicInterval) 99 | elseif c <= 0 <= d 100 | DyadicInterval(min(a * d, b * c), max(a * c, b * d)) 101 | else # c, d ≤ 0 102 | DyadicInterval(b * c, a * c) 103 | end 104 | elseif b <= 0 <= a 105 | if 0 <= c && 0 <= d 106 | DyadicInterval(a * c, b * c) 107 | elseif d <= 0 <= c 108 | DyadicInterval(max(a * c, b * d), min(a * d, b * c)) 109 | elseif c <= 0 <= d 110 | zero(DyadicInterval) 111 | else # c, d ≤ 0 112 | DyadicInterval(b * d, a * d) 113 | end 114 | else # 0 ≤ a, b 115 | if 0 <= c && 0 <= d 116 | DyadicInterval(a * c, b * d) 117 | elseif d <= 0 <= c 118 | DyadicInterval(a * c, a * d) 119 | elseif c <= 0 <= d 120 | DyadicInterval(b * c, b * d) 121 | else # c, d ≤ 0 122 | DyadicInterval(b * c, a * d) 123 | end 124 | end 125 | end 126 | 127 | function Base.:^(i::DyadicInterval, p::Int64; precision = 53) 128 | p < 0 && throw(ArgumentError("Negative exponents not supported yet")) 129 | lo, hi = low(i), high(i) 130 | isodd(p) && return DyadicInterval(lo^p, hi^p) 131 | lop, hip = if lo >= 0 132 | hi >= 0 ? (lo^p, hi^p) : (max(hi^p, lo^p), zero(DyadicReal)) 133 | else 134 | hi >= 0 ? (zero(DyadicReal), max(hi^p, lo^p)) : (hi^p, lo^p) 135 | end 136 | DyadicInterval(lop, hip) 137 | end 138 | 139 | function Base.inv(i::DyadicInterval; precision = DEFAULT_PRECISION) 140 | 0 ∈ i && throw(DomainError(i, "Interval contains 0")) 141 | lo, hi = low(i), high(i) 142 | DyadicInterval(_inv(hi, RoundDown; precision), _inv(lo, RoundUp; precision)) 143 | end 144 | 145 | function Base.:/(i1::DyadicInterval, i2::DyadicInterval; precision = DEFAULT_PRECISION) 146 | # TODO: could maybe be faster? 147 | i1 * inv(i2; precision) 148 | end 149 | 150 | ############### 151 | # Comparisons # 152 | ############### 153 | 154 | function Base.:(==)(i1::DyadicInterval, i2::DyadicInterval) 155 | return low(i1) == low(i2) && high(i1) == high(i2) 156 | end 157 | 158 | Base.:<(i1::AbstractDyadic, i2::AbstractDyadic) = high(i1) < low(i2) 159 | Base.:<=(i1::AbstractDyadic, i2::AbstractDyadic) = high(i1) <= low(i2) 160 | 161 | Base.:>(i1::AbstractDyadic, i2::AbstractDyadic) = low(i1) > high(i2) 162 | Base.:>=(i1::AbstractDyadic, i2::AbstractDyadic) = low(i1) >= high(i2) 163 | 164 | ######### 165 | # Utils # 166 | ######### 167 | 168 | """ 169 | Given precision `p` and interval `i`, compute a precision which is better than `p` and 170 | is suitable for working with intervals of width `i`. 171 | 172 | Taken from: https://github.com/andrejbauer/marshall/blob/c9f1f6466e879e8db11a12b9bc030e62b07d8bd2/src/eval.ml#L22-L26 173 | """ 174 | function make_prec(p::Int64, i::DyadicInterval) 175 | w = width(i) 176 | e1 = get_exp(w) 177 | e2 = max(get_exp(low(i)), get_exp(high(i))) 178 | max(2, p, (-5 * (e1 - e2)) >> 2) 179 | end 180 | -------------------------------------------------------------------------------- /src/macros.jl: -------------------------------------------------------------------------------- 1 | function parse_quantifier_body(ex, head) 2 | dom, body = if @capture(ex, x_∈dom_:f_ $f - $g) 4 | elseif @capture(ex, x_∈dom_:f_>g_) 5 | dom, :($x -> $g - $f) 6 | else 7 | throw(ArgumentError("Invalid body for quantifier")) 8 | end 9 | a, b = parse_domain(dom) 10 | :($head(DyadicInterval($a, $b), $body)) 11 | end 12 | 13 | """ 14 | Check whether ``∃ x ∈ dom : prop(x)`` 15 | """ 16 | macro exists(ex) 17 | parse_quantifier_body(ex, :exists) 18 | end 19 | const var"@∃" = var"@exists" 20 | 21 | """ 22 | Check whether ``∀ x ∈ dom : prop(x)`` 23 | """ 24 | macro forall(ex) 25 | parse_quantifier_body(ex, :forall) 26 | end 27 | const var"@∀" = var"@forall" 28 | 29 | """ 30 | Transformations on the expression before being processed by [`@cut`](@ref) 31 | """ 32 | function preprocess_expression(ex) 33 | MacroTools.postwalk(ex) do s 34 | s == :∞ && return big(typemax(Int)) 35 | @capture(s, a_∧b_) && return :($a && $b) 36 | @capture(s, a_∨b_) && return :($a || $b) 37 | @capture(s, ∃(prop_)) && return parse_quantifier_body(prop, :exists) 38 | @capture(s, ∀(prop_)) && return parse_quantifier_body(prop, :forall) 39 | s 40 | end 41 | end 42 | 43 | function parse_domain(dom) 44 | if dom == :ℝ 45 | -big(typemax(Int)), big(typemax(Int)) 46 | elseif @capture(dom, [a_, b_]) 47 | a, b 48 | else 49 | throw(ArgumentError("Invalid domain $dom")) 50 | end 51 | end 52 | 53 | """ 54 | Macro to construct a [`DedekindCut`](@ref). 55 | 56 | A cut is defined using the following syntax 57 | 58 | ```julia 59 | @cut x ∈ [a, b], low, high 60 | ``` 61 | 62 | This defines a real number ``x`` in the interval ``[a, b]`` 63 | which is approximated by the inequality ``φ(x)`` from below and ``ψ(x)`` from above. 64 | 65 | ``φ`` and ``ψ`` can be any julia expression, also referring to variables in the scope where the macro is called. 66 | 67 | Similarly, ``a`` and ``b`` can also be julia expressions, but they cannot depend on ``x``. 68 | """ 69 | macro cut(ex) 70 | ex2 = preprocess_expression(ex) 71 | if @capture(ex2, (x_ ∈ dom_, low_, high_)) 72 | a, b = parse_domain(dom) 73 | esc(:(DedekindCut($x -> $low, $x -> $high, DyadicInterval($a, $b)))) 74 | else 75 | throw(ArgumentError("Invalid cut expression $ex")) 76 | end 77 | end 78 | 79 | function parse_decimal(s::AbstractString) 80 | num_exp = split(s, ('e', 'E')) 81 | 1 <= length(num_exp) <= 2 || throw(Meta.ParseError("invalid literal $s")) 82 | n = first(num_exp) 83 | int_dec = split(n, '.') 84 | 1 <= length(int_dec) <= 2 || throw(Meta.ParseError("invalid literal $s")) 85 | num, logden = if length(int_dec) == 2 86 | int, dec = int_dec 87 | parse(BigInt, int * dec), -length(dec) 88 | else 89 | parse(BigInt, first(int_dec)), 0 90 | end 91 | if length(num_exp) == 2 92 | logden += parse(BigInt, last(num_exp)) 93 | end 94 | if logden < 0 95 | num // big(10)^(-logden) 96 | else 97 | num * big(10)^logden 98 | end 99 | end 100 | 101 | """ 102 | Parse a decimal literal into an exact real 103 | 104 | ```julia 105 | julia> a = exact"0.1" 106 | [0.099999999999999867, 0.10000000000000009] 107 | 108 | julia> refine!(a; precision = 80) 109 | [0.099999999999999992, 0.10000000000000001] 110 | ``` 111 | 112 | This is needed because literals are already parsed before constructing the AST, hence 113 | when writing :(x - 0.1) one would get the floating point approximation of 1//10 instead of the 114 | exact rational. 115 | """ 116 | macro exact_str(s::AbstractString) 117 | RationalCauchyCut(parse_decimal(s)) 118 | end 119 | -------------------------------------------------------------------------------- /src/promotions.jl: -------------------------------------------------------------------------------- 1 | 2 | Base.promote_rule(::Type{DyadicReal}, ::Type{<:Integer}) = DyadicReal 3 | Base.promote_rule(::Type{DyadicInterval}, ::Type{<:Integer}) = DyadicInterval 4 | Base.promote_rule(::Type{DyadicInterval}, ::Type{DyadicReal}) = DyadicInterval 5 | Base.promote_rule(::Type{RationalCauchyCut}, ::Type{<:AbstractFloat}) = RationalCauchyCut 6 | Base.promote_rule(::Type{RationalCauchyCut}, ::Type{<:Rational}) = RationalCauchyCut 7 | Base.promote_rule(::Type{<:AbstractDedekindReal}, ::Type{<:_Real}) = AbstractDedekindReal 8 | 9 | AbstractDedekindReal(x::Integer) = DyadicReal(x) 10 | AbstractDedekindReal(q::Union{Rational, AbstractFloat}) = RationalCauchyCut(q) 11 | -------------------------------------------------------------------------------- /src/quantifiers.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Check whether ``∃ x ∈ dom : f(x) < 0`` 3 | """ 4 | function exists(dom::DyadicInterval, f::Function)::Bool 5 | f(DyadicInterval(midpoint(dom))) < 0 && return true 6 | 7 | # try to avoid bisection by checking for monotonicity 8 | d = ForwardDiff.derivative(f, dom) 9 | low(d) > 0 && f(low(dom)) >= 0 && return false 10 | high(d) < 0 && f(high(dom)) >= 0 && return false 11 | 12 | high(f(dual(dom))) >= 0 && return false 13 | 14 | i1, i2 = split(dom) 15 | exists(i1, f) || exists(i2, f) 16 | end 17 | 18 | """ 19 | Check whether ``∀ x ∈ dom : f(x) < 0`` 20 | """ 21 | function forall(dom::DyadicInterval, f::Function)::Bool 22 | f(DyadicInterval(midpoint(dom))) >= 0 && return false 23 | 24 | # try to avoid bisection by checking for monotonicity 25 | d = ForwardDiff.derivative(f, dom) 26 | low(d) > 0 && f(high(dom)) < 0 && return true 27 | high(d) < 0 && f(low(dom)) < 0 && return true 28 | 29 | f(dom) < 0 && return true 30 | 31 | i1, i2 = split(dom) 32 | forall(i1, f) && forall(i2, f) 33 | end 34 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using SafeTestsets, Test 2 | 3 | #= 4 | Don't add your tests to runtests.jl. Instead, create files named 5 | 6 | test-title-for-my-test.jl 7 | 8 | The file will be automatically included inside a `@testset` with title "Title For My Test". 9 | =# 10 | for (root, dirs, files) in walkdir(@__DIR__) 11 | for file in files 12 | if isnothing(match(r"^test-.*\.jl$", file)) 13 | continue 14 | end 15 | title = titlecase(replace(splitext(file[6:end])[1], "-" => " ")) 16 | @time @eval @safetestset $title begin 17 | include($file) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/test-cuts.jl: -------------------------------------------------------------------------------- 1 | using DedekindCutArithmetic 2 | using Test 3 | 4 | @testset "simple arithmetic" begin 5 | a = RationalCauchyCut(1, 10) 6 | a_ref = 1 // 10 7 | for op in (+, -, *) 8 | b = op(a, a) 9 | i = refine!(b) 10 | @test width(i) < DyadicReal(1, 53) 11 | @test Rational(low(i)) < op(a_ref, a_ref) < Rational(high(i)) 12 | end 13 | 14 | b = -a 15 | i = refine!(b) 16 | @test width(i) < DyadicReal(1, 53) 17 | @test Rational(low(i)) < -1 // 10 < Rational(high(i)) 18 | 19 | ia2 = refine!(a^2) 20 | @test width(ia2) < DyadicReal(1, 53) 21 | @test Rational(low(ia2)) < 1 // 100 < Rational(high(ia2)) 22 | 23 | ia3 = refine!(a^3) 24 | @test width(ia3) < DyadicReal(1, 53) 25 | @test Rational(low(ia3)) < 1 // 1000 < Rational(high(ia3)) 26 | 27 | @test zero(ia2) == zero(typeof(ia2)) == DyadicReal(0, 0) 28 | @test one(ia2) == one(typeof(ia2)) == DyadicReal(1, 0) 29 | end 30 | 31 | @testset "square root" begin 32 | a = DyadicReal(2) 33 | sqrt2 = sqrt(a) 34 | 35 | isqrt2 = refine!(sqrt2; precision = 80) 36 | @test BigFloat(width(isqrt2)) <= 0x1p-80 37 | @test BigFloat(low(isqrt2)) <= sqrt(big(2)) <= BigFloat(high(isqrt2)) 38 | 39 | isqrt2pow2 = refine!(sqrt2^2) 40 | @test BigFloat(width(isqrt2pow2)) <= 0x1p-53 41 | @test low(isqrt2pow2) <= 2 <= high(isqrt2pow2) 42 | end 43 | 44 | @testset "README example" begin 45 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a) 46 | 47 | sqrt2 = my_sqrt(2) # lazy computation, however it is evaluated to 53 bits precision when printing 48 | 49 | isqrt2 = refine!(sqrt2; precision = 80) 50 | @test BigFloat(width(isqrt2)) <= 0x1p-80 51 | @test BigFloat(low(isqrt2)) <= sqrt(big(2)) <= BigFloat(high(isqrt2)) 52 | @test isqrt2 == sqrt2.mpa 53 | 54 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1]:f(x) > a), ∀(x ∈ [0, 1]:f(x) < a) 55 | f = x -> x * (1 - x) 56 | fmax = my_max(f) 57 | ifmax = refine!(fmax) 58 | @test ifmax == fmax.mpa 59 | @test BigFloat(width(ifmax)) <= 0x1p-53 60 | @test low(ifmax) < 1 // 4 < high(ifmax) 61 | end 62 | 63 | @testset "exact macro" begin 64 | a = exact"0.1" 65 | @test a.num == 1 66 | @test a.den == 10 67 | 68 | # check denominator doesn't overflow 69 | a = exact"0.09999999999999999167332731531132594682276248931884765625" 70 | b = 9999999999999999167332731531132594682276248931884765625 // big(10)^56 71 | 72 | @test a.num == numerator(b) 73 | @test a.den == denominator(b) 74 | end 75 | -------------------------------------------------------------------------------- /test/test-dyadic.jl: -------------------------------------------------------------------------------- 1 | using DedekindCutArithmetic 2 | using Test 3 | 4 | @testset "Dyadic numbers construction" begin 5 | @test DyadicReal(1) == DyadicReal(big(1), 0) 6 | @test zero(DyadicReal) == zero(DyadicReal(1)) == DyadicReal(0, 0) 7 | @test iszero(zero(DyadicReal)) 8 | 9 | d = DyadicReal(1, 2) 10 | @test low(d) == high(d) == midpoint(d) == d 11 | @test radius(d) == width(d) == zero(DyadicReal) 12 | 13 | @test repr("text/plain", d) == "0.25" 14 | 15 | x1 = Float64(DyadicReal(1, 1)) 16 | x2 = BigFloat(DyadicReal(1, 1)) 17 | x3 = Rational(DyadicReal(1, 1)) 18 | @test x1 == x2 == x3 == 0.5 19 | @test x1 isa Float64 20 | @test x2 isa BigFloat 21 | @test x3 isa Rational{BigInt} 22 | 23 | @test refine!(d) == DyadicInterval(d, d) 24 | @test d == d 25 | end 26 | 27 | @testset "Arithmetic operations" begin 28 | d1 = DyadicReal(1, 3) 29 | d2 = DyadicReal(1, 2) 30 | 31 | @test +d1 == d1 32 | @test -d1 == DyadicReal(-1, 3) 33 | 34 | @test d1 + d2 == DyadicReal(3, 3) 35 | @test d2 + d1 == DyadicReal(3, 3) 36 | 37 | @test d1 - d2 == DyadicReal(-1, 3) 38 | @test d2 - d1 == DyadicReal(1, 3) 39 | 40 | @test d1 * d2 == DyadicReal(1, 5) 41 | @test DyadicReal(-typemax(Int), 2) * DyadicReal(-typemax(Int), 3) == 42 | DyadicReal(big(typemax(Int))^2, 5) 43 | 44 | @test d1 < d2 45 | @test d1 <= d2 46 | @test d2 > d1 47 | @test d2 >= d1 48 | @test DyadicReal(1, 0) > DyadicReal(-1, 0) 49 | 50 | @test d1 == d1 51 | @test d1 != d2 52 | @test d1 == DyadicReal(2, 4) 53 | 54 | @test d1 + 1 == DyadicReal(9, 3) 55 | @test iszero(d1 * 0) 56 | @test iszero(0 * d1) 57 | @test d1 * 1 == d1 58 | @test d1 * 3 == DyadicReal(3, 3) 59 | @test d1 - 0 == d1 60 | 61 | @test (d1 << 1) == DyadicReal(1, 2) == d1 * 2 62 | @test (d1 << 4) == DyadicReal(2, 0) == d1 * 16 63 | @test (d1 >> 3) == DyadicReal(1, 6) 64 | 65 | @test abs(d1) == abs(-d1) == d1 66 | @test round(d1) == DyadicReal(0, 0) 67 | @test floor(d1) == DyadicReal(0, 0) 68 | @test ceil(d1) == DyadicReal(1, 0) 69 | 70 | d2 = DyadicReal(123, 5) 71 | @test round(d2) == ceil(d2) 72 | @test round(BigInt, d2) == ceil(BigInt, d2) == big(4) 73 | @test floor(BigInt, d2) == big(3) 74 | end 75 | 76 | @testset "inexact arithmetic operations" begin 77 | d1 = DyadicReal(1, 2) 78 | inv_d1 = inv(d1) 79 | @test inv_d1.num == 4 80 | @test inv_d1.den == 1 81 | 82 | d2 = DyadicReal(1, 0) 83 | d3 = DyadicReal(3, 0) 84 | 85 | rat1 = d1 / d2 86 | @test rat1.num == 1 87 | @test rat1.den == 4 88 | 89 | rat2 = d1 / d3 90 | @test rat2.num == 1 91 | @test rat2.den == 12 92 | end 93 | -------------------------------------------------------------------------------- /test/test-interval.jl: -------------------------------------------------------------------------------- 1 | using DedekindCutArithmetic 2 | using Test 3 | 4 | @testset "Basic interval construction" begin 5 | i1 = DyadicInterval(1, 2) 6 | @test i1 == DyadicInterval(DyadicReal(1, 0), DyadicReal(2, 0)) 7 | 8 | @test isforward(i1) 9 | @test dual(i1) == DyadicInterval(DyadicReal(2, 0), DyadicReal(1, 0)) 10 | @test isbackward(dual(i1)) 11 | @test dual(dual(i1)) == i1 12 | @test zero(DyadicInterval) == DyadicInterval(zero(DyadicReal), zero(DyadicReal)) 13 | 14 | @test refine!(i1; precision = 12345) == i1 15 | end 16 | 17 | @testset "Basic set operations" begin 18 | @test overlaps(DyadicInterval(0, 1), DyadicInterval(DyadicReal(1, 1), 1)) 19 | @test split(DyadicInterval(0, 2)) == (DyadicInterval(0, 1), DyadicInterval(1, 2)) 20 | @test thirds(DyadicInterval(0, 2)) == (DyadicReal(1, 1), DyadicReal(3, 1)) 21 | 22 | i = DyadicInterval(0, 1) 23 | @test low(i) == 0 24 | @test high(i) == 1 25 | @test midpoint(i) == DyadicReal(1, 1) 26 | @test width(i) == 1 27 | @test radius(i) == DyadicReal(1, 1) 28 | end 29 | 30 | @testset "Arithmetic operations" begin 31 | i1 = DyadicInterval(0, 1) 32 | i2 = DyadicInterval(2, 3) 33 | 34 | @test +i1 == i1 35 | @test -i2 == DyadicInterval(-3, -2) 36 | 37 | @test i1 + i2 == DyadicInterval(2, 4) 38 | @test i1 - i2 == DyadicInterval(-3, -1) 39 | 40 | @test i1 + DyadicReal(1) == i1 + 1 == DyadicInterval(1, 2) 41 | 42 | # TODO: more cases for multiplication 43 | @test i1 * i2 == DyadicInterval(0, 3) 44 | 45 | @test DyadicInterval(0, 1) < DyadicInterval(2, 3) 46 | @test DyadicInterval(0, 1) <= DyadicInterval(2, 3) 47 | @test DyadicInterval(2, 3) > DyadicInterval(0, 1) 48 | @test DyadicInterval(2, 3) >= DyadicInterval(0, 1) 49 | 50 | @test !(DyadicInterval(0, 2) < DyadicInterval(1, 3)) 51 | @test !(DyadicInterval(1, 3) > DyadicInterval(0, 2)) 52 | 53 | @test DyadicInterval(1, 2)^2 == DyadicInterval(1, 4) 54 | @test DyadicInterval(3, 2)^2 == DyadicInterval(9, 4) 55 | @test DyadicInterval(-3, 2)^2 == DyadicInterval(0, 9) 56 | @test DyadicInterval(2, -3)^2 == DyadicInterval(9, 0) 57 | @test DyadicInterval(-3, -2)^2 == DyadicInterval(4, 9) 58 | 59 | @test DyadicInterval(-2, 2)^3 == DyadicInterval(-8, 8) 60 | @test DyadicInterval(2, -2)^3 == DyadicInterval(8, -8) 61 | end 62 | 63 | @testset "inexact arithmetic operations" begin 64 | i1 = DyadicInterval(1, 3) 65 | inv_i1 = inv(i1) 66 | @test 1 // 3 - 1 // (big(1) << 52) < low(inv_i1) < 1 // 3 67 | @test 1 < high(inv_i1) < 1 + 1 // (big(1) << 52) 68 | 69 | inv_i2 = inv(i1; precision = 80) 70 | @test 1 // 3 - 1 // (big(1) << 79) < low(inv_i2) < 1 // 3 71 | @test 1 < high(inv_i2) < 1 + 1 // (big(1) << 79) 72 | 73 | i2 = DyadicInterval(-1, 1) 74 | @test_throws DomainError inv(i2) 75 | @test_throws DomainError i1/i2 76 | 77 | div_int1 = DyadicInterval(1, 1) / DyadicInterval(1, 3) 78 | @test 1 // 3 - 1 // (big(1) << 52) < low(div_int1) < 1 // 3 79 | @test 1 < high(div_int1) < 1 + 1 // (big(1) << 52) 80 | 81 | div_int2 = /(DyadicInterval(1, 1), DyadicInterval(1, 3); precision = 80) 82 | @test 1 // 3 - 1 // (big(1) << 79) < low(div_int2) < 1 // 3 83 | @test 1 < high(div_int2) < 1 + 1 // (big(1) << 79) 84 | end 85 | -------------------------------------------------------------------------------- /test/test-library-quality.jl: -------------------------------------------------------------------------------- 1 | using Aqua, DedekindCutArithmetic 2 | 3 | @testset "Code quality (Aqua.jl)" begin 4 | Aqua.test_all(DedekindCutArithmetic) 5 | end 6 | 7 | if VERSION >= v"1.11" 8 | @testset "Public API is documented" begin 9 | @testset "Symbol $n" for n in names(DedekindCutArithmetic) 10 | @test Docs.hasdoc(DedekindCutArithmetic, n) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/test-quantifiers.jl: -------------------------------------------------------------------------------- 1 | using DedekindCutArithmetic 2 | using Test 3 | 4 | @testset "Universal quantifier" begin 5 | @test @∀ x ∈ [0, 1]:x > -1 6 | @test @∀ x ∈ [0, 1]:x > -0.0001 7 | @test @∀ x ∈ [0, 1]:x < 2 8 | @test @∀ x ∈ [0, 1]:x < 1.0001 9 | 10 | @test @∀ x ∈ [0, 1]:x > exact"-0.1e-12" 11 | 12 | @test !(@∀ x ∈ [0, 1]:x > 0.0001) 13 | @test !(@∀ x ∈ [0, 1]:x < 0.9999) 14 | end 15 | 16 | @testset "Existential quantifier" begin 17 | @test @∃ x ∈ [0, 1]:x > -1 18 | @test @∃ x ∈ [0, 1]:x > 0.9999 19 | @test @∃ x ∈ [0, 1]:x < 2 20 | @test @∃ x ∈ [0, 1]:x < 0.0001 21 | 22 | @test !(@∃ x ∈ [0, 1]:x > 1.0001) 23 | @test !(@∃ x ∈ [0, 1]:x < -0.0001) 24 | end 25 | --------------------------------------------------------------------------------