├── .github ├── DOCS.md ├── codecov.yml ├── dependabot.yml └── workflows │ ├── check.yml │ ├── scheduled.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── zipf_bench.rs └── src └── lib.rs /.github/DOCS.md: -------------------------------------------------------------------------------- 1 | # Github config and workflows 2 | 3 | In this folder there is configuration for codecoverage, dependabot, and ci 4 | workflows that check the library more deeply than the default configurations. 5 | 6 | This folder can be or was merged using a --allow-unrelated-histories merge 7 | strategy from which provides a 8 | reasonably sensible base for writing your own ci on. By using this strategy 9 | the history of the CI repo is included in your repo, and future updates to 10 | the CI can be merged later. 11 | 12 | To perform this merge run: 13 | 14 | ```shell 15 | git remote add ci https://github.com/jonhoo/rust-ci-conf.git 16 | git fetch ci 17 | git merge --allow-unrelated-histories ci/main 18 | ``` 19 | 20 | An overview of the files in this project is available at: 21 | , which contains some 22 | rationale for decisions and runs through an example of solving minimal version 23 | and OpenSSL issues. 24 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference 2 | coverage: 3 | # Hold ourselves to a high bar 4 | range: 85..100 5 | round: down 6 | precision: 1 7 | status: 8 | # ref: https://docs.codecov.com/docs/commit-status 9 | project: 10 | default: 11 | # Avoid false negatives 12 | threshold: 1% 13 | 14 | # Test files aren't important for coverage 15 | ignore: 16 | - "tests" 17 | 18 | # Make comments less noisy 19 | comment: 20 | layout: "files" 21 | require_changes: true 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: daily 11 | ignore: 12 | - dependency-name: "*" 13 | # patch and minor updates don't matter for libraries as consumers of this library build 14 | # with their own lockfile, rather than the version specified in this library's lockfile 15 | # remove this ignore rule if your package has binaries to ensure that the binaries are 16 | # built with the exact set of dependencies and those are up to date. 17 | update-types: 18 | - "version-update:semver-patch" 19 | - "version-update:semver-minor" 20 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs 2 | # several checks: 3 | # - fmt: checks that the code is formatted according to rustfmt 4 | # - clippy: checks that the code does not contain any clippy warnings 5 | # - doc: checks that the code can be documented without errors 6 | # - hack: check combinations of feature flags 7 | # - msrv: check that the msrv specified in the crate is correct 8 | permissions: 9 | contents: read 10 | # This configuration allows maintainers of this repo to create a branch and pull request based on 11 | # the new branch. Restricting the push trigger to the main branch ensures that the PR only gets 12 | # built once. 13 | on: 14 | push: 15 | branches: [main] 16 | pull_request: 17 | # If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that 18 | # we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 21 | cancel-in-progress: true 22 | name: check 23 | jobs: 24 | fmt: 25 | runs-on: ubuntu-latest 26 | name: stable / fmt 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: true 31 | - name: Install stable 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | components: rustfmt 35 | - name: cargo fmt --check 36 | run: cargo fmt --check 37 | clippy: 38 | runs-on: ubuntu-latest 39 | name: ${{ matrix.toolchain }} / clippy 40 | permissions: 41 | contents: read 42 | checks: write 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | # Get early warning of new lints which are regularly introduced in beta channels. 47 | toolchain: [stable, beta] 48 | steps: 49 | - uses: actions/checkout@v4 50 | with: 51 | submodules: true 52 | - name: Install ${{ matrix.toolchain }} 53 | uses: dtolnay/rust-toolchain@master 54 | with: 55 | toolchain: ${{ matrix.toolchain }} 56 | components: clippy 57 | - name: cargo clippy 58 | uses: giraffate/clippy-action@v1 59 | with: 60 | reporter: 'github-pr-check' 61 | github_token: ${{ secrets.GITHUB_TOKEN }} 62 | semver: 63 | runs-on: ubuntu-latest 64 | name: semver 65 | steps: 66 | - uses: actions/checkout@v4 67 | with: 68 | submodules: true 69 | - name: Install stable 70 | uses: dtolnay/rust-toolchain@stable 71 | with: 72 | components: rustfmt 73 | - name: cargo-semver-checks 74 | uses: obi1kenobi/cargo-semver-checks-action@v2 75 | doc: 76 | # run docs generation on nightly rather than stable. This enables features like 77 | # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an 78 | # API be documented as only available in some specific platforms. 79 | runs-on: ubuntu-latest 80 | name: nightly / doc 81 | steps: 82 | - uses: actions/checkout@v4 83 | with: 84 | submodules: true 85 | - name: Install nightly 86 | uses: dtolnay/rust-toolchain@nightly 87 | - name: Install cargo-docs-rs 88 | uses: dtolnay/install@cargo-docs-rs 89 | - name: cargo docs-rs 90 | run: cargo docs-rs 91 | hack: 92 | # cargo-hack checks combinations of feature flags to ensure that features are all additive 93 | # which is required for feature unification 94 | runs-on: ubuntu-latest 95 | name: ubuntu / stable / features 96 | steps: 97 | - uses: actions/checkout@v4 98 | with: 99 | submodules: true 100 | - name: Install stable 101 | uses: dtolnay/rust-toolchain@stable 102 | - name: cargo install cargo-hack 103 | uses: taiki-e/install-action@cargo-hack 104 | # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 105 | # --feature-powerset runs for every combination of features 106 | - name: cargo hack 107 | run: cargo hack --feature-powerset check 108 | msrv: 109 | # check that we can build using the minimal rust version that is specified by this crate 110 | runs-on: ubuntu-latest 111 | # we use a matrix here just because env can't be used in job names 112 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability 113 | strategy: 114 | matrix: 115 | msrv: ["1.63.0"] # libc 116 | name: ubuntu / ${{ matrix.msrv }} 117 | steps: 118 | - uses: actions/checkout@v4 119 | with: 120 | submodules: true 121 | - name: Install ${{ matrix.msrv }} 122 | uses: dtolnay/rust-toolchain@master 123 | with: 124 | toolchain: ${{ matrix.msrv }} 125 | - name: cargo +${{ matrix.msrv }} check 126 | run: cargo check 127 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yml: -------------------------------------------------------------------------------- 1 | # Run scheduled (rolling) jobs on a nightly basis, as your crate may break independently of any 2 | # given PR. E.g., updates to rust nightly and updates to this crates dependencies. See check.yml for 3 | # information about how the concurrency cancellation and workflow triggering works 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | schedule: 11 | - cron: '7 7 * * *' 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | name: rolling 16 | jobs: 17 | # https://twitter.com/mycoliza/status/1571295690063753218 18 | nightly: 19 | runs-on: ubuntu-latest 20 | name: ubuntu / nightly 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | submodules: true 25 | - name: Install nightly 26 | uses: dtolnay/rust-toolchain@nightly 27 | - name: cargo generate-lockfile 28 | if: hashFiles('Cargo.lock') == '' 29 | run: cargo generate-lockfile 30 | - name: cargo test --locked 31 | run: cargo test --locked --all-features --all-targets 32 | # https://twitter.com/alcuadrado/status/1571291687837732873 33 | update: 34 | # This action checks that updating the dependencies of this crate to the latest available that 35 | # satisfy the versions in Cargo.toml does not break this crate. This is important as consumers 36 | # of this crate will generally use the latest available crates. This is subject to the standard 37 | # Cargo semver rules (i.e cargo does not update to a new major version unless explicitly told 38 | # to). 39 | runs-on: ubuntu-latest 40 | name: ubuntu / beta / updated 41 | # There's no point running this if no Cargo.lock was checked in in the first place, since we'd 42 | # just redo what happened in the regular test job. Unfortunately, hashFiles only works in if on 43 | # steps, so we repeat it. 44 | steps: 45 | - uses: actions/checkout@v4 46 | with: 47 | submodules: true 48 | - name: Install beta 49 | if: hashFiles('Cargo.lock') != '' 50 | uses: dtolnay/rust-toolchain@beta 51 | - name: cargo update 52 | if: hashFiles('Cargo.lock') != '' 53 | run: cargo update 54 | - name: cargo test 55 | if: hashFiles('Cargo.lock') != '' 56 | run: cargo test --locked --all-features --all-targets 57 | env: 58 | RUSTFLAGS: -D deprecated 59 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This is the main CI workflow that runs the test suite on all pushes to main and all pull requests. 2 | # It runs the following jobs: 3 | # - required: runs the test suite on ubuntu with stable and beta rust toolchains 4 | # - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the 5 | # requirements of this crate, and its dependencies 6 | # - os-check: runs the test suite on mac and windows 7 | # - coverage: runs the test suite and collects coverage information 8 | # See check.yml for information about how the concurrency cancellation and workflow triggering works 9 | permissions: 10 | contents: read 11 | on: 12 | push: 13 | branches: [main] 14 | pull_request: 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 17 | cancel-in-progress: true 18 | name: test 19 | jobs: 20 | required: 21 | runs-on: ubuntu-latest 22 | name: ubuntu / ${{ matrix.toolchain }} 23 | strategy: 24 | matrix: 25 | # run on stable and beta to ensure that tests won't break on the next version of the rust 26 | # toolchain 27 | toolchain: [stable, beta] 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | submodules: true 32 | - name: Install ${{ matrix.toolchain }} 33 | uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.toolchain }} 36 | - name: cargo generate-lockfile 37 | # enable this ci template to run regardless of whether the lockfile is checked in or not 38 | if: hashFiles('Cargo.lock') == '' 39 | run: cargo generate-lockfile 40 | # https://twitter.com/jonhoo/status/1571290371124260865 41 | - name: cargo test --locked 42 | run: cargo test --locked --all-features 43 | minimal: 44 | # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure 45 | # that this crate is compatible with the minimal version that this crate and its dependencies 46 | # require. This will pickup issues where this create relies on functionality that was introduced 47 | # later than the actual version specified (e.g., when we choose just a major version, but a 48 | # method was added after this version). 49 | # 50 | # This particular check can be difficult to get to succeed as often transitive dependencies may 51 | # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There 52 | # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for 53 | # direct dependencies of this crate, while selecting the maximal versions for the transitive 54 | # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase 55 | # the minimal dependency, which you do with e.g.: 56 | # ```toml 57 | # # for minimal-versions 58 | # [target.'cfg(any())'.dependencies] 59 | # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions 60 | # ``` 61 | # The optional = true is necessary in case that dependency isn't otherwise transitively required 62 | # by your library, and the target bit is so that this dependency edge never actually affects 63 | # Cargo build order. See also 64 | # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49. 65 | # This action is run on ubuntu with the stable toolchain, as it is not expected to fail 66 | runs-on: ubuntu-latest 67 | name: ubuntu / stable / minimal-versions 68 | steps: 69 | - uses: actions/checkout@v4 70 | with: 71 | submodules: true 72 | - name: Install stable 73 | uses: dtolnay/rust-toolchain@stable 74 | - name: Install nightly for -Zminimal-versions 75 | uses: dtolnay/rust-toolchain@nightly 76 | - name: rustup default stable 77 | run: rustup default stable 78 | - name: cargo update -Zminimal-versions 79 | run: cargo +nightly update -Zminimal-versions 80 | - name: cargo test 81 | run: cargo test --locked --all-features 82 | os-check: 83 | # run cargo test on mac and windows 84 | runs-on: ${{ matrix.os }} 85 | name: ${{ matrix.os }} / stable 86 | strategy: 87 | fail-fast: false 88 | matrix: 89 | os: [macos-latest, windows-latest] 90 | steps: 91 | # if your project needs OpenSSL, uncomment this to fix Windows builds. 92 | # it's commented out by default as the install command takes 5-10m. 93 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append 94 | # if: runner.os == 'Windows' 95 | # - run: vcpkg install openssl:x64-windows-static-md 96 | # if: runner.os == 'Windows' 97 | - uses: actions/checkout@v4 98 | with: 99 | submodules: true 100 | - name: Install stable 101 | uses: dtolnay/rust-toolchain@stable 102 | - name: cargo generate-lockfile 103 | if: hashFiles('Cargo.lock') == '' 104 | run: cargo generate-lockfile 105 | - name: cargo test 106 | run: cargo test --locked --all-features 107 | coverage: 108 | # use llvm-cov to build and collect coverage and outputs in a format that 109 | # is compatible with codecov.io 110 | # 111 | # note that codecov as of v4 requires that CODECOV_TOKEN from 112 | # 113 | # https://app.codecov.io/gh///settings 114 | # 115 | # is set in two places on your repo: 116 | # 117 | # - https://github.com/jonhoo/guardian/settings/secrets/actions 118 | # - https://github.com/jonhoo/guardian/settings/secrets/dependabot 119 | # 120 | # (the former is needed for codecov uploads to work with Dependabot PRs) 121 | # 122 | # PRs coming from forks of your repo will not have access to the token, but 123 | # for those, codecov allows uploading coverage reports without a token. 124 | # it's all a little weird and inconvenient. see 125 | # 126 | # https://github.com/codecov/feedback/issues/112 127 | # 128 | # for lots of more discussion 129 | runs-on: ubuntu-latest 130 | name: ubuntu / stable / coverage 131 | steps: 132 | - uses: actions/checkout@v4 133 | with: 134 | submodules: true 135 | - name: Install stable 136 | uses: dtolnay/rust-toolchain@stable 137 | with: 138 | components: llvm-tools-preview 139 | - name: cargo install cargo-llvm-cov 140 | uses: taiki-e/install-action@cargo-llvm-cov 141 | - name: cargo generate-lockfile 142 | if: hashFiles('Cargo.lock') == '' 143 | run: cargo generate-lockfile 144 | - name: cargo llvm-cov 145 | run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info 146 | - name: Record Rust version 147 | run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" 148 | - name: Upload to codecov.io 149 | uses: codecov/codecov-action@v5 150 | with: 151 | fail_ci_if_error: true 152 | token: ${{ secrets.CODECOV_TOKEN }} 153 | env_vars: OS,RUST 154 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "byteorder" 7 | version = "1.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "getrandom" 19 | version = "0.2.15" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 22 | dependencies = [ 23 | "cfg-if", 24 | "libc", 25 | "wasi", 26 | ] 27 | 28 | [[package]] 29 | name = "libc" 30 | version = "0.2.169" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 33 | 34 | [[package]] 35 | name = "ppv-lite86" 36 | version = "0.2.20" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 39 | dependencies = [ 40 | "zerocopy", 41 | ] 42 | 43 | [[package]] 44 | name = "proc-macro2" 45 | version = "1.0.92" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 48 | dependencies = [ 49 | "unicode-ident", 50 | ] 51 | 52 | [[package]] 53 | name = "quote" 54 | version = "1.0.38" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 57 | dependencies = [ 58 | "proc-macro2", 59 | ] 60 | 61 | [[package]] 62 | name = "rand" 63 | version = "0.8.5" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 66 | dependencies = [ 67 | "libc", 68 | "rand_chacha", 69 | "rand_core", 70 | ] 71 | 72 | [[package]] 73 | name = "rand_chacha" 74 | version = "0.3.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 77 | dependencies = [ 78 | "ppv-lite86", 79 | "rand_core", 80 | ] 81 | 82 | [[package]] 83 | name = "rand_core" 84 | version = "0.6.4" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 87 | dependencies = [ 88 | "getrandom", 89 | ] 90 | 91 | [[package]] 92 | name = "syn" 93 | version = "2.0.93" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" 96 | dependencies = [ 97 | "proc-macro2", 98 | "quote", 99 | "unicode-ident", 100 | ] 101 | 102 | [[package]] 103 | name = "unicode-ident" 104 | version = "1.0.14" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 107 | 108 | [[package]] 109 | name = "wasi" 110 | version = "0.11.0+wasi-snapshot-preview1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 113 | 114 | [[package]] 115 | name = "zerocopy" 116 | version = "0.7.35" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 119 | dependencies = [ 120 | "byteorder", 121 | "zerocopy-derive", 122 | ] 123 | 124 | [[package]] 125 | name = "zerocopy-derive" 126 | version = "0.7.35" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 129 | dependencies = [ 130 | "proc-macro2", 131 | "quote", 132 | "syn", 133 | ] 134 | 135 | [[package]] 136 | name = "zipf" 137 | version = "7.0.2" 138 | dependencies = [ 139 | "rand", 140 | ] 141 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zipf" 3 | version = "7.0.2" 4 | readme = "./README.md" 5 | description = "A fast generator of discrete, bounded Zipf-distributed random numbers" 6 | authors = ["Jon Gjengset "] 7 | edition = "2018" 8 | 9 | license = "Apache-2.0" 10 | keywords = ["random", "distribution", "zipf"] 11 | categories = ["algorithms"] 12 | 13 | repository = "https://github.com/jonhoo/rust-zipf" 14 | documentation = "https://docs.rs/zipf" 15 | 16 | [dependencies] 17 | rand = { version = "0.8.0", default-features = false } 18 | 19 | [dev-dependencies] 20 | rand = "0.8.0" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!CAUTION] 2 | > This crate is deprecated. Prefer 3 | > [`rand_distr::Zipf`](https://docs.rs/rand_distr/latest/rand_distr/struct.Zipf.html). 4 | 5 | --- 6 | 7 | # rust-zipf 8 | 9 | [![Crates.io](https://img.shields.io/crates/v/zipf.svg)](https://crates.io/crates/zipf) 10 | [![Documentation](https://docs.rs/zipf/badge.svg)](https://docs.rs/zipf/) 11 | [![codecov](https://codecov.io/gh/jonhoo/rust-zipf/graph/badge.svg?token=IuYZGwTWik)](https://codecov.io/gh/jonhoo/rust-zipf) 12 | [![Dependency status](https://deps.rs/repo/github/jonhoo/rust-zipf/status.svg)](https://deps.rs/repo/github/jonhoo/rust-zipf) 13 | 14 | Rust implementation of a fast, discrete, bounded, 15 | [Zipf-distributed](https://en.wikipedia.org/wiki/Zipf's_law) random 16 | number generator. Compared to the implementation provided by 17 | [`randomkit`](https://github.com/stygstra/rust-randomkit) (which binds 18 | to NumPy's fork of RandomKit), this crate is approximately twice as 19 | fast: 20 | 21 | ```console 22 | $ cargo +nightly bench 23 | test tests::bench_randomkit ... bench: 339 ns/iter (+/- 18) 24 | test tests::bench_us ... bench: 68 ns/iter (+/- 1) 25 | test tests::bench_threadrng ... bench: 11 ns/iter (+/- 0) 26 | ``` 27 | 28 | It is also both driven by, and provides, a [Rust random number 29 | generator](https://doc.rust-lang.org/rand/rand/trait.Rng.html). 30 | 31 | This implementation is effectively a direct port of Apache Common's 32 | [RejectionInversionZipfSampler](https://github.com/apache/commons-rng/blob/6a1b0c16090912e8fc5de2c1fb5bd8490ac14699/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java), 33 | written in Java. It is based on the method described by Wolfgang Hörmann and Gerhard Derflinger 34 | in [*Rejection-inversion to generate variates from monotone discrete 35 | distributions*](https://dl.acm.org/citation.cfm?id=235029) from *ACM Transactions on Modeling 36 | and Computer Simulation (TOMACS) 6.3 (1996)*. 37 | -------------------------------------------------------------------------------- /benches/zipf_bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate rand; 3 | extern crate test; 4 | extern crate zipf; 5 | 6 | use test::Bencher; 7 | 8 | #[bench] 9 | fn bench_us(b: &mut Bencher) { 10 | use rand; 11 | use rand::distributions::Distribution; 12 | use zipf::ZipfDistribution; 13 | let mut rng = rand::thread_rng(); 14 | let us = ZipfDistribution::new(1000000, 1.07).unwrap(); 15 | b.iter(|| us.sample(&mut rng)); 16 | } 17 | 18 | #[bench] 19 | fn bench_threadrng(b: &mut Bencher) { 20 | use rand::{self, Rng}; 21 | let mut rng = rand::thread_rng(); 22 | b.iter(|| rng.gen::()); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A fast generator of discrete, bounded 2 | //! [Zipf-distributed](https://en.wikipedia.org/wiki/Zipf's_law) random numbers. 3 | //! 4 | //! For a random variable `X` whose values are distributed according to this distribution, the 5 | //! probability mass function is given by 6 | //! 7 | //! ```ignore 8 | //! P(X = k) = H(N,s) * 1 / k^s for k = 1,2,...,N 9 | //! ``` 10 | //! 11 | //! `H(N,s)` is the normalizing constant which corresponds to the generalized harmonic number 12 | //! of order `N` of `s`. 13 | //! 14 | //! # Example 15 | //! 16 | //! ``` 17 | //! use rand::distributions::Distribution; 18 | //! 19 | //! let mut rng = rand::thread_rng(); 20 | //! let mut zipf = zipf::ZipfDistribution::new(1000, 1.03).unwrap(); 21 | //! let sample = zipf.sample(&mut rng); 22 | //! ``` 23 | //! 24 | //! This implementation is effectively a direct port of Apache Common's 25 | //! [RejectionInversionZipfSampler][ref], written in Java. It is based on the method described by 26 | //! Wolfgang Hörmann and Gerhard Derflinger in [*Rejection-inversion to generate variates from 27 | //! monotone discrete distributions*](https://dl.acm.org/citation.cfm?id=235029) from *ACM 28 | //! Transactions on Modeling and Computer Simulation (TOMACS) 6.3 (1996)*. 29 | //! 30 | //! [ref]: https://github.com/apache/commons-rng/blob/6a1b0c16090912e8fc5de2c1fb5bd8490ac14699/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java 31 | 32 | #![warn(rust_2018_idioms)] 33 | 34 | use rand::Rng; 35 | 36 | /// Random number generator that generates Zipf-distributed random numbers using rejection 37 | /// inversion. 38 | #[derive(Clone, Copy)] 39 | #[deprecated = "prefer rand_distr::Zipf"] 40 | pub struct ZipfDistribution { 41 | /// Number of elements 42 | num_elements: f64, 43 | /// Exponent parameter of the distribution 44 | exponent: f64, 45 | /// `hIntegral(1.5) - 1}` 46 | h_integral_x1: f64, 47 | /// `hIntegral(num_elements + 0.5)}` 48 | h_integral_num_elements: f64, 49 | /// `2 - hIntegralInverse(hIntegral(2.5) - h(2)}` 50 | s: f64, 51 | } 52 | 53 | impl ZipfDistribution { 54 | /// Creates a new [Zipf-distributed](https://en.wikipedia.org/wiki/Zipf's_law) 55 | /// random number generator. 56 | /// 57 | /// Note that both the number of elements and the exponent must be greater than 0. 58 | pub fn new(num_elements: usize, exponent: f64) -> Result { 59 | if num_elements == 0 { 60 | return Err(()); 61 | } 62 | if exponent <= 0f64 { 63 | return Err(()); 64 | } 65 | 66 | let z = ZipfDistribution { 67 | num_elements: num_elements as f64, 68 | exponent, 69 | h_integral_x1: ZipfDistribution::h_integral(1.5, exponent) - 1f64, 70 | h_integral_num_elements: ZipfDistribution::h_integral( 71 | num_elements as f64 + 0.5, 72 | exponent, 73 | ), 74 | s: 2f64 75 | - ZipfDistribution::h_integral_inv( 76 | ZipfDistribution::h_integral(2.5, exponent) 77 | - ZipfDistribution::h(2f64, exponent), 78 | exponent, 79 | ), 80 | }; 81 | 82 | // populate cache 83 | 84 | Ok(z) 85 | } 86 | } 87 | 88 | impl ZipfDistribution { 89 | fn next(&self, rng: &mut R) -> usize { 90 | // The paper describes an algorithm for exponents larger than 1 (Algorithm ZRI). 91 | // 92 | // The original method uses 93 | // H(x) = (v + x)^(1 - q) / (1 - q) 94 | // as the integral of the hat function. 95 | // 96 | // This function is undefined for q = 1, which is the reason for the limitation of the 97 | // exponent. 98 | // 99 | // If instead the integral function 100 | // H(x) = ((v + x)^(1 - q) - 1) / (1 - q) 101 | // is used, for which a meaningful limit exists for q = 1, the method works for all 102 | // positive exponents. 103 | // 104 | // The following implementation uses v = 0 and generates integral number in the range [1, 105 | // num_elements]. This is different to the original method where v is defined to 106 | // be positive and numbers are taken from [0, i_max]. This explains why the implementation 107 | // looks slightly different. 108 | 109 | let hnum = self.h_integral_num_elements; 110 | 111 | loop { 112 | use std::cmp; 113 | let u: f64 = hnum + rng.gen::() * (self.h_integral_x1 - hnum); 114 | // u is uniformly distributed in (h_integral_x1, h_integral_num_elements] 115 | 116 | let x: f64 = ZipfDistribution::h_integral_inv(u, self.exponent); 117 | 118 | // Limit k to the range [1, num_elements] if it would be outside 119 | // due to numerical inaccuracies. 120 | let k64 = x.max(1.0).min(self.num_elements); 121 | // float -> integer rounds towards zero, so we add 0.5 122 | // to prevent bias towards k == 1 123 | let k = cmp::max(1, (k64 + 0.5) as usize); 124 | 125 | // Here, the distribution of k is given by: 126 | // 127 | // P(k = 1) = C * (hIntegral(1.5) - h_integral_x1) = C 128 | // P(k = m) = C * (hIntegral(m + 1/2) - hIntegral(m - 1/2)) for m >= 2 129 | // 130 | // where C = 1 / (h_integral_num_elements - h_integral_x1) 131 | if k64 - x <= self.s 132 | || u >= ZipfDistribution::h_integral(k64 + 0.5, self.exponent) 133 | - ZipfDistribution::h(k64, self.exponent) 134 | { 135 | // Case k = 1: 136 | // 137 | // The right inequality is always true, because replacing k by 1 gives 138 | // u >= hIntegral(1.5) - h(1) = h_integral_x1 and u is taken from 139 | // (h_integral_x1, h_integral_num_elements]. 140 | // 141 | // Therefore, the acceptance rate for k = 1 is P(accepted | k = 1) = 1 142 | // and the probability that 1 is returned as random value is 143 | // P(k = 1 and accepted) = P(accepted | k = 1) * P(k = 1) = C = C / 1^exponent 144 | // 145 | // Case k >= 2: 146 | // 147 | // The left inequality (k - x <= s) is just a short cut 148 | // to avoid the more expensive evaluation of the right inequality 149 | // (u >= hIntegral(k + 0.5) - h(k)) in many cases. 150 | // 151 | // If the left inequality is true, the right inequality is also true: 152 | // Theorem 2 in the paper is valid for all positive exponents, because 153 | // the requirements h'(x) = -exponent/x^(exponent + 1) < 0 and 154 | // (-1/hInverse'(x))'' = (1+1/exponent) * x^(1/exponent-1) >= 0 155 | // are both fulfilled. 156 | // Therefore, f(x) = x - hIntegralInverse(hIntegral(x + 0.5) - h(x)) 157 | // is a non-decreasing function. If k - x <= s holds, 158 | // k - x <= s + f(k) - f(2) is obviously also true which is equivalent to 159 | // -x <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)), 160 | // -hIntegralInverse(u) <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)), 161 | // and finally u >= hIntegral(k + 0.5) - h(k). 162 | // 163 | // Hence, the right inequality determines the acceptance rate: 164 | // P(accepted | k = m) = h(m) / (hIntegrated(m+1/2) - hIntegrated(m-1/2)) 165 | // The probability that m is returned is given by 166 | // P(k = m and accepted) = P(accepted | k = m) * P(k = m) 167 | // = C * h(m) = C / m^exponent. 168 | // 169 | // In both cases the probabilities are proportional to the probability mass 170 | // function of the Zipf distribution. 171 | 172 | return k; 173 | } 174 | } 175 | } 176 | } 177 | 178 | impl rand::distributions::Distribution for ZipfDistribution { 179 | fn sample(&self, rng: &mut R) -> usize { 180 | self.next(rng) 181 | } 182 | } 183 | 184 | use std::fmt; 185 | impl fmt::Debug for ZipfDistribution { 186 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 187 | f.debug_struct("ZipfDistribution") 188 | .field("e", &self.exponent) 189 | .field("n", &self.num_elements) 190 | .finish() 191 | } 192 | } 193 | 194 | impl ZipfDistribution { 195 | /// Computes `H(x)`, defined as 196 | /// 197 | /// - `(x^(1 - exponent) - 1) / (1 - exponent)`, if `exponent != 1` 198 | /// - `log(x)`, if `exponent == 1` 199 | /// 200 | /// `H(x)` is an integral function of `h(x)`, the derivative of `H(x)` is `h(x)`. 201 | fn h_integral(x: f64, exponent: f64) -> f64 { 202 | let log_x = x.ln(); 203 | helper2((1f64 - exponent) * log_x) * log_x 204 | } 205 | 206 | /// Computes `h(x) = 1 / x^exponent` 207 | fn h(x: f64, exponent: f64) -> f64 { 208 | (-exponent * x.ln()).exp() 209 | } 210 | 211 | /// The inverse function of `H(x)`. 212 | /// Returns the `y` for which `H(y) = x`. 213 | fn h_integral_inv(x: f64, exponent: f64) -> f64 { 214 | let mut t: f64 = x * (1f64 - exponent); 215 | if t < -1f64 { 216 | // Limit value to the range [-1, +inf). 217 | // t could be smaller than -1 in some rare cases due to numerical errors. 218 | t = -1f64; 219 | } 220 | (helper1(t) * x).exp() 221 | } 222 | } 223 | 224 | /// Helper function that calculates `log(1 + x) / x`. 225 | /// A Taylor series expansion is used, if x is close to 0. 226 | fn helper1(x: f64) -> f64 { 227 | if x.abs() > 1e-8 { 228 | x.ln_1p() / x 229 | } else { 230 | 1f64 - x * (0.5 - x * (1.0 / 3.0 - 0.25 * x)) 231 | } 232 | } 233 | 234 | /// Helper function to calculate `(exp(x) - 1) / x`. 235 | /// A Taylor series expansion is used, if x is close to 0. 236 | fn helper2(x: f64) -> f64 { 237 | if x.abs() > 1e-8 { 238 | x.exp_m1() / x 239 | } else { 240 | 1f64 + x * 0.5 * (1f64 + x * 1.0 / 3.0 * (1f64 + 0.25 * x)) 241 | } 242 | } 243 | 244 | #[cfg(test)] 245 | mod test { 246 | use super::ZipfDistribution; 247 | use rand::distributions::Distribution; 248 | 249 | #[inline] 250 | fn test(alpha: f64) { 251 | const N: usize = 100; 252 | 253 | // as the alpha increases, we need more samples, since the frequency in the tail grows so low 254 | let samples = (2.0f64.powf(alpha) * 5000000.0) as usize; 255 | 256 | let mut rng = rand::thread_rng(); 257 | let zipf = ZipfDistribution::new(N, alpha).unwrap(); 258 | 259 | let harmonic: f64 = (1..=N).map(|n| 1.0 / (n as f64).powf(alpha)).sum(); 260 | 261 | // sample a bunch 262 | let mut buckets = vec![0; N]; 263 | for _ in 0..samples { 264 | let sample = zipf.sample(&mut rng); 265 | buckets[sample - 1] += 1; 266 | } 267 | 268 | // for each bucket, see that frequency roughly matches what we expect 269 | // note that the ratios here are ratios _of fractions_, so 0.5 does not mean we're off by 270 | // 50%, it means we're off by 50% _of the frequency_. in other words, if the frequency was 271 | // supposed to be 0.10, and it was actually 0.05, that's a 50% difference. 272 | for i in 0..N { 273 | let freq = buckets[i] as f64 / samples as f64; 274 | let expected = (1.0 / ((i + 1) as f64).powf(alpha)) / harmonic; 275 | // println!("{} {} {}", i + 1, freq, expected); 276 | 277 | let off_by = (expected - freq).abs(); 278 | assert!(off_by < 0.1); // never be off by more than 10% in absolute terms 279 | 280 | let good = off_by < expected; 281 | if !good { 282 | panic!("got {}, expected {} for k = {}", freq, expected, i + 1); 283 | } 284 | } 285 | } 286 | 287 | #[test] 288 | fn one() { 289 | test(1.00); 290 | } 291 | 292 | #[test] 293 | fn two() { 294 | test(2.00); 295 | } 296 | 297 | #[test] 298 | fn three() { 299 | test(3.00); 300 | } 301 | 302 | #[test] 303 | fn float() { 304 | test(1.08); 305 | } 306 | 307 | #[test] 308 | fn debug() { 309 | eprintln!("{:?}", ZipfDistribution::new(100, 1.0).unwrap()); 310 | } 311 | 312 | #[test] 313 | fn errs() { 314 | ZipfDistribution::new(0, 1.0).unwrap_err(); 315 | ZipfDistribution::new(100, 0.0).unwrap_err(); 316 | ZipfDistribution::new(100, -1.0).unwrap_err(); 317 | } 318 | } 319 | --------------------------------------------------------------------------------