Repository: ajkeller34/Unitful.jl Branch: master Commit: 4098e0526252 Files: 52 Total size: 490.5 KB Directory structure: gitextract_2l21c_cz/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── CompatHelper.yml │ ├── TagBot.yml │ ├── ci.yml │ └── documentation.yaml ├── .gitignore ├── LICENSE.md ├── NEWS.md ├── Project.toml ├── README.md ├── docs/ │ ├── Project.toml │ ├── generate_latex_images.jl │ ├── make.jl │ └── src/ │ ├── LICENSE.md │ ├── conversion.md │ ├── dates.md │ ├── defaultunits.md │ ├── display.md │ ├── extending.md │ ├── highlights.md │ ├── index.md │ ├── latexify.md │ ├── logarithm.md │ ├── manipulations.md │ ├── newunits.md │ ├── temperature.md │ ├── trouble.md │ └── types.md ├── ext/ │ ├── ConstructionBaseUnitfulExt.jl │ ├── ForwardDiffExt.jl │ ├── InverseFunctionsUnitfulExt.jl │ ├── LatexifyExt.jl │ ├── NaNMathExt.jl │ └── PrintfExt.jl ├── src/ │ ├── Unitful.jl │ ├── complex.jl │ ├── conversion.jl │ ├── dates.jl │ ├── dimensions.jl │ ├── display.jl │ ├── fastmath.jl │ ├── logarithm.jl │ ├── pkgdefaults.jl │ ├── promotion.jl │ ├── quantities.jl │ ├── range.jl │ ├── types.jl │ ├── units.jl │ ├── user.jl │ └── utils.jl └── test/ ├── dates.jl └── runtests.jl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" # Location of package manifests schedule: interval: "weekly" ================================================ FILE: .github/workflows/CompatHelper.yml ================================================ name: CompatHelper on: schedule: - cron: 8 14 * * * jobs: build: runs-on: ubuntu-latest steps: - name: Pkg.add("CompatHelper") run: julia -e 'using Pkg; Pkg.add("CompatHelper")' - name: CompatHelper.main() run: julia -e 'using CompatHelper; CompatHelper.main()' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} ================================================ FILE: .github/workflows/TagBot.yml ================================================ name: TagBot on: issue_comment: types: - created workflow_dispatch: jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} ssh: ${{ secrets.DOCUMENTER_KEY }} ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: "master" tags: ["*"] paths: - '.github/workflows/ci.yml' - 'src/**' - 'test/**' - 'Project.toml' pull_request: paths: - '.github/workflows/ci.yml' - 'src/**' - 'test/**' - 'Project.toml' release: concurrency: # Skip intermediate builds: always. # Cancel intermediate builds: only if it is a pull request build. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: test: name: Julia ${{ matrix.julia-version }} - ${{ matrix.os }} - ${{ matrix.julia-arch }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: julia-version: - "min" - "lts" - "1" - "pre" os: - ubuntu-latest - macos-latest - windows-latest julia-arch: - "default" # Until minimum supported version is v1.6, we can't test on Apple # Silicon macOS for that version and we need to use Julia v1.8 instead exclude: - julia-version: "min" os: macos-latest include: - julia-version: "1.8" os: macos-latest julia-arch: "default" steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v3 with: version: ${{ matrix.julia-version }} arch: ${{ matrix.julia-arch }} - uses: julia-actions/cache@v3 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v6 with: files: lcov.info fail_ci_if_error: false - uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: lcov.info flag-name: run-${{ join(matrix.*, '-') }} parallel: true ================================================ FILE: .github/workflows/documentation.yaml ================================================ name: Documentation on: push: branches: "master" tags: ["*"] paths: - '.github/workflows/documentation.yaml' - 'src/**' - 'docs/**' - 'Project.toml' pull_request: paths: - '.github/workflows/documentation.yaml' - 'src/**' - 'docs/**' - 'Project.toml' release: concurrency: # Skip intermediate builds: always. # Cancel intermediate builds: only if it is a pull request build. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: Documentation: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v3 with: version: 1 - uses: julia-actions/cache@v3 - uses: julia-actions/julia-docdeploy@v1 env: DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} - uses: actions/upload-artifact@v7 with: name: documentation-build path: docs/build retention-days: 90 ================================================ FILE: .gitignore ================================================ # Files generated by invoking Julia with --code-coverage *.jl.cov *.jl.*.cov # Files generated by invoking Julia with --track-allocation *.jl.mem # Build artifacts for creating documentation generated by the Documenter package docs/build/ docs/site/ # File generated by Pkg, the package manager, based on a corresponding Project.toml # It records a fixed state of all packages used by the project. As such, it should not be # committed for packages, but should be committed for applications that require a static # environment. Manifest.toml *~ ================================================ FILE: LICENSE.md ================================================ The Unitful.jl package is licensed under the MIT "Expat" License: > Copyright (c) 2016: California Institute of Technology and > [other contributors](https://github.com/JuliaPhysics/Unitful.jl/graphs/contributors). > > Permission is hereby granted, free of charge, to any person obtaining > a copy of this software and associated documentation files (the > "Software"), to deal in the Software without restriction, including > without limitation the rights to use, copy, modify, merge, publish, > distribute, sublicense, and/or sell copies of the Software, and to > permit persons to whom the Software is furnished to do so, subject to > the following conditions: > > The above copyright notice and this permission notice shall be > included in all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Portions borrow from [EngUnits.jl](https://github.com/dhoegh/EngUnits.jl/blob/master/LICENSE.md), which is (c) 2016: Daniel Høegh. Unitful.jl's fastmath implementation and tests draw heavily on those found in [Julia](https://github.com/JuliaLang/julia/blob/master/LICENSE.md). Neither the name of the California Institute of Technology (“Caltech”) nor the names of its contributors (and/or sponsors) may be used to endorse or promote products derived from this software without specific prior written permission. Andrew Keller (original package author, assigning his copyright to California Institute of Technology) acknowledges the support of an IQIM Postdoctoral Scholarship (Institute for Quantum Information and Matter, an NSF Physics Frontiers Center, NSF Grant PHY-1125565). ================================================ FILE: NEWS.md ================================================ # Unitful.jl changelog ## v1.28.0 (2026-01-29) * ![Feature:](https://img.shields.io/badge/-feature-green) Dimensionless quantities now support `iseven` and `isodd` ([#829](https://github.com/JuliaPhysics/Unitful.jl/pull/829)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Quantities that have equal values and equal units now have the same hash ([#833](https://github.com/JuliaPhysics/Unitful.jl/pull/833)). For now, quantities that are equal (`isequal`) but have different units still have different hashes, see [#379](https://github.com/JuliaPhysics/Unitful.jl/issues/379). ## v1.27.0 (2025-12-08) * ![Feature:](https://img.shields.io/badge/-feature-green) `NaNMath.pow` and `NaNMath.sqrt` from [NaNMath.jl](https://github.com/JuliaMath/NaNMath.jl) are supported via a package extension ([#824](https://github.com/JuliaPhysics/Unitful.jl/pull/824)). ## v1.26.0 (2025-12-05) * ![Feature:](https://img.shields.io/badge/-feature-green) The aliases `degC` and `degF` for `°C` and `°F` are added ([#826](https://github.com/JuliaPhysics/Unitful.jl/pull/826)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Correct LaTeX printing of affine units ([#825](https://github.com/JuliaPhysics/Unitful.jl/pull/825)). * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) Fix a world-age warning on Julia 1.12 ([#819](https://github.com/JuliaPhysics/Unitful.jl/issues/819)). ## v1.25.1 (2025-10-18) * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) Fix a parsing error on Julia 1.13 ([#817](https://github.com/JuliaPhysics/Unitful.jl/issues/817)). ## v1.25.0 (2025-09-16) * ![Feature:](https://img.shields.io/badge/-feature-green) Quantities and units can now be converted to a LaTeX representation using [`Latexify.jl`](https://github.com/korsbo/Latexify.jl). This is provided via a package extension and replaces the [UnitfulLatexify.jl](https://github.com/gustaphe/UnitfulLatexify.jl) package ([#795](https://github.com/JuliaPhysics/Unitful.jl/pull/795)). * This package now requires Julia ≥ 1.6. ## v1.24.0 (2025-07-31) * ![Feature:](https://img.shields.io/badge/-feature-green) The alias `deg` for `°` is added ([#764](https://github.com/JuliaPhysics/Unitful.jl/pull/764)). ## v1.23.1 (2025-06-10) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Fix a world-age issue with the new mechanism to preserve the floating-point precision on unit conversion ([#790](https://github.com/JuliaPhysics/Unitful.jl/issues/790)). ## v1.23.0 (2025-06-08) * ![Feature:](https://img.shields.io/badge/-feature-green) On Julia ≥ 1.9, dimensionless quantities can now be converted to `ForwardDiff.Dual`. This is important for compatibility with the SciML ecosystem and is provided via a package extension ([#765](https://github.com/JuliaPhysics/Unitful.jl/pull/765)). * ![Feature:](https://img.shields.io/badge/-feature-green) On Julia ≥ 1.9, `@printf` and `@sprintf` can now be used with Unitful quantities. The specified format is applied to the numeric part of the quantity and the unit is appended to that. This is provided via a package extension ([#772](https://github.com/JuliaPhysics/Unitful.jl/pull/772)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Certain unit conversions involving units that are defined using non-integer exponents no longer error ([#783](https://github.com/JuliaPhysics/Unitful.jl/pull/783)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) `uconvert` now preserves the floating-point precision of quantities in a non-breaking way ([#782](https://github.com/JuliaPhysics/Unitful.jl/pull/782)). ## v1.22.1 (2025-05-13) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) The behaviour to preserve the floating-point precision of quantities ([#754](https://github.com/JuliaPhysics/Unitful.jl/pull/754), added in v1.22.0) is reverted because it is not compatible with [IntervalArithmetic.jl](https://github.com/JuliaIntervals/IntervalArithmetic.jl) (see [#758](https://github.com/JuliaPhysics/Unitful.jl/issues/758)). The feature will be added in a non-breaking way in a future release. ## v1.22.0 (2025-01-02) * ![Feature:](https://img.shields.io/badge/-feature-green) `Base.big` can now be used with quantities ([#755](https://github.com/JuliaPhysics/Unitful.jl/pull/755)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Using `Base.convert` to convert to a unitless quantity no longer errors ([#724](https://github.com/JuliaPhysics/Unitful.jl/pull/724)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Using `Base.convert` to convert to a mixed logarithmic quantity no longer returns wrong results. In cases that returned a result even though it was unknown whether the quantity was a power or root-power quantity, an error is thrown instead ([#724](https://github.com/JuliaPhysics/Unitful.jl/pull/724)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) `uconvert` now preserves the floating-point precision of quantities ([#754](https://github.com/JuliaPhysics/Unitful.jl/pull/754)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) When printing arrays, quantities are now aligned at the decimal point just like unitless numbers ([#752](https://github.com/JuliaPhysics/Unitful.jl/pull/752)). ## v1.21.1 (2024-11-29) * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) `Base.cis` now returns more accurate results for arguments in degrees. This unfortunately is slower. If you prioritize speed over precision you can convert to `NoUnits` before calling `cis` or use `@fastmath` ([#745](https://github.com/JuliaPhysics/Unitful.jl/pull/745)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Trigonometric functions now return more accurate results for arguments in degrees when using `BigFloat` precision and `@fastmath` ([#750](https://github.com/JuliaPhysics/Unitful.jl/pull/750)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Performance of trigonometric functions with `@fastmath` is improved for some argument types ([#750](https://github.com/JuliaPhysics/Unitful.jl/pull/750)). * The documentation now contains a list of all units and constants defined in this package ([#729](https://github.com/JuliaPhysics/Unitful.jl/pull/729)). ## v1.21.0 (2024-07-19) * ![Feature:](https://img.shields.io/badge/-feature-green) Arithmetic between `Dates.TimeType` and `Unitful.Time` is added, e.g., `Dates.now() + 1u"hr"` now works ([#731](https://github.com/JuliaPhysics/Unitful.jl/pull/731)). ## v1.20.0 (2024-05-17) * ![Feature:](https://img.shields.io/badge/-feature-green) `isapprox` with arrays of quantities now supports the `nans` keyword argument ([#719](https://github.com/JuliaPhysics/Unitful.jl/pull/719)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `isapprox` with arrays of quantities now calculates the default `rtol` correctly, i.e., `rtol` defaults to zero if a positive `atol` is specified, like in the scalar or unitless case ([#719](https://github.com/JuliaPhysics/Unitful.jl/pull/719)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `isapprox` with arrays of quantities now checks whether `norm(x-y) ≤ max(atol, rtol*max(norm(x), norm(y)))`, like in the scalar or unitless case, instead of `norm(x-y) ≤ atol + rtol*max(norm(x), norm(y))` ([#719](https://github.com/JuliaPhysics/Unitful.jl/pull/719)). ## v1.19.1 (2024-05-13) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Broadcasting `upreferred`, `ustrip`, or a unit (when used as a conversion function) over ranges now works correctly again ([#711](https://github.com/JuliaPhysics/Unitful.jl/pull/711), [#715](https://github.com/JuliaPhysics/Unitful.jl/pull/715)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Broadcasting `upreferred`, `ustrip`, or a unit (when used as a conversion function) over a `StepRange` now returns a `StepRangeLen` when the conversion results in floating-point numbers ([#715](https://github.com/JuliaPhysics/Unitful.jl/pull/715)). ## v1.19.0 (2023-11-29) * ![Feature:](https://img.shields.io/badge/-feature-green) The dimensionless units parts per cent mille (`pcm`, 10^-5), parts per million (`ppm`, 10^-6), parts per billion (`ppb`, 10^-9), parts per trillion (`ppt`, 10^-12), and parts per quadrillion (`ppq`, 10^-15) are added ([#699](https://github.com/JuliaPhysics/Unitful.jl/pull/699)). ## v1.18.0 (2023-11-13) * ![Feature:](https://img.shields.io/badge/-feature-green) The two-argument versions of `nextfloat` and `prefloat` now allow quantities as their first argument ([#692](https://github.com/JuliaPhysics/Unitful.jl/pull/692)). ## v1.17.0 (2023-08-24) * ![Feature:](https://img.shields.io/badge/-feature-green) The standard atmosphere (`atm`) now accepts SI prefixes, e.g., `μatm` is defined ([#664](https://github.com/JuliaPhysics/Unitful.jl/pull/664)). ## v1.16.3 (2023-08-14) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Calling `min` and `max` with quantities of different units can no longer return wrong results due to floating-point overflow in the unit conversion ([#675](https://github.com/JuliaPhysics/Unitful.jl/pull/675)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `min` and `max` now handle quantities with `NaN` values correctly ([#675](https://github.com/JuliaPhysics/Unitful.jl/pull/675)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `Base.hastypemax` is now correctly implemented for quantity types ([#674](https://github.com/JuliaPhysics/Unitful.jl/pull/674)). ## v1.16.2 (2023-08-05) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) The conversion factors between units can no longer be wrongly calculated as `NaN`, `Inf`, or `0` (which could happen, e.g., in the case of large exponents). The conversion factor is now calculated correctly in more cases, and an error is thrown if it cannot be calculated due to floating-point over- or underflow ([#648](https://github.com/JuliaPhysics/Unitful.jl/pull/648)). ## v1.16.1 (2023-08-02) * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Replaced occurrences of single-argument `@doc` for duplicating docstrings, which could lead to errors when creating a Docker image with Julia 1.9 and Unitful ([#671](https://github.com/JuliaPhysics/Unitful.jl/pull/671)). ## v1.16.0 (2023-08-01) * ![Feature:](https://img.shields.io/badge/-feature-green) The derived dimension `MolarMass` (`𝐌/𝐍`) is added ([#663](https://github.com/JuliaPhysics/Unitful.jl/pull/663)). * ![Feature:](https://img.shields.io/badge/-feature-green) Dimensionless quantities now support the `tanpi` function added in Julia 1.10 ([#620](https://github.com/JuliaPhysics/Unitful.jl/pull/620)). ## v1.15.0 (2023-07-05) * ![Feature:](https://img.shields.io/badge/-feature-green) Support for [InverseFunctions.jl](https://github.com/JuliaMath/InverseFunctions.jl) is extended to all supported Julia versions. On Julia < 1.9, InverseFunctions.jl is added as a regular dependency ([#652](https://github.com/JuliaPhysics/Unitful.jl/pull/652)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) On Julia ≥ 1.9, [ConstructionBase.jl](https://github.com/JuliaObjects/ConstructionBase.jl) is now a weak dependency. On older versions, it is still a regular dependency. ([#658](https://github.com/JuliaPhysics/Unitful.jl/pull/658)). ## v1.14.0 (2023-05-11) * ![Feature:](https://img.shields.io/badge/-feature-green) On Julia ≥ 1.9, [InverseFunctions.jl](https://github.com/JuliaMath/InverseFunctions.jl) can be used to get the inverse function of `Base.Fix1(ustrip, u::Units)` ([#622](https://github.com/JuliaPhysics/Unitful.jl/pull/622)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `<=` now works correctly for `AbstractQuantity{T}` when `T` is a type for which `<=(x::T,y::T)` is different than `x < y || x == y` ([#646](https://github.com/JuliaPhysics/Unitful.jl/pull/646)). ## v1.13.1 (2023-04-15) * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) Adapt test suite for Julia 1.9 ([#643](https://github.com/JuliaPhysics/Unitful.jl/pull/643)). ## v1.13.0 (2023-04-11) * ![Feature:](https://img.shields.io/badge/-feature-green) `Base.sleep` now accepts quantities of time as argument ([#628](https://github.com/JuliaPhysics/Unitful.jl/pull/628)). * ![Feature:](https://img.shields.io/badge/-feature-green) `Base.copysign` and `Base.flipsign` can now be called with a plain number as first argument and a quantity as second argument ([#612](https://github.com/JuliaPhysics/Unitful.jl/pull/612)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) All known method ambiguities of the package are resolved ([#606](https://github.com/JuliaPhysics/Unitful.jl/pull/606), [#626](https://github.com/JuliaPhysics/Unitful.jl/pull/626)). * The package now has a logo. It was created by Leandro Martínez and shows the International Prototype of the Kilogram ([#567](https://github.com/JuliaPhysics/Unitful.jl/pull/567), [#634](https://github.com/JuliaPhysics/Unitful.jl/pull/634)). ## v1.12.4 (2023-02-27) * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) `@fastmath` with quantities now uses functions from `Base.FastMath` instead of intrinsic functions, because the latter may be removed at any time ([#617](https://github.com/JuliaPhysics/Unitful.jl/pull/617)). ## v1.12.3 (2023-02-10) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Multiplication is no longer assumed to be commutative, which is wrong for, e.g., quaternions ([#608](https://github.com/JuliaPhysics/Unitful.jl/pull/608)). * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) Adapt the documentation on extending Unitful for Julia ≥ 1.9 ([#600](https://github.com/JuliaPhysics/Unitful.jl/pull/600)). ## v1.12.2 (2022-11-30) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Broadcasting `upreferred` over floating-point ranges now works again ([#577](https://github.com/JuliaPhysics/Unitful.jl/pull/577)). ## v1.12.1 (2022-11-18) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Fixed `istriu`/`istril` for affine quantities ([#572](https://github.com/JuliaPhysics/Unitful.jl/pull/572)). ## v1.12.0 (2022-09-17) * ![Feature:](https://img.shields.io/badge/-feature-green) Dimensionless quantities now support `cispi`, `sincospi`, and `modf` ([#533](https://github.com/JuliaPhysics/Unitful.jl/pull/533), [#539](https://github.com/JuliaPhysics/Unitful.jl/pull/539)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Ranges of affine quantities are now printed correctly ([#551](https://github.com/JuliaPhysics/Unitful.jl/pull/551)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) The non-existent functions `convertr` and `convertrp` are no longer exported ([#530](https://github.com/JuliaPhysics/Unitful.jl/pull/530)). ## v1.11.0 (2022-02-10) * ![Feature:](https://img.shields.io/badge/-feature-green) `Base.zero` now works on heterogeneous arrays of quantities, e.g., `zero([1m, 1s]) == [0m, 0s]` ([#533](https://github.com/JuliaPhysics/Unitful.jl/pull/533), [#516](https://github.com/JuliaPhysics/Unitful.jl/pull/516)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `StepRangeLen`s of complex-valued quantities are now printed correctly ([#513](https://github.com/JuliaPhysics/Unitful.jl/pull/513)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Method ambiguities of `Base._range` are resolved ([#514](https://github.com/JuliaPhysics/Unitful.jl/pull/514)). * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) Updated `range` implementation for Julia ≥ 1.8 ([#514](https://github.com/JuliaPhysics/Unitful.jl/pull/514)). ## v1.10.1 (2022-01-03) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Fixed `isapprox` for arrays of complex-valued quantities ([#468](https://github.com/JuliaPhysics/Unitful.jl/pull/468)). ## v1.10.0 (2021-12-27) * ![Feature:](https://img.shields.io/badge/-feature-green) Dimensions and units can now be documented by adding a docstring before the `@dimension`, `@refunit`, `@unit`, and `@affineunit` macro calls. The `@dimension`, `@derived_dimension`, `@refunit`, and `@unit` macros have an optional boolean argument `autodocs` to add autogenerated docstrings to some objects generated by these macros. All dimensions, units and constants defined in this package now have docstrings ([#476](https://github.com/JuliaPhysics/Unitful.jl/pull/476)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Calling `preferunits` with non-pure units (e.g., `preferunits(C/ms)`) no longer results in wrong behavior ([#478](https://github.com/JuliaPhysics/Unitful.jl/pull/478)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Fixed some invalidations to improve compile times ([#509](https://github.com/JuliaPhysics/Unitful.jl/pull/509)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Broadcasting `ustrip`, `upreferred`, and `*` over a range now returns another range instead of a `Vector` ([#501](https://github.com/JuliaPhysics/Unitful.jl/pull/501), [#503](https://github.com/JuliaPhysics/Unitful.jl/pull/503)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) `LinearAlgebra.norm` now returns a floating-point quantity, which matches the behavior for `Base` numbers ([#500](https://github.com/JuliaPhysics/Unitful.jl/pull/500)). ## v1.9.2 (2021-11-12) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) The functions `Unitful.cos_fast`, `Unitful.sin_fast`, and `Unitful.tan_fast` are removed. Due to an implementation error, they always threw a `MethodError`, so removing them is not breaking. This fixes a warning during precompilation ([#497](https://github.com/JuliaPhysics/Unitful.jl/pull/497)). ## v1.9.1 (2021-10-27) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Multiplying a `StepRangeLen` by `Units` now preserves the floating-point precision ([#485](https://github.com/JuliaPhysics/Unitful.jl/pull/485)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Make `^(::AbstractQuantity, ::Rational)` inferrable on Julia ≥ 1.8 ([#487](https://github.com/JuliaPhysics/Unitful.jl/pull/487)). * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) Updated multiplication of range and quantity for Julia ≥ 1.8 compatibility ([#489](https://github.com/JuliaPhysics/Unitful.jl/pull/489), [#495](https://github.com/JuliaPhysics/Unitful.jl/pull/495)). ## v1.9.0 (2021-07-16) * ![Feature:](https://img.shields.io/badge/-feature-green) `deg2rad` and `rad2deg` can now be used to convert between `°` and `rad` ([#459](https://github.com/JuliaPhysics/Unitful.jl/pull/459)). ## v1.8.0 (2021-05-31) * ![Feature:](https://img.shields.io/badge/-feature-green) The `IOContext` property `:fancy_exponent` can be used to control the printing of exponents in units (i.e., `m²` or `m^2`). Previously, this could only be done by setting the environment variable `UNITFUL_FANCY_EXPONENTS`. The `:fancy_exponent` property overrides the environment variable ([#446](https://github.com/JuliaPhysics/Unitful.jl/pull/446)). ## v1.7.0 (2021-04-02) * ![Feature:](https://img.shields.io/badge/-feature-green) The functions `dimension`, `unit`, `absoluteunit`, `upreferred`, and `numtype` now support `AbstractQuantity` (instead of just `Quantity`) arguments ([#431](https://github.com/JuliaPhysics/Unitful.jl/pull/431)). * ![Feature:](https://img.shields.io/badge/-feature-green) Support for conversion between `Unitful.Time` and `Dates.FixedPeriod` types is added ([#331](https://github.com/JuliaPhysics/Unitful.jl/pull/331)). ## v1.6.0 (2021-02-14) * ![Feature:](https://img.shields.io/badge/-feature-green) Support for the `Base.unordered` function is added ([#406](https://github.com/JuliaPhysics/Unitful.jl/pull/406)). * ![Feature:](https://img.shields.io/badge/-feature-green) The CGS units Gauss (`Gauss`), Oersted (`Oe`), and Maxwell (`Mx`) are added ([#397](https://github.com/JuliaPhysics/Unitful.jl/pull/397)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Removed a wrong use of `@eval` that broke precompilation ([#417](https://github.com/JuliaPhysics/Unitful.jl/pull/417)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) The traits `Base.ArithmeticStyle` and `Base.OrderStyle` are now implemented correctly to support number types that are not defined in `Base` ([#407](https://github.com/JuliaPhysics/Unitful.jl/pull/407)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `==` and `isequal` now work correctly for `Gain`s and `Level`s with bignums ([#404](https://github.com/JuliaPhysics/Unitful.jl/pull/404)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) `range(start; step, length)` now always creates a functioning range when `start` and `step` have different units ([#411](https://github.com/JuliaPhysics/Unitful.jl/pull/411)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) It is no longer possible to create a `Level` with non-real value or reference quantity ([#400](https://github.com/JuliaPhysics/Unitful.jl/pull/400), [#421](https://github.com/JuliaPhysics/Unitful.jl/pull/421)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Macro hygiene is improved ([#390](https://github.com/JuliaPhysics/Unitful.jl/pull/390)). ## v1.5.0 (2020-10-21) * ![Feature:](https://img.shields.io/badge/-feature-green) Dimensionless quantities now support inverse and hyperbolic trig functions ([#387](https://github.com/JuliaPhysics/Unitful.jl/pull/387)). ## v1.4.1 (2020-09-17) * ![Maintenance:](https://img.shields.io/badge/-maintenance-grey) Adapt test suite to Julia ≥ 1.6 type parameter printing ([#380](https://github.com/JuliaPhysics/Unitful.jl/pull/380)). ## v1.4.0 (2020-08-11) * ![Feature:](https://img.shields.io/badge/-feature-green) It is now possible to divide an array by units ([#369](https://github.com/JuliaPhysics/Unitful.jl/pull/369)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Complex and mixed quantities are now printed with brackets ([#366](https://github.com/JuliaPhysics/Unitful.jl/pull/366)). ## v1.3.0 (2020-06-26) * ![Feature:](https://img.shields.io/badge/-feature-green) `isless` is now defined for logarithmic quantities ([#315](https://github.com/JuliaPhysics/Unitful.jl/pull/315)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Calling `div`, `rem`, etc. with affine quantities now errors ([#354](https://github.com/JuliaPhysics/Unitful.jl/pull/354)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Custom printing of types was removed ([#322](https://github.com/JuliaPhysics/Unitful.jl/pull/322)). ## v1.2.1 (2020-05-26) * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Fix an error when converting units with fractional power ([#335](https://github.com/JuliaPhysics/Unitful.jl/pull/335)). ## v1.2.0 (2020-05-10) * ![Feature:](https://img.shields.io/badge/-feature-green) The unit `Year` now allows SI prefixes ([#320](https://github.com/JuliaPhysics/Unitful.jl/pull/320)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Unit conversions can now return integer-valued quantities if the conversion factor is whole ([#323](https://github.com/JuliaPhysics/Unitful.jl/pull/323)). ## v1.1.0 (2020-04-09) * ![Feature:](https://img.shields.io/badge/-feature-green) `div`, `fld`, `cld` now allow arguments of different dimensions as long as one of them is a plain number (i.e., not an `AbstractQuantity`), e.g., `div(10m, 3) == 3m` and `cld(10, 3m) == 4/m` ([#317](https://github.com/JuliaPhysics/Unitful.jl/pull/317)). * ![Feature:](https://img.shields.io/badge/-feature-green) Unicode superscript can now be used to to display powers in units and dimensions (e.g., `m²` instead of `m^2`). The `UNITFUL_FANCY_EXPONENTS` environment variable can be used to control whether unicode powers are used or not ([#297](https://github.com/JuliaPhysics/Unitful.jl/pull/297)). * ![Feature:](https://img.shields.io/badge/-feature-green) The unit `Year` (`yr`) is defined, equal to 365.25 days ([#288](https://github.com/JuliaPhysics/Unitful.jl/pull/288)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `round` with the `digits`/`sigdigits` keyword now works correctly for quantities that are not based on floating-point numbers. It returns a float-based quantity in those cases ([#308](https://github.com/JuliaPhysics/Unitful.jl/pull/308)). ## v1.0.0 (2020-01-27) * ![Feature:](https://img.shields.io/badge/-feature-green) The `uparse` function can be used to parse units and quantities from a string ([#298](https://github.com/JuliaPhysics/Unitful.jl/pull/298)). * ![Feature:](https://img.shields.io/badge/-feature-green) The constructors `Float16`, `Float32`, `Float64`, and `BigFloat` can be used to convert a quantity to one based on the specified floating-point type, e.g., `Float64(1m) === 1.0m`. The `float` function can be used to convert a quantity to an appropriate floating-point type ([#296](https://github.com/JuliaPhysics/Unitful.jl/pull/296)). * ![Feature:](https://img.shields.io/badge/-feature-green) The unit `Pertenthousand` (`‱`) is added ([#294](https://github.com/JuliaPhysics/Unitful.jl/pull/294)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Calling the two-argument `atan` with quantities that have the same numeric type and dimension but different units no longer errors ([#293](https://github.com/JuliaPhysics/Unitful.jl/pull/293)). ## v0.18.0 (2019-11-27) * ![Feature:](https://img.shields.io/badge/-feature-green) `Quantity` types now support the `constructorof` function from the [ConstructionBase.jl](https://github.com/JuliaObjects/ConstructionBase.jl) package ([#280](https://github.com/JuliaPhysics/Unitful.jl/pull/280)). * ![Feature:](https://img.shields.io/badge/-feature-green) Using units as conversion functions now supports `missing` ([#278](https://github.com/JuliaPhysics/Unitful.jl/pull/278)). * ![Feature:](https://img.shields.io/badge/-feature-green) The unit `Angstrom` (`Å` or `angstrom`) is added ([#271](https://github.com/JuliaPhysics/Unitful.jl/pull/271)). ## v0.17.0 (2019-09-08) * ![BREAKING:](https://img.shields.io/badge/-BREAKING-red) The unit `rps` (revolutions per second) is now equal to `2π*rad/s` instead of `1/s` and the unit `rpm` (revolutions per minute) is now equal to `2π*rad/minute` instead of `1/minute` ([#268](https://github.com/JuliaPhysics/Unitful.jl/pull/268)). * ![Feature:](https://img.shields.io/badge/-feature-green) The derived dimensions `MassFlow` (`𝐌/𝐓`), `MolarFlow` (`𝐍/𝐓`), and `VolumeFlow` (`𝐋^3/𝐓`) are added ([#269](https://github.com/JuliaPhysics/Unitful.jl/pull/269)). * ![Feature:](https://img.shields.io/badge/-feature-green) The dimensions power density (`𝐌 𝐋^-1 𝐓^-3`) and work (`𝐋^2 𝐌 𝐓^-2`) are marked as power-like for use with logarithmic quantities ([#267](https://github.com/JuliaPhysics/Unitful.jl/pull/267)). * ![Feature:](https://img.shields.io/badge/-feature-green) `zero(::Type{<:AbstractQuantity{T,D}}) where {T,D}` can be used to get an additive identity with numeric type `T` and dimension `D` ([#266](https://github.com/JuliaPhysics/Unitful.jl/pull/266)). * ![Feature:](https://img.shields.io/badge/-feature-green) The unit `Molar` (`M`) is added ([#258](https://github.com/JuliaPhysics/Unitful.jl/pull/258)). * ![Feature:](https://img.shields.io/badge/-feature-green) `Unitful.register` now extends `Unitful.basefactors`, so packages that define units don’t have to do it themselves ([#251](https://github.com/JuliaPhysics/Unitful.jl/pull/251)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Ranges of quantities are now printed in a more concise way ([#256](https://github.com/JuliaPhysics/Unitful.jl/pull/256)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Angular degrees are now printed without a space between the number and unit, in compliance with the SI standard ([#255](https://github.com/JuliaPhysics/Unitful.jl/pull/255)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `zero` now errors if the dimension of its argument is unspecified ([#266](https://github.com/JuliaPhysics/Unitful.jl/pull/266)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) `Unitful.promote_to_derived` works again ([#252](https://github.com/JuliaPhysics/Unitful.jl/pull/252)). ## v0.16.0 (2019-07-01) * ![BREAKING:](https://img.shields.io/badge/-BREAKING-red) The physical constants are updated to the CODATA 2018 recommended values ([#235](https://github.com/JuliaPhysics/Unitful.jl/pull/235)). * ![Feature:](https://img.shields.io/badge/-feature-green) On Julia v1, the rounding functions `round`, `ceil`, `floor`, and `trunc` now accept all keyword arguments that are supported for plain numbers. In addition, the first argument to these functions can be a unit instead of a type ([#246](https://github.com/JuliaPhysics/Unitful.jl/pull/246), [#249](https://github.com/JuliaPhysics/Unitful.jl/pull/249), [#250](https://github.com/JuliaPhysics/Unitful.jl/pull/250)). * ![Feature:](https://img.shields.io/badge/-feature-green) The functions `Base.complex`, `Base.reim`, and `Base.widen` can now be called with unitful quantities ([#227](https://github.com/JuliaPhysics/Unitful.jl/pull/227)). * ![Feature:](https://img.shields.io/badge/-feature-green) The function `upreferred` now supports `missing` ([#224](https://github.com/JuliaPhysics/Unitful.jl/pull/224)). * ![Feature:](https://img.shields.io/badge/-feature-green) Two-argument and three-argument `ustrip` now support dimensionless quantities and `missing` ([#212](https://github.com/JuliaPhysics/Unitful.jl/pull/212)). * ![Enhancement:](https://img.shields.io/badge/-enhancement-blue) Better support for number types that customize their `MIME"text/plain"` printing ([#213](https://github.com/JuliaPhysics/Unitful.jl/pull/213)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Ranges which use `Base.TwicePrecision` internally now work correctly ([#245](https://github.com/JuliaPhysics/Unitful.jl/pull/245)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Fixed some issues around use of `@generated` functions that could lead to world-age errors or wrong behavior ([#233](https://github.com/JuliaPhysics/Unitful.jl/pull/233), [#243](https://github.com/JuliaPhysics/Unitful.jl/pull/243)). ## v0.15.0 (2019-03-05) * ![Feature:](https://img.shields.io/badge/-feature-green) The functions `uconvert`, `ustrip`, `unit`, and `dimension` as well as arithmetic with units now support `missing` ([#208](https://github.com/JuliaPhysics/Unitful.jl/pull/208)). * ![Feature:](https://img.shields.io/badge/-feature-green) Two-argument `ustrip(unit, x)` and three-argument `ustrip(T, unit, x)` methods are added ([#205](https://github.com/JuliaPhysics/Unitful.jl/pull/205)). * ![Feature:](https://img.shields.io/badge/-feature-green) The `AbstractQuantity{T,D,U}` type is defined to support defining quantity types other than `Quantity{T,D,U}` ([#204](https://github.com/JuliaPhysics/Unitful.jl/pull/204)). * ![Feature:](https://img.shields.io/badge/-feature-green) The derived dimensions `Molarity` (`𝐍/𝐋^3`) and `Molality` (`𝐍/𝐌`) are added ([#198](https://github.com/JuliaPhysics/Unitful.jl/pull/198)). * ![Bugfix:](https://img.shields.io/badge/-bugfix-purple) Multiplying a range by units now works correctly ([#206](https://github.com/JuliaPhysics/Unitful.jl/pull/206)). ## Older changes - v0.14.0 - Support for `digits` kwarg (#196). - Try to support precompilation with `u_str` macro (#201). - v0.13.0 - Implement affine quantities for better temperature handling (#177, #182). - Rename `°Ra` to `Ra` to emphasize that it is an absolute scale. - Fix some precompilation issues (#161). - Add `Velocity`, `Acceleration`, `Density` derived dimensions (#187). - Days display as `d` now (#184). - Type signature of `Quantity`s has been simplified. Helps with reading error messages (#183). - Support `isequal` with `NaN` quantities (#172). - v0.12.0 - Bug fixes. - Support carrier-to-noise-density ratio (C/N0) in dB-Hz. - Added dimensions: `DField`, `EField`, `ElectricDipoleMoment`, `ElectricQuadrupoleMoment`, `MagneticDipoleMoment`. - Added unit: `barn`. - Some documentation improvements. - v0.11.0--v0.9.0 - Fixes for Julia 0.7 update, primarily. - Some new TwicePrecision functionality for quantities. - v0.8.0 - Add Rydberg constant, unified atomic mass unit, mils, rpm, rps, percent, permille. - Introduce/rename derived dimensions: ElectricalConductivity, ElectricalResistivity, ElectricalConductance, ElectricalResistance. - Fix some Julia 0.7 deprecations. - This will probably be the last release that supports Julia 0.6. - v0.7.1 - Bug fixes, mainly. - v0.7.0 - Implement `mod2pi` for degrees, cleanup display of degree units. - Tweak implementation of `Gain` types for usability. - Implement `zero` and `one` for `Level` and `Gain`. - Add a few more cgs units. - Tests pass on 32-bit systems, for the first time in a long time (ever?). - v0.6.1 - Permit symbols that are bound to `Number`s to be used in `u_str` macro, such that π and other non-literal numbers can be used. - Add some cgs units and a few dimensions [#115](https://github.com/JuliaPhysics/Unitful.jl/pull/115). - Fix a comparison / promotion bug introduced in v0.6.0. - v0.6.0 - Restore compatibility with 0.7.0-DEV. - v0.5.1 - Dimensionless quantities no longer lose their units when dividing by a real number. - Ranges constructed via `range` or `colon` should work more reliably (e.g., 0:10°:350° works now). - v0.5.0 - Add `dBΩ` and `dBS` to permit working with impedances and admittances in dB. These are used in the Touchstone format and in microwave measurements. - Implement `angle` for `Quantity{<:Complex}`. - Implement `float` for `Gain`, `Level`. - Replace `fieldratio` and `rootpowerratio` with `uconvertrp`. - Permits unit conversion between `NoUnits` and `dB`, etc. by presuming unitless ratios are of root-power quantities (hence the `rp` after `uconvert`). - `uconvertrp` has generic fallbacks and can be used as a drop-in replacement for `uconvert` otherwise. - Likewise, replace `powerratio` with `uconvertp` for ratios of power quantities. - Introduce `convertrp` and `convertp`. These are like `convert` but they make similar assumptions about unitless ratios being of power or root-power quantities, respectively. - Implement more division operations for `Gain`s (accidental omissions) - v0.4.0 - Introduce logarithmic quantities (experimental!) - Update syntax for Julia 0.6 and reorganize code for clarity. - Redefine `ustrip(x::Quantity) = ustrip(x.val)`. In most cases, this is unlikely to affect user code. The generic fallback `ustrip(x::Number)` remains unchanged. - `isapprox(1.0u"m",5)` returns `false` instead of throwing a `DimensionError`, in keeping with the behavior of an equality check (`==`). - Display of some units has changed to match their symbols [#104](https://github.com/JuliaPhysics/Unitful.jl/issues/104). - Don't export `cd` from Unitful.DefaultSymbols in order to avoid conflicts [#102](https://github.com/JuliaPhysics/Unitful.jl/issues/102). - Deprecated `dimension(x::AbstractArray{T}) where T<:Number`, use broadcasting instead. - Deprecated `dimension(x::AbstractArray{T}) where T<:Units`, use broadcasting instead. - Deprecated `ustrip(A::AbstractArray{T}) where T<:Number`, use broadcasting instead. - Deprecated `ustrip(A::AbstractArray{T}) where T<:Quantity`, use broadcasting instead. - v0.3.0 - Require Julia 0.6 - Adds overloads for `rand` and `ones` [#96](https://github.com/JuliaPhysics/Unitful.jl/issues/96). - Improve symbol resolution in `u_str` macro [#98](https://github.com/JuliaPhysics/Unitful.jl/pull/98). - More work is done inside the `u_str` macro, such that the macro returns units, dimensions, numbers (quantities), or tuples rather than expressions. - v0.2.6 - Fix and close [#52](https://github.com/JuliaPhysics/Unitful.jl/issues/52). - Implement `Base.rtoldefault` for Quantity types (needed for AxisArrays [#52](https://github.com/JuliaArrays/AxisArrays.jl/pull/52)). - v0.2.5 - Fix and close [#79](https://github.com/JuliaPhysics/Unitful.jl/issues/79). - Add support for `round(T, ::DimensionlessQuantity)` where `T <: Integer` (also `floor`, `ceil`, `trunc`) [#90](https://github.com/JuliaPhysics/Unitful.jl/pull/90). - v0.2.4 - Bug fix: avoid four-argument `promote_type` - Bug fix: define method for `*(::Base.TwicePrecision, ::Quantity)` - Bug fix: definition of Bohr magneton had `e` instead of `q` - v0.2.3 - Dimensionful quantities are no longer accepted for `floor`, `ceil`, `trunc`, `round`, `isinteger`. The choice of units can yield physically different results. The functions are defined for dimensionless quantities, and return unitless numbers. Closes [#78](https://github.com/JuliaPhysics/Unitful.jl/issues/78). - Added `gn`, a constant quantity for the gravitational acceleration on earth [#75](https://github.com/JuliaPhysics/Unitful.jl/pull/75). - Added `ge`, the gravitational acceleration on earth as a unit [#75](https://github.com/JuliaPhysics/Unitful.jl/pull/75). - Added `lbf`, pounds-force unit [#75](https://github.com/JuliaPhysics/Unitful.jl/pull/75). - v0.2.2 - Fixed a bug in promotion involving `ContextUnits` where the promotion context might not be properly retained. - v0.2.1 - Fixed `isapprox` bug [#74](https://github.com/JuliaPhysics/Unitful.jl/pull/74). - Added `DimensionlessQuantity` methods for `exp`, `exp10`, `exp2`, `expm1`, `log1p`, `log2` [#71](https://github.com/JuliaPhysics/Unitful.jl/pull/71). - v0.2.0 - `Units{N,D}` is now an abstract type. Different concrete types for units give different behavior under conversion and promotion. The currently implemented concrete types are: - `FreeUnits{N,D}`: these give the typical behavior from prior versions of Unitful. Units defined in Unitful.jl and reachable by the `u_str` macro are all `FreeUnits`. - `ContextUnits{N,D,P}`, where P is some type `FreeUnits{M,D}`: these enable context-specific promotion rules, e.g. if units are defined in different packages. - `FixedUnits{N,D}`: these inhibit automatic conversion of quantities with different units. - `LengthUnit`, `EnergyUnit`, etc. are renamed to `LengthUnits`, `EnergyUnits`, etc. for consistency (they are related more to `Units` objects than `Unit` objects). You can still use the old names for now, but please switch over to using `...Units` instead of `...Unit` in this release as the old names will be removed in a future release. - `c` is now a unit, to permit converting mass into `MeV/c^2`, for example. `c0` is still a quantity equal to the speed of light in vacuum, in units of `m/s` [#67](https://github.com/JuliaPhysics/Unitful.jl/issues/67). - v0.1.5 - Patch for Julia PR [#20889](https://github.com/JuliaLang/julia/pull/20889), which changes how lowering is done for exponentiation of integer literals. - Bug fix to enable registering Main as a module for `u_str` (fixes [#61](https://github.com/JuliaPhysics/Unitful.jl/issues/61)). - Implement readable message for `DimensionError` [#62](https://github.com/JuliaPhysics/Unitful.jl/pull/62). - v0.1.4 - Critical bug fix owing to `mod_fast` changes. - v0.1.3 - Fix symmetry of `==` [#56](https://github.com/JuliaPhysics/Unitful.jl/issues/56). - Using `@refunit` will implicitly specify the ref. unit as the default for promotion. This will not change behavior for most people; it just ensures promotion won't fail for quantities with user-defined dimensions. - Remove `mod_fast` in anticipation of Julia PR [#20859](https://github.com/JuliaLang/julia/pull/20859). - Allow tolerance args for `isapprox` [#57](https://github.com/JuliaPhysics/Unitful.jl/pull/57) - v0.1.2 - On Julia 0.6, exponentiation by a literal is now type stable for integers. - v0.1.1 - Fixed a macro hygiene issue that prevented `@dimension` and `@derived_dimension` from working properly if Compat was not imported in the calling namespace. - v0.1.0 - Julia 0.6 compatibility. - On Julia 0.6, exponentiation by a literal is now type stable for common integer powers: -3, -2, -1, 0, 1, 2, 3. - Added missing methods for dot operators `.<` and `.<=` (Julia 0.5, fix [#55](https://github.com/JuliaPhysics/Unitful.jl/issues/55)). - Fix [#45](https://github.com/JuliaPhysics/Unitful.jl/issues/45). Ranges should work as expected on Julia 0.6. On Julia 0.5, [Ranges.jl](https://github.com/JuliaArrays/Ranges.jl) is used to make ranges work as well as possible given limitations in Base. - Fix [#33](https://github.com/JuliaPhysics/Unitful.jl/issues/33), [#42](https://github.com/JuliaPhysics/Unitful.jl/issues/42), and [#50](https://github.com/JuliaPhysics/Unitful.jl/issues/50). `deps/Defaults.jl` is dead. Long live `deps/Defaults.jl`. To define your own units, dimensions, and so on, you should now put them in a module, or ideally a package so that others can use the definitions too. You can override default promotion rules immediately after loading Unitful and dependent packages; this will generate method overwrite warnings on Julia 0.5 but not on 0.6. - `@u_str` macro has been improved. It can now traverse separate unit packages, as well as return tuples of `Units` objects. - `@preferunit` has been replaced with a function `preferunits`. - Added some methods for `ustrip`. - Implement `typemin`, `typemax`, `cbrt` for `Quantity`s. - Added matrix inversion for `StridedMatrix{T<:Quantity}`. - Added `istriu`, `istril` for `AbstractMatrix{T<:Quantity}`. - The `Unitful.SIUnits` module has been renamed to `Unitful.DefaultSymbols`. - Add `lb`, `oz`, `dr`, `gr` to Unitful (international Avoirdupois mass units). - v0.0.4 - Be aware, breaking changes to `deps/Defaults.jl` caused by some of the following! - Fix [#40](https://github.com/JuliaPhysics/Unitful.jl/issues/40). - Fix [#30](https://github.com/JuliaPhysics/Unitful.jl/issues/30). - Support relevant `@fastmath` operations for `Quantity`s. - Implement `fma`, `atan2` for `Quantity`s. - Implement `cis` for dimensionless `Quantity`s. - Removed `DimensionedUnits` and `DimensionedQuantity` abstract types. They were of dubious utility, and this change shortened the promotion code considerably. More importantly, this change has made it possible to write methods like the following, without method ambiguities: `uconvert(e::EnergyUnit, f::Frequency) = uconvert(e, u"h"*f)`. - Promotion wraps usual `Number` types in dimensionless, unitless `Quantity` types when promoted together with dimensionful `Quantity`s. With `Quantity`s it is not always possible to promote to a common concrete type, but this way we can at least ensure that the numeric backing types are all promoted: (`promote(1.0u"m", 1u"N"//2, 0x08) == (1.0 m,0.5 N,8.0)`, where `8.0` is actually a dimensionless, unitless `Quantity`). The usual outer constructor for `Quantity`s (`Quantity(val::T, unit)`) continues to return a number of type `T` if the unit is `NoUnits`, since most of the time the user would prefer a `Number` type from base rather than a dimensionless, unitless quantity. - Add more units to defaults: `bar` (bar), `Torr` (torr), `atm` (atmosphere), `l` or `L` (liter; both symbols accepted). You will need to delete `deps/Defaults.jl` in the Unitful package directory to get the new units. - Two character encodings for `μ` in SI prefixes are now generated automatically (some logic moved out of defaults). - Moved definition of `sin`, `cos`, `tan`, `sec`, `csc`, `cot` out of `deps/build.jl` and into `src/Unitful.jl`. - v0.0.3 - Bug fix: `uconvert(°C, 0x01°C)` no longer disturbs the numeric type - Allow μ-prefixed units to be typed with option-m on a Mac, in addition to using Unicode. Previously only `μm` could be typed this way. - Include a `baremodule` called `SIUnits` in the factory defaults. You can now do `using Unitful.SIUnits` to bring all of the SI units into the calling namespace. - Added remaining SI units to the factory defaults: `sr` (steradian), `lm` (luminous flux), `lx` (illuminance), `Bq` (becquerel), `Gy` (gray), `Sv` (sievert), `kat` (katal). - Simplify array creation, as in `[1, 2]u"km"` [#29](https://github.com/JuliaPhysics/Unitful.jl/pull/29) - Support multiplying ranges by units, as in `(1:3)*mm` [#28](https://github.com/JuliaPhysics/Unitful.jl/pull/28) - Bug fix [#26](https://github.com/JuliaPhysics/Unitful.jl/issues/26) - Promoting `Quantity`s with different dimensions now returns quantities with the same numeric backing type, e.g. `Quantity{Float64}`. Ideally, this would also be true if you mixed unitless and unitful numbers during promotion, but that is not yet the case. See [#24](https://github.com/JuliaPhysics/Unitful.jl/issues/24) for motivation. - v0.0.2 - Bug fixes (`[1.0m, 2.0m] ./ 3` would throw a `Unitful.DimensionError()`). Promotion still isn't perfect, but it is hard for me to see what `@inferred` errors are real until https://github.com/JuliaLang/julia/issues/18465 is resolved. - Made units callable for unit conversion: `u"cm"(1u"m") == 100u"cm"//1`. Note that `Units` objects have no fields, so this is totally unambiguous. Moreover, we have convenient syntax for unit conversion by function chaining: `1u"m" |> u"cm" == 100u"cm"//1`. Note that `uconvert` will remain supported. - v0.0.1 - Initial release ================================================ FILE: Project.toml ================================================ name = "Unitful" uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" version = "1.28.0" [deps] ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [weakdeps] ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" [extensions] ConstructionBaseUnitfulExt = "ConstructionBase" ForwardDiffExt = "ForwardDiff" InverseFunctionsUnitfulExt = "InverseFunctions" LatexifyExt = ["Latexify", "LaTeXStrings"] NaNMathExt = "NaNMath" PrintfExt = "Printf" [compat] Aqua = "0.8" ConstructionBase = "1" Dates = "<0.0.1, 1" ForwardDiff = "0.10, 1" InverseFunctions = "0.1" LaTeXStrings = "1.2.0" Latexify = "0.16.8" LinearAlgebra = "<0.0.1, 1" NaNMath = "1" Printf = "<0.0.1, 1" REPL = "<0.0.1, 1" Random = "<0.0.1, 1" Test = "<0.0.1, 1" julia = "1.6" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Aqua", "ConstructionBase", "ForwardDiff", "InverseFunctions", "Latexify", "LaTeXStrings", "LinearAlgebra", "NaNMath", "Test", "Random", "REPL", "Printf"] ================================================ FILE: README.md ================================================ [![CI](https://github.com/JuliaPhysics/Unitful.jl/workflows/CI/badge.svg)](https://github.com/JuliaPhysics/Unitful.jl/actions?query=workflow%3ACI) [![Coverage Status](https://coveralls.io/repos/github/JuliaPhysics/Unitful.jl/badge.svg?branch=master)](https://coveralls.io/github/JuliaPhysics/Unitful.jl?branch=master) [![codecov.io](https://codecov.io/github/JuliaPhysics/Unitful.jl/coverage.svg?branch=master)](https://codecov.io/github/JuliaPhysics/Unitful.jl?branch=master) # Unitful.jl Unitful is a Julia package for physical units. We want to support not only SI units but also any other unit system. We also want to minimize or in some cases eliminate the run-time penalty of units. There should be facilities for dimensional analysis. All of this should integrate easily with the usual mathematical operations and collections that are found in Julia base. ### Documentation: [![](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaPhysics.github.io/Unitful.jl/stable) [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaPhysics.github.io/Unitful.jl/dev) ## Other packages in the Unitful family ### Units packages - [UnitfulUS.jl](https://github.com/PainterQubits/UnitfulUS.jl): U.S. customary units. Serves as an example for how to implement a units package. - [UnitfulAstro.jl](https://github.com/mweastwood/UnitfulAstro.jl): Astronomical units. - [UnitfulAngles.jl](https://github.com/yakir12/UnitfulAngles.jl): More angular units, additional trigonometric functionalities, and clock-angle conversion. - [UnitfulAtomic.jl](https://github.com/sostock/UnitfulAtomic.jl): Easy conversion from and to atomic units. - [PowerSystemsUnits.jl](https://github.com/invenia/PowerSystemsUnits.jl): Common units for dealing with power systems. - [UnitfulMoles.jl](https://github.com/rafaqz/UnitfulMoles.jl) for defining mol units of chemical elements and compounds. ### Feature additions - [UnitfulEquivalences.jl](https://github.com/sostock/UnitfulEquivalences.jl): Enables conversion between equivalent quantities of different dimensions, e.g. between energy and wavelength of a photon. - [UnitfulParsableString.jl](https://github.com/michikawa07/UnitfulParsableString.jl): Add a `Base.string` method that converts quantities and units to parsable strings. - [UnitfulBuckinghamPi.jl](https://github.com/rmsrosa/UnitfulBuckinghamPi.jl): Solves for the adimensional Pi groups in a list of Unitful parameters, according to the Buckingham-Pi Theorem. - [NaturallyUnitful.jl](https://github.com/MasonProtter/NaturallyUnitful.jl): Convert to and from natural units in physics. - [UnitfulChainRules.jl](https://github.com/SBuercklin/UnitfulChainRules.jl): Enables use of Unitful quantities with [ChainRules.jl](https://github.com/JuliaDiff/ChainRules.jl)-compatible autodifferentiation systems. - [DimensionfulAngles.jl](https://github.com/JuliaOceanWaves/DimensionfulAngles.jl): Adds angle as a dimension. This allows dispatching on angles and derived quantities. - [Dimensionless.jl](https://github.com/martinkosch/Dimensionless.jl): Contains tools to switch between dimensional bases, conduct dimensional analysis and solve similitude problems. - [UnitfulRecipes.jl](https://github.com/jw3126/UnitfulRecipes.jl) (deprecated): Adds automatic labels and supports plot axes with units for [Plots.jl](https://github.com/JuliaPlots/Plots.jl). (UnitfulRecipes.jl is now included in Plots.jl.) - [UnitfulLatexify.jl](https://github.com/gustaphe/UnitfulLatexify.jl) (deprecated): Pretty print units and quantities in LaTeX format. (This package is now an extension to Unitful, so that loading both Unitful and [Latexify.jl](https://github.com/korsbo/Latexify.jl) loads this functionality.) ## Related packages Unitful was inspired by: - [SIUnits.jl](https://github.com/keno/SIUnits.jl) - [EngUnits.jl](https://github.com/dhoegh/EngUnits.jl) - [Units.jl](https://github.com/timholy/Units.jl) ================================================ FILE: docs/Project.toml ================================================ [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Fontconfig = "186bb1d3-e1f7-5a2c-a377-96d770f13627" InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" [sources] Unitful = {path = ".."} [compat] Documenter = "1" Latexify = "0.16.10" Plots = "1" Fontconfig = "0.4" ================================================ FILE: docs/generate_latex_images.jl ================================================ using LaTeXStrings, Unitful, Latexify import tectonic_jll # needed for lightweight LaTeX render using Fontconfig: format, match, Pattern # Since the docs can get built on different systems, we need to find a locally installed # monospaced font that has enough Unicode coverage to handle π monofont = format(match(Pattern(spacing=100, charset="3c0")), "%{family}") commands = [ :(latexify(612.2u"nm")), :(latexify(u"kg*m/s^2")), :(latexify(612.2u"nm"; fmt=SiunitxNumberFormatter())), :(latexify(u"kg*m/s^2"; fmt=SiunitxNumberFormatter())), :(latexify(612.2u"nm"; fmt=SiunitxNumberFormatter(; simple=true))), :(latexify(u"kg*m/s^2"; fmt=SiunitxNumberFormatter(; simple=true))), :(latexify((1, 2, 4) .* u"m"; fmt=SiunitxNumberFormatter())), ] tab1 = map(commands) do command LaTeXString.([ "\\verb+$(string(command))+", "\\verb+$(eval(command))+", "$(eval(command)) ", ]) end ltab1 = latextabular(tab1, adjustment=:l, transpose=true, latex=false, booktabs=true, head=["julia", "\\LaTeX", "Result"]) # Setting an explicit white background color results in transparent PDF, so go offwhite. ltab1 = LaTeXString(""" \\setmonofont{$monofont} \\definecolor{offwhite}{rgb}{0.999,0.999,0.999} \\pagecolor{offwhite} \\color{black} """ * ltab1) render(ltab1, MIME("image/png"); use_tectonic=true, open=false, name=(@__DIR__)*"/src/assets/latex-examples", packages=["booktabs", "color", "siunitx", "fontspec"], documentclass=("standalone")) functions = [ x -> "\\verb+$(string(x))+", x -> latexify(x), x -> latexify(x; fmt=SiunitxNumberFormatter()), x -> latexify(x; fmt=SiunitxNumberFormatter(; simple=true)), ] allunits = begin uparse.([ "nH*m/Hz", "m", "s", "A", "K", "cd", "g", "mol", "sr", "rad", "°", "Hz", "N", "Pa", "J", "W", "C", "V", "S", "F", "H", "T", "Wb", "lm", "lx", "Bq", "Gy", "Sv", "kat", #"percent", # Messes with comments # "permille", # Undefined in all formats # "pertenthousand", # Undefined in all formats (butchered) "°C", "°F", # No longer in siunitx "minute", "hr", "d", "wk", # Undefined in siunitx "yr", # Undefined in siunitx "rps", # Undefined in siunitx "rpm", # Undefined in siunitx "a", # Undefined in siunitx "b", "L", "M", # Undefined in siunitx "eV", "Hz2π", # Butchered by encoding "bar", "atm", # Undefined in siunitx "Torr", # Undefined in siunitx "c", # Undefined in siunitx "u", # Undefined in siunitx "ge", # Undefined in siunitx "Gal", # Undefined in siunitx "dyn", # Undefined in siunitx "erg", # Undefined in siunitx "Ba", # Undefined in siunitx "P", # Undefined in siunitx "St", # Undefined in siunitx #"Gauss", # errors in testing, maybe from Unitful.jl's dev branch? #"Oe", # errors in testing, maybe from Unitful.jl's dev branch? #"Mx", # errors in testing, maybe from Unitful.jl's dev branch? "inch", # Undefined in siunitx "mil", # Undefined in siunitx "ft", # Undefined in siunitx "yd", # Undefined in siunitx "mi", # Undefined in siunitx "angstrom", # Undefined in mathrm,siunitxsimple "ac", # Undefined in siunitx "Ra", # Undefined in siunitx "lb", # Undefined in siunitx "oz", # Undefined in siunitx "slug", # Undefined in siunitx "dr", # Undefined in siunitx "gr", # Undefined in siunitx "lbf", # Undefined in siunitx "cal", # Undefined in siunitx "btu", # Undefined in siunitx "psi", # Undefined in siunitx #"dBHz", # Cannot *yet* be latexified. #"dBm", # Cannot *yet* be latexified. #"dBV", # Cannot *yet* be latexified. #"dBu", # Cannot *yet* be latexified. #"dBμV", # Cannot *yet* be latexified. #"dBSPL", # Cannot *yet* be latexified. #"dBFS", # Cannot *yet* be latexified. #"dBΩ", # Cannot *yet* be latexified. #"dBS", # Cannot *yet* be latexified. ]) end tab2 = map(allunits) do unit [LaTeXString(f(unit)) for f in functions] end ltab2 = latextabular(tab2, adjustment=:l, transpose=true, latex=false, booktabs=true, head=["Name", "Default number formatter", "\\verb+SiunitxNumberFormatter()+", "\\verb+SiunitxNumberFormatter(;simple=true)+"]) # Set background to not-quite-white so it doesn't get treated as transparent ltab2 = LaTeXString( """ \\setmonofont{$monofont} \\definecolor{offwhite}{rgb}{0.999,0.999,0.999} \\pagecolor{offwhite} \\color{black} """ * ltab2) render(ltab2, MIME("image/png"); use_tectonic=true, open=false, tectonic_flags=`-Z continue-on-errors`, name=(@__DIR__)*"/src/assets/latex-allunits", packages=["booktabs", "color", "siunitx", "fontspec"], documentclass=("standalone")) ================================================ FILE: docs/make.jl ================================================ using Documenter, Unitful, Dates @info "Generating latex images for documentation" include("generate_latex_images.jl") DocMeta.setdocmeta!(Unitful, :DocTestSetup, :(using Unitful)) withenv("UNITFUL_FANCY_EXPONENTS" => "false") do makedocs( sitename = "Unitful.jl", format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"), warnonly = [:missing_docs], modules = [Unitful], workdir = joinpath(@__DIR__, ".."), pages = [ "Home" => "index.md" "Highlighted features" => "highlights.md" "Types" => "types.md" "Defining new units" => "newunits.md" "Conversion/promotion" => "conversion.md" "Manipulating units" => "manipulations.md" "How units are displayed" => "display.md" "Logarithmic scales" => "logarithm.md" "Temperature scales" => "temperature.md" "Interoperability with `Dates`" => "dates.md" "Latexifying units" => "latexify.md" "Extending Unitful" => "extending.md" "Troubleshooting" => "trouble.md" "Pre-defined dimensions, units, and constants" => "defaultunits.md" "License" => "LICENSE.md" ] ) end deploydocs(repo = "github.com/JuliaPhysics/Unitful.jl.git") ================================================ FILE: docs/src/LICENSE.md ================================================ # License ```@eval using Markdown open(Markdown.parse, "LICENSE.md") ``` ================================================ FILE: docs/src/conversion.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Conversion/promotion ## Converting between units Since `convert` in Julia already means something specific (conversion between Julia types), we define [`uconvert`](@ref) for conversion between units. Typically this will also involve a conversion between types, but this function takes care of figuring out which type is appropriate for representing the desired units. Exact conversions between units are respected where possible. If rational arithmetic would result in an overflow, then floating-point conversion should proceed. Use of floating-point numbers inhibits exact conversion. ```@docs uconvert ``` Since objects are callable, we can also make [`Unitful.Units`](@ref) callable with a `Number` as an argument, for a unit conversion shorthand: ```jldoctest julia> u"cm"(1u"m") 100 cm ``` This syntax is a little confusing, but becomes appealing with the function chaining operator `|>`: ```jldoctest julia> 1u"m" |> u"cm" 100 cm ``` Note that since [`Unitful.Units`](@ref) objects have no fields, we don't have to worry about ambiguity with constructor calls. This way of converting units results in behavior identical to calling [`uconvert`](@ref). ### Dimensionless quantities For dimensionless quantities, `uconvert` can be used with the [`NoUnits`](@ref) unit to strip the units without losing power-of-ten information: ```jldoctest julia> uconvert(NoUnits, 1.0u"μm/m") 1.0e-6 julia> uconvert(NoUnits, 1.0u"m") ERROR: DimensionError: and m are not dimensionally compatible. ``` ```@docs Unitful.NoUnits ``` You can also directly convert to a subtype of `Real` or `Complex`: ```jldoctest julia> convert(Float64, 1.0u"μm/m") 1.0e-6 ``` ## Basic promotion mechanisms We decide the result units for addition and subtraction operations based on looking at the types only. We can't take runtime values into account without compromising runtime performance. If two quantities with the same units are added or subtracted, then the result units will be the same. If two quantities with differing units (but same dimension) are added or subtracted, then the result units will be specified by promotion. ### Promotion rules for specific dimensions You can specify the result units for promoting quantities of a specific dimension once at the start of a Julia session. For example, you can specify that when promoting two quantities with different energy units, the resulting quantities should be in `g*cm^2/s^2`. This is accomplished by defining a `Unitful.promote_unit` method for the units themselves. Here's an example. ```jldoctest julia> using Unitful julia> Unitful.promote_unit(::S, ::T) where {S<:Unitful.EnergyUnits, T<:Unitful.EnergyUnits} = u"g*cm^2/s^2" julia> promote(2.0u"J", 1.0u"kg*m^2/s^2") (2.0e7 g cm^2 s^-2, 1.0e7 g cm^2 s^-2) julia> Unitful.promote_unit(::S, ::T) where {S<:Unitful.EnergyUnits, T<:Unitful.EnergyUnits} = u"J" julia> promote(2.0u"J", 1.0u"kg*m^2/s^2") (2.0 J, 1.0 J) ``` If you're wondering where `Unitful.EnergyUnits` comes from, it is defined in `src/pkgdefaults.jl` by the [`@derived_dimension`](@ref) macro. Similarly, the calls to the [`@dimension`](@ref) macro define `Unitful.LengthUnits`, `Unitful.MassUnits`, etc. None of these are exported. Existing users of Unitful may want to call [`Unitful.promote_to_derived`](@ref) after Unitful loads to give similar behavior to Unitful 0.0.4 and below. It is not called by default. ```@docs Unitful.promote_to_derived ``` ### Fallback promotion rules The [`Unitful.preferunits`](@ref) function is used to designate fallback preferred units for each pure dimension for promotion. Such a fallback is required because you need some generic logic to take over when manipulating quantities with arbitrary dimensions. The default behavior is to promote to a combination of the base SI units, i.e. a quantity of dimension `𝐌*𝐋^2/(𝐓^2*𝚯)` would be converted to `kg*m^2/(s^2*K)`: ```jldoctest julia> promote(1.0u"J/K", 1.0u"g*cm^2/s^2/K") (1.0 kg m^2 K^-1 s^-2, 1.0e-7 kg m^2 K^-1 s^-2) ``` You can however override this behavior by calling [`Unitful.preferunits`](@ref) at the start of a Julia session, specifically *before* [`Unitful.upreferred`](@ref) *has been called or quantities have been promoted*. ```@docs Unitful.preferunits Unitful.upreferred ``` ### Array promotion Arrays are typed with as much specificity as possible upon creation. consider the following three cases: ```jldoctest julia> [1.0u"m", 2.0u"m"] 2-element Vector{Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}: 1.0 m 2.0 m julia> [1.0u"m", 2.0u"cm"] 2-element Vector{Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}: 1.0 m 0.02 m julia> [1.0u"m", 2.0] 2-element Vector{Quantity{Float64}}: 1.0 m 2.0 ``` In the first case, an array with a concrete type is created. Good performance should be attainable. The second case invokes promotion so that an array of concrete type can be created. The third case falls back to an abstract type, which cannot be stored efficiently and will incur a performance penalty. An additional benefit of having a concrete type is that we can dispatch on the dimensions of the array's elements: ```jldoctest julia> f(x::AbstractArray{T}) where {T<:Unitful.Length} = sum(x) f (generic function with 1 method) julia> f([1.0u"m", 2.0u"cm"]) 1.02 m julia> f([1.0u"g", 2.0u"cm"]) ERROR: MethodError: no method matching f(::Vector{Quantity{Float64}}) [...] ``` ## Advanced promotion mechanisms There are some new types as of Unitful.jl v0.2.0 that enable some fairly sophisticated promotion logic. Three concrete subtypes of [`Unitful.Units{N,D,A}`](@ref) are defined: [`Unitful.FreeUnits{N,D,A}`](@ref), [`Unitful.ContextUnits{N,D,P,A}`](@ref), and [`Unitful.FixedUnits{N,D,A}`](@ref). Units defined in the Unitful.jl package itself are all `Unitful.FreeUnits{N,D,A}` objects. The "free" in `FreeUnits` indicates that the object carries no information on its own about how it should respond during promotion. Other code in Unitful dictates that by default, quantities should promote to SI units. `FreeUnits` use the promotion mechanisms described in the above section, [Basic promotion mechanisms](@ref). They used to be called `Units` in prior versions of Unitful. ### ContextUnits Sometimes, a package may want to default to a particular behavior for promotion, in the presence of other packages that may require differing default behaviors. An example would be a CAD package for nanoscale device design: it makes more sense to promote to nanometers or microns than to meters. For this purpose we define `Unitful.ContextUnits{N,D,P,A}`. The `P` in this type signature should be some type `Unitful.FreeUnits{M,D,B}` (the dimensions must be the same). We refer to this as the "context." `ContextUnits` may be easily instantiated by e.g. `ContextUnits(nm, μm)` for a `nm` unit that will promote to `μm`. Here's an example: ```jldoctest julia> μm = Unitful.ContextUnits(u"μm", u"μm") μm julia> nm = Unitful.ContextUnits(u"nm", u"μm") nm julia> 1.0μm + 1.0nm 1.001 μm ``` If the context does not agree, then we fall back to `FreeUnits`: ```jldoctest julia> μm = Unitful.ContextUnits(u"μm", u"μm") μm julia> nm = Unitful.ContextUnits(u"nm", u"cm") nm julia> 1.0μm + 1.0nm 1.001e-6 m ``` Multiplying a `ContextUnits` by a `FreeUnits` yields a `ContextUnits` object, with the preferred units for the additional dimensions being determined by calling [`Unitful.upreferred`](@ref) on the `FreeUnits` object: ```jldoctest julia> mm = Unitful.ContextUnits(u"mm", u"μm") mm julia> isa(u"g", Unitful.FreeUnits) true julia> upreferred(u"g") kg julia> mm*u"g" g mm julia> isa(mm*u"g", Unitful.ContextUnits) true julia> upreferred(mm*u"g") kg μm ``` ### FixedUnits Sometimes, there may be times where it is required to disable automatic conversion between quantities with different units. For this purpose there are `Unitful.FixedUnits{N,D,A}`. Trying to add or compare two quantities with `FixedUnits` will throw an error, provided the units are not the same. Note that you can still add/compare a quantity with `FixedUnits` to a quantity with another kind of units; in that case, the result units (if applicable) are determined by the `FixedUnits`, overriding the preferred units from `ContextUnits` or `FreeUnits`. Multiplying `FixedUnits` with any other kind of units returns `FixedUnits`: ```jldoctest julia> mm_fix = Unitful.FixedUnits(u"mm") mm julia> cm_fix = Unitful.FixedUnits(u"cm") cm julia> 1mm_fix+2mm_fix 3 mm julia> 1mm_fix+2u"cm" # u"cm" is a FreeUnits object. 21 mm julia> 1mm_fix+2*Unitful.ContextUnits(u"cm", u"cm") 21 mm julia> isa(mm_fix*u"cm", Unitful.FixedUnits) true julia> 1mm_fix+2cm_fix ERROR: automatic conversion prohibited. [...] julia> 1mm_fix == 1mm_fix true julia> 1mm_fix == 0.1u"cm" true julia> 1mm_fix == 0.1cm_fix ERROR: automatic conversion prohibited. [...] ``` Much of this functionality is enabled by `promote_unit` definitions. These are not readily extensible by the user at this point. ```@docs Unitful.promote_unit ``` ## Unit cancellation For multiplication and division, note that powers-of-ten prefixes are significant in unit cancellation. For instance, `mV/V` is not simplified, although `V/V` is. Also, `N*m/J` is not simplified: there is currently no logic to decide whether or not units on a dimensionless quantity seem "intentional" or not. It is however possible to cancel units manually, by converting the dimensionless quantity to the [`NoUnits`](@ref) unit. This takes into account different SI-prefixes: ```jldoctest julia> using Unitful julia> 1u"kN*m"/4u"J" |> NoUnits 250.0 ``` ================================================ FILE: docs/src/dates.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Interoperability with the `Dates` standard library [Julia's `Dates` standard library](https://docs.julialang.org/en/v1/stdlib/Dates/) provides data types for representing specific points in time `Date`/`DateTime` and differences between them, i.e., periods. Unitful provides methods for using period types from the `Dates` standard library together with `Quantity`s. ## Support for `Dates.FixedPeriod`s The `Dates.FixedPeriod` union type includes all `Dates.Period`s that represent a fixed period of time, i.e., `Dates.Week`, `Dates.Day`, `Dates.Hour`, `Dates.Minute`, `Dates.Second`, `Dates.Millisecond`, `Dates.Microsecond`, and `Dates.Nanosecond`. These types can be converted to `Quantity`s or used in place of them. !!! note `Dates.Year` does not represent a fixed period and cannot be converted to a `Quantity`. While Unitful's `yr` unit is exactly equal to 365.25 days, a `Dates.Year` may contain 365 or 366 days. Each `FixedPeriod` is considered equivalent to a `Quantity`. For example, `Dates.Millisecond(5)` corresponds to the quantity `Int64(5)*u"ms"`. A `FixedPeriod` can be converted to the equivalent `Quantity` with a constructor: ```@docs Unitful.Quantity(::Dates.FixedPeriod) ``` In most respects, `FixedPeriod`s behave like their equivalent quantities. They can be converted to other units using `uconvert`, used in arithmetic operations with other quantities, and they have a `unit` and `dimension`: ```jldoctest julia> using Dates: Hour julia> p = Hour(3) 3 hours julia> uconvert(u"s", p) 10800 s julia> p == 180u"minute" true julia> p < 1u"d" true julia> 5u"s" + p 10805 s julia> 210u"km" / p 70.0 km hr^-1 julia> unit(p) === u"hr" true julia> dimension(p) 𝐓 ``` Conversely, a `FixedPeriod` can be created from a quantity using the appropriate constructor, `convert`, or `round` methods. This will fail (i.e., throw an `InexactError`) if the resulting value cannot be represented as an `Int64`: ```jldoctest julia> using Dates: Day, Hour, Millisecond julia> Millisecond(1.5u"s") 1500 milliseconds julia> convert(Hour, 1u"yr") 8766 hours julia> Day(1u"yr") ERROR: InexactError: Int64(1461//4) [...] julia> round(Day, 1u"yr") 365 days ``` ## Support for `Dates.CompoundPeriod`s The `Dates` standard library provides the `Dates.CompoundPeriod` type to represent sums of periods of different types: ```@repl using Dates: Day, Second Day(5) + Second(1) typeof(ans) ``` Unitful provides facilities to work with `CompoundPeriod`s as long as they consist only of `FixedPeriod`s. Such `CompoundPeriod`s can be converted to `Quantity`s using `convert`, `uconvert`, or `round`: ```@jldoctest julia> using Dates: Day, Second julia> p = Day(5) + Second(1) 5 days, 1 second julia> uconvert(u"s", p) 432001//1 s julia> convert(typeof(1.0u"yr"), p) 0.01368928562374832 yr julia> round(u"d", p) 5//1 d julia> q = Month(1) + Day(1) # Month is not a fixed period 1 month, 1 day julia> uconvert(u"s", q) ERROR: MethodError: no method matching Quantity{Rational{Int64},𝐓,Unitful.FreeUnits{(s,),𝐓,nothing}}(::Month) [...] ``` However, not all operations that are defined for `FixedPeriod`s support `CompoundPeriod`s as well. The reason for that is that a `CompoundPeriod` does not correspond to a specific unit: ```@jldoctest julia> p = Day(365) + Hour(6) 365 days, 6 hours julia> unit(p) # A CompoundPeriod does not have a corresponding unit ... ERROR: MethodError: no method matching unit(::Dates.CompoundPeriod) [...] julia> dimension(p) # ... but it does have a dimension 𝐓 julia> Quantity(p) # As a result, there is no Quantity type associated with it ... ERROR: MethodError: no method matching Quantity(::Int64) [...] julia> T = typeof(1.0u"hr"); T(p) # ... but it can be converted to a concrete time quantity 8766.0 hr ``` Consequently, any operation whose result would depend on the input unit is not supported by `CompoundPeriod`s. For example: * `+(::Quantity, ::CompoundPeriod)` and `+(::CompoundPeriod, ::Quantity)` error, since the unit of the result depends on the units of both arguments. * `div(::Quantity, ::CompoundPeriod)` and `div(::CompoundPeriod, ::Quantity)` work, since the result is a dimensionless number. * `mod(::CompoundPeriod, ::Quantity)` works, but `mod(::Quantity, ::CompoundPeriod)` does not, since the second argument determines the unit of the returned quantity. ================================================ FILE: docs/src/defaultunits.md ================================================ # Pre-defined units and сonstants In the following, only non-prefixed units are listed. To get a more detailed information about a unit, and to get information about prefixed units, use `Julia` help, e.g. ``` help?> Unitful.kW Unitful.kW A prefixed unit, equal to 10^3 W. Dimension: 𝐋² 𝐌 𝐓⁻³ See also: Unitful.W. ``` For prefixes, see [below](#Metric-(SI)-Prefixes). ## Base dimensions ### Amount ```@docs Unitful.𝐍 Unitful.Amount Unitful.mol ``` ### Current ```@docs Unitful.𝐈 Unitful.Current Unitful.A ``` ### Length ```@docs Unitful.𝐋 Unitful.Length Unitful.angstrom Unitful.cm Unitful.fm Unitful.ft Unitful.inch Unitful.m Unitful.mi Unitful.mil Unitful.nm Unitful.yd ``` ### Luminosity ```@docs Unitful.𝐉 Unitful.Luminosity Unitful.cd Unitful.lm ``` ### Mass ```@docs Unitful.𝐌 Unitful.Mass Unitful.dr Unitful.g Unitful.gr Unitful.kg Unitful.lb Unitful.oz Unitful.slug Unitful.u ``` ### Temperature ```@docs Unitful.𝚯 Unitful.Temperature Unitful.K Unitful.Ra Unitful.°C Unitful.°F ``` ### Time ```@docs Unitful.𝐓 Unitful.Time Unitful.d Unitful.hr Unitful.minute Unitful.s Unitful.wk Unitful.yr ``` ## Derived dimensions ### Acceleration ```@docs Unitful.Acceleration Unitful.Gal Unitful.ge ``` ### Area ```@docs Unitful.Area Unitful.a Unitful.ac Unitful.b Unitful.ha ``` ### BField ```@docs Unitful.BField Unitful.Gauss Unitful.T ``` ### Capacitance ```@docs Unitful.Capacitance Unitful.F ``` ### Charge ```@docs Unitful.Charge Unitful.C ``` ### DynamicViscosity ```@docs Unitful.DynamicViscosity Unitful.P ``` ### ElectricalConductance ```@docs Unitful.ElectricalConductance Unitful.S ``` ### ElectricalResistance ```@docs Unitful.ElectricalResistance Unitful.Ω ``` ### Energy ```@docs Unitful.Energy Unitful.btu Unitful.cal Unitful.erg Unitful.eV Unitful.J ``` ### Force ```@docs Unitful.Force Unitful.dyn Unitful.lbf Unitful.N ``` ### Frequency ```@docs Unitful.Frequency Unitful.Bq Unitful.Hz Unitful.Hz2π Unitful.rpm Unitful.rps ``` ### HField ```@docs Unitful.HField Unitful.Oe ``` ### Inductance ```@docs Unitful.Inductance Unitful.H ``` ### KinematicViscosity ```@docs Unitful.KinematicViscosity Unitful.St ``` ### MagneticFlux ```@docs Unitful.MagneticFlux Unitful.Mx Unitful.Wb ``` ### MolarFlow ```@docs Unitful.MolarFlow Unitful.kat ``` ### Molarity ```@docs Unitful.Molarity Unitful.M ``` ### Power ```@docs Unitful.Power Unitful.W ``` ### Pressure ```@docs Unitful.Pressure Unitful.atm Unitful.Ba Unitful.bar Unitful.kPa Unitful.Pa Unitful.psi Unitful.Torr ``` ### Velocity ```@docs Unitful.Velocity Unitful.c ``` ### Voltage ```@docs Unitful.Voltage Unitful.V ``` ### Volume ```@docs Unitful.Volume Unitful.L ``` ## Dimensionless units ```@docs Unitful.° Unitful.pcm Unitful.percent Unitful.permille Unitful.pertenthousand Unitful.ppb Unitful.ppm Unitful.ppq Unitful.ppt Unitful.rad Unitful.sr ``` ## Logarithmic units | Unit | Name | |----------------|---------------------------------| | `dB` | Decibel | | `B` | Bel | | `Np` | Neper | | `cNp` | Centineper | ### Log units related to reference levels | Unit | Reference level | |----------------|---------------------------------| | `dBHz` | 1Hz | | `dBm` | 1mW | | `dBV` | 1V | | `dBu` | sqrt(0.6)V | | `dBμV` | 1μV | | `dBSPL` | 20μPa | | `dBFS` | RootPowerRatio(1) | | `dBΩ` | 1Ω | | `dBS` | 1S | ## Physical constants ```@docs Unitful.c0 Unitful.G Unitful.gn Unitful.h Unitful.k Unitful.me Unitful.mn Unitful.mp Unitful.Na Unitful.q Unitful.R Unitful.R∞ Unitful.Z0 Unitful.ħ Unitful.ε0 Unitful.μ0 Unitful.μB Unitful.σ Unitful.Φ0 ``` ## Metric (SI) Prefixes | Prefix | Name | Power of Ten | |--------|--------|--------| | y | yocto | -24 | | z | zepto | -21 | | a | atto | -18 | | f | femto | -15 | | p | pico | -12 | | n | nano | -9 | | μ | micro | -6 | | m | milli | -3 | | c | centi | -2 | | d | deci | -1 | | da | deca | 1 | | h | hecto | 2 | | k | kilo | 3 | | M | mega | 6 | | G | giga | 9 | | T | tera | 12 | | P | peta | 15 | | E | exa | 18 | | Z | zetta | 21 | | Y | yotta | 24 | ================================================ FILE: docs/src/display.md ================================================ # How units are displayed By default, exponents on units or dimensions are indicated using Unicode superscripts on macOS and without superscripts on other operating systems. You can set the environment variable `UNITFUL_FANCY_EXPONENTS` to either `true` or `false` to force using or not using the exponents. You can also set the `:fancy_exponent` IO context property to either `true` or `false` to force using or not using the exponents. ```@docs Unitful.BracketStyle Unitful.abbr Unitful.prefix Unitful.show(::IO, ::Quantity) Unitful.show(::IO, ::Unitful.Unitlike) Unitful.showrep Unitful.showval Unitful.superscript ``` ================================================ FILE: docs/src/extending.md ================================================ # Extending Unitful ## Making your own units package New units or dimensions can be defined from the Julia REPL or from within other packages. To avoid duplication of code and effort, it is advised to put new unit definitions into a Julia package that is then published for others to use. For an example of how to do this, examine the code in [`UnitfulUS.jl`](https://github.com/PainterQubits/UnitfulUS.jl), which defines U.S. customary units. It's actually very easy! Just make sure you read all of the cautionary notes on this page. If you make a units package for Unitful, please submit a pull request so that I can provide a link from Unitful's README! ## Some limitations ### Precompilation When creating new units in a precompiled package that need to persist into run-time (usually true), it is important that the following make it into your code: ```julia function __init__() Unitful.register(YourModule) end ``` By calling [`Unitful.register`](@ref) in your `__init__` function, you tell Unitful about some internal data required to make Unit conversions work and also make your units accessible to Unitful's [`@u_str`](@ref) macro. Your unit symbols should ideally be distinctive to avoid colliding with symbols defined in other packages or in Unitful. If there is a collision, the [`@u_str`](@ref) macro will still work, but it will use the unit found in whichever package was registered most recently, and it will emit a warning every time. If you use the `@u_str` macro with the units defined in your package, you'll also need to call `Unitful.register()` at the top level of your package at compile time. In the unlikely case that you've used `@dimension`, you will also need the following incantation: ```julia const localpromotion = copy(Unitful.promotion) function __init__() Unitful.register(YourModule) merge!(Unitful.promotion, localpromotion) end ``` The definition of `localpromotion` must happen *after all new units (dimensions) have been defined*. ### Type uniqueness Currently, when the [`@dimension`](@ref), [`@derived_dimension`](@ref), [`@refunit`](@ref), or [`@unit`](@ref) macros are used, some unique symbols must be provided which are used to differentiate types in dispatch. These are typically the names of dimensions or units (e.g. `Length`, `Meter`, etc.) One problem that could occur is that if multiple units or dimensions are defined with the same name, then they will be indistinguishable in dispatch and errors will result. I don't expect a flood of units packages to come out, so probably the likelihood of name collision is pretty small. When defining units yourself, do take care to use unique symbols, perhaps with the aid of `Base.gensym()` if creating units at runtime. When making packages, look and see what symbols are used by existing units packages to avoid trouble. ## Archaic or fictitious unit systems In the rare event that you want to define physical units which are not convertible to SI units, you need to do a bit of extra work. To be clear, such a conversion should always exist, in principle. One can imagine, however, archaic or fictitious unit systems for which a precise conversion to SI units is unknown. For example, a [cullishigay](https://en.wikipedia.org/wiki/Cullishigay) is one-third of a mudi, but only *approximately* 1.25 imperial bushels. There may be cases where you don't even have an approximate conversion to imperial bushels. At such a time, you may feel uncomfortable specifying the "base unit" of this hypothetical unit system in terms of an SI quantity, and may want to explicitly forbid any attempt to convert to SI units. One can achieve this by defining new dimensions with the [`@dimension`](@ref) or [`@derived_dimension`](@ref) macros. The trick is to define dimensions that display suggestively like physical dimensions, like `𝐋*`, `𝐓*` etc., but are distinct as far as Julia's type system is concerned. Then, you can use [`@refunit`](@ref) to base units for these new dimensions without reference to SI. The result will be that attempted conversion between the hypothetical unit system and SI will fail with a `DimensionError`, so be sure you provide some hints in how your new dimensions are displayed to avoid confusing users. It would be confusing to throw a `DimensionError` when attempting to convert between lengths which are incompatible in the sense of the previous paragraph, when both lengths display their dimension as `𝐋`. ================================================ FILE: docs/src/highlights.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Highlighted features ## Dispatch on dimensions Consider the following toy example, converting from voltage or power ratios to decibels: ```jldoctest julia> whatsit(x::Unitful.Voltage) = "voltage!" whatsit (generic function with 1 method) julia> whatsit(x::Unitful.Length) = "length!" whatsit (generic function with 2 methods) julia> whatsit(1u"mm") "length!" julia> whatsit(1u"kV") "voltage!" julia> whatsit(1u"A" * 2.5u"Ω") "voltage!" ``` ### Dimensions in a type definition It may be tempting to specify the dimensions of a quantity in a type definition, e.g. ```julia struct Person height::Unitful.Length mass::Unitful.Mass end ``` However, these are abstract types. If performance is important, it may be better just to pick a concrete `Quantity` type: ```julia struct Person height::typeof(1.0u"m") mass::typeof(1.0u"kg") end ``` You can still create a `Person` as `Person(5u"ft"+10u"inch", 75u"kg")`; the unit conversion happens automatically. ## Making new units and dimensions You can make new units using the [`@unit`](@ref) macro on the fly: ```jldoctest julia> @unit yd5 "yd5" FiveYards 5u"yd" false yd5 ``` ## Arrays Promotion is used to create arrays of a concrete type where possible, such that arrays of unitful quantities are stored efficiently in memory. However, if necessary, arrays can hold quantities with different dimensions, even mixed with unitless numbers. Doing so will suffer a performance penalty compared with the fast performance attainable with an array of concrete type (e.g. as resulting from `[1.0u"m", 2.0u"cm", 3.0u"km"]`). However, it could be useful in toy calculations for [general relativity](https://en.wikipedia.org/wiki/Metric_tensor_(general_relativity)) where some conventions yield matrices with mixed dimensions: ```jldoctest julia> using LinearAlgebra julia> Diagonal([-1.0u"c^2", 1.0, 1.0, 1.0]); ``` ## Logarithmic units ```jldoctest julia> uconvert(u"mW*s", 20u"dBm/Hz") 100.0 s mW ``` ## Units with rational exponents ```jldoctest julia> 1.0u"V/sqrt(Hz)" 1.0 V Hz^-1/2 ``` ## Exact conversions respected ```jldoctest julia> uconvert(u"ft",1u"inch") 1//12 ft ``` ================================================ FILE: docs/src/index.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Unitful.jl A Julia package for physical units. Available [here](https://github.com/JuliaPhysics/Unitful.jl). Inspired by: - [SIUnits.jl](https://github.com/keno/SIUnits.jl) - [EngUnits.jl](https://github.com/dhoegh/EngUnits.jl) - [Units.jl](https://github.com/timholy/Units.jl) We want to support not only SI units but also any other unit system. We also want to minimize or in some cases eliminate the run-time penalty of units. There should be facilities for dimensional analysis. All of this should integrate easily with the usual mathematical operations and collections that are defined in Julia. ## Quick start - This package requires Julia 1.0. Older versions will not be supported. - `] add Unitful` - `using Unitful` Unitful aims for generality, but has some useful functionality out of the box. - Base dimensions like length, mass, time, etc. are defined. - Derived dimensions like volume, energy, momentum, etc. are defined. - Base and derived SI units with their power-of-ten prefixes are defined. - Some other common units are defined, without power-of-ten prefixes. - Sensible default promotion behavior is specified. Take a look at `src/pkgdefaults.jl` for a complete list. Note that some unit abbreviations conflict with other definitions or syntax: - `inch` is used instead of `in`, since `in` conflicts with Julia syntax - `minute` is used instead of `min`, since `min` is a commonly used function - `hr` is used instead of `h`, since `h` is revered as the Planck constant - `hbar` is hectobars in the SI system, so `ħ` is used for the reduced Plank constant ## Important note on namespaces Units, dimensions, and fundamental constants are not exported from Unitful. This is to avoid proliferating symbols in your namespace unnecessarily. You can retrieve them from Unitful in one of three ways: 1. Use the [`@u_str`](@ref) string macro. 2. Explicitly import from the `Unitful` package to bring specific symbols into the calling namespace. 3. `using Unitful.DefaultSymbols` will bring the following symbols into the calling namespace: - Dimensions `𝐋,𝐌,𝐓,𝐈,𝚯,𝐉,𝐍` for length, mass, time, current, temperature, luminosity, and amount, respectively. - Base and derived SI units, with SI prefixes (except for `cd`, which conflicts with `Base.cd`) - `°` (degrees) If you have been using the [SIUnits.jl](https://github.com/keno/SIUnits.jl) package, this is not unlike typing `using SIUnits.ShortUnits` with that package. ## Usage examples ```@meta DocTestSetup = quote using Unitful °C = Unitful.°C °F = Unitful.°F Ra = Unitful.Ra K = Unitful.K μm = Unitful.μm m = Unitful.m hr = Unitful.hr minute = Unitful.minute s = Unitful.s F = Unitful.F end ``` ```jldoctest julia> 1u"kg" == 1000u"g" # Equivalence implies unit conversion true julia> !(1u"kg" === 1000u"g") # ...and yet we can distinguish these... true julia> 1u"kg" === 1u"kg" # ...and these are indistinguishable. true ``` In the next examples we assume we have brought some units into our namespace, e.g. `const m = u"m"`, etc. ```jldoctest julia> uconvert(°C, 212°F) 100//1 °C julia> uconvert(μm/(m*Ra), 9μm/(m*K)) 5//1 μm m^-1 Ra^-1 julia> mod(1hr+3minute+5s, 24s) 17 s ``` One useful interactive function is being able to convert to preferred (in this case SI) units. ```jldoctest julia> upreferred(F/m) A^2 s^4 kg^-1 m^-3 ``` !!! note Quantities in `°C` or `⁠°F` always unit-convert under an affine transformation that takes their relative scales into account. To avoid ambiguities that can lead to incorrect results, the units `°C` and `°F` cannot be used in Unitful to represent temperature differences. Fortunately, `1°C - 0°C == 1K` and `1°F - 0°F == 1Ra`, so the absolute temperature scales Kelvin (`K`) and Rankine (`Ra`) can be used easily to represent temperature differences. See `test/runtests.jl` for more usage examples. ## About the logo The logo is a pictorial representation of the [International Prototype of the Kilogram](https://en.wikipedia.org/wiki/International_Prototype_of_the_Kilogram), which was the standard definition of one kilogram from 1889 to 2019, when it was replaced by a definition based on the Planck constant, the speed of light, and the ground-state hyperfine transition frequency of ¹³³Cs. ================================================ FILE: docs/src/latexify.md ================================================ # Latexify extension Unitful has an extension for [Latexify](https://github.com/korsbo/Latexify.jl), which was formerly implemented as a separate package called UnitfulLatexify.jl. The default usage is pretty intuitive: ```@setup main using LaTeXStrings # for some manual pretty-printing ``` ```@example main using Unitful, Latexify a = 9.82u"m/s^2" t = 4u"s" x = a*t^2 latexify(x) ``` or more usefully: ```@example main latexify(:(x = a*t^2 = $x)) ``` This of course also works for `Units` objects by themselves: ```@example main latexify(u"kg*m") ``` Some more usage examples: ![](assets/latex-examples.png) ## Arrays Because Latexify is recursive, an array of unitful quantities is shown as expected: ```@example main latexify([12u"m", 1u"m^2", 4u"m^3"]) LaTeXString("\$" * chopsuffix(chopprefix(ans, "\\begin{equation}\n"), "\n\\end{equation}\n") * "\$") # hide ``` A special case is an array where all elements have the same unit, and here the extension does some extra work: ```@example main latexify([1, 2, 3]u"cm") LaTeXString("\$" * chopsuffix(chopprefix(ans, "\\begin{equation}\n"), "\n\\end{equation}\n") * "\$") # hide ``` ## siunitx.sty If you are exporting your numbers to an actual LaTeX document, you will of course want to use the commands from `siunitx.sty` rather than the `\mathrm` style used by default. To this end you can use Latexify's `fmt=SiunitxNumberFormatter` for `\qty{8}{\second\meter\per\kilo\gram}` style and `fmt=SiunitxNumberFormatter(simple=true)` for `\qty{8}{s.m/kg}`. Like other Latexify keywords, this can be set to be a default by using `set_default(fmt=SiunitxNumberFormatter())`, or given with each latexification command: ```@example main latexify(612.2u"nm"; fmt=SiunitxNumberFormatter()) # This will not render right without the `siunitx` package print(ans) # hide ``` ### Lists Another thing that `siunitx` does uniquely is lists and ranges of quantities. To get `siunitx`'s list behavior, pass a tuple instead of an array; if you want a tuple to be written as an array instead, use `collect(x)` or `[x...]` to convert it into an array first. ```@example main latexify((1, 2, 3).*u"m") print(ans) # hide ``` ```@example main latexify((1, 2, 3).*u"m"; fmt=SiunitxNumberFormatter()) print(ans) # hide ``` ```@example main latexify(collect((1, 2, 3).*u"m"); fmt=SiunitxNumberFormatter()) print(ans) # hide ``` ## Plots labels This extension also interfaces with `Plots` by way of implementing a two-argument `(label, unit)` recipe: ```@example main latexify("v", u"km/s") ``` This enables this dreamlike example: ```@example plot using Unitful, Plots, Latexify gr() default(fontfamily="Computer Modern") m = randn(10)u"kg" v = randn(10)u"m/s" plot(m, v; xguide="\\mathrm{mass}", yguide="v_x", unitformat=latexify) ``` This format, ``v_x\;\left/\;\mathrm{m}\,\mathrm{s}^{-1}\right.``, is subject to personal preference. A couple other defaults are provided: - `:slash`, ``v_x\;\left/\;\mathrm{m}\,\mathrm{s}^{-1}\right.`` - `:round`, ``v_x\;\left(\mathrm{m}\,\mathrm{s}^{-1}\right)`` - `:square`, ``v_x\;\left[\mathrm{m}\,\mathrm{s}^{-1}\right]`` - `:frac`, ``\frac{v_x}{\mathrm{m}\,\mathrm{s}^{-1}}`` To use these in a plot call, either pass a function like ``` (l,u) -> latexify(l, u; labelformat=:slash)` ``` or call `Latexify.set_default(labelformat=:square)`, then pass `latexify` as your unitformat. ```@example plot args = (m, v) kwargs = (xguide="\\mathrm{mass}", yguide="v_x", legend=false) Latexify.set_default(labelformat=:square) plot( plot(args...; kwargs..., unitformat=(l,u)->latexify(l, u, labelformat=:slash)), plot(args...; kwargs..., unitformat=(l, u)->latexify(l, u, labelformat=:round)), plot(args...; kwargs..., unitformat=latexify), plot(args...; kwargs..., unitformat=(l, u)->latexify(l, u, labelformat=:frac)), plot(args...; kwargs..., unitformat=(l, u)->string("\$", l, " \\rightarrow ", latexraw(u), "\$")), ) ``` ## Pluto notebooks One use case is in Pluto notebooks, where you can write ```julia Markdown.parse(""" The period is $(@latexrun T = $(2.5u"ms")), so the frequency is $(@latexdefine f = 1/T post=u"kHz"). """) ``` which renders as > The period is $T = 2.5\;\mathrm{ms}$, so the frequency is $f = \frac{1}{T} = 0.4\;\mathrm{kHz}$. Note that the quantity has to be interpolated (put inside a dollar-parenthesis), or Latexify will interpret it as a multiplication between a number and a call to `@u_str`. ## Per-modes In mathrm-mode, one might prefer ``\mathrm{J}\,/\,\mathrm{kg}`` or ``\frac{\mathrm{J}}{\mathrm{kg}}`` over ``\mathrm{J}\,\mathrm{kg}^{-1}``. This can be achieved by supplying `permode=:slash` or `permode=:frac` respectively, rather than the default `permode=:power`. These will have no effect with `SiunitxNumberFormatter`, because the latex package handles this for you, and you can set it in your document. ## New siunitx syntax The new syntax from `siunitx v3` (`\qty, \unit` rather than `\SI, \si`) is used by default. If you cannot upgrade `siunitx`, there's the option to use `fmt=SiunitxNumberFormatter(version=2)`. ## A more complete list of defined units Below is a poorly scraped list of units defined in `Unitful` and what comes out if you run it through `latexify`. Feel free to create an issue if there's a unit missing or being incorrectly rendered (and suggest a better ``\LaTeX`` representation if you know one). ![](assets/latex-allunits.png) ================================================ FILE: docs/src/logarithm.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Logarithmic scales !!! note Logarithmic scales should be considered experimental because they break some of the basic assumptions about equality and hashing (see [#402](https://github.com/JuliaPhysics/Unitful.jl/issues/402)) Unitful provides a way to use logarithmically-scaled quantities. Some compromises have been made in striving for logarithmic quantities to be both usable and consistent. In the following discussion, for pedagogical purposes, we will assume prior familiarity with the definitions of `dB` and `dBm`. ## Constructing logarithmic quantities Left- or right-multiplying a pure number by a logarithmic "unit", whether dimensionful or dimensionless, is short-hand for constructing a logarithmic quantity. ```jldoctest julia> 3u"dB" 3 dB julia> 3u"dBm" 3.0 dBm julia> u"dB"*3 === 3u"dB" true ``` Currently implemented are `dB`, `B`, `dBm`, `dBV`, `dBu`, `dBμV`, `dBSPL`, `dBFS`, `cNp`, `Np`. One can also construct logarithmic quantities using the `@dB`, `@B`, `@cNp`, `@Np` macros to use an arbitrary reference level: ```jldoctest julia> using Unitful: mW, V julia> @dB 10mW/mW 10.0 dBm julia> @dB 10V/V 20.0 dBV julia> @dB 3V/4V -2.498774732165999 dB (4 V) julia> @Np ℯ*V/V # ℯ = 2.71828... 1.0 Np (1 V) ``` These macros are exported by default since empirically macros are defined less often than variables and generic functions. When using the macros, the levels are constructed at parse time. The scales themselves are callable as functions if you need to construct a level that way (they are not exported): ```jldoctest julia> using Unitful: dB, mW, V julia> dB(10mW,mW) 10.0 dBm ``` In calculating the logarithms, the log function appropriate to the scale in question is used (`log10` for decibels, `log` for nepers). There is an important difference in these two approaches to constructing logarithmic quantities. When we construct `0dBm`, the power in `mW` is calculated and stored, resulting in a lossy floating-point conversion. This can be avoided by constructing `0 dBm` as `@dB 1mW/mW`. It is important to keep in mind that the reference level is just used to calculate the logarithms, and nothing more. When there is ambiguity about what to do, we fall back to the underlying linear quantities, paying no mind to the reference levels: ```jldoctest julia> using Unitful: mW julia> (@dB 10mW/1mW) + (@dB 10mW/2mW) 20 mW ``` Addition will be discussed more later. Note that logarithmic "units" can only multiply or be multiplied by pure numbers and linear units, not other logarithmic units or quantities. This is done to avoid issues with commutativity and associativity, e.g. `3*dB*m^-1 == (3dB)/m`, but `3*m^-1*dB == (3m^-1)*dB` does not make much sense. This is because `dB` acts more like a constructor than a proper unit. The `@dB` and `@Np` macros will fail if either a dimensionless number or a ratio of dimensionless numbers is used. This is because the ratio could be of power quantities or of root-power quantities, leading to ambiguities. After all, usually it is the ratio that is dimensionless, not the numerator and denominator that make up the ratio. In some cases it may nonetheless be convenient to have a dimensionless reference level. By providing an extra `Bool` argument to these macros, you can explicitly choose whether the resulting ratio should be considered a "root-power" or "power" ratio. You can only do this for dimensionless numbers: ```jldoctest julia> @dB 10/1 true # is a root-power (amplitude) ratio 20.0 dBFS julia> @dB 10/1 false # is not a root-power ratio; is a power ratio 10.0 dB (power ratio with reference 1) ``` Note that `dBFS` is defined to represent amplitudes relative to 1 in `dB`, hence the custom display logic. Also, you can of course use functions instead of macros: ```jldoctest julia> using Unitful: dB, mW julia> dB(10,1,true) 20.0 dBFS julia> dB(10mW,mW,true) ERROR: ArgumentError: when passing a final Bool argument, this can only be used with dimensionless numbers. [...] ``` ### Logarithmic quantities with no reference level specified Logarithmic quantities with no reference level specified typically represent some amount of gain or attenuation, i.e. a ratio which is dimensionless. These can be constructed as, for example, `10*dB`, which displays similarly (`10 dB`). The type of this kind of logarithmic quantity is: ```@docs Unitful.Gain ``` One might expect that any gain / attenuation factor should be convertible to a pure number, that is, to `x == y/z` if you had `10*log10(x)` dB. However, it turns out that in dB, a ratio of powers is defined as `10*log10(y/z)`, but a ratio of voltages or other root-power quantities is defined as `20*log10(y/z)`. Clearly, converting back from decibels to a real number is ambiguous, and so we have not implemented automatic promotion to avoid incorrect results. You can use [`Unitful.uconvertp`](@ref) to interpret a `Gain` as a ratio of power quantities (hence the `p` in `uconvertp`), or [`Unitful.uconvertrp`](@ref) to interpret as a ratio of root-power (field) quantities. ### "Dimensionful" logarithmic quantities? In this package, quantities with units like `dBm` are considered to have the dimension of power, even though the expression `P(dBm) = 10*log10(P/1mW)` is dimensionless and formed from a dimensionless ratio. Practically speaking, these kinds of logarithmic quantities are fungible whenever they share the same dimensions, so it is more convenient to adopt this convention (people refer to `dBm/Hz` as a power spectral density, etc.) Presumably, one would like to have `10dBm isa Unitful.Power` for dispatch too. Therefore, in the following discussion, we will shamelessly (okay, with some shame) speak of dimensionful logarithmic quantities, or `Level`s for short: ```@docs Unitful.Level ``` Actually, the defining characteristic of a `Level` is that it has a reference level, which may or may not be dimensionful. It usually is, but is not in the case of e.g. `dBFS`. Finally, for completeness we note that both `Level` and `Gain` are subtypes of `LogScaled`: ```@docs Unitful.LogScaled ``` ## Multiplication rules Multiplying a dimensionless logarithmic quantity by a pure number acts as like it does for linear quantities: ```jldoctest julia> 3u"dB" * 2 6 dB julia> 2 * 0u"dB" 0 dB ``` Justification by example: consider the example of the exponential attenuation of a signal on a lossy transmission line. If the attenuation goes like $10^{-kx}$, then the (power) attenuation in dB is $-10kx$. We see that the attenuation in dB is linear in length. For an attenuation constant of 3dB/m, we better calculate 6dB for a length of 2m. Multiplying a dimensionful logarithmic quantity by a pure number acts differently than multiplying a gain/attenuation by a pure number. Since `0dBm == 1mW`, we better have that `0dBm * 2 == 2mW`, implying: ```jldoctest julia> 0u"dBm" * 2 3.010299956639812 dBm ``` Logarithmic quantities can only be multiplied by pure numbers, linear units, or quantities, but not logarithmic "units" or quantities. When a logarithmic quantity is multiplied by a linear quantity, the logarithmic quantity is linearized and multiplication proceeds as usual: ```jldoctest julia> (0u"dBm") * (1u"W") 1.0 mW W ``` The previous example returns a floating point value because in constructing the level `0 dBm`, the power in `mW` is calculated and stored, entailing a floating point conversion. This can be avoided by constructing `0 dBm` as `@dB 1mW/mW`: ```jldoctest julia> (@dB 1u"mW"/u"mW") * (1u"W") 1 mW W ``` We refer to a quantity with both logarithmic "units" and linear units as a mixed quantity. For mixed quantities, the numeric value associates with the logarithmic unit, and the quantity is displayed in a way that makes this explicit: ```jldoctest julia> (0u"dBm")/u"Hz" [0.0 dBm] Hz^-1 julia> (0u"dB")/u"Hz" [0 dB] Hz^-1 julia> 0u"dB/Hz" [0 dB] Hz^-1 ``` Mathematical operations are forwarded to the logarithmic part, so that for example, `100*((0dBm)/s) == (20dBm)/s`. We allow linear units to commute with logarithmic quantities for convenience, though the association is understood (e.g. `s^-1*(3dBm) == (3dBm)/s`). The behavior of multiplication is summarized in the following table, with entries marked by † indicating prohibited operations. ```@eval using Latexify, Unitful head = ["10", "Hz^-1", "dB", "dBm", "1/Hz", "1mW", "3dB", "3dBm"] side = ["*"; "**" .* head .* "**"] quantities = uparse.(head) tab = fill("", length(head), length(head)) for col = eachindex(head), row = 1:col try tab[row, col] = sprint(show, quantities[row] * quantities[col], context = :compact => true) catch if quantities[row] === u"1/Hz" && quantities[col] === u"3dB" tab[row, col] = "† ‡" else tab[row, col] = "†" end end end mdtable(tab, latex=false, head=head, side=side) ``` ‡: `1/Hz * 3dB` could be allowed, technically, but we throw an error if it's unclear whether a quantity is a root-power or power quantity: ```jldoctest julia> u"1/Hz" * u"3dB" ERROR: undefined behavior. Please file an issue with the code needed to reproduce. ``` On the other hand, if it can be determined that a power quantity or root-power quantity is being multiplied by a gain, then the gain is interpreted as a power ratio or root-power ratio, respectively: ```jldoctest julia> 1u"mW" * 20u"dB" 100.0 mW julia> 1u"V" * 20u"dB" 10.0 V ``` ## Addition rules We can add logarithmic quantities without reference levels specified (`Gain`s): ```jldoctest julia> 20u"dB" + 20u"dB" 40 dB ``` The numbers out front of the `dB` just add: when we talk about gain or attenuation, we work in logarithmic units so that we can add rather than multiply gain factors. The same behavior holds when we add a `Gain` to a `Level` or vice versa: ```jldoctest julia> 20u"dBm" + 20u"dB" 40.0 dBm ``` In the case where you have differing logarithmic scales for the `Level` and the `Gain`, the logarithmic scale of the `Level` is used for the result: ```jldoctest julia> 10u"dBm" - 1u"Np" 1.3141103619349632 dBm ``` For logarithmic quantities with the same reference levels, the numbers out in front do not simply add: ```jldoctest julia> 20u"dBm" + 20u"dBm" 23.010299956639813 dBm julia> 2 * 20u"dBm" 23.010299956639813 dBm ``` This is because `dBm` represents a power, ultimately. If we have some amount of power and we double it, we'd better get roughly `3 dB` more power. Note that the juxtaposition `20dBm` will ensure that 20 dBm is constructed before multiplication by 2 in the above example. If you were to type `2*20*dBm`, you'd get 40 dBm. If the reference levels differ but both levels represent a power, we fall back to linear quantities: ```jldoctest julia> 20u"dBm" + @dB 1u"W"/u"W" 1.1 kg m^2 s^-3 ``` i.e. `1.1 W`. Rules for addition are summarized in the following table, with entries marked by † indicating prohibited operations. ```@eval using Latexify, Unitful head = ["100", "20dB", "1Np", "10.0dBm", "10.0dBV", "1mW"] side = ["+"; "**" .* head .* "**"] quantities = uparse.(head) tab = fill("", length(head), length(head)) for col = eachindex(head), row = 1:col try tab[row, col] = sprint(show, quantities[row] + quantities[col], context = :compact => true) catch tab[row, col] = "†" end end mdtable(tab, latex=false, head=head, side=side) ``` Notice that we disallow implicit conversions between dimensionless logarithmic quantities and real numbers. This is because the results can depend on promotion rules in addition to being ambiguous because of the root-power vs. power ratio issue. If `100 + 10dB` were evaluated as `20dB + 10dB == 30dB`, then we'd get `1000`, but if it were evaluated as `100+10`, we'd get `110`. Also, although it is possible in principle to add e.g. `20dB + 1Np`, notice that we have not implemented that because it is unclear whether the result should be in nepers or decibels, and it is also unclear how to handle that question more generally as other logarithmic scales are introduced. ## Conversion As alluded to earlier, conversions can be tricky because so-called logarithmic units are not units in the conventional sense. You may use [`linear`](@ref) to convert to a linear scale when you have a `Level` or `Quantity{<:Level}` type. There is a fallback for `Number`, which just returns the number. ```jldoctest julia> linear(@dB 10u"mW"/u"mW") 10 mW julia> linear(20u"dBm/Hz") 100.0 mW Hz^-1 julia> linear(30u"W") 30 W julia> linear(12) 12 ``` Linearizing a `Quantity{<:Gain}` or a `Gain` to a real number is ambiguous, because the real number may represent a ratio of powers or a ratio of root-power (field) quantities. We implement [`Unitful.uconvertp`](@ref) and [`Unitful.uconvertrp`](@ref) which may be thought of as disambiguated `uconvert` functions. There is a one argument version that assumes you are converting to a unitless number. These functions can take either a `Gain` or a `Real` so that they may be used somewhat generically. ```jldoctest julia> uconvertrp(NoUnits, 20u"dB") 10.0 julia> uconvertp(NoUnits, 20u"dB") 100.0 julia> uconvertp(u"dB", 100) 20.0 dB julia> uconvertp(u"Np", ℯ^2) 1.0 Np julia> uconvertrp(u"Np", ℯ) 1//1 Np ``` ## Notation This package displays logarithmic quantities using shorthand like `dBm` where available. This should probably not be done in polite company. To quote "Guide for the Use of the International System of Units (SI)," NIST Special Pub. 811 (2008): > The rules of Ref. [5: IEC 60027-3] preclude, for example, the use of the symbol dBm to > indicate a reference level of power of 1 mW. This restriction is based on the rule of Sec. > 7.4, which does not permit attachments to unit symbols. The authorities say the reference level should always specified. In practice, this hasn't stopped the use of `dBm` and the like on commercially available test equipment. Dealing with these units is unavoidable in practice. When no shorthand exists, we follow NIST's advice in displaying logarithmic quantities: > When such data are presented in a table or in a figure, the following condensed notation > may be used instead: -0.58 Np (1 μV/m); 25 dB (20 μPa). ## Custom logarithmic scales ```@docs Unitful.@logscale ``` ## API ```@docs Unitful.linear Unitful.logunit Unitful.reflevel Unitful.uconvertp Unitful.uconvertrp ``` ================================================ FILE: docs/src/manipulations.md ================================================ ```@meta DocTestSetup = quote using Unitful using InverseFunctions end ``` # Manipulating units ## Unitful string macro ```@docs Unitful.@u_str Unitful.register ``` ## Dimension and unit inspection We define a function [`dimension`](@ref) that turns, for example, `acre^2` or `1*acre^2` into `𝐋^4`. We can usually add quantities with the same dimension, regardless of specific units (`FixedUnits` cannot be automatically converted, however). Note that dimensions cannot be determined by powers of the units: `ft^2` is an area, but so is `ac^1` (an acre). There is also a function [`unit`](@ref) that turns, for example, `1*acre^2` into `acre^2`. You can then query whether the units are `FreeUnits`, `FixedUnits`, etc. ```@docs Unitful.unit Unitful.dimension ``` ## Unit stripping ```@docs Unitful.ustrip ``` ## Unit multiplication ```@docs *(::Unitful.Units, ::Unitful.Units...) *(::Unitful.Dimensions, ::Unitful.Dimensions...) ``` ================================================ FILE: docs/src/newunits.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Defining new units !!! note Logarithmic units should not be used in the `@refunit` or `@unit` macros described below. See the section on logarithmic scales for customization help. The package automatically generates a useful set of units and dimensions in the `Unitful` module in `src/pkgdefaults.jl`. If a different set of default units or dimensions is desired, macros for generating units and dimensions are provided. To create new units interactively, most users will be happy with the [`@unit`](@ref) macro and the [`Unitful.register`](@ref) function, which makes units defined in a module available to the [`@u_str`](@ref) string macro. An example of defining units in a module: ```jldoctest julia> module MyUnits; using Unitful; @unit myMeter "m" MyMeter 1u"m" false; end MyUnits julia> using Unitful julia> u"myMeter" ERROR: LoadError: [...] julia> Unitful.register(MyUnits); julia> u"myMeter" m ``` You could have also called `Unitful.register` inside the `MyUnits` module; the choice is somewhat analogous to whether or not to export symbols from a module, although the symbols are never really exported, just made available to the `@u_str` macro. If you want to make a precompiled units package, rather than define a module at the REPL, see [Making your own units package](@ref). You can also define units directly in the `Main` module at the REPL: ```jldoctest julia> using Unitful julia> Unitful.register(@__MODULE__); julia> @unit M "M" Molar 1u"mol/L" true; julia> 1u"mM" 1 mM ``` A note for the experts: Some care should be taken if explicitly creating [`Unitful.Units`](@ref) objects. The ordering of [`Unitful.Unit`](@ref) objects inside a tuple matters for type comparisons. Using the unary multiplication operator on the `Units` object will return a "canonically sorted" `Units` object. Indeed, this is how we avoid ordering issues when multiplying quantities together. ## Defining units in precompiled packages See [Precompilation](@ref). ## Useful functions and macros ```@docs Unitful.@dimension Unitful.@derived_dimension Unitful.@refunit Unitful.@unit Unitful.@affineunit ``` ## Internals ```@docs Unitful.@prefixed_unit_symbols Unitful.@unit_symbols Unitful.basefactor ``` ================================================ FILE: docs/src/temperature.md ================================================ ```@meta DocTestSetup = quote using Unitful using Unitful:AffineError end ``` # Temperature scales Temperatures require some care. Temperature scales like `K` and `Ra` are thermodynamic temperature scales, with zero on the scale corresponding to absolute zero. Unit conversions between thermodynamic or absolute temperatures are done by multiplying conversion factors, as usual. Also in common use are temperature scales like `°C` or `°F`, which are defined relative to arbitrary offsets. For example, in the case of `°C`, zero on the scale is the freezing point of water, not absolute zero. To convert between relative temperature scales, an affine transformation is required. Absolute and relative temperatures can be distinguished by type to avoid ambiguities that could yield erroneous or unexpected results. On relative temperature scales, problems can arise because e.g. `0°C + 0°C` could mean `0°C` or `273.15°C`, depending on whether the operands are variously interpreted as temperature differences or as absolute temperatures. On thermodynamic temperature scales, there is no ambiguity. ## Temperatures on absolute scales Unit conversions between temperatures on absolute scales like Kelvin or Rankine are done in the usual way by multiplication of a scale factor. For example, we have: ```jldoctest julia> uconvert(u"K", 1u"Ra") 5//9 K ``` We can identify absolute temperatures using the `Unitful.AbsoluteScaleTemperature` type alias: ```jldoctest julia> 1u"K" isa Unitful.AbsoluteScaleTemperature true ``` ## Temperatures on relative scales Unit conversions between temperatures on relative scales like Celsius or Fahrenheit involve an affine transformation, that is, a scaling plus some translation (scale offset). In Unitful, relative scale temperatures are considered to have the same dimension as absolute scale temperatures, as expected. However, temperatures on relative and absolute scales are distinguished by the type of the [`Unitful.Units`](@ref) object (and therefore the type of the [`Unitful.Quantity`](@ref) object). ```jldoctest julia> uconvert(u"°C", 32u"°F") 0//1 °C ``` We can identify relative scale temperatures using the `Unitful.RelativeScaleTemperature` type alias, e.g.: ```jldoctest julia> 1u"°C" isa Unitful.RelativeScaleTemperature true ``` Some operations are not well defined with relative scale temperatures, and therefore throw an `Unitful.AffineError` (please report any unexpected behavior on the GitHub issue tracker). ```jldoctest julia> 32u"°F" + 1u"°F" ERROR: AffineError: an invalid operation was attempted with affine quantities: 32 °F + 1 °F [...] julia> 32u"°F" * 2 ERROR: AffineError: an invalid operation was attempted with affine quantities: 32 °F*2 [...] ``` There is a general mechanism for making units that indicate quantities should unit-convert under some affine transformation. While the usual use case is for relative scale temperatures, nothing in the implementation limits it as such. Accordingly, relative scale temperatures are considered to be [`Unitful.AffineQuantity`](@ref) objects with dimensions of temperature. The units on "affine quantities" are [`Unitful.AffineUnits`](@ref) objects. Making your own affine units typically requires two steps. First, define the absolute unit using the [`Unitful.@unit`](@ref) macro. Second, use the [`Unitful.@affineunit`](@ref) macro to make a corresponding affine unit. As an example, this is how `Ra` and `°F` are implemented: ```julia @unit Ra "Ra" Rankine (5//9)*K false @affineunit °F "°F" (45967//100)Ra ``` The preferred unit for promoting temperatures is usually `K` when using [`Unitful.FreeUnits`](@ref). ```@docs Unitful.AffineUnits Unitful.AffineQuantity Unitful.ScalarUnits Unitful.ScalarQuantity Unitful.absoluteunit ``` ================================================ FILE: docs/src/trouble.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Troubleshooting ## Why do unit conversions yield rational numbers sometimes? We use rational numbers in this package to permit exact conversions between different units where possible. As an example, one inch is exactly equal to 2.54 cm. However, in Julia, the floating-point `2.54` is not equal to `254//100`. As a consequence, `1inch != 2.54cm`, because Unitful respects exact conversions. To test for equivalence, instead use `≈` (`\approx` tab-completion). ### But I want a floating point number... `float(x)` is defined for [`Unitful.Quantity`](@ref) types, and is forwarded to the underlying numeric type (units are not affected). We may consider adding an option in the defaults to turn on/off use of `Rational` numbers. They permit exact conversions, but they aren't preferred as a result type in much of Julia Base (consider that `inv(2) === 0.5`, not `1//2`). ## Exponentiation Most operations with this package should in principle suffer little performance penalty if any at run time. An exception to this is rule is exponentiation. Since units and their powers are encoded in the type signature of a [`Unitful.Quantity`](@ref) object, raising a `Quantity` to some power, which is just some run-time value, necessarily results in different result types. This type instability could impact performance: ```jldoctest julia> square(x) = (p = 2; x^p) square (generic function with 1 method) ``` In Julia, constant literal integers are lowered specially for exponentiation. (See Julia PR [#20530](https://github.com/JuliaLang/julia/pull/20530) for details.) In this case, type stability can be maintained: ```jldoctest julia> square(x) = x^2 square (generic function with 1 method) ``` Because the functions `inv`, `sqrt`, and `cbrt` are raising a `Quantity` to a fixed power (-1, 1/2, and 1/3, respectively), we can use a generated function to ensure type stability in these cases. Also note that squaring a `Quantity` can be type-stable if done as `x*x`. ## Promotion with dimensionless numbers Most of the time, you are only permitted to do sensible operations in Unitful. With dimensionless numbers, some of the safe logic breaks down. Consider for instance that `μm/m` and `rad` are both dimensionless units, but kind of have nothing to do with each other. It would be a little weird to add them. Nonetheless, we permit this to happen since they have the same dimensions. Otherwise, we would have to special-case operations for two dimensionless quantities rather than dispatching on the empty dimension. The result of addition and subtraction with dimensionless but unitful numbers is always a pure number with no units. With angles, `1 rad` is essentially just `1`, giving sane behavior: ```jldoctest julia> π/2*u"rad"+90u"°" 3.141592653589793 ``` ## Broken display of dimension characters in the REPL On some terminals with some fonts, dimension characters such as `𝐌` are displayed as an empty box. Setting a wider font spacing in your terminal settings can solve this problem. ## I have a different problem Please raise an issue. This package is in development and there may be bugs. Feature requests may also be considered and pull requests are welcome. ================================================ FILE: docs/src/types.md ================================================ ```@meta DocTestSetup = quote using Unitful end ``` # Types ## Overview We define a [`Unitful.Unit{U,D}`](@ref) type to represent a unit (`U` is a symbol, like `:Meter`, and `D` keeps track of dimensional information). Fields of a `Unit` object keep track of a rational exponents and a power-of-ten prefix. We don't allow arbitrary floating point exponents of units because they probably aren't very useful. The prefixes on units (e.g. `nm` or `km`) may help to avoid overflow issues and general ugliness. Usually, the user interacts only with `Units` objects, not `Unit` objects. This is because generically, more than one unit is needed to describe a quantity. An abstract type [`Unitful.Units{N,D,A}`](@ref) is defined, where `N` is always a tuple of `Unit` objects, `D` is a [`Unitful.Dimensions{N}`](@ref) object such as `𝐋`, the object representing the length dimension, and `A` is a translation for affine quantities. Subtypes of `Unitful.Units{N,D,A}` are used to implement different behaviors for how to promote dimensioned quantities. The concrete subtypes have no fields and are therefore immutable singletons. Currently implemented subtypes of `Unitful.Units{N,D,A}` include [`Unitful.FreeUnits{N,D,A}`](@ref), [`Unitful.ContextUnits{N,D,P,A}`](@ref), and [`Unitful.FixedUnits{N,D,A}`](@ref). Units defined in the Unitful.jl package itself are all `Unitful.FreeUnits{N,D,A}` objects. Finally, we define physical quantity types as [`Quantity{T<:Number, D, U}`](@ref), where `D :: Dimensions` and `U <: Units`. By putting units in the type signature of a quantity, staged functions can be used to offload as much of the unit computation to compile-time as is possible. By also having the dimensions explicitly in the type signature, dispatch can be done on dimensions: `isa(1u"m", Unitful.Length) == true`. This works because `Length` is a type alias for some subset of `Unitful.Quantity` subtypes. ## API ### Quantities ```@docs Unitful.AbstractQuantity Unitful.Quantity Unitful.DimensionlessQuantity ``` ### Units and dimensions ```@docs Unitful.Unitlike Unitful.Units Unitful.FreeUnits Unitful.ContextUnits Unitful.FixedUnits Unitful.Dimensions Unitful.Unit Unitful.Dimension Unitful.NoDims ``` ================================================ FILE: ext/ConstructionBaseUnitfulExt.jl ================================================ module ConstructionBaseUnitfulExt using Unitful import ConstructionBase: constructorof constructorof(::Type{Unitful.Quantity{_,D,U}}) where {_,D,U} = Unitful.Quantity{T,D,U} where T end ================================================ FILE: ext/ForwardDiffExt.jl ================================================ module ForwardDiffExt using Unitful using ForwardDiff function Base.convert(d::Type{ForwardDiff.Dual{T, V, N}}, q::Quantity{T2, NoDims}) where {T, V, N, T2} return d(uconvert(NoUnits, q)) end end ================================================ FILE: ext/InverseFunctionsUnitfulExt.jl ================================================ module InverseFunctionsUnitfulExt using Unitful import InverseFunctions: inverse # `true` plays the role of 1, but doesn't promote unnecessary inverse(f::Base.Fix1{typeof(ustrip), <:Unitful.Units}) = Base.Fix1(*, true*f.x) end ================================================ FILE: ext/LatexifyExt.jl ================================================ #=========================================# # Extension for Unitful.jl + Latexify.jl, # # based on UnitfulLatexify.jl by # # David Gustavsson (@gustaphe) # #=========================================# module LatexifyExt using Unitful: Unitful, Unit, Units, AbstractQuantity, AffineUnits, AffineQuantity, power, abbr, name, tens, sortexp, unit, NoDims, ustrip, @u_str, genericunit, has_unit_spacing using Latexify: Latexify, @latexrecipe, latexify, _latexarray, latexraw, FancyNumberFormatter, PlainNumberFormatter, StyledNumberFormatter, SiunitxNumberFormatter, AbstractNumberFormatter using LaTeXStrings: LaTeXString import Latexify: latexify import Base.* # utility functions ------------------ function get_formatter(kwargs) fmt = get(kwargs, :fmt, FancyNumberFormatter()) if fmt isa String fmt = StyledNumberFormatter(fmt) end return fmt end get_format_env(fmt::SiunitxNumberFormatter) = :raw get_format_env(fmt) = :inline function getunitname(p::T, unitformat) where {T<:Unit} unitname = get(unitnames, (unitformat, name(p)), nothing) isnothing(unitname) || return unitname if unitformat === :siunitx return "\\$(lowercase(String(name(p))))" end return abbr(p) end function listunits(::T) where {T<:Units} return sortexp(T.parameters[1]) end """ ```julia intersperse(t, delim) ``` Create a vector whose elements alternate between the elements of `t` and `delim`, analogous to `join` for strings. # Example ```julia julia> intersperse((1, 2, 3, 4), :a) [1, :a, 2, :a, 3, :a, 4] ``` """ function intersperse(t::T, delim::U) where {T,U} iszero(length(t)) && return () L = length(t) * 2 - 1 out = Vector{Union{typeof.(t)...,U}}(undef, L) out[1:2:L] .= t out[2:2:L] .= delim return out end # default ------------------------------ @latexrecipe function f(p::Unit) fmt = get_formatter(kwargs) env --> get_format_env(fmt) return _transform(p, fmt) end @latexrecipe function f(u::Units; permode=:power) fmt = get_formatter(kwargs) env --> get_format_env(fmt) return _transform(u, fmt) end @latexrecipe function f(q::AbstractQuantity) fmt = get_formatter(kwargs) env --> get_format_env(fmt) operation := :* return _transform(q, fmt) end struct NakedUnits u::Units end struct NakedNumber n::Number end @latexrecipe function f(u::NakedUnits; permode=:power) fmt = get_formatter(kwargs) unitlist = listunits(u.u) if fmt isa SiunitxNumberFormatter fmt.simple && return Expr(:latexifymerge, intersperse(unitlist, ".")...) return Expr(:latexifymerge, unitlist...) end if permode === :power return Expr(:latexifymerge, intersperse(unitlist, "\\,")...) end numunits = [x for x in unitlist if power(x) >= 0] denunits = [typeof(x)(tens(x), -power(x)) for x in unitlist if power(x) < 0] numerator = intersperse(numunits, "\\,") if isempty(denunits) return Expr(:latexifymerge, numerator...) end if isempty(numunits) numerator = [1] end denominator = intersperse(denunits, "\\,") if permode === :slash return Expr(:latexifymerge, numerator..., "\\,/\\,", denominator...) end if permode === :frac return Expr(:latexifymerge, "\\frac{", numerator..., "}{", denominator..., "}") end return error("permode $permode undefined.") end @latexrecipe function f(n::NakedNumber) fmt = get_formatter(kwargs) if fmt isa SiunitxNumberFormatter fmt := PlainNumberFormatter() end return n.n end function _transform(p::Unit, fmt::SiunitxNumberFormatter) unitformat = fmt.simple ? :siunitxsimple : :siunitx prefix = prefixes[(unitformat, tens(p))] pow = power(p) unitname = getunitname(p, unitformat) if fmt.simple per = "" expo = pow == 1//1 ? "" : "^{$(latexify(pow; fmt="%g", env=:raw))}" else per = pow < 0 ? "\\per" : "" pow = abs(pow) expo = pow == 1//1 ? "" : "\\tothe{$(latexify(pow; fmt="%g", env=:raw))}" end return LaTeXString("$per$prefix$unitname$expo") end function _transform(p::Unit, fmt::AbstractNumberFormatter) prefix = prefixes[(:mathrm, tens(p))] unitname = getunitname(p, :mathrm) pow = power(p) expo = pow == 1//1 ? "" : "^{$(latexify(pow; fmt="%g", env=:raw))}" return LaTeXString("\\mathrm{$prefix$unitname}$expo") end function _transform(u::Units, fmt::SiunitxNumberFormatter) opening = fmt.version < 3 ? "\\si{" : "\\unit{" return Expr(:latexifymerge, opening, NakedUnits(u), "}") end _transform(u::Units, ::AbstractNumberFormatter) = Expr(:latexifymerge, NakedUnits(u)) function _transform(q::AbstractQuantity, fmt::SiunitxNumberFormatter) opening = fmt.version < 3 ? "\\SI{" : "\\qty{" return Expr(:latexifymerge, opening, NakedNumber(q.val), "}{", NakedUnits(unit(q)), "}") end function _transform(q::AbstractQuantity, ::AbstractNumberFormatter) Expr( :latexifymerge, NakedNumber(q.val), has_unit_spacing(unit(q)) ? "\\," : nothing, NakedUnits(unit(q)), ) end _transform(n::NakedNumber, ::SiunitxNumberFormatter) = PlainNumberFormatter(n.n) # affine ------------------------------- @latexrecipe function f(u::AffineUnits) fmt = get_formatter(kwargs) if u == Unitful.°C unitname = :Celsius elseif u == Unitful.°F unitname = :Fahrenheit else # If it's not celsius or farenheit, let it do the default thing return genericunit(u) end if fmt isa SiunitxNumberFormatter env --> :raw return Expr(:latexifymerge, "\\unit{", unitnames[(:siunitx, unitname)], "}") end env --> :inline return Expr(:latexifymerge, "\\mathrm{", unitnames[(:mathrm, unitname)], "}") end @latexrecipe function f(q::AffineQuantity) fmt = get_formatter(kwargs) u = unit(q) if u == Unitful.°C unitname = :Celsius elseif u == Unitful.°F unitname = :Fahrenheit else # If it's not celsius or farenheit, let it do the default thing return ustrip(q)*genericunit(u) end if fmt isa SiunitxNumberFormatter env --> :raw return Expr( :latexifymerge, "\\qty{", NakedNumber(q.val), "}{", unitnames[(:siunitx, unitname)], "}", ) end env --> :inline return Expr(:latexifymerge, q.val, "\\,\\mathrm{", unitnames[(:mathrm, unitname)], "}") end # arrays ------------------------- @latexrecipe function f( # Array{Quantity{U}} a::AbstractArray{<:AbstractQuantity{N,D,U}}; ) where {N<:Number,D,U} # Array of quantities with the same unit env --> :equation return Expr( :latexifymerge, ustrip.(a), has_unit_spacing(first(a)) ? "\\," : "", unit(first(a)) ) end @latexrecipe function f( # Tuple{Quantity{U}} l::Tuple{T,Vararg{T}}, ) where {T<:AbstractQuantity{N,D,U}} where {N<:Number,D,U} fmt = get_formatter(kwargs) if fmt isa SiunitxNumberFormatter env --> :raw opening = fmt.version < 3 ? "\\SIlist{" : "\\qtylist{" return Expr( :latexifymerge, opening, intersperse(NakedNumber.(ustrip.(l)), ";")..., "}{", NakedUnits(unit(first(l))), "}", ) end return collect(l) end # label (for plots) ------------------------------------ @latexrecipe function f(l::AbstractString, u::Units; labelformat=:slash) labelformat === :slash && return Expr(:latexifymerge, l, "\\;\\left/\\;", u, "\\right.") labelformat === :square && return Expr(:latexifymerge, l, "\\;\\left[", u, "\\right]") labelformat === :round && return Expr(:latexifymerge, l, "\\;\\left(", u, "\\right)") labelformat === :frac && return Expr(:latexifymerge, "\\frac{", l, "}{", u, "}") error("Unknown labelformat $labelformat") end # prefixes ------------------------------ """ prefixes are listed in this dictionary `(unitformat::Symbol, pow::Integer) => prefix::String` """ const prefixes = begin Dict( (:mathrm, -24) => "y", (:mathrm, -21) => "z", (:mathrm, -18) => "a", (:mathrm, -15) => "f", (:mathrm, -12) => "p", (:mathrm, -9) => "n", (:mathrm, -6) => "\\mu{}", (:mathrm, -3) => "m", (:mathrm, -2) => "c", (:mathrm, -1) => "d", (:mathrm, 0) => "", (:mathrm, 1) => "D", (:mathrm, 2) => "h", (:mathrm, 3) => "k", (:mathrm, 6) => "M", (:mathrm, 9) => "G", (:mathrm, 12) => "T", (:mathrm, 15) => "P", (:mathrm, 18) => "E", (:mathrm, 21) => "Z", (:mathrm, 24) => "Y", (:siunitx, -24) => "\\yocto", (:siunitx, -21) => "\\zepto", (:siunitx, -18) => "\\atto", (:siunitx, -15) => "\\femto", (:siunitx, -12) => "\\pico", (:siunitx, -9) => "\\nano", (:siunitx, -6) => "\\micro", (:siunitx, -3) => "\\milli", (:siunitx, -2) => "\\centi", (:siunitx, -1) => "\\deci", (:siunitx, 0) => "", (:siunitx, 1) => "\\deka", (:siunitx, 2) => "\\hecto", (:siunitx, 3) => "\\kilo", (:siunitx, 6) => "\\mega", (:siunitx, 9) => "\\giga", (:siunitx, 12) => "\\tera", (:siunitx, 15) => "\\peta", (:siunitx, 18) => "\\exa", (:siunitx, 21) => "\\zetta", (:siunitx, 24) => "\\yotta", (:siunitxsimple, -24) => "y", (:siunitxsimple, -21) => "z", (:siunitxsimple, -18) => "a", (:siunitxsimple, -15) => "f", (:siunitxsimple, -12) => "p", (:siunitxsimple, -9) => "n", (:siunitxsimple, -6) => "\\u", (:siunitxsimple, -3) => "m", (:siunitxsimple, -2) => "c", (:siunitxsimple, -1) => "d", (:siunitxsimple, 0) => "", (:siunitxsimple, 1) => "D", (:siunitxsimple, 2) => "h", (:siunitxsimple, 3) => "k", (:siunitxsimple, 6) => "M", (:siunitxsimple, 9) => "G", (:siunitxsimple, 12) => "T", (:siunitxsimple, 15) => "P", (:siunitxsimple, 18) => "E", (:siunitxsimple, 21) => "Z", (:siunitxsimple, 24) => "Y", ) end # unit names ------------------------------ """" `unitnames` Unit names generally follow a simple scheme, but there are exceptions, listed in this dictionary: `(unitformat::Symbol, name::Symbol) => unitname::String` """ const unitnames = begin Dict( (:mathrm, :Percent) => "\\%", (:siunitxsimple, :Percent) => "\\%", (:mathrm, :Degree) => "^{\\circ}", (:siunitxsimple, :Degree) => "\\degree", (:siunitx, :eV) => "\\electronvolt", (:mathrm, :Ohm) => "\\Omega", (:mathrm, :Celsius) => "^\\circ C", (:siunitx, :Celsius) => "\\celsius", (:siunitxsimple, :Celsius) => "\\celsius", (:mathrm, :Fahrenheit) => "^\\circ F", (:siunitx, :Fahrenheit) => "\\fahrenheit", (:siunitxsimple, :Fahrenheit) => "\\fahrenheit", (:siunitxsimple, :Angstrom) => "\\angstrom", (:mathrm, :Angstrom) => "\\AA", (:mathrm, :DoubleTurn) => "\\S", (:mathrm, :Turn) => "\\tau", (:mathrm, :HalfTurn) => "\\pi", (:mathrm, :Quadrant) => "\\frac{\\pi}{2}", (:mathrm, :Sextant) => "\\frac{\\pi}{3}", (:mathrm, :Octant) => "\\frac{\\pi}{4}", (:mathrm, :ClockPosition) => "\\frac{\\pi}{12}", (:mathrm, :HourAngle) => "\\frac{\\pi}{24}", (:mathrm, :CompassPoint) => "\\frac{\\pi}{32}", (:mathrm, :Hexacontade) => "\\frac{\\pi}{60}", (:mathrm, :BinaryRadian) => "\\frac{\\pi}{256}", (:mathrm, :DiameterPart) => "\\oslash", # This is slightly wrong (:mathrm, :Gradian) => "^g", (:mathrm, :Arcminute) => "'", (:mathrm, :Arcsecond) => "''", (:mathrm, :ArcsecondShort) => "''", ) end end # LatexifyExt ================================================ FILE: ext/NaNMathExt.jl ================================================ module NaNMathExt using Unitful import NaNMath NaNMath.sqrt(q::Unitful.AbstractQuantity) = NaNMath.sqrt(ustrip(q))*sqrt(unit(q)) NaNMath.pow(q::Unitful.AbstractQuantity, r) = NaNMath.pow(ustrip(q), r)*unit(q)^r end ================================================ FILE: ext/PrintfExt.jl ================================================ module PrintfExt using Printf using Unitful using Unitful: AbstractQuantity Printf.plength(f::Printf.Spec{<:Printf.Ints}, x::AbstractQuantity{<:Real}) = Printf.plength(f, ustrip(x)) + length(string(unit(x))) + Unitful.has_unit_spacing(unit(x)) # separate methods for disambiguation Printf.fmt(buf, pos, arg::AbstractQuantity{<:Real}, spec::Printf.Spec{<:Printf.Floats}) = _fmt(buf, pos, arg, spec) Printf.fmt(buf, pos, arg::AbstractQuantity{<:Real}, spec::Printf.Spec{<:Printf.Ints}) = _fmt(buf, pos, arg, spec) function _fmt(buf, pos, arg, spec) pos = Printf.fmt(buf, pos, ustrip(arg), spec) if Unitful.has_unit_spacing(unit(arg)) pos = Printf.fmt(buf, pos, ' ', only((Printf.format"%c").formats)) end pos = Printf.fmt(buf, pos, string(unit(arg)), only((Printf.format"%s").formats)) return pos end end ================================================ FILE: src/Unitful.jl ================================================ module Unitful import Base: ==, <, <=, +, -, *, /, //, ^, isequal, hash import Base: show, convert import Base: abs, abs2, angle, big, float, fma, muladd, inv, sqrt, cbrt import Base: min, max, floor, ceil, real, imag, conj import Base: complex, widen, reim # handled in complex.jl import Base: exp, exp10, exp2, expm1, log, log10, log1p, log2 import Base: sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, sinpi, cospi, sinc, cosc, cis, cispi, sincos import Base: eps, mod, rem, div, fld, cld, divrem, trunc, round, sign, signbit import Base: isless, isapprox, isinteger, isreal, isinf, isfinite, isnan import Base: iseven, isodd import Base: copysign, flipsign import Base: prevfloat, nextfloat, maxintfloat, rat, step import Base: length, float, last, one, oneunit, zero, range import Base: getindex, eltype, step, last, first, frexp import Base: Integer, Rational, typemin, typemax import Base: steprange_last, unsigned import Base: sleep @static if VERSION ≥ v"1.7.0-DEV.119" import Base: isunordered end import Dates import LinearAlgebra: Diagonal, Bidiagonal, Tridiagonal, SymTridiagonal import LinearAlgebra: istril, istriu, norm import Random export logunit, unit, absoluteunit, dimension, uconvert, ustrip, upreferred export @dimension, @derived_dimension, @refunit, @unit, @affineunit, @u_str export Quantity, DimensionlessQuantity, NoUnits, NoDims export uconvertp, uconvertrp, reflevel, linear export @logscale, @logunit, @dB, @B, @cNp, @Np export Level, Gain export uparse const unitmodules = Vector{Module}() function _basefactors(m::Module) # A hidden symbol which will be automatically attached to any module # defining units, allowing `Unitful.register()` to merge in the units from # that module. basefactors_name = Symbol("#Unitful_basefactors") if isdefined(m, basefactors_name) # It's not guaranteed that a world age update happened since the hidden # symbol was added to another module, so use `invokelatest` to avoid #781. Base.invokelatest(getproperty, m, basefactors_name) else Core.eval(m, :(const $basefactors_name = Dict{Symbol,Tuple{Float64,Rational{Int}}}())) end end const basefactors = _basefactors(Unitful) include("types.jl") const promotion = Dict{Symbol,FreeUnits}() include("user.jl") include("utils.jl") include("dimensions.jl") include("units.jl") include("quantities.jl") include("display.jl") include("promotion.jl") include("conversion.jl") include("range.jl") include("fastmath.jl") include("logarithm.jl") include("complex.jl") include("pkgdefaults.jl") include("dates.jl") if !isdefined(Base, :get_extension) include("../ext/ConstructionBaseUnitfulExt.jl") include("../ext/InverseFunctionsUnitfulExt.jl") end end ================================================ FILE: src/complex.jl ================================================ # This file is meant to provide all the methods in # https://github.com/JuliaLang/julia/blob/master/base/complex.jl that # are defined for Real or Complex numbers, just for # AbstractQuantities{T,D,U} where T is Real or Complex, respectively. # # It is currently incomplete. complex(z::Quantity{T,D,U}) where {T<:Complex,D,U} = z function complex(x::Quantity{T}, y = zero(x)) where {T<:Real} r, i = promote(x, y) return Quantity(complex(ustrip(r), ustrip(i)), unit(r)) end complex(::Type{Quantity{T,D,U}}) where {T,D,U} = Quantity{complex(T),D,U} # implement Base.widen for real and complex quantities because Unitful # does not have an implementation for widen yet Base.widen(::Type{Quantity{T,D,U}}) where {T,D,U} = Quantity{widen(T),D,U} # skip Base.float, Base.real, Base.imag because it is already # implemented # Base.real for types has a general implementation in julia; a faster # method could be provided but is not strictly required. # Base.isreal, etc., are already implemented in Unitful. # Base.flipsign is already implemented in Unitful. # To Do: Check if Base.show, Base.read, Base.write, etc. need any # attention # ... ================================================ FILE: src/conversion.jl ================================================ """ convfact(s::Units, t::Units) Find the conversion factor from unit `t` to unit `s`, e.g., `convfact(m, cm) == 1//100`. """ @generated function convfact(s::Units, t::Units) # Check if conversion is possible in principle dimension(s()) != dimension(t()) && throw(DimensionError(s(), t())) # use absoluteunit because division is invalid for AffineUnits; # convert to FreeUnits first because absolute ContextUnits might still # promote to AffineUnits conv_units = absoluteunit(FreeUnits(t())) / absoluteunit(FreeUnits(s())) inex, ex = basefactor(conv_units) pow = tensfactor(conv_units) inex_orig = inex fpow = 10.0^pow if fpow > typemax(Int) || 1/fpow > typemax(Int) inex *= fpow else comp = (pow > 0 ? fpow * numerator(ex) : 1/fpow * denominator(ex)) if comp > typemax(Int) inex *= fpow else ex *= (10//1)^pow end end if ex isa Rational && denominator(ex) == 1 ex = numerator(ex) end result = (inex ≈ 1.0 ? 1 : inex) * ex if fp_overflow_underflow(inex_orig, result) throw(ArgumentError( "Floating point overflow/underflow, probably due to large " * "exponents and/or SI prefixes in units" )) end return :($result) end """ convfact{S}(s::Units{S}, t::Units{S}) Returns 1. (Avoids effort when unnecessary.) """ convfact(s::Units{S}, t::Units{S}) where {S} = 1 """ convfact(T::Type, s::Units, t::Units) Returns the appropriate conversion factor from unit `t` to unit `s` for the number type `T`. """ function convfact(::Type{T}, s::Units, t::Units) where T cf = convfact(s, t) if cf isa AbstractFloat F = convfact_floattype(T) # Since conversion factors only have Float64 precision, # there is no point in converting to BigFloat convert(F == BigFloat ? Float64 : F, cf) else cf end end function convfact_floattype(::Type{T}) where T # Use try-catch instead of hasmethod because a # fallback method might exist but throw an error try _convfact_floattype(float(real(T))) catch Float64 end end _convfact_floattype(::Type) = Float64 _convfact_floattype(::Type{Float16}) = Float16 _convfact_floattype(::Type{Float32}) = Float32 """ uconvert(a::Units, x::Quantity{T,D,U}) where {T,D,U} Convert a [`Unitful.Quantity`](@ref) to different units. The conversion will fail if the target units `a` have a different dimension than the dimension of the quantity `x`. You can use this method to switch between equivalent representations of the same unit, like `N m` and `J`. Example: ```jldoctest julia> uconvert(u"hr",3602u"s") 1801//1800 hr julia> uconvert(u"J",1.0u"N*m") 1.0 J ``` """ function uconvert(a::Units, x::Quantity{T,D,U}) where {T,D,U} if typeof(a) == U return Quantity(x.val, a) # preserves numeric type if convfact is 1 elseif (a isa AffineUnits) || (x isa AffineQuantity) return uconvert_affine(a, x) else return Quantity(x.val * convfact(T, a, U()), a) end end function uconvert(a::Units, x::Number) if dimension(a) == NoDims Quantity(x * convfact(a, NoUnits), a) else throw(DimensionError(a,x)) end end uconvert(a::Units, x::Missing) = missing @generated function uconvert_affine(a::Units, x::Quantity) # TODO: test, may be able to get bad things to happen here when T<:LogScaled auobj = a() xuobj = x.parameters[3]() conv = convfact(auobj, xuobj) t0 = x <: AffineQuantity ? x.parameters[3].parameters[end].parameters[end] : :(zero($(x.parameters[1]))) t1 = a <: AffineUnits ? a.parameters[end].parameters[end] : :(zero($(x.parameters[1]))) quote dimension(a) != dimension(x) && throw(DimensionError(a, x)) return Quantity(((x.val - $t0) * $conv) + $t1, a) end end function convert(::Type{Quantity{T,D,U}}, x::Number) where {T,D,U} (dimension(x) != D) && throw(DimensionError(U(), x)) q = uconvert(U(), x) Quantity{T,D,U}(isa(q, AbstractQuantity) ? q.val : q) end # needed ever since julialang/julia#28216 convert(::Type{Quantity{T,D,U}}, x::Quantity{T,D,U}) where {T,D,U} = x function convert(::Type{Quantity{T,D}}, x::Quantity) where {T,D} (dimension(x) !== D) && throw(DimensionError(D, x)) return Quantity{T,D,typeof(unit(x))}(convert(T, x.val)) end function convert(::Type{Quantity{T,D}}, x::Number) where {T,D} (D !== NoDims) && throw(DimensionError(D, NoDims)) Quantity{T,NoDims,typeof(NoUnits)}(x) end function convert(::Type{Quantity{T}}, x::Quantity) where {T} Quantity{T,dimension(x),typeof(unit(x))}(convert(T, x.val)) end function convert(::Type{Quantity{T}}, x::Number) where {T} Quantity{T,NoDims,typeof(NoUnits)}(x) end convert(::Type{DimensionlessQuantity{T,U}}, x::Number) where {T,U} = uconvert(U(), convert(T,x)) function convert(::Type{DimensionlessQuantity{T,U}}, x::Quantity) where {T,U} if dimension(x) == NoDims _Quantity(T(x.val), U()) else throw(DimensionError(NoDims,x)) end end convert(::Type{Number}, y::Quantity) = y convert(::Type{T}, y::Quantity) where {T <: Real} = T(uconvert(NoUnits, y)) convert(::Type{T}, y::Quantity) where {T <: Complex} = T(uconvert(NoUnits, y)) ================================================ FILE: src/dates.jl ================================================ # Conversion from and to types from the `Dates` stdlib # Dates.FixedPeriod for (period, unit) = ((Dates.Week, wk), (Dates.Day, d), (Dates.Hour, hr), (Dates.Minute, minute), (Dates.Second, s), (Dates.Millisecond, ms), (Dates.Microsecond, μs), (Dates.Nanosecond, ns)) @eval unit(::Type{$period}) = $unit @eval (::Type{$period})(x::AbstractQuantity) = $period(ustrip(unit($period), x)) end dimension(p::Dates.FixedPeriod) = dimension(typeof(p)) dimension(::Type{<:Dates.FixedPeriod}) = 𝐓 """ unit(x::Dates.FixedPeriod) unit(x::Type{<:Dates.FixedPeriod}) Return the units that correspond to a particular period. # Examples ```jldoctest julia> using Dates julia> unit(Second(15)) == u"s" true julia> unit(Hour) == u"hr" true ``` """ unit(p::Dates.FixedPeriod) = unit(typeof(p)) numtype(x::Dates.FixedPeriod) = numtype(typeof(x)) numtype(::Type{T}) where {T<:Dates.FixedPeriod} = Int64 quantitytype(::Type{T}) where {T<:Dates.FixedPeriod} = Quantity{numtype(T),dimension(T),typeof(unit(T))} ustrip(p::Dates.FixedPeriod) = Dates.value(p) """ Quantity(period::Dates.FixedPeriod) Create a `Quantity` that corresponds to the given `period`. The numerical value of the resulting `Quantity` is of type `Int64`. # Example ```jldoctest julia> using Dates: Second julia> Quantity(Second(5)) 5 s ``` """ Quantity(period::Dates.FixedPeriod) = Quantity(ustrip(period), unit(period)) uconvert(u::Units, period::Dates.FixedPeriod) = uconvert(u, Quantity(period)) (T::Type{<:AbstractQuantity})(period::Dates.FixedPeriod) = T(Quantity(period)) convert(T::Type{<:AbstractQuantity}, period::Dates.FixedPeriod) = T(period) convert(T::Type{<:Dates.FixedPeriod}, x::AbstractQuantity) = T(x) round(T::Type{<:Dates.FixedPeriod}, x::AbstractQuantity, r::RoundingMode=RoundNearest) = T(round(numtype(T), ustrip(unit(T), x), r)) round(u::Units, period::Dates.FixedPeriod, r::RoundingMode=RoundNearest; kwargs...) = round(u, Quantity(period), r; kwargs...) round(T::Type{<:Number}, u::Units, period::Dates.FixedPeriod, r::RoundingMode=RoundNearest; kwargs...) = round(T, u, Quantity(period), r; kwargs...) round(T::Type{<:AbstractQuantity}, period::Dates.FixedPeriod, r::RoundingMode=RoundNearest; kwargs...) = round(T, Quantity(period), r; kwargs...) for (f, r) in ((:floor,:RoundDown), (:ceil,:RoundUp), (:trunc,:RoundToZero)) @eval $f(T::Type{<:Dates.FixedPeriod}, x::AbstractQuantity) = round(T, x, $r) @eval $f(u::Units, period::Dates.FixedPeriod; kwargs...) = round(u, period, $r; kwargs...) @eval $f(T::Type{<:Number}, u::Units, period::Dates.FixedPeriod; kwargs...) = round(T, u, period, $r; kwargs...) @eval $f(T::Type{<:AbstractQuantity}, period::Dates.FixedPeriod; kwargs...) = round(T, period, $r; kwargs...) end for op = (:+, :-, :*, :/, ://, :fld, :cld, :mod, :rem, :atan, :(==), :isequal, :<, :isless, :≤) @eval $op(x::Dates.FixedPeriod, y::AbstractQuantity) = $op(Quantity(x), y) @eval $op(x::AbstractQuantity, y::Dates.FixedPeriod) = $op(x, Quantity(y)) end for op = (:*, :/, ://) @eval $op(x::Dates.FixedPeriod, y::Units) = $op(Quantity(x), y) @eval $op(x::Units, y::Dates.FixedPeriod) = $op(x, Quantity(y)) end div(x::Dates.FixedPeriod, y::AbstractQuantity, r...) = div(Quantity(x), y, r...) div(x::AbstractQuantity, y::Dates.FixedPeriod, r...) = div(x, Quantity(y), r...) isapprox(x::Dates.FixedPeriod, y::AbstractQuantity; kwargs...) = isapprox(Quantity(x), y; kwargs...) isapprox(x::AbstractQuantity, y::Dates.FixedPeriod; kwargs...) = isapprox(x, Quantity(y); kwargs...) function isapprox(x::AbstractArray{<:AbstractQuantity}, y::AbstractArray{T}; kwargs...) where {T<:Dates.Period} if isconcretetype(T) y′ = reinterpret(quantitytype(T), y) else y′ = Quantity.(y) end isapprox(x, y′; kwargs...) end isapprox(x::AbstractArray{<:Dates.FixedPeriod}, y::AbstractArray{<:AbstractQuantity}; kwargs...) = isapprox(y, x; kwargs...) Base.promote_rule(::Type{Quantity{T,𝐓,U}}, ::Type{S}) where {T,U,S<:Dates.FixedPeriod} = promote_type(Quantity{T,𝐓,U}, quantitytype(S)) # Dates.CompoundPeriod dimension(p::Dates.CompoundPeriod) = dimension(typeof(p)) dimension(::Type{<:Dates.CompoundPeriod}) = 𝐓 uconvert(u::Units, period::Dates.CompoundPeriod) = Quantity{promote_type(Int64,typeof(convfact(u,ns))),dimension(u),typeof(u)}(period) try_uconvert(u::Units, period::Dates.CompoundPeriod) = nothing function try_uconvert(u::TimeUnits, period::Dates.CompoundPeriod) T = Quantity{promote_type(Int64,typeof(convfact(u,ns))),dimension(u),typeof(u)} val = zero(T) for p in period.periods p isa Dates.FixedPeriod || return nothing val += T(p) end val end (T::Type{<:AbstractQuantity})(period::Dates.CompoundPeriod) = mapreduce(T, +, period.periods, init=zero(T)) convert(T::Type{<:AbstractQuantity}, period::Dates.CompoundPeriod) = T(period) round(u::Units, period::Dates.CompoundPeriod, r::RoundingMode=RoundNearest; kwargs...) = round(u, uconvert(u, period), r; kwargs...) round(T::Type{<:Number}, u::Units, period::Dates.CompoundPeriod, r::RoundingMode=RoundNearest; kwargs...) = round(T, u, uconvert(u, period), r; kwargs...) round(T::Type{<:AbstractQuantity}, period::Dates.CompoundPeriod, r::RoundingMode=RoundNearest; kwargs...) = round(T, T(period), r; kwargs...) for (f, r) in ((:floor,:RoundDown), (:ceil,:RoundUp), (:trunc,:RoundToZero)) @eval $f(u::Units, period::Dates.CompoundPeriod; kwargs...) = round(u, period, $r; kwargs...) @eval $f(T::Type{<:Number}, u::Units, period::Dates.CompoundPeriod; kwargs...) = round(T, u, period, $r; kwargs...) @eval $f(T::Type{<:AbstractQuantity}, period::Dates.CompoundPeriod; kwargs...) = round(T, period, $r; kwargs...) end for op = (:fld, :cld, :atan, :<, :isless, :≤) @eval $op(x::Dates.CompoundPeriod, y::AbstractQuantity) = $op(uconvert(unit(y),x), y) @eval $op(x::AbstractQuantity, y::Dates.CompoundPeriod) = $op(x, uconvert(unit(x),y)) end div(x::Dates.CompoundPeriod, y::AbstractQuantity, r...) = div(uconvert(unit(y),x), y, r...) div(x::AbstractQuantity, y::Dates.CompoundPeriod, r...) = div(x, uconvert(unit(x),y), r...) mod(x::Dates.CompoundPeriod, y::AbstractQuantity) = mod(uconvert(unit(y),x), y) rem(x::Dates.CompoundPeriod, y::AbstractQuantity) = rem(uconvert(unit(y),x), y) for op = (:(==), :isequal) @eval $op(x::Dates.CompoundPeriod, y::AbstractQuantity{T,𝐓,U}) where {T,U} = $op(try_uconvert(U(), x), y) @eval $op(x::AbstractQuantity{T,𝐓,U}, y::Dates.CompoundPeriod) where {T,U} = $op(x, try_uconvert(U(), y)) end isapprox(x::Dates.CompoundPeriod, y::AbstractQuantity; kwargs...) = dimension(y) === 𝐓 ? isapprox(uconvert(unit(y), x), y; kwargs...) : false isapprox(x::AbstractQuantity, y::Dates.CompoundPeriod; kwargs...) = dimension(x) === 𝐓 ? isapprox(x, uconvert(unit(x), y); kwargs...) : false function isapprox(x::AbstractArray{<:AbstractQuantity}, y::AbstractArray{Dates.CompoundPeriod}; kwargs...) if dimension(eltype(x)) === 𝐓 isapprox(x, uconvert.(unit(eltype(x)), y); kwargs...) else false end end isapprox(x::AbstractArray{Dates.CompoundPeriod}, y::AbstractArray{<:AbstractQuantity}; kwargs...) = isapprox(y, x; kwargs...) sleep(x::AbstractQuantity) = sleep(ustrip(s, x)) # Dates, Times, DateTimes for f in (:+, :-) @eval Base.$f(x::Dates.DateTime, y::Quantity) = $f(x, trunc(Dates.Millisecond, y)) @eval Base.$f(x::Dates.Time, y::Quantity) = $f(x, trunc(Dates.Nanosecond, y)) @eval Base.$f(x::Dates.Date, y::Quantity) = $f(x, Dates.Day(y)) end Base.:+(y::Quantity, x::Dates.DateTime) = x + y Base.:+(y::Quantity, x::Dates.Time) = x + y Base.:+(y::Quantity, x::Dates.Date) = x + y ================================================ FILE: src/dimensions.jl ================================================ """ ``` *(a0::Dimensions, a::Dimensions...) ``` Given however many dimensions, multiply them together. Collect [`Unitful.Dimension`](@ref) objects from the type parameter of the [`Unitful.Dimensions`](@ref) objects. For identical dimensions, collect powers and sort uniquely by the name of the `Dimension`. Examples: ```jldoctest julia> u"𝐌*𝐋/𝐓^2" 𝐋 𝐌 𝐓^-2 julia> u"𝐋*𝐌/𝐓^2" 𝐋 𝐌 𝐓^-2 julia> typeof(u"𝐋*𝐌/𝐓^2") == typeof(u"𝐌*𝐋/𝐓^2") true ``` """ @generated function *(a0::Dimensions, a::Dimensions...) # Implementation is very similar to *(::Units, ::Units...) b = Vector{Dimension}() a0p = a0.parameters[1] length(a0p) > 0 && append!(b, a0p) for x in a xp = x.parameters[1] length(xp) > 0 && append!(b, xp) end sort!(b, by=power) sort!(b, by=name) c = Vector{Dimension}() if !isempty(b) next = iterate(b) p = 0//1 oldvalue = next[1] while next !== nothing (value, state) = next if name(value) == name(oldvalue) p += power(value) else if p != 0 push!(c, Dimension{name(oldvalue)}(p)) end p = power(value) end oldvalue = value next = iterate(b, state) end if p != 0 push!(c, Dimension{name(oldvalue)}(p)) end end d = (c...,) :(Dimensions{$d}()) end /(x::Dimensions, y::Dimensions) = *(x,inv(y)) //(x::Dimensions, y::Dimensions) = x/y # Both methods needed for ambiguity resolution ^(x::Dimension{T}, y::Integer) where {T} = Dimension{T}(power(x)*y) ^(x::Dimension{T}, y::Number) where {T} = Dimension{T}(power(x)*y) # A word of caution: # Exponentiation is not type-stable for `Dimensions` objects in many cases ^(x::Dimensions{T}, y::Integer) where {T} = *(Dimensions{map(a->a^y, T)}()) ^(x::Dimensions{T}, y::Number) where {T} = *(Dimensions{map(a->a^y, T)}()) @generated function Base.literal_pow(::typeof(^), x::Dimensions{T}, ::Val{p}) where {T,p} z = *(Dimensions{map(a->a^p, T)}()) :($z) end # Since exponentiation is not type stable, we define a special `inv` method to enable fast # division. For julia 0.6.0, the appropriate methods for ^ and * need to be defined before # this one! for (fun,pow) in ((:inv, -1//1), (:sqrt, 1//2), (:cbrt, 1//3)) # The following are generated functions to ensure type stability. @eval @generated function ($fun)(x::Dimensions) dimtuple = map(x->x^($pow), x.parameters[1]) y = *(Dimensions{dimtuple}()) # sort appropriately :($y) end end ================================================ FILE: src/display.jl ================================================ # Convenient dictionary for mapping powers of ten to an SI prefix. const prefixdict = Dict( -24 => "y", -21 => "z", -18 => "a", -15 => "f", -12 => "p", -9 => "n", -6 => "μ", -3 => "m", -2 => "c", -1 => "d", 0 => "", 1 => "da", 2 => "h", 3 => "k", 6 => "M", 9 => "G", 12 => "T", 15 => "P", 18 => "E", 21 => "Z", 24 => "Y" ) """ abbr(x) Provides abbreviations for units or dimensions. Since a method should always be defined for each unit and dimension type, absence of a method for a specific unit or dimension type is likely an error. Consequently, we return ❓ for generic arguments to flag unexpected behavior. """ abbr(x) = "❓" # Indicate missing abbreviations """ prefix(x::Unit) Returns a string representing the SI prefix for the power-of-ten held by this particular unit. """ function prefix(x::Unit) if haskey(prefixdict, tens(x)) return prefixdict[tens(x)] else error("Invalid power-of-ten prefix.") end end function show(io::IO, x::Unit{N,D}) where {N,D} show(io, FreeUnits{(x,), D, nothing}()) end abstract type BracketStyle end struct NoBrackets <: BracketStyle end print_opening_bracket(io::IO, ::NoBrackets) = nothing print_closing_bracket(io::IO, ::NoBrackets) = nothing struct RoundBrackets <: BracketStyle end print_opening_bracket(io::IO, ::RoundBrackets) = print(io, '(') print_closing_bracket(io::IO, ::RoundBrackets) = print(io, ')') struct SquareBrackets <: BracketStyle end print_opening_bracket(io::IO, ::SquareBrackets) = print(io, '[') print_closing_bracket(io::IO, ::SquareBrackets) = print(io, ']') print_opening_bracket(io::IO, x) = print_opening_bracket(io, BracketStyle(x)) print_closing_bracket(io::IO, x) = print_closing_bracket(io, BracketStyle(x)) """ BracketStyle(x) BracketStyle(typeof(x)) `BracketStyle` specifies whether the numeric value of a `Quantity` is printed in brackets (and what kind of brackets). Three styles are defined: * `NoBrackets()`: this is the default, for example used for real numbers: `1.2 m` * `RoundBrackets()`: used for complex numbers: `(2.5 + 1.0im) V` * `SquareBrackets()`: used for [`Level`](@ref)/[`Gain`](@ref): `[3 dB] Hz^-1` """ BracketStyle(x) = BracketStyle(typeof(x)) BracketStyle(::Type) = NoBrackets() BracketStyle(::Type{<:Complex}) = RoundBrackets() """ showval(io::IO, x::Number, brackets::Bool=true) Show the numeric value `x` of a quantity. Depending on the type of `x`, the value may be enclosed in brackets (see [`BracketStyle`](@ref)). If `brackets` is set to `false`, the brackets are not printed. """ function showval(io::IO, x::Number, brackets::Bool=true) brackets && print_opening_bracket(io, x) show(io, x) brackets && print_closing_bracket(io, x) end function showval(io::IO, mime::MIME, x::Number, brackets::Bool=true) brackets && print_opening_bracket(io, x) show(io, mime, x) brackets && print_closing_bracket(io, x) end # Space between numerical value and unit should always be included # except for angular degrees, minutes and seconds (° ′ ″) # See SI 9th edition, section 5.4.3; "Formatting the value of a quantity" # https://www.bipm.org/utils/common/pdf/si-brochure/SI-Brochure-9.pdf has_unit_spacing(u) = true has_unit_spacing(u::Units{(Unit{:Degree, NoDims}(0, 1//1),), NoDims}) = false """ show(io::IO, x::Quantity) Show a unitful quantity by calling [`showval`](@ref) on the numeric value, appending a space, and then calling `show` on a units object `U()`. """ function show(io::IO, x::Quantity) if isunitless(unit(x)) showval(io, x.val, false) else showval(io, x.val, true) has_unit_spacing(unit(x)) && print(io, ' ') show(io, unit(x)) end end function show(io::IO, mime::MIME"text/plain", x::Quantity) if isunitless(unit(x)) showval(io, mime, x.val, false) else showval(io, mime, x.val, true) has_unit_spacing(unit(x)) && print(io, ' ') show(io, mime, unit(x)) end end function show(io::IO, r::StepRange{T}) where T<:Quantity a,s,b = first(r), step(r), last(r) U = unit(a) V = absoluteunit(U) print(io, '(') if ustrip(V, s) == 1 show(io, ustrip(U, a):ustrip(U, b)) else show(io, ustrip(U, a):ustrip(V, s):ustrip(U, b)) end print(io, ')') has_unit_spacing(U) && print(io,' ') show(io, U) end function show(io::IO, r::StepRangeLen{T}) where T<:Quantity a,s,b = first(r), step(r), last(r) U = unit(a) V = absoluteunit(U) print(io, '(') show(io, StepRangeLen(ustrip(U, a), ustrip(V, s), length(r))) print(io, ')') has_unit_spacing(U) && print(io,' ') show(io, U) end function show(io::IO, x::typeof(NoDims)) print(io, "NoDims") end """ show(io::IO, x::Unitlike) Call [`Unitful.showrep`](@ref) on each object in the tuple that is the type variable of a [`Unitful.Units`](@ref) or [`Unitful.Dimensions`](@ref) object. """ function show(io::IO, x::Unitlike) showoperators = get(io, :showoperators, false) sep = showoperators ? "*" : " " isfirst = true for y in sortexp(x) isfirst || print(io, sep) showrep(io, y) isfirst = false end nothing end """ sortexp(xs) Sort units to show positive exponents first. """ sortexp(xs) = sort!(collect(xs), by = u->signbit(power(u)), alg = InsertionSort) @generated sortexp(::Dimensions{D}) where D = (sortexp(D)...,) @generated sortexp(::Units{U}) where U = (sortexp(U)...,) """ showrep(io::IO, x::Unit) Show the unit, prefixing with any decimal prefix and appending the exponent as formatted by [`Unitful.superscript`](@ref). """ function showrep(io::IO, x::Unit) print(io, prefix(x)) print(io, abbr(x)) print(io, (power(x) == 1//1 ? "" : superscript(power(x); io=io))) nothing end """ showrep(io::IO, x::Dimension) Show the dimension, appending any exponent as formatted by [`Unitful.superscript`](@ref). """ function showrep(io::IO, x::Dimension) print(io, abbr(x)) print(io, (power(x) == 1//1 ? "" : superscript(power(x); io=io))) end """ superscript(i::Rational; io::Union{IO, Nothing} = nothing) Returns exponents as a string. This function returns the value as a string. It does not print to `io`. `io` is only used for IO context values. If `io` contains the `:fancy_exponent` property and the value is a `Bool`, this value will override the behavior of fancy exponents. """ function superscript(i::Rational; io::Union{IO, Nothing} = nothing) if io === nothing iocontext_value = nothing else iocontext_value = get(io, :fancy_exponent, nothing) end if iocontext_value isa Bool fancy_exponent = iocontext_value else v = get(ENV, "UNITFUL_FANCY_EXPONENTS", Sys.isapple() ? "true" : "false") t = tryparse(Bool, lowercase(v)) fancy_exponent = (t === nothing) ? false : t end if fancy_exponent return i.den == 1 ? superscript(i.num) : string(superscript(i.num), '\u141F', superscript(i.den)) else i.den == 1 ? "^" * string(i.num) : "^" * replace(string(i), "//" => "/") end end # Taken from SIUnits.jl superscript(i::Integer) = map(repr(i)) do c c == '-' ? '\u207b' : c == '1' ? '\u00b9' : c == '2' ? '\u00b2' : c == '3' ? '\u00b3' : c == '4' ? '\u2074' : c == '5' ? '\u2075' : c == '6' ? '\u2076' : c == '7' ? '\u2077' : c == '8' ? '\u2078' : c == '9' ? '\u2079' : c == '0' ? '\u2070' : error("unexpected character") end if isdefined(Base, :alignment_from_show) printed_length(io, x) = Base.alignment_from_show(io, x) else printed_length(io, x) = length(sprint(show, x, context=io)) end function Base.alignment(io::IO, x::Quantity) if isunitless(unit(x)) return Base.alignment(io, x.val) end length = printed_length(io, x) left, _ = Base.alignment(io, x.val) left += BracketStyle(x.val) != NoBrackets() return left, length - left end ================================================ FILE: src/fastmath.jl ================================================ import Base.FastMath import Base.FastMath: @fastmath, FloatTypes, ComplexTypes, add_fast, sub_fast, mul_fast, div_fast, rem_fast, cmp_fast, # mod_fast, eq_fast, ne_fast, lt_fast, le_fast, pow_fast, sqrt_fast, cos_fast, sin_fast, tan_fast, sincos_fast, atan_fast, hypot_fast, max_fast, min_fast, minmax_fast, cis_fast, angle_fast, fast_op sub_fast(x::Quantity{T}) where {T <: FloatTypes} = typeof(x)(sub_fast(x.val)) add_fast(x::Quantity{T,D,U}, y::Quantity{T,D,U}) where {T <: FloatTypes,D,U} = Quantity{T,D,U}(add_fast(x.val, y.val)) sub_fast(x::Quantity{T,D,U}, y::Quantity{T,D,U}) where {T <: FloatTypes,D,U} = Quantity{T,D,U}(sub_fast(x.val, y.val)) function mul_fast(x::Quantity{T}, y::Quantity{T}) where {T <: FloatTypes} D = dimension(x) * dimension(y) U = typeof(unit(x) * unit(y)) Quantity{T,D,U}(mul_fast(x.val, y.val)) end function div_fast(x::Quantity{T}, y::Quantity{T}) where {T <: FloatTypes} D = dimension(x) / dimension(y) U = typeof(unit(x) / unit(y)) Quantity{T,D,U}(div_fast(x.val, y.val)) end rem_fast(x::Quantity{T,D,U}, y::Quantity{T,D,U}) where {T <: FloatTypes,D,U} = Quantity{T,D,U}(rem_fast(x.val, y.val)) add_fast(x::Quantity{T}, y::Quantity{T}, z::Quantity{T}, t::Quantity{T}...) where {T <: FloatTypes} = add_fast(add_fast(add_fast(x, y), z), t...) mul_fast(x::Quantity{T}, y::Quantity{T}, z::Quantity{T}, t::Quantity{T}...) where {T <: FloatTypes} = mul_fast(mul_fast(mul_fast(x, y), z), t...) @fastmath begin cmp_fast(x::Quantity{T,D,U}, y::Quantity{T,D,U}) where {T <: FloatTypes,D,U} = ifelse(x==y, 0, ifelse(x x, y, x) min_fast(x::Quantity{T,D,U}, y::Quantity{T,D,U}) where {T <: FloatTypes,D,U} = ifelse(y > x, x, y) minmax_fast(x::Quantity{T,D,U}, y::Quantity{T,D,U}) where {T <: FloatTypes,D,U} = ifelse(y > x, (x,y), (y,x)) # complex numbers angle_fast(x::Quantity{T}) where {T <: ComplexTypes} = atan(imag(x), real(x)) end ================================================ FILE: src/logarithm.jl ================================================ base(::LogInfo{N,B}) where {N,B} = B prefactor(::LogInfo{N,B,P}) where {N,B,P} = P zero(x::T) where {T<:Level} = T(zero(x.val)) zero(::Type{X}) where {L,S,T,X<:Level{L,S,T}} = X(zero(T)) one(x::T) where {T<:Level} = one(x.val) one(::Type{X}) where {L,S,T,X<:Level{L,S,T}} = one(T) function Base.float(x::Level{L,S}) where {L,S} v = float(x.val) return Level{L,S,typeof(v)}(v) end big(x::Level{L,S}) where {L,S} = Level{L,S}(big(x.val)) """ logunit(x::LogScaled) logunit(x::Union{Type{<:LogScaled}, MixedUnits}) Returns the logarithmic "units" associated with a `LogScaled` instance, a `LogScaled` type, or a `MixedUnits`. Examples: ```jldoctest julia> using Unitful julia> logunit(1*u"dB") dB julia> logunit(u"dB") dB julia> logunit(u"dB/s") dB ``` See also: [`unit`](@ref). """ function logunit end logunit(x::Level{L,S}) where {L,S} = MixedUnits{Level{L,S}}() logunit(x::Type{T}) where {L,S,T<:Level{L,S}} = MixedUnits{Level{L,S}}() abbr(x::Level{L,S}) where {L,S} = join([abbr(L()), " (", S, ")"]) Base.convert(::Type{LogScaled{L1}}, x::Level{L2,S}) where {L1,L2,S} = Level{L1,S}(x.val) Base.convert(T::Type{<:Level}, x::Level) = T(x.val) Base.convert(::Type{Quantity{T,D,U}}, x::Level) where {T,D,U} = convert(Quantity{T,D,U}, x.val) Base.convert(::Type{Quantity{T}}, x::Level) where {T<:Number} = convert(Quantity{T}, x.val) Base.convert(::Type{T}, x::Quantity) where {L,S,T<:Level{L,S}} = T(x) Base.convert(::Type{T}, x::Level) where {T<:Real} = T(x.val) function Base.float(x::Gain{L,S}) where {L,S} v = float(x.val) return Gain{L,S,typeof(v)}(v) end big(x::Gain{L,S}) where {L,S} = Gain{L,S}(big(x.val)) logunit(x::Gain{L,S}) where {L,S} = MixedUnits{Gain{L,S}}() logunit(x::Type{T}) where {L,S, T<:Gain{L,S}} = MixedUnits{Gain{L,S}}() abbr(x::Gain{L}) where {L} = abbr(L()) zero(x::T) where {T<:Gain} = T(zero(x.val)) zero(::Type{X}) where {L,S,T, X<:Gain{L,S,T}} = X(zero(T)) one(x::T) where {T<:Gain} = T(zero(x.val)) one(::Type{X}) where {L,S,T, X<:Gain{L,S,T}} = X(zero(T)) function Gain{L}(val::Real) where {L <: LogInfo} dimension(val) != NoDims && throw(DimensionError(val,1)) return Gain{L, :?, typeof(val)}(val) end function Gain{L,S}(val::Real) where {L <: LogInfo,S} dimension(val) != NoDims && throw(DimensionError(val,1)) return Gain{L, S, typeof(val)}(val) end Base.convert(::Type{Gain{L}}, x::Gain{L,S}) where {L,S} = Gain{L,S}(x.val) Base.convert(::Type{Gain{L1}}, x::Gain{L2,S}) where {L1,L2,S} = Gain{L1,S}(_gconv(L1,L2,x)) Base.convert(::Type{Gain{L,S}}, x::Gain{L,S}) where {L,S} = Gain{L,S}(x.val) Base.convert(::Type{Gain{L1,S}}, x::Gain{L2,S}) where {L1,L2,S} = Gain{L1,S}(_gconv(L1,L2,x)) Base.convert(::Type{Gain{L,S1}}, x::Gain{L,S2}) where {L,S1,S2} = Gain{L,S1}(x.val) Base.convert(::Type{Gain{L1,S1}}, x::Gain{L2,S2}) where {L1,L2,S1,S2} = Gain{L1,S1}(_gconv(L1,L2,x)) Base.convert(::Type{Gain{L,S,T}}, x::Gain{L,S}) where {L,S,T} = Gain{L,S,T}(x.val) Base.convert(::Type{Gain{L1,S,T}}, x::Gain{L2,S}) where {L1,L2,S,T} = Gain{L1,S,T}(_gconv(L1,L2,x)) Base.convert(::Type{Gain{L,S1,T}}, x::Gain{L,S2}) where {L,S1,S2,T} = Gain{L,S1,T}(x.val) Base.convert(::Type{Gain{L1,S1,T}}, x::Gain{L2,S2}) where {L1,L2,S1,S2,T} = Gain{L1,S1,T}(_gconv(L1,L2,x)) Base.convert(::Type{LogScaled{L1}}, x::Gain{L2}) where {L1,L2} = Gain{L1}(_gconv(L1,L2,x)) Base.convert(::Type{T}, y::Gain) where {T<:Real} = convert(T, uconvert(NoUnits, y)) Base.convert(::Type{G}, x::Real) where {L, G <: Gain{L,:p}} = G(ifelse(isrootpower(L), 0.5, 1) * tolog(L, x)) Base.convert(::Type{G}, x::Real) where {L, G <: Gain{L,:rp}} = G(ifelse(isrootpower(L), 1, 2) * tolog(L, x)) Base.convert(::Type{<:Gain}, x::Real) = error("$x is not obviously a ratio of power or ", "root-power quantities; use `uconvertrp` or `uconvertp` instead.") function _gconv(L1,L2,x) if isrootpower(L1) == isrootpower(L2) gain = tolog(L1,fromlog(L2,x.val)) elseif isrootpower(L1) && !isrootpower(L2) gain = tolog(L1,fromlog(L2,0.5*x.val)) else gain = tolog(L1,fromlog(L2,2*x.val)) end return gain end tolog(L,S,x) = (1+isrootpower(S)) * prefactor(L()) * (logfn(L()))(x) tolog(L,x) = (1+isrootpower(L)) * prefactor(L()) * (logfn(L()))(x) fromlog(L,S,x) = unwrap(S) * expfn(L())( x / ((1+isrootpower(S))*prefactor(L())) ) fromlog(L,x) = expfn(L())( x / ((1+isrootpower(L))*prefactor(L())) ) function Base.show(io::IO, x::MixedUnits{T,U}) where {T,U} print(io, abbr(x)) if x.units != NoUnits print(io, " ") show(io, x.units) end end abbr(::MixedUnits{L}) where {L <: Level} = abbr(L(reflevel(L))) abbr(::MixedUnits{L}) where {L <: Gain} = abbr(L(1)) unit(a::MixedUnits{L,U}) where {L,U} = U() logunit(a::MixedUnits{L}) where {L} = MixedUnits{L}() isunitless(::MixedUnits) = false Base.:*(::MixedUnits, ::MixedUnits) = throw(ArgumentError("cannot multiply logarithmic units together.")) Base.:/(::MixedUnits{T}, ::MixedUnits{S}) where {T,S} = throw(ArgumentError("cannot divide logarithmic units except to cancel.")) Base.:/(x::MixedUnits{T}, y::MixedUnits{T}) where {T} = x.units / y.units Base.:*(x::MixedUnits{T}, y::Units) where {T} = MixedUnits{T}(x.units * y) Base.:*(x::Units, y::MixedUnits) = y * x Base.:/(x::MixedUnits{T}, y::Units) where {T} = MixedUnits{T}(x.units / y) Base.:/(x::Units, y::MixedUnits) = throw(ArgumentError("cannot divide logarithmic units except to cancel.")) Base.:*(x::Real, y::MixedUnits{Level{L,S}}) where {L,S} = (Level{L,S}(fromlog(L,S,x)))*y.units Base.:*(x::Real, y::MixedUnits{Gain{L,S}}) where {L,S} = (Gain{L,S}(x))*y.units Base.:*(x::MixedUnits, y::Number) = y * x Base.:/(x::Number, y::MixedUnits) = throw(ArgumentError("cannot divide $x by logarithmic units.")) Base.:/(x::MixedUnits, y::Number) = inv(y) * x function uconvert(a::Units, x::Level) dimension(a) != dimension(x) && throw(DimensionError(a,x)) return uconvert(a, x.val) end function uconvert(a::Units, x::Gain) dimension(a) != dimension(x) && throw(DimensionError(a,x)) uconvert(a, linear(x)) end uconvert(a::Units, x::Quantity{<:Level}) = uconvert(a, linear(x)) uconvert(a::Units, x::Quantity{<:Gain}) = uconvert(a, linear(x)) function uconvert(a::MixedUnits{Level{L,S}}, x::Number) where {L,S} dimension(a) != dimension(x) && throw(DimensionError(a,x)) q1 = uconvert(unit(unwrap(S))*a.units, linear(x)) / a.units return Level{L,S}(q1) * a.units end function uconvert(a::MixedUnits{Gain{L,S}}, x::Gain) where {L,S} dimension(a) != dimension(x) && throw(DimensionError(a,x)) return convert(Gain{L,S}, x) end function uconvert(a::MixedUnits{Gain{L,S}}, x::Number) where {L,S} dimension(a) != dimension(x) && throw(DimensionError(a,x)) if S == :rp return uconvertrp(a, x) elseif S == :p return uconvertp(a, x) else error("$x is not obviously a ratio of power or root-power quantities; ", "use `uconvertrp` or `uconvertp` instead.") end end function uconvert(a::MixedUnits{Gain{L,S}}, x::Quantity) where {L,S} dimension(a) != dimension(x) && throw(DimensionError(a,x)) convert(Gain{L,S}, x.val) * convfact(unit(a), unit(x)) * unit(a) end function uconvert(a::MixedUnits{Gain{L1,S1,<:Real}}, x::Level{L2,S2}) where {L1,L2,S1,S2} dimension(a) != dimension(x) && throw(DimensionError(a,x)) return Level{L1,S2}(x.val) end ustrip(x::Level{L,S}) where {L<:LogInfo,S} = tolog(L,S,x.val/reflevel(x)) ustrip(x::Gain) = x.val isrootpower(y::IsRootPowerRatio{T}) where {T} = T isrootpower(y) = isrootpower_dim(dimension(y)) isrootpower_dim(y) = error("undefined behavior. Please file an issue with the code needed to reproduce.") ==(x::Gain, y::Level) = ==(y,x) ==(x::Level, y::Gain) = false for op in (:(==), :isequal) @eval Base.$op(x::Level, y::Level) = $op(x.val, y.val) @eval Base.$op(x::Gain{L,S}, y::Gain{L,S}) where {L,S} = $op(x.val, y.val) end # A consistent `hash` method for `Gain` is impossible with the current promotion rules # (https://github.com/JuliaPhysics/Unitful.jl/issues/402), therefore we don't define one. Base.hash(x::Level, h::UInt) = hash(x.val, h) # Addition and subtraction for op in (:+, :-) @eval Base.$op(x::Level{L,S}, y::Level{L,S}) where {L,S} = Level{L,S}(($op)(x.val, y.val)) @eval Base.$op(x::Gain{L,S}, y::Gain{L,S}) where {L,S} = Gain{L,S}(($op)(x.val, y.val)) @eval function Base.$op(x::Gain{L,S1}, y::Gain{L,S2}) where {L,S1,S2} if S1 == :? return Gain{L,S2}(($op)(x.val, y.val)) elseif S2 == :? return Gain{L,S1}(($op)(x.val, y.val)) else return Gain{L,:?}(($op)(x.val, y.val)) end end @eval Base.$op(x::Level{L,S}, y::Gain{L}) where {L,S} = Level{L,S}(fromlog(L, S, ($op)(ustrip(x), y.val))) end Base.:+(x::Gain, y::Level) = +(y,x) Base.:-(x::Gain, y::Level) = throw(ArgumentError("cannot subtract a level from a gain.")) # Multiplication and division leveltype(x::Level{L,S}) where {L,S} = Level{L,S} Base.:*(x::Level, y::Number) = (leveltype(x))(x.val * y) Base.:*(x::Level, y::Bool) = (leveltype(x))(x.val * y) # for method ambiguity Base.:*(x::Level, y::AbstractQuantity) = *(x.val, y) Base.:*(x::Level, y::Level) = *(x.val, y.val) Base.:*(x::Level, y::Gain) = *(promote(x,y)...) Base.:*(x::Number, y::Level) = *(y,x) Base.:*(x::Bool, y::Level) = *(y,x) # for method ambiguity Base.:*(x::AbstractQuantity, y::Level) = *(y,x) # for method ambiguity gaintype(::Gain{L,S}) where {L,S} = Gain{L,S} Base.:*(x::Gain, y::Number) = (gaintype(x))(x.val * y) Base.:*(x::Gain, y::Bool) = (gaintype(x))(x.val * y) # for method ambiguity Base.:*(x::Gain, y::AbstractQuantity) = *(y,x) Base.:*(x::Gain, y::Level) = *(promote(x,y)...) Base.:*(x::Gain, y::Gain) = *(promote(x,y)...) Base.:*(x::Number, y::Gain) = *(y,x) Base.:*(x::Bool, y::Gain) = *(y,x) # for method ambiguity Base.:*(x::AbstractQuantity, y::Gain) = isrootpower(x) ? uconvertrp(NoUnits, y) * x : uconvertp(NoUnits, y) * x for (op1,op2) in ((:*, :+), (:/, :-)) @eval Base.$op1(x::Gain{L,S}, y::Gain{L,S}) where {L,S} = Gain{L,S}(($op2)(x.val, y.val)) @eval function Base.$op1(x::Gain{L,S1}, y::Gain{L,S2}) where {L,S1,S2} if S1 == :? return Gain{L,S2}(($op2)(x.val, y.val)) elseif S2 == :? return Gain{L,S1}(($op2)(x.val, y.val)) else return Gain{L,:?}(($op2)(x.val, y.val)) end end @eval Base.$op1(x::Level{L,S}, y::Gain{L}) where {L,S} = Level{L,S}(fromlog(L, S, ($op2)(ustrip(x), y.val))) end Base.:*(x::Gain{L}, y::Level{L,S}) where {L,S} = Level{L,S}(fromlog(L, S, ustrip(y)+x.val)) Base.:/(x::Gain, y::Level) = throw(ArgumentError("cannot divide a gain by a level.")) Base.:/(x::Level, y::Number) = (leveltype(x))(linear(x) / y) Base.://(x::Level, y::Number) = (leveltype(x))(linear(x) // y) Base.:/(x::Level, y::AbstractQuantity) = linear(x) / y Base.://(x::Level, y::AbstractQuantity) = linear(x) // y Base.:/(x::Level, y::Level) = linear(x) / linear(y) Base.://(x::Level, y::Level) = linear(x) // linear(y) Base.://(x::Level, y::Complex) = linear(x) // y # ambiguity resolution Base.://(x::Number, y::Level) = x // linear(y) Base.:/(x::AbstractQuantity, y::Level) = x / linear(y) Base.://(x::AbstractQuantity, y::Level) = x // linear(y) Base.:/(x::AbstractQuantity, y::Gain) = isrootpower(x) ? x / uconvertrp(NoUnits, y) : x / uconvertp(NoUnits, y) Base.://(x::AbstractQuantity, y::Gain) = isrootpower(x) ? x // uconvertrp(NoUnits, y) : x // uconvertp(NoUnits, y) Base.://(x::Level, y::Units) = x/y Base.://(x::Units, y::Level) = x//linear(y) Base.://(x::Gain, y::Units) = x/y Base.://(x::Units, y::Gain) = x//linear(y) Base.isless(x::T, y::T) where {T<:LogScaled} = isless(x.val, y.val) function (Base.promote_rule(::Type{Level{L1,S1,T1}}, ::Type{Level{L2,S2,T2}}) where {L1,L2,S1,S2,T1,T2}) if L1 == L2 if S1 == S2 # Use convert(promote_type(typeof(S1), typeof(S2)), S1) instead of S1? return Level{L1, S1, promote_type(T1,T2)} else return promote_type(T1,T2) end else return promote_type(T1,T2) end end function Base.promote_rule(::Type{Quantity{T,D,U}}, ::Type{Level{L,R,S}}) where {L,R,S,T,D,U} return promote_type(S, Quantity{T,D,U}) end function Base.promote_rule(::Type{Level{L,R,S}}, ::Type{T}) where {L,R,S,T<:Real} return promote_type(S,T) end function (Base.promote_rule(::Type{Gain{L1,S1,T1}}, ::Type{Gain{L2,S2,T2}}) where {L1,L2,S1,S2,T1,T2}) if L1 == L2 if S1 == :? return Gain{L1,S2,promote_type(T1,T2)} elseif S2 == :? return Gain{L1,S1,promote_type(T1,T2)} else return Gain{L1,:?,promote_type(T1,T2)} end else return promote_type(float(T1), float(T2)) end end function Base.promote_rule(::Type{G}, ::Type{N}) where {L,S,T1, G<:Gain{L,S,T1}, N<:Number} if S == :? error("no automatic promotion of $G and $N.") else return Gain{L,S,promote_type(float(T1), N)} end end Base.promote_rule(A::Type{G}, B::Type{L}) where {G<:Gain, L2, L<:Level{L2}} = LogScaled{L2} function Base.show(io::IO, x::Gain) print(io, x.val, " ", abbr(x)) nothing end function Base.show(io::IO, x::Level) print(io, ustrip(x), " ", abbr(x)) nothing end function Base.alignment(io::IO, x::Gain) length = printed_length(io, x) left, _ = Base.alignment(io, x.val) return left, length - left end function Base.alignment(io::IO, x::Level) length = printed_length(io, x) left, _ = Base.alignment(io, ustrip(x)) return left, length - left end BracketStyle(::Type{<:Union{Level,Gain}}) = SquareBrackets() """ uconvertp(u::Units, x) uconvertp(u::MixedUnits, x) Generically, this is the same as [`Unitful.uconvert`](@ref). In cases where unit conversion would be ambiguous without further information (e.g. `uconvert(dB, 10)`), `uconvertp` presumes ratios are of power quantities. It is important to note that careless use of this function can lead to erroneous calculations. Consider `Quantity{<:Gain}` types: it is tempting to use this to transform `-20dB/m` into `0.1/m`, however this means something fundamentally different than `-20dB/m`. Consider what happens when you try to compute exponential attenuation by multiplying `0.1/m` by a length. Examples: ```jldoctest julia> using Unitful julia> uconvertp(u"dB", 10) 10.0 dB julia> uconvertp(NoUnits, 20u"dB") 100.0 ``` """ function uconvertp end uconvertp(u, x) = uconvert(u, x) # fallback uconvertp(::Units{()}, x::Gain{L}) where {L} = fromlog(L, ifelse(isrootpower(L), 2, 1)*x.val) uconvertp(u::T, x::Real) where {L, G <: Gain{L}, T <: MixedUnits{G, <:Units{()}}} = convert(Gain{L,:p}, x) # function uconvertp(a::MixedUnits{Gain{L}}, x::Number) where {L} # dimension(a) != dimension(x) && throw(DimensionError(a,x)) # # end """ uconvertrp(u::Units, x) uconvertrp(u::MixedUnits, x) In most cases, this is the same as [`Unitful.uconvert`](@ref). In cases where unit conversion would be ambiguous without further information (e.g. `uconvert(dB, 10)`), `uconvertrp` presumes ratios are of root-power quantities. It is important to note that careless use of this function can lead to erroneous calculations. Consider `Quantity{<:Gain}` types: it is tempting to use this to transform `-20dB/m` into `0.01/m`, however this means something fundamentally different than `-20dB/m`. Consider what happens when you try to compute exponential attenuation by multiplying `0.01/m` by a length. """ function uconvertrp end uconvertrp(u, x) = uconvert(u, x) uconvertrp(::Units{()}, x::Gain{L}) where {L} = fromlog(L, ifelse(isrootpower(L), 1.0, 0.5)*x.val) uconvertrp(u::T, x::Real) where {L, G <: Gain{L}, T <: MixedUnits{G, <:Units{()}}} = convert(Gain{L,:rp}, x) """ linear(x::Quantity) linear(x::Level) linear(x::Number) = x Returns a quantity equivalent to `x` but without any logarithmic scales. It is important to note that this operation will error for `Quantity{<:Gain}` types. This is for two reasons: - `20dB` could be interpreted as either a power or root-power ratio. - Even if `-20dB/m` were interpreted as, say, `0.01/m`, this means something fundamentally different than `-20dB/m`. `0.01/m` cannot be used to calculate exponential attenuation. """ linear(x::Quantity{<:Level}) = (x.val.val)*unit(x) linear(x::Quantity{<:Gain{L,:?}}) where {L} = error("ambiguous how to linearize. Cannot determine ", "whether to use `uconvertrp` or `uconvertp` from the type of $x: `$(typeof(x))`.") linear(x::Quantity{<:Gain{L,:rp}}) where {L} = uconvertrp(NoUnits, x.val)*unit(x) linear(x::Quantity{<:Gain{L,:p}}) where {L} = uconvertp(NoUnits, x.val)*unit(x) linear(x::Level) = x.val linear(x::Gain{L,:rp}) where {L} = uconvertrp(NoUnits, x) linear(x::Gain{L,:p}) where {L} = uconvertp(NoUnits, x) linear(x::Gain{L,:?}) where {L} = error("ambiguous how to linearize. Cannot determine ", "whether to use `uconvertrp` or `uconvertp` from the type of $x: `$(typeof(x))`.") linear(x::Number) = x """ logfn(x::LogInfo) Returns the appropriate logarithm function to use in calculations involving the logarithmic unit / quantity. For example, decibel-based units yield `log10`, Neper-based yield `ln`, and so on. Returns `x->log(base, x)` as a fallback. """ function logfn end logfn(x::LogInfo{N,10}) where {N} = log10 logfn(x::LogInfo{N,2}) where {N} = log2 logfn(x::LogInfo{N,ℯ}) where {N} = log logfn(x::LogInfo{N,B}) where {N,B} = x->log(B,x) """ expfn(x::LogInfo) Returns the appropriate exponential function to use in calculations involving the logarithmic unit / quantity. For example, decibel-based units yield `exp10`, Neper-based yield `exp`, and so on. Returns `x->(base)^x` as a fallback. """ function expfn end expfn(x::LogInfo{N,10}) where {N} = exp10 expfn(x::LogInfo{N,2}) where {N} = exp2 expfn(x::LogInfo{N,ℯ}) where {N} = exp expfn(x::LogInfo{N,B}) where {N,B} = x->B^x Base.rtoldefault(::Type{Level{L,S,T}}) where {L,S,T} = Base.rtoldefault(typeof(tolog(L,S,oneunit(T)/unwrap(S)))) Base.rtoldefault(::Type{Gain{L,S,T}}) where {L,S,T} = Base.rtoldefault(T) Base.isapprox(x::Level, y::Level; kwargs...) = isapprox(promote(x,y)...; kwargs...) Base.isapprox(x::T, y::T; kwargs...) where {T <: Level} = _isapprox(x, y; kwargs...) _isapprox(x::Level{L,S,T}, y::Level{L,S,T}; atol = Level{L,S}(reflevel(x)), kwargs...) where {L,S,T} = isapprox(ustrip(x), ustrip(y); atol = ustrip(convert(Level{L,S}, atol)), kwargs...) Base.isapprox(x::Gain, y::Gain; kwargs...) = isapprox(promote(x,y)...; kwargs...) Base.isapprox(x::T, y::T; kwargs...) where {T <: Gain} = _isapprox(x, y; kwargs...) _isapprox(x::Gain{L,S,T}, y::Gain{L,S,T}; atol = Gain{L}(oneunit(T)), kwargs...) where {L,S,T} = #TODO isapprox(ustrip(x), ustrip(y); atol = ustrip(convert(Gain{L,S,T}, atol)), kwargs...) *(A::MixedUnits, B::AbstractArray) = broadcast(*, A, B) *(A::AbstractArray, B::MixedUnits) = broadcast(*, A, B) Base.broadcastable(x::MixedUnits) = Ref(x) ================================================ FILE: src/pkgdefaults.jl ================================================ # Default dimensions and their abbreviations. # The dimension symbols are generated by tab completion: \bfL is 𝐋, etc. # At the expense of easy typing, this gives a visual cue to distinguish # dimensions from units, and also helps prevent common namespace collisions. " Unitful.𝐋 \nA dimension representing length." @dimension 𝐋 "𝐋" Length true " Unitful.𝐌 \nA dimension representing mass." @dimension 𝐌 "𝐌" Mass true " Unitful.𝐓 \nA dimension representing time." @dimension 𝐓 "𝐓" Time true " Unitful.𝐈 \nA dimension representing electric current." @dimension 𝐈 "𝐈" Current true " Unitful.𝚯 \nA dimension representing thermodynamic temperature." @dimension 𝚯 "𝚯" Temperature true # This one is \bfTheta " Unitful.𝐉 \nA dimension representing luminous intensity." @dimension 𝐉 "𝐉" Luminosity true " Unitful.𝐍 \nA dimension representing amount of substance." @dimension 𝐍 "𝐍" Amount true const RelativeScaleTemperature = Quantity{T, 𝚯, <:AffineUnits} where T const AbsoluteScaleTemperature = Quantity{T, 𝚯, <:ScalarUnits} where T # Define derived dimensions. @derived_dimension Area 𝐋^2 true @derived_dimension Volume 𝐋^3 true @derived_dimension Density 𝐌/𝐋^3 true @derived_dimension Frequency inv(𝐓) true @derived_dimension Velocity 𝐋/𝐓 true @derived_dimension Acceleration 𝐋/𝐓^2 true @derived_dimension Force 𝐌*𝐋/𝐓^2 true @derived_dimension Pressure 𝐌*𝐋^-1*𝐓^-2 true @derived_dimension Energy 𝐌*𝐋^2/𝐓^2 true @derived_dimension Momentum 𝐌*𝐋/𝐓 true @derived_dimension Power 𝐋^2*𝐌*𝐓^-3 true @derived_dimension Charge 𝐈*𝐓 true @derived_dimension Voltage 𝐈^-1*𝐋^2*𝐌*𝐓^-3 true @derived_dimension ElectricalResistance 𝐈^-2*𝐋^2*𝐌*𝐓^-3 true @derived_dimension ElectricalResistivity 𝐈^-2*𝐋^3*𝐌*𝐓^-3 true @derived_dimension ElectricalConductance 𝐈^2*𝐋^-2*𝐌^-1*𝐓^3 true @derived_dimension ElectricalConductivity 𝐈^2*𝐋^-3*𝐌^-1*𝐓^3 true @derived_dimension Capacitance 𝐈^2*𝐋^-2*𝐌^-1*𝐓^4 true @derived_dimension Inductance 𝐈^-2*𝐋^2*𝐌*𝐓^-2 true @derived_dimension MagneticFlux 𝐈^-1*𝐋^2*𝐌*𝐓^-2 true @derived_dimension DField 𝐈*𝐓/𝐋^2 true @derived_dimension EField 𝐋*𝐌*𝐓^-3*𝐈^-1 true @derived_dimension HField 𝐈/𝐋 true @derived_dimension BField 𝐈^-1*𝐌*𝐓^-2 true @derived_dimension Action 𝐋^2*𝐌*𝐓^-1 true @derived_dimension DynamicViscosity 𝐌*𝐋^-1*𝐓^-1 true @derived_dimension KinematicViscosity 𝐋^2*𝐓^-1 true @derived_dimension Wavenumber inv(𝐋) true @derived_dimension ElectricDipoleMoment 𝐋*𝐓*𝐈 true @derived_dimension ElectricQuadrupoleMoment 𝐋^2*𝐓*𝐈 true @derived_dimension MagneticDipoleMoment 𝐋^2*𝐈 true @derived_dimension Molarity 𝐍/𝐋^3 true @derived_dimension Molality 𝐍/𝐌 true @derived_dimension MolarMass 𝐌/𝐍 true @derived_dimension MassFlow 𝐌/𝐓 true @derived_dimension MolarFlow 𝐍/𝐓 true @derived_dimension VolumeFlow 𝐋^3/𝐓 true # Define base units. This is not to imply g is the base SI unit instead of kg. # See the documentation for further details. # #key: Symbol Display Name Dimension Prefixes? " Unitful.m \nThe meter, the SI base unit of length. \nDimension: [`Unitful.𝐋`](@ref)." @refunit m "m" Meter 𝐋 true true " Unitful.s \nThe second, the SI base unit of time. \nDimension: [`Unitful.𝐓`](@ref)." @refunit s "s" Second 𝐓 true true " Unitful.A \nThe ampere, the SI base unit of electric current. \nDimension: [`Unitful.𝐈`](@ref)." @refunit A "A" Ampere 𝐈 true true " Unitful.K \nThe kelvin, the SI base unit of thermodynamic temperature. \nDimension: [`Unitful.𝚯`](@ref)." @refunit K "K" Kelvin 𝚯 true true " Unitful.cd \nThe candela, the SI base unit of luminous intensity. \nDimension: [`Unitful.𝐉`](@ref)." @refunit cd "cd" Candela 𝐉 true true # the docs for all gram-based units are defined later, to ensure kg is the base unit. @refunit g "g" Gram 𝐌 true " Unitful.mol \nThe mole, the SI base unit for amount of substance. \nDimension: [`Unitful.𝐍`](@ref)." @refunit mol "mol" Mole 𝐍 true true # Angles and solid angles " Unitful.sr \nThe steradian, a unit of spherical angle. There are 4π sr in a sphere. \nDimension: [`Unitful.NoDims`](@ref)." @unit sr "sr" Steradian 1 true true " Unitful.rad \nThe radian, a unit of angle. There are 2π rad in a circle. \nDimension: [`Unitful.NoDims`](@ref)." @unit rad "rad" Radian 1 true true " Unitful.° Unitful.deg \nThe degree, a unit of angle. There are 360° in a circle. \nDimension: [`Unitful.NoDims`](@ref)." ((@unit ° "°" Degree pi/180 false), const deg = °) # For numerical accuracy, specific to the degree import Base: sind, cosd, sincosd, tand, secd, cscd, cotd for (_x,_y) in ((:sin,:sind), (:cos,:cosd), (:sincos,:sincosd), (:tan,:tand), (:sec,:secd), (:csc,:cscd), (:cot,:cotd)) @eval ($_x)(x::Quantity{T, NoDims, typeof(°)}) where {T} = ($_y)(ustrip(x)) @eval ($_y)(x::Quantity{T, NoDims, typeof(°)}) where {T} = ($_y)(ustrip(x)) end for f in (:cos, :sin, :tan, :sincos, :cis) f_fast = fast_op[f] # Use deg2rad because uncnvert only has Float64 precision @eval $f_fast(x::Quantity{T, NoDims, typeof(°)}) where {T} = $f_fast(deg2rad(x.val)) end # conversion between degrees and radians import Base: deg2rad, rad2deg deg2rad(d::Quantity{T, NoDims, typeof(°)}) where {T} = deg2rad(ustrip(°, d)) * rad rad2deg(r::Quantity{T, NoDims, typeof(rad)}) where {T} = rad2deg(ustrip(rad, r)) * ° # SI and related units " Unitful.Hz \nThe hertz, an SI unit of frequency, defined as 1 s^-1. \nDimension: 𝐓^-1. \nSee also: [`Unitful.s`](@ref)." @unit Hz "Hz" Hertz 1/s true true " Unitful.N \nThe newton, an SI unit of force, defined as 1 kg × m / s^2. \nDimension: 𝐋 𝐌 𝐓^-2. \nSee also: [`Unitful.kg`](@ref), [`Unitful.m`](@ref), [`Unitful.s`](@ref)." @unit N "N" Newton 1kg*m/s^2 true true " Unitful.Pa \nThe pascal, an SI unit of pressure, defined as 1 N / m^2. \nDimension: 𝐌 𝐋^-1 𝐓^-2. \nSee also: [`Unitful.N`](@ref), [`Unitful.m`](@ref)." @unit Pa "Pa" Pascal 1N/m^2 true true " Unitful.J \nThe joule, an SI unit of energy, defined as 1 N × m. \nDimension: 𝐋^2 𝐌 𝐓^-2. \nSee also: [`Unitful.N`](@ref), [`Unitful.m`](@ref)." @unit J "J" Joule 1N*m true true " Unitful.W \nThe watt, an SI unit of power, defined as 1 J / s. \nDimension: 𝐋^2 𝐌 𝐓^-3. \nSee also: [`Unitful.J`](@ref), [`Unitful.s`](@ref)." @unit W "W" Watt 1J/s true true " Unitful.C \nThe coulomb, an SI unit of electric charge, defined as 1 A × s. \nDimension: 𝐈 𝐓. \nSee also: [`Unitful.A`](@ref), [`Unitful.s`](@ref)." @unit C "C" Coulomb 1A*s true true " Unitful.V \nThe volt, an SI unit of electric potential, defined as 1 W / A. \nDimension: 𝐋^2 𝐌 𝐈^-1 𝐓^-3. \nSee also: [`Unitful.W`](@ref), [`Unitful.A`](@ref)" @unit V "V" Volt 1W/A true true " Unitful.Ω \nThe ohm, an SI unit of electrical resistance, defined as 1 V / A. \nDimension: 𝐋^2 𝐌 𝐈^-2 𝐓^-3. \nSee also: [`Unitful.V`](@ref), [`Unitful.A`](@ref)." @unit Ω "Ω" Ohm 1V/A true true " Unitful.S \nThe siemens, an SI unit of electrical conductance, defined as 1 Ω^-1. \nDimension: 𝐈^2 𝐓^3 𝐋^-2 𝐌^-1. \nSee also: [`Unitful.Ω`](@ref)" @unit S "S" Siemens 1/Ω true true " Unitful.F \nThe farad, an SI unit of electrical capacitance, defined as 1 s^4 × A^2 / (kg × m^2). \nDimension: 𝐈^2 𝐓^4 𝐋^-2 𝐌^-1. \nSee also: [`Unitful.s`](@ref), [`Unitful.A`](@ref), [`Unitful.kg`](@ref), [`Unitful.m`](@ref)." @unit F "F" Farad 1s^4*A^2/(kg*m^2) true true " Unitful.H \nThe henry, an SI unit of electrical inductance, defined as 1 J / A^2. \nDimension: 𝐋^2 𝐌 𝐈^-2 𝐓^-2. \nSee also: [`Unitful.J`](@ref), [`Unitful.A`](@ref)." @unit H "H" Henry 1J/(A^2) true true " Unitful.T \nThe tesla, an SI unit of magnetic B-field strength, defined as 1 kg / (A × s^2). \nDimension: 𝐌 𝐈^-1 𝐓^-2. \nSee also: [`Unitful.kg`](@ref), [`Unitful.A`](@ref), [`Unitful.s`](@ref)." @unit T "T" Tesla 1kg/(A*s^2) true true " Unitful.Wb \nThe weber, an SI unit of magnetic flux, defined as 1 kg × m^2 / (A × s^2). \nDimension: 𝐋^2 𝐌 𝐈^-1 𝐓^-2. \nSee also: [`Unitful.kg`](@ref), [`Unitful.m`](@ref), [`Unitful.A`](@ref), [`Unitful.s`](@ref)." @unit Wb "Wb" Weber 1kg*m^2/(A*s^2) true true " Unitful.lm \nThe lumen, an SI unit of luminous flux, defined as 1 cd × sr. \nDimension: [`Unitful.𝐉`](@ref). \nSee also: [`Unitful.cd`](@ref), [`Unitful.sr`](@ref)." @unit lm "lm" Lumen 1cd*sr true true " Unitful.lx \nThe lux, an SI unit of illuminance, defined as 1 lm / m^2. \nDimension: 𝐉 𝐋^-2. \nSee also: [`Unitful.lm`](@ref), [`Unitful.m`](@ref)." @unit lx "lx" Lux 1lm/m^2 true true " Unitful.Bq \nThe becquerel, an SI unit of radioactivity, defined as 1 nuclear decay per s. \nDimension: 𝐓^-1. \nSee also: [`Unitful.s`](@ref)." @unit Bq "Bq" Becquerel 1/s true true " Unitful.Gy \nThe gray, an SI unit of ionizing radiation dose, defined as the absorption of 1 J per kg of matter. \nDimension: 𝐋^2 𝐓^-2. \nSee also: [`Unitful.lm`](@ref), [`Unitful.m`](@ref)." @unit Gy "Gy" Gray 1J/kg true true " Unitful.Sv \nThe sievert, an SI unit of the biological effect of an ionizing radiation dose. Defined as the health effect of 1 Gy of radiation, scaled by a quality factor. \nDimension: 𝐋^2 𝐓^-2. \nSee also: [`Unitful.Gy`](@ref)." @unit Sv "Sv" Sievert 1J/kg true true " Unitful.kat \nThe katal, an SI unit of catalytic activity, defined as 1 mol of catalyzed substrate per s. \nDimension: 𝐍 𝐓^-1. \nSee also: [`Unitful.mol`](@ref), [`Unitful.s`](@ref)." @unit kat "kat" Katal 1mol/s true true " Unitful.percent \nPercent, a unit meaning parts per hundred. Printed as \"%\". \nDimension: [`Unitful.NoDims`](@ref)." @unit percent "%" Percent 1//100 false " Unitful.permille \nPermille, a unit meaning parts per thousand. Printed as \"‰\". \nDimension: [`Unitful.NoDims`](@ref)." @unit permille "‰" Permille 1//1000 false " Unitful.pertenthousand \nPermyriad, a unit meaning parts per ten thousand. Printed as \"‱\". \nDimension: [`Unitful.NoDims`](@ref)." @unit pertenthousand "‱" Pertenthousand 1//10000 false " Unitful.pcm \nPercentmille, a unit meaning parts per hundred thousand. \nDimension: [`Unitful.NoDims`](@ref)." @unit pcm "pcm" Percentmille 1//100000 false " Unitful.ppm \nPermillion, a unit meaning parts per million. \nDimension: [`Unitful.NoDims`](@ref)." @unit ppm "ppm" Permillion 1//1000000 false " Unitful.ppb \nPerbillion, a unit meaning parts per billion (in the short-scale sense), i.e., 10^-9. \nDimension: [`Unitful.NoDims`](@ref)." @unit ppb "ppb" Perbillion 1//1000000000 false " Unitful.ppt \nPertrillion, a unit meaning parts per trillion (in the short-scale sense), i.e., 10^-12. \nDimension: [`Unitful.NoDims`](@ref)." @unit ppt "ppt" Pertrillion 1//1000000000000 false " Unitful.ppq \nPerquadrillion, a unit meaning parts per quadrillion (in the short-scale sense), i.e., 10^-15. \nDimension: [`Unitful.NoDims`](@ref)." @unit ppq "ppq" Perquadrillion 1//1000000000000000 false # Temperature " Unitful.°C Unitful.degC \nThe degree Celsius, an SI unit of temperature, defined such that 0 °C = 273.15 K. \nDimension: [`Unitful.𝚯`](@ref). \nSee also: [`Unitful.K`](@ref)." ((@affineunit °C "°C" (27315//100)K), const degC = °C) # Common units of time " Unitful.minute \nThe minute, a unit of time defined as 60 s. The full name `minute` is used instead of the symbol `min` to avoid confusion with the Julia function `min`. \nDimension: [`Unitful.𝐓`](@ref). \nSee Also: [`Unitful.s`](@ref)." @unit minute "minute" Minute 60s false " Unitful.hr \nThe hour, a unit of time defined as 60 minutes. \nDimension: [`Unitful.𝐓`](@ref). \nSee Also: [`Unitful.minute`](@ref)." @unit hr "hr" Hour 3600s false " Unitful.d \nThe day, a unit of time defined as 24 hr. \nDimension: [`Unitful.𝐓`](@ref). \nSee Also: [`Unitful.hr`](@ref)." @unit d "d" Day 86400s false " Unitful.wk \nThe week, a unit of time, defined as 7 d. \nDimension: [`Unitful.𝐓`](@ref). \nSee Also: [`Unitful.d`](@ref)." @unit wk "wk" Week 604800s false " Unitful.yr \nThe year, a unit of time, defined as 365.25 d. \nDimension: [`Unitful.𝐓`](@ref). \nSee Also: [`Unitful.hr`](@ref)." @unit yr "yr" Year 31557600s true true " Unitful.rps \nRevolutions per second, a unit of rotational speed, defined as 2π rad / s. \nDimension: 𝐓^-1. \nSee Also: [`Unitful.rad`](@ref), [`Unitful.s`](@ref)." @unit rps "rps" RevolutionsPerSecond 2π*rad/s false " Unitful.rpm \nRevolutions per minute, a unit of rotational speed, defined as 2π rad / minute. \nDimension: 𝐓^-1. \nSee Also: [`Unitful.minute`](@ref), [`Unitful.rad`](@ref)." @unit rpm "rpm" RevolutionsPerMinute 2π*rad/minute false # Area # The hectare is used more frequently than any other power-of-ten of an are. " Unitful.a \nThe are, a metric unit of area, defined as 100 m^2. \nDimension: 𝐋^2. \nSee Also: [`Unitful.m`](@ref)." @unit a "a" Are 100m^2 false " Unitful.ha \nThe hectare, a metric unit of area, defined as 100 a. \nDimension: 𝐋^2. \nSee Also: [`Unitful.a`](@ref)." const ha = Unitful.FreeUnits{(Unitful.Unit{:Are, 𝐋^2}(2, 1//1),), 𝐋^2}() " Unitful.b \nThe barn, a metric unit of area, defined as 100 fm^2. \nDimension: 𝐋^2. \nSee Also: [`Unitful.fm`](@ref)." @unit b "b" Barn 100fm^2 true true # Volume # `l` is also an acceptable symbol for liters " Unitful.L Unitful.l \nThe liter, a metric unit of volume, defined as 1000 cm^3. \nDimension: 𝐋^3. \nSee Also: [`Unitful.cm`](@ref)." ((@unit L "L" Liter m^3//1000 true), const l = L) for (k,v) in prefixdict if k != 0 sym_L = Symbol(v,:L) sym_l = Symbol(v,:l) docstring = """ Unitful.$sym_L Unitful.$sym_l A prefixed unit, equal to 10^$k L. Dimension: 𝐋^3. See also: [`Unitful.L`](@ref). """ run = quote @doc $docstring ((const $sym_l = $sym_L), $sym_L) end eval(run) end end # Molarity " Unitful.M \nA unit for measuring molar concentration, equal to 1 mol/L. \nDimension: 𝐍 𝐋^-3. \nSee Also: [`Unitful.L`](@ref), [`Unitful.mol`](@ref)." @unit M "M" Molar 1mol/L true true # Energy " Unitful.q \nA quantity equal to the elementary charge, the charge of a single electron, with a value of exactly 1.602,176,634 × 10^-19 C. The letter `q` is used instead of `e` to avoid confusion with Euler's number. \nDimension: 𝐈 𝐓. \nSee Also: [`Unitful.C`](@ref)." const q = 1.602_176_634e-19*C # CODATA 2018; `e` means 2.718... " Unitful.eV \nThe electron-volt, a unit of energy, defined as q*V. \nDimension: 𝐋^2 𝐌 𝐓^-2. \nSee also: [`Unitful.q`](@ref), [`Unitful.V`](@ref)." @unit eV "eV" eV q*V true true # For convenience " Unitful.Hz2π \nA unit for convenience in angular frequency, equal to 2π Hz. \nDimension: 𝐓^-1. \nSee also: [`Unitful.Hz`](@ref)." @unit Hz2π "Hz2π" AngHertz 2π/s true true " Unitful.bar \nThe bar, a metric unit of pressure, defined as 100 kPa. \nDimension: 𝐌 𝐋^-1 𝐓^-2. \nSee also: [`Unitful.kPa`](@ref)." @unit bar "bar" Bar 100000Pa true true " Unitful.atm \nThe standard atmosphere, a unit of pressure, defined as 101,325 Pa. \nDimension: 𝐌 𝐋^-1 𝐓^-2. \nSee also: [`Unitful.Pa`](@ref)." @unit atm "atm" Atmosphere 101325Pa true true " Unitful.Torr \nThe torr, a unit of pressure, defined as 1/760 atm. \nDimension: 𝐌 𝐋^-1 𝐓^-2. \nSee also: [`Unitful.atm`](@ref)." @unit Torr "Torr" Torr 101325Pa//760 true true # Constants (2018 CODATA values) (uncertainties in final digits) " Unitful.c0 \nA quantity representing the speed of light in a vacuum, defined as exactly 2.997,924,58 × 10^8 m/s. \n`Unitful.c0` is a quantity (with units `m/s`) whereas [`Unitful.c`](@ref) is a unit equal to `c0`. \nDimension: 𝐋 𝐓^-1. \nSee also: [`Unitful.m`](@ref), [`Unitful.s`](@ref)." const c0 = 299_792_458*m/s # exact " Unitful.c \nThe speed of light in a vacuum, a unit of speed, defined as exactly 2.997,924,58 × 10^8 m/s. \n[`Unitful.c0`](@ref) is a quantity (with units `m/s`) whereas `Unitful.c` is a unit equal to `c0`. \nDimension: 𝐋 𝐓^-1. \nSee also: [`Unitful.m`](@ref), [`Unitful.s`](@ref)." @unit c "c" SpeedOfLight 1c0 false " Unitful.μ0 \nA quantity representing the vacuum permeability constant, defined as 4π × 10^-7 H / m. \nDimension: 𝐋 𝐌 𝐈^-2 𝐓^-2. \nSee also: [`Unitful.H`](@ref), [`Unitful.m`](@ref)." const μ0 = 4π*(1//10)^7*H/m # exact (but gets promoted to Float64...), magnetic constant " Unitful.ε0 Unitful.ϵ0 \nA quantity representing the vacuum permittivity constant, defined as 1 / (μ0 × c^2). \nDimension: 𝐈^2 𝐓^4 𝐋^-3 𝐌^-1. \nSee also: [`Unitful.μ0`](@ref), [`Unitful.c`](@ref)." ((const ε0 = 1/(μ0*c^2)), const ϵ0 = ε0) # exact, electric constant; changes here may affect test of issue 79. " Unitful.Z0 \nA quantity representing the impedance of free space, a constant defined as μ0 × c. \nDimension: 𝐋^2 𝐌 𝐈^-2 𝐓^-3. \nSee also: [`Unitful.μ0`](@ref), [`Unitful.c`](@ref)." const Z0 = μ0*c # exact, impedance of free space " Unitful.G \nA quantity representing the universal gravitational constant, equal to 6.674,30 × 10^-11 m^3 / (kg × s^2) (the CODATA 2018 recommended value). \nDimension: 𝐋^3 𝐌^-1 𝐓^-2. \nSee also: [`Unitful.m`](@ref), [`Unitful.kg`](@ref), [`Unitful.s`](@ref)." const G = 6.674_30e-11*m^3/kg/s^2 # (15) gravitational constant " Unitful.gn \nA quantity representing the nominal acceleration due to gravity in a vacuum near the surface of the earth, defined by standard to be exactly 9.806,65 m / s^2. \n`Unitful.gn` is a quantity (with units `m/s^2`) whereas [`Unitful.ge`](@ref) is a unit equal to `gn`. \nDimension: 𝐋 𝐓^-2. \nSee also: [`Unitful.m`](@ref), [`Unitful.s`](@ref)." const gn = 9.80665*m/s^2 # exact, standard acceleration of gravity " Unitful.h \nA quantity representing Planck's constant, defined as exactly 6.626,070,15 × 10^-34 J × s. \nDimension: 𝐋^2 𝐌 𝐓^-1. \nSee also: [`Unitful.J`](@ref), [`Unitful.s`](@ref)." const h = 6.626_070_15e-34*J*s # exact, Planck constant " Unitful.ħ \nA quantity representing the reduced Planck constant, defined as h / 2π. \nDimension: 𝐋^2 𝐌 𝐓^-1. \nSee also: [`Unitful.h`](@ref)." const ħ = h/2π # hbar " Unitful.Φ0 \nA quantity representing the superconducting magnetic flux quantum, defined as h / (2 × q). \nDimension: 𝐋^2 𝐌 𝐈^-1 𝐓^-2. \nSee also: [`Unitful.h`](@ref), [`Unitful.q`](@ref)." const Φ0 = h/(2q) # Superconducting magnetic flux quantum " Unitful.me \nA quantity representing the rest mass of an electron, equal to 9.109,383,7015 × 10^-31 kg (the CODATA 2018 recommended value). \nDimension: [`Unitful.𝐌`](@ref). \nSee also: [`Unitful.kg`](@ref)." const me = 9.109_383_7015e-31*kg # (28) electron rest mass " Unitful.mn \nA quantity representing the rest mass of a neutron, equal to 1.674,927,498,04 × 10^-27 kg (the CODATA 2018 recommended value). \nDimension: [`Unitful.𝐌`](@ref). \nSee also: [`Unitful.kg`](@ref)." const mn = 1.674_927_498_04e-27*kg # (95) neutron rest mass " Unitful.mp \nA quantity representing the rest mass of a proton, equal to 1.672,621,923,69 × 10^-27 kg (the CODATA 2018 recommended value). \nDimension: [`Unitful.𝐌`](@ref). \nSee also: [`Unitful.kg`](@ref)." const mp = 1.672_621_923_69e-27*kg # (51) proton rest mass " Unitful.μB \nA quantity representing the Bohr magneton, equal to q × ħ / (2 × me). \nDimension: 𝐈 𝐋^2. \nSee also: [`Unitful.q`](@ref), [`Unitful.ħ`](@ref), [`Unitful.me`](@ref)." const μB = q*ħ/(2*me) # Bohr magneton " Unitful.Na \nA quantity representing Avogadro's constant, defined as exactly 6.022,140,76 × 10^23 / mol. \nDimension: 𝐍^-1. \nSee also: [`Unitful.mol`](@ref)." const Na = 6.022_140_76e23/mol # exact, Avogadro constant " Unitful.k \nA quantity representing the Boltzmann constant, defined as exactly 1.380,649 × 10^-23 J / K. \nDimension: 𝐋^2 𝐌 𝚯^-1 𝐓^-2. \nSee also: [`Unitful.J`](@ref), [`Unitful.K`](@ref)." const k = 1.380_649e-23*(J/K) # exact, Boltzmann constant " Unitful.R \nA quantity representing the molar gas constant, defined as Na × k. \nDimension: 𝐋^2 𝐌 𝐍^-1 𝚯^-1 𝐓^-2. \nSee also: [`Unitful.Na`](@ref), [`Unitful.k`](@ref)." const R = Na*k # molar gas constant " Unitful.σ \nA quantity representing the Stefan-Boltzmann constant, defined as π^2 × k^4 / (60 × ħ^3 × c^2). \nDimension: 𝐌 𝚯^-4 𝐓^-3. \nSee also: [`Unitful.k`](@ref), [`Unitful.ħ`](@ref), [`Unitful.c`](@ref)." const σ = π^2*k^4/(60*ħ^3*c^2) # Stefan-Boltzmann constant " Unitful.R∞ \nA quantity representing the Rydberg constant, equal to 1.097,373,156,8160 × 10^-7 / m (the CODATA 2018 recommended value). \nDimension: 𝐋^-1. \nSee also: [`Unitful.m`](@ref)." const R∞ = 10_973_731.568_160/m # (21) Rydberg constant " Unitful.u \nThe unified atomic mass unit, or dalton, a unit of mass defined as 1/12 the mass of an unbound neutral atom of carbon-12, equal to 1.660,539,066,60 × 10^-27 kg (the CODATA 2018 recommended value). \nDimension: [`Unitful.𝐌`](@ref). \nSee Also: [`Unitful.kg`](@ref)." @unit u "u" UnifiedAtomicMassUnit 1.660_539_066_60e-27*kg false # (50) # Acceleration " Unitful.ge \nThe nominal acceleration due to gravity in a vacuum near the surface of the earth, a unit of acceleration, defined by standard to be exactly 9.806,65 m / s^2. \n[`Unitful.gn`](@ref) is a quantity (with units `m/s^2`) whereas `Unitful.ge` is a unit equal to `gn`. \nDimension: 𝐋 𝐓^-2. \nSee also: [`Unitful.m`](@ref), [`Unitful.s`](@ref)." @unit ge "ge" EarthGravity gn false # CGS units " Unitful.Gal \nThe gal, a CGS unit of acceleration, defined as 1 cm / s^2. \nDimension: 𝐋 𝐓^-2. \nSee also: [`Unitful.cm`](@ref), [`Unitful.s`](@ref)." @unit Gal "Gal" Gal 1cm/s^2 true true " Unitful.dyn \nThe dyne, a CGS unit of force, defined as 1 g × cm / s^2. \nDimension: 𝐋 𝐌 𝐓^-2. \nSee also: [`Unitful.cm`](@ref), [`Unitful.s`](@ref), [`Unitful.g`](@ref)." @unit dyn "dyn" Dyne 1g*cm/s^2 true true " Unitful.erg \nThe erg, a CGS unit of energy, defined as 1 dyn × cm. \nDimension: 𝐋^2 𝐌 𝐓^-2. \nSee also: [`Unitful.cm`](@ref), [`Unitful.dyn`](@ref)" @unit erg "erg" Erg 1g*cm^2/s^2 true true " Unitful.Ba \nThe barye, a CGS unit of pressure, defined as 1 dyn / cm^2. \nDimension: 𝐌 𝐋^-1 𝐓^-2. \nSee also: [`Unitful.cm`](@ref), [`Unitful.dyn`](@ref)" @unit Ba "Ba" Barye 1g/cm/s^2 true true " Unitful.P \nThe poise, a CGS unit of dynamic viscosity, defined as 1 dyn × s / cm^2. \nDimension: 𝐌 𝐋^-1 𝐓^-1. \nSee also: [`Unitful.cm`](@ref), [`Unitful.dyn`](@ref), [`Unitful.s`](@ref)" @unit P "P" Poise 1g/cm/s true true " Unitful.St \nThe stokes, a CGS unit of kinematic viscosity, defined as 1 cm^2 / s. \nDimension: 𝐌^2 𝐓^-1. \nSee also: [`Unitful.cm`](@ref), [`Unitful.s`](@ref)" @unit St "St" Stokes 1cm^2/s true true " Unitful.Gauss \nThe gauss, a CGS unit of magnetic B-field strength, defined as 1 Mx / cm^2. \nDimension: 𝐌 𝐈^-1 𝐓^-2. \nSee also: [`Unitful.cm`](@ref), [`Unitful.Mx`](@ref)" @unit Gauss "Gauss" Gauss (1//10_000)*T true true " Unitful.Oe \nThe oersted, a CGS unit of magnetic H-field strength, defined as 1000 A / (4π × m). \nDimension: 𝐈 𝐋^-1. \nSee also: [`Unitful.A`](@ref), [`Unitful.m`](@ref)" @unit Oe "Oe" Oersted (1_000/4π)*A/m true true " Unitful.Mx \nThe maxwell, a CGS unit of magnetic flux, defined as 1 Gauss × cm^2. \nDimension: 𝐋^2 𝐌 𝐈^-1 𝐓^-2. \nSee also: [`Unitful.cm`](@ref), [`Unitful.Gauss`](@ref)" @unit Mx "Mx" Maxwell (1//100_000_000)*Wb true true ######### # Shared Imperial / US customary units # Length #key: Symbol Display Name Equivalent to 10^n prefixes? " Unitful.inch \nThe inch, a US customary unit of length defined as 2.54 cm. \nDimension: [`Unitful.𝐋`](@ref). \nSee Also: [`Unitful.cm`](@ref)." @unit inch "inch" Inch (254//10000)*m false " Unitful.mil \nThe mil, a US customary unit of length defined as 1/1000 inch. \nDimension: [`Unitful.𝐋`](@ref). \nSee Also: [`Unitful.inch`](@ref)." @unit mil "mil" Mil (1//1000)*inch false " Unitful.ft \nThe foot, a US customary unit of length defined as 12 inch. \nDimension: [`Unitful.𝐋`](@ref). \nSee Also: [`Unitful.inch`](@ref)." @unit ft "ft" Foot 12inch false " Unitful.yd \nThe yard, a US customary unit of length defined as 3 ft. \nDimension: [`Unitful.𝐋`](@ref). \nSee Also: [`Unitful.ft`](@ref)." @unit yd "yd" Yard 3ft false " Unitful.mi \nThe mile, a US customary unit of length defined as 1760 yd. \nDimension: [`Unitful.𝐋`](@ref). \nSee Also: [`Unitful.yd`](@ref)." @unit mi "mi" Mile 1760yd false " Unitful.angstrom Unitful.Å \nThe angstrom, a metric unit of length defined as 1/10 nm. \nDimension: [`Unitful.𝐋`](@ref). \nSee Also: [`Unitful.nm`](@ref)." ((@unit angstrom "Å" Angstrom (1//10)*nm false), const Å = angstrom) # Area " Unitful.ac \nThe acre, a US customary unit of area defined as 4840 yd^2. \nDimension: 𝐋^2. \nSee Also: [`Unitful.yd`](@ref)." @unit ac "ac" Acre (316160658//78125)*m^2 false # Temperatures " Unitful.Ra \nThe rankine, a US customary unit of temperature defined as 5/9 K. \nDimension: [`Unitful.𝚯`](@ref). \nSee Also: [`Unitful.K`](@ref)." @unit Ra "Ra" Rankine (5//9)*K false " Unitful.°F Unitful.degF \nThe degree Fahrenheit, a US customary unit of temperature, defined such that 0 °F = 459.67 Ra. \nDimension: [`Unitful.𝚯`](@ref). \nSee also: [`Unitful.Ra`](@ref)." ((@affineunit °F "°F" (45967//100)Ra), const degF = °F) # Masses " Unitful.lb \nThe pound-mass, a US customary unit of mass defined as exactly 0.453,592,37 kg. \nDimension: [`Unitful.𝐌`](@ref). \nSee Also: [`Unitful.kg`](@ref)." @unit lb "lb" Pound 0.45359237kg false # is exact " Unitful.oz \nThe ounce, a US customary unit of mass defined as 1/16 lb. \nDimension: [`Unitful.𝐌`](@ref). \nSee Also: [`Unitful.lb`](@ref)." @unit oz "oz" Ounce lb//16 false " Unitful.slug \nThe slug, a US customary unit of mass defined as 1 lbf × s^2 / ft. \nDimension: [`Unitful.𝐌`](@ref). \nSee Also: [`Unitful.lbf`](@ref), [`Unitful.s`](@ref), [`Unitful.ft`](@ref)." @unit slug "slug" Slug 1lb*ge*s^2/ft false " Unitful.dr \nThe dram, a US customary unit of mass defined as 1/16 oz. \nDimension: [`Unitful.𝐌`](@ref). \nSee Also: [`Unitful.oz`](@ref)." @unit dr "dr" Dram oz//16 false " Unitful.gr \nThe grain, a US customary unit of mass defined as 1/7000 lb. \nDimension: [`Unitful.𝐌`](@ref). \nSee Also: [`Unitful.lb`](@ref)." @unit gr "gr" Grain (32//875)*dr false # Force " Unitful.lbf \nThe pound-force, a US customary unit of force defined as 1 lb × ge. \nDimension: 𝐋 𝐌 𝐓^-2. \nSee Also: [`Unitful.lb`](@ref), [`Unitful.ge`](@ref)." @unit lbf "lbf" PoundsForce 1lb*ge false # Energy # Use ISO 31-4 for BTU definition " Unitful.cal \nThe calorie, a unit of energy defined as exactly 4.184 J. \nDimension: 𝐋^2 𝐌 𝐓^-2. \nSee Also: [`Unitful.J`](@ref)." @unit cal "cal" Calorie 4.184J true true " Unitful.btu \nThe British thermal unit, a US customary unit of heat defined by ISO 31-4 as exactly 1055.06 J. \nDimension: 𝐋^2 𝐌 𝐓^-2. \nSee Also: [`Unitful.J`](@ref)." @unit btu "btu" BritishThermalUnit 1055.06J false # Pressure " Unitful.psi \nPounds per square inch, a US customary unit of pressure defined as 1 lbf / inch^2. \nDimension: 𝐌 𝐋^-1 𝐓^-2. \nSee Also: [`Unitful.lbf`](@ref), [`Unitful.inch`](@ref)." @unit psi "psi" PoundsPerSquareInch 1lbf/inch^2 false ######### # Logarithmic scales and units @logscale dB "dB" Decibel 10 10 false @logscale B "B" Bel 10 1 false @logscale Np "Np" Neper ℯ 1//2 true @logscale cNp "cNp" Centineper ℯ 50 true @logunit dBHz "dB-Hz" Decibel 1Hz @logunit dBm "dBm" Decibel 1mW @logunit dBV "dBV" Decibel 1V @logunit dBu "dBu" Decibel sqrt(0.6)V @logunit dBμV "dBμV" Decibel 1μV @logunit dBSPL "dBSPL" Decibel 20μPa @logunit dBFS "dBFS" Decibel RootPowerRatio(1) @logunit dBΩ "dBΩ" Decibel 1Ω @logunit dBS "dBS" Decibel 1S # TODO: some more dimensions? isrootpower_dim(::typeof(dimension(W))) = false isrootpower_dim(::typeof(dimension(V))) = true isrootpower_dim(::typeof(dimension(A))) = true isrootpower_dim(::typeof(dimension(Pa))) = true isrootpower_dim(::typeof(dimension(W/m^2/Hz))) = false # spectral flux dens. isrootpower_dim(::typeof(dimension(W/m^2))) = false # intensity isrootpower_dim(::typeof(dimension(W/m^2/m))) = false isrootpower_dim(::typeof(𝐋^3)) = false # reflectivity isrootpower_dim(::typeof(dimension(Ω))) = true isrootpower_dim(::typeof(dimension(S))) = true isrootpower_dim(::typeof(dimension(Hz))) = false isrootpower_dim(::typeof(dimension(J))) = false ######### # `using Unitful.DefaultSymbols` will bring the following into the calling namespace: # - Dimensions 𝐋,𝐌,𝐓,𝐈,𝚯,𝐉,𝐍 # - Base and derived SI units, with SI prefixes # - Candela conflicts with `Base.cd` so it is not brought in (issue #102) # - Degrees: ° const si_prefixes = (:y, :z, :a, :f, :p, :n, :μ, :m, :c, :d, Symbol(""), :da, :h, :k, :M, :G, :T, :P, :E, :Z, :Y) const si_no_prefix = (:m, :s, :A, :K, :g, :mol, :rad, :sr, :Hz, :N, :Pa, #:cd, :J, :W, :C, :V, :F, :Ω, :S, :Wb, :T, :H, :lm, :lx, :Bq, :Gy, :Sv, :kat) baremodule DefaultSymbols import Unitful for u in (:𝐋,:𝐌,:𝐓,:𝐈,:𝚯,:𝐉,:𝐍) Core.eval(DefaultSymbols, Expr(:import, Expr(:(.), :Unitful, u))) Core.eval(DefaultSymbols, Expr(:export, u)) end for p in Unitful.si_prefixes for u in Unitful.si_no_prefix Core.eval(DefaultSymbols, Expr(:import, Expr(:(.), :Unitful, Symbol(p,u)))) Core.eval(DefaultSymbols, Expr(:export, Symbol(p,u))) end end Core.eval(DefaultSymbols, Expr(:import, Expr(:(.), :Unitful, :°C))) Core.eval(DefaultSymbols, Expr(:export, :°C)) Core.eval(DefaultSymbols, Expr(:import, Expr(:(.), :Unitful, :°))) Core.eval(DefaultSymbols, Expr(:export, :°)) end ######### preferunits(kg) # others done in @refunit # Fix documentation for all kg based units for (k,v) in prefixdict if k != 3 sym = Symbol(v,:g) docstring = """ Unitful.$sym A prefixed unit, equal to 10^$(k-3) kg. Note that `kg`, not `g`, is the base unit. Dimension: [`Unitful.𝐌`](@ref). See also: [`Unitful.kg`](@ref). """ run = quote @doc $docstring $sym end eval(run) end end @doc " Unitful.kg \nThe kilogram, the SI base unit of mass. Note that `kg`, not `g`, is the base unit. \nDimension: [`Unitful.𝐌`](@ref)." kg """ Unitful.promote_to_derived() Defines promotion rules to use derived SI units in promotion for common dimensions of quantities: - `J` (joule) for energy - `N` (newton) for force - `W` (watt) for power - `Pa` (pascal) for pressure - `C` (coulomb) for charge - `V` (volt) for voltage - `Ω` (ohm) for resistance - `F` (farad) for capacitance - `H` (henry) for inductance - `Wb` (weber) for magnetic flux - `T` (tesla) for B-field - `J*s` (joule-second) for action If you want this as default behavior (it was for versions of Unitful prior to 0.1.0), consider invoking this function in your `startup.jl` file which is loaded when you open Julia. This function is not exported. """ function promote_to_derived() eval(quote Unitful.promote_unit(::S, ::T) where {S<:EnergyFreeUnits, T<:EnergyFreeUnits} = Unitful.J Unitful.promote_unit(::S, ::T) where {S<:ForceFreeUnits, T<:ForceFreeUnits} = Unitful.N Unitful.promote_unit(::S, ::T) where {S<:PowerFreeUnits, T<:PowerFreeUnits} = Unitful.W Unitful.promote_unit(::S, ::T) where {S<:PressureFreeUnits, T<:PressureFreeUnits} = Unitful.Pa Unitful.promote_unit(::S, ::T) where {S<:ChargeFreeUnits, T<:ChargeFreeUnits} = Unitful.C Unitful.promote_unit(::S, ::T) where {S<:VoltageFreeUnits, T<:VoltageFreeUnits} = Unitful.V Unitful.promote_unit(::S, ::T) where {S<:ElectricalResistanceFreeUnits, T<:ElectricalResistanceFreeUnits} = Unitful.Ω Unitful.promote_unit(::S, ::T) where {S<:CapacitanceFreeUnits, T<:CapacitanceFreeUnits} = Unitful.F Unitful.promote_unit(::S, ::T) where {S<:InductanceFreeUnits, T<:InductanceFreeUnits} = Unitful.H Unitful.promote_unit(::S, ::T) where {S<:MagneticFluxFreeUnits, T<:MagneticFluxFreeUnits} = Unitful.Wb Unitful.promote_unit(::S, ::T) where {S<:BFieldFreeUnits, T<:BFieldFreeUnits} = Unitful.T Unitful.promote_unit(::S, ::T) where {S<:ActionFreeUnits, T<:ActionFreeUnits} = Unitful.J * Unitful.s end) nothing end ================================================ FILE: src/promotion.jl ================================================ """ promote_unit(::Units, ::Units...) Given `Units` objects as arguments, this function returns a `Units` object appropriate for the result of promoting quantities which have these units. This function is kind of like `promote_rule`, except that it doesn't take types. It also does not return a tuple, but rather just a [`Unitful.Units`](@ref) object (or it throws an error). Although we had used `promote_rule` for `Units` objects in prior versions of Unitful, this was always kind of a hack; it doesn't make sense to promote units directly for a variety of reasons. """ function promote_unit end # Generic methods @inline promote_unit(x) = _promote_unit(x) @inline _promote_unit(x::Units) = x @inline promote_unit(x,y) = _promote_unit(x,y) promote_unit(x::Units, y::Units, z::Units, t::Units...) = promote_unit(_promote_unit(x,y), z, t...) @inline _promote_unit(x::T, y::T) where {T <: FreeUnits} = T() # Use configurable fall-back mechanism for FreeUnits @inline _promote_unit(x::FreeUnits{N1,D}, y::FreeUnits{N2,D}) where {N1,N2,D} = upreferred(dimension(x)) @inline _promote_unit(x::ContextUnits{N,D,P,A}, y::ContextUnits{N,D,P,A}) where {N,D,P,A} = x #ambiguity reasons # same units, but promotion context disagrees @inline _promote_unit(x::ContextUnits{N,D,P1,A}, y::ContextUnits{N,D,P2,A}) where {N,D,P1,P2,A} = ContextUnits{N,D,promote_unit(P1(), P2()),A}() # different units, but promotion context agrees @inline _promote_unit(x::ContextUnits{N1,D,P}, y::ContextUnits{N2,D,P}) where {N1,N2,D,P} = ContextUnits(P(), P()) # different units, promotion context disagrees, fall back to FreeUnits @inline _promote_unit(x::ContextUnits{N1,D,P1}, y::ContextUnits{N2,D,P2}) where {N1,N2,D,P1,P2} = promote_unit(FreeUnits(x), FreeUnits(y)) # ContextUnits beat FreeUnits @inline _promote_unit(x::ContextUnits{N,D,P,A}, y::FreeUnits{N,D,A}) where {N,D,P,A} = x @inline _promote_unit(x::ContextUnits{N,D,P,A1}, y::FreeUnits{N,D,A2}) where {N,D,P,A1,A2} = ContextUnits(P(), P()) @inline _promote_unit(x::ContextUnits{N1,D,P}, y::FreeUnits{N2,D}) where {N1,N2,D,P} = ContextUnits(P(), P()) @inline _promote_unit(x::FreeUnits, y::ContextUnits) = promote_unit(y,x) # FixedUnits beat everything @inline _promote_unit(x::T, y::T) where {T <: FixedUnits} = T() @inline _promote_unit(x::FixedUnits{M,D}, y::Units{N,D}) where {M,N,D} = x @inline _promote_unit(x::Units, y::FixedUnits) = promote_unit(y,x) # Different units but same dimension are not fungible for FixedUnits @inline _promote_unit(x::FixedUnits{M,D}, y::FixedUnits{N,D}) where {M,N,D} = error("automatic conversion prohibited.") # If we didn't handle it above, the dimensions mismatched. @inline _promote_unit(x::Units, y::Units) = throw(DimensionError(x,y)) #### # Base.promote_rule # quantity, quantity (different dims) Base.promote_rule(::Type{Quantity{S1,D1,U1}}, ::Type{Quantity{S2,D2,U2}}) where {S1,D1,U1,S2,D2,U2} = Quantity{promote_type(S1,S2)} # quantity, quantity (same dims, different units) function Base.promote_rule(::Type{Quantity{S1,D,U1}}, ::Type{Quantity{S2,D,U2}}) where {S1,S2,D,U1,U2} p = promote_unit(U1(), U2()) c1 = convfact(p, U1()) c1′ = affinetranslation(U1()) c2 = convfact(p, U2()) c2′ = affinetranslation(U2()) numtype = promote_type(S1, S2, promote_type(typeof(c1), typeof(c2), typeof(c1′), typeof(c2′))) if !isunitless(p) if U1 <: ContextUnits && U2 <: ContextUnits up1 = upreferred(U1()) if up1 === upreferred(U2()) return Quantity{numtype,D,typeof(ContextUnits(p,up1))} else return Quantity{numtype,D,typeof(p)} end elseif U1 <: ContextUnits || U2 <: ContextUnits return Quantity{numtype,D,typeof(ContextUnits(p,p))} else return Quantity{numtype,D,typeof(p)} end else return numtype end end # number, quantity function Base.promote_rule(::Type{Quantity{S,D,U}}, ::Type{T}) where {S,T <: Number,D,U} if D == NoDims promote_type(S,T,typeof(convfact(NoUnits,U()))) else Quantity{promote_type(S,T)} end end Base.promote_rule(::Type{S}, ::Type{T}) where {S<:AbstractIrrational,S2,T<:Quantity{S2}} = promote_type(promote_type(S, real(S2)), T) Base.promote_rule(::Type{Quantity{S}}, ::Type{T}) where {S,T <: Number} = Quantity{promote_type(S,T)} # With only one of these, you can get a segmentation fault because you # fall back to the # number, quantity promote_rule above and there is an infinite recursion. Base.promote_rule(::Type{Quantity{T}}, ::Type{Quantity{S,D,U}}) where {T,S,D,U} = Quantity{promote_type(T,S)} Base.promote_rule(::Type{Quantity{S,D,U}}, ::Type{Quantity{T}}) where {T,S,D,U} = Quantity{promote_type(T,S)} ================================================ FILE: src/quantities.jl ================================================ # This is a generated function to avoid determining the dimensions of a given # set of units each time a new quantity is made. @generated function _Quantity(x::Number, y::Units) u = y() du = dimension(u) dx = dimension(x) d = du*dx :(Quantity{typeof(x), $d, typeof($u)}(x)) end """ Quantity(x::Number, y::Units) Create a `Quantity` with numerical value `x` and units `y`. # Example ```jldoctest julia> Quantity(5, u"m") 5 m ``` """ Quantity(x::Number, y::Units) = _Quantity(x, y) Quantity(x::Number, y::Units{()}) = x *(x::Number, y::Units, z::Units...) = Quantity(x,*(y,z...)) *(x::Units, y::Number) = *(y,x) *(x::AbstractQuantity, y::Units, z::Units...) = Quantity(x.val, *(unit(x),y,z...)) *(x::AbstractQuantity, y::AbstractQuantity) = Quantity(x.val*y.val, unit(x)*unit(y)) function *(x::Number, y::AbstractQuantity) y isa AffineQuantity && throw(AffineError("an invalid operation was attempted with affine quantities: $x*$y")) return Quantity(x*y.val, unit(y)) end function *(x::AbstractQuantity, y::Number) x isa AffineQuantity && throw(AffineError("an invalid operation was attempted with affine quantities: $x*$y")) return Quantity(x.val*y, unit(x)) end *(A::Units, B::AbstractArray) = broadcast(*, A, B) *(A::AbstractArray, B::Units) = broadcast(*, A, B) /(A::AbstractArray, B::Units) = broadcast(/, A, B) # Division (units) /(x::AbstractQuantity, y::Units) = Quantity(x.val, unit(x) / y) /(x::Units, y::AbstractQuantity) = Quantity(1/y.val, x / unit(y)) /(x::Number, y::Units) = Quantity(x,inv(y)) /(x::Units, y::Number) = (1/y) * x //(x::AbstractQuantity, y::Units) = Quantity(x.val, unit(x) / y) //(x::Units, y::AbstractQuantity) = Quantity(1//y.val, x / unit(y)) //(x::Number, y::Units) = Rational(x)/y //(x::Units, y::Number) = (1//y) * x /(x::AbstractQuantity, y::AbstractQuantity) = Quantity(/(x.val, y.val), unit(x) / unit(y)) /(x::AbstractQuantity, y::Number) = Quantity(/(x.val, y), unit(x) / unit(y)) /(x::Number, y::AbstractQuantity) = Quantity(/(x, y.val), unit(x) / unit(y)) //(x::AbstractQuantity, y::AbstractQuantity) = Quantity(//(x.val, y.val), unit(x) / unit(y)) //(x::AbstractQuantity, y::Number) = Quantity(//(x.val, y), unit(x) // unit(y)) //(x::Number, y::AbstractQuantity) = Quantity(//(x, y.val), unit(x) / unit(y)) # ambiguity resolution //(x::AbstractQuantity, y::Complex) = Quantity(//(x.val, y), unit(x)) for f in (:fld, :cld) @eval begin function ($f)(x::AbstractQuantity, y::AbstractQuantity) z = uconvert(unit(y), x) # TODO: use promote? ($f)(z.val,y.val) end ($f)(x::Number, y::AbstractQuantity) = Quantity(($f)(x, ustrip(y)), unit(x) / unit(y)) ($f)(x::AbstractQuantity, y::Number) = Quantity(($f)(ustrip(x), y), unit(x)) end end function div(x::AbstractQuantity, y::AbstractQuantity, r...) z = uconvert(unit(y), x) # TODO: use promote? div(z.val,y.val, r...) end function div(x::Number, y::AbstractQuantity, r...) Quantity(div(x, ustrip(y), r...), unit(x) / unit(y)) end function div(x::AbstractQuantity, y::Number, r...) Quantity(div(ustrip(x), y, r...), unit(x)) end for f in (:mod, :rem) @eval function ($f)(x::AbstractQuantity, y::AbstractQuantity) z = uconvert(unit(y), x) # TODO: use promote? Quantity(($f)(z.val,y.val), unit(y)) end end _affineerror(f, args...) = throw(AffineError("an invalid operation was attempted with affine quantities: $f($(join(args, ", ")))")) for f in (:div, :rem, :divrem) for r = (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp, RoundToZero, RoundUp, RoundDown) @eval begin $f(x::AffineQuantity, y::AffineQuantity, ::typeof($r)) = _affineerror($f, x, y, $r) $f(x::AffineQuantity, y::AbstractQuantity, ::typeof($r)) = _affineerror($f, x, y, $r) $f(x::AbstractQuantity, y::AffineQuantity, ::typeof($r)) = _affineerror($f, x, y, $r) end end end for f = (:div, :cld, :fld, :rem, :mod) @eval begin $f(x::AffineQuantity, y::AffineQuantity) = _affineerror($f, x, y) $f(x::AffineQuantity, y::AbstractQuantity) = _affineerror($f, x, y) $f(x::AbstractQuantity, y::AffineQuantity) = _affineerror($f, x, y) end end Base.mod2pi(x::DimensionlessQuantity) = mod2pi(uconvert(NoUnits, x)) Base.mod2pi(x::AbstractQuantity{S, NoDims, <:Units{(Unitful.Unit{:Degree, NoDims}(0, 1//1),), NoDims}}) where S = mod(x, 360°) Base.modf(x::DimensionlessQuantity) = modf(uconvert(NoUnits, x)) # Addition / subtraction for op in [:+, :-] @eval ($op)(x::AbstractQuantity{S,D,U}, y::AbstractQuantity{T,D,U}) where {S,T,D,U} = Quantity(($op)(x.val, y.val), U()) @eval function ($op)(x::AbstractQuantity{S,D,SU}, y::AbstractQuantity{T,D,TU}) where {S,T,D,SU,TU} ($op)(promote(x,y)...) end @eval ($op)(x::AbstractQuantity, y::AbstractQuantity) = throw(DimensionError(x,y)) @eval ($op)(x::AbstractQuantity) = Quantity(($op)(x.val), unit(x)) end function +(x::AffineQuantity{S,D}, y::AbstractQuantity{T,D}) where {S,T,D} pu = promote_unit(unit(x), unit(y)) # units for the final result. # Get x on an absolute scale. FreeUnits in the line below prevents # promote(x′, y) from yielding affine quantities. If x had `ContextUnits` and # the promotion units were affine units, x′+y would error without this. x′ = Quantity(x.val - affinetranslation(unit(x)), FreeUnits(absoluteunit(x))) # Likewise if y were not affine but y had ContextUnits and the promotion units were # affine, x′+y could also fail. y′ = Quantity(y.val, FreeUnits(unit(y))) return uconvert(pu, x′+y′) # we get back the promotion context in the end end +(x::AbstractQuantity, y::AffineQuantity) = +(y,x) # Disallow addition of affine quantities +(x::AffineQuantity, y::AffineQuantity) = throw(AffineError( "an invalid operation was attempted with affine quantities: $x + $y")) # Specialize subtraction of affine quantities -(x::AffineQuantity, y::AffineQuantity) = -(promote(x,y)...) function -(x::T, y::T) where T <: AffineQuantity return Quantity(x.val - y.val, absoluteunit(unit(x))) end # Disallow subtracting an affine quantity from a quantity -(x::AbstractQuantity, y::AffineQuantity) = throw(AffineError("an invalid operation was attempted with affine quantities: $x - $y")) # Needed until LU factorization is made to work with unitful numbers function inv(x::StridedMatrix{T}) where {T <: AbstractQuantity} m = inv(ustrip(x)) iq = eltype(m) reinterpret(Quantity{iq, inv(dimension(T)), typeof(inv(unit(T)))}, m) end # Other mathematical functions # `fma` and `muladd` # The idea here is that if the numeric backing types are not the same, they # will be promoted to be the same by the generic `fma(::Number, ::Number, ::Number)` # method. We then catch the possible results and handle the units logic with one # performant method. for (_x,_y) in [(:fma, :_fma), (:muladd, :_muladd)] # Catch some signatures pre-promotion @eval @inline ($_x)(x::Number, y::AbstractQuantity, z::AbstractQuantity) = ($_y)(x,y,z) @eval @inline ($_x)(x::AbstractQuantity, y::Number, z::AbstractQuantity) = ($_y)(x,y,z) # Post-promotion @eval @inline ($_x)(x::AbstractQuantity, y::AbstractQuantity, z::AbstractQuantity) = ($_y)(x,y,z) # It seems like most of this is optimized out by the compiler, including the # apparent runtime check of dimensions, which does not appear in @code_llvm. @eval @inline function ($_y)(x,y,z) dimension(x) * dimension(y) != dimension(z) && throw(DimensionError(x*y,z)) uI = unit(x)*unit(y) uF = promote_unit(uI, unit(z)) c = ($_x)(ustrip(x), ustrip(y), ustrip(uconvert(uI, z))) uconvert(uF, Quantity(c, uI)) end end sqrt(x::AbstractQuantity) = Quantity(sqrt(x.val), sqrt(unit(x))) cbrt(x::AbstractQuantity) = Quantity(cbrt(x.val), cbrt(unit(x))) for _y in (:sin, :cos, :tan, :asin, :acos, :atan, :sinh, :cosh, :tanh, :asinh, :acosh, :atanh, :sinpi, :cospi, :tanpi, :sinc, :cosc, :sincos, :sincospi) if isdefined(Base, _y) @eval Base.$(_y)(x::DimensionlessQuantity) = Base.$(_y)(uconvert(NoUnits, x)) end end cis(x::DimensionlessQuantity{<:Real}) = Complex(reverse(sincos(x))...) cis(x::DimensionlessQuantity{<:Complex}) = exp(-imag(x)) * cis(real(x)) cispi(x::DimensionlessQuantity{<:Real}) = Complex(reverse(sincospi(x))...) cispi(x::DimensionlessQuantity{<:Complex}) = exp(-(π*imag(x))) * cispi(real(x)) atan(y::AbstractQuantity{T1,D,U1}, x::AbstractQuantity{T2,D,U2}) where {T1,T2,D,U1,U2} = atan(promote(y,x)...) atan(y::AbstractQuantity{T,D,U}, x::AbstractQuantity{T,D,U}) where {T,D,U} = atan(y.val,x.val) atan(y::AbstractQuantity, x::AbstractQuantity) = throw(DimensionError(x,y)) abs(x::AbstractQuantity) = Quantity(abs(x.val), unit(x)) abs2(x::AbstractQuantity) = Quantity(abs2(x.val), unit(x)*unit(x)) angle(x::AbstractQuantity{<:Complex}) = angle(x.val) copysign(x::AbstractQuantity, y::Number) = Quantity(copysign(x.val,y/unit(y)), unit(x)) copysign(x::Number, y::AbstractQuantity) = copysign(x,y/unit(y)) copysign(x::AbstractQuantity, y::AbstractQuantity) = Quantity(copysign(x.val,y/unit(y)), unit(x)) flipsign(x::AbstractQuantity, y::Number) = Quantity(flipsign(x.val,y/unit(y)), unit(x)) flipsign(x::Number, y::AbstractQuantity) = flipsign(x,y/unit(y)) flipsign(x::AbstractQuantity, y::AbstractQuantity) = Quantity(flipsign(x.val,y/unit(y)), unit(x)) for (i,j) in zip((:<, :<=, :isless), (:_lt, :_le, :_isless)) @eval ($i)(x::AbstractQuantity, y::AbstractQuantity) = ($j)(x,y) @eval ($i)(x::AbstractQuantity, y::Number) = ($i)(promote(x,y)...) @eval ($i)(x::Number, y::AbstractQuantity) = ($i)(promote(x,y)...) # promotion might not yield Quantity types @eval @inline ($j)(x::AbstractQuantity{T1}, y::AbstractQuantity{T2}) where {T1,T2} = ($i)(promote(x,y)...) # If it does yield Quantity types, we'll get back here, # since at least the numeric part can be promoted. @eval @inline ($j)(x::AbstractQuantity{T,D,U}, y::AbstractQuantity{T,D,U}) where {T,D,U} = ($i)(x.val,y.val) @eval @inline ($j)(x::AbstractQuantity{T,D,U1}, y::AbstractQuantity{T,D,U2}) where {T,D,U1,U2} = ($i)(promote(x,y)...) @eval @inline ($j)(x::AbstractQuantity{T,D1,U1}, y::AbstractQuantity{T,D2,U2}) where {T,D1,D2,U1,U2} = throw(DimensionError(x,y)) end Base.rtoldefault(::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U} = Base.rtoldefault(T) function isapprox( x::AbstractQuantity{T,D,U}, y::AbstractQuantity{T,D,U}; atol = zero(Quantity{real(T),D,U}), kwargs..., ) where {T,D,U} return isapprox(x.val, y.val; atol=ustrip(unit(y), atol), kwargs...) end function isapprox(x::AbstractQuantity, y::AbstractQuantity; kwargs...) dimension(x) != dimension(y) && return false return isapprox(promote(x,y)...; kwargs...) end function isapprox(x::AbstractQuantity, y::Number; atol=nothing, kwargs...) if atol === nothing return isapprox(promote(x, y)...; kwargs...) end xp, yp, atolp = promote(x, y, atol) return isapprox(xp, yp; atol=atolp, kwargs...) end isapprox(x::Number, y::AbstractQuantity; kwargs...) = isapprox(y, x; kwargs...) function isapprox( x::AbstractArray{<:AbstractQuantity{T1,D,U1}}, y::AbstractArray{<:AbstractQuantity{T2,D,U2}}; atol=zero(Quantity{real(T1),D,U1}), rtol::Real=Base.rtoldefault(T1,T2,atol>zero(atol)), nans::Bool=false, norm::Function=norm, ) where {T1,D,U1,T2,U2} d = norm(x - y) if isfinite(d) return iszero(rtol) ? d <= atol : d <= max(atol, rtol*max(norm(x), norm(y))) else # Fall back to a component-wise approximate comparison return all(ab -> isapprox(ab[1], ab[2]; rtol=rtol, atol=atol, nans=nans), zip(x, y)) end end isapprox(x::AbstractArray{S}, y::AbstractArray{T}; kwargs...) where {S <: AbstractQuantity,T <: AbstractQuantity} = false function isapprox(x::AbstractArray{S}, y::AbstractArray{N}; atol=nothing, kwargs...) where {S <: AbstractQuantity,N <: Number} if dimension(N) == dimension(S) if atol === nothing isapprox(map(x->uconvert(NoUnits,x),x), y; kwargs...) else isapprox(map(x->uconvert(NoUnits,x),x), y; atol = ustrip(NoUnits, atol), kwargs...) end else false end end isapprox(y::AbstractArray{N}, x::AbstractArray{S}; kwargs...) where {S <: AbstractQuantity,N <: Number} = isapprox(x,y; kwargs...) for cmp in [:(==), :isequal] @eval $cmp(x::AbstractQuantity{S,D,U}, y::AbstractQuantity{T,D,U}) where {S,T,D,U} = $cmp(x.val, y.val) @eval function $cmp(x::AbstractQuantity, y::AbstractQuantity) dimension(x) != dimension(y) && return false $cmp(promote(x,y)...) end @eval function $cmp(x::AbstractQuantity, y::Number) $cmp(promote(x,y)...) end @eval $cmp(x::Number, y::AbstractQuantity) = $cmp(y,x) end # For now, hashes of quantities that are equal but have different units are not equal function hash(x::AbstractQuantity, h::UInt) hash(x.val, hash(FreeUnits(unit(x)), h)) end _dimerr(f) = error("$f can only be well-defined for dimensionless ", "numbers. For dimensionful numbers, different input units yield physically ", "different results.") for f in [:isinteger, :iseven, :isodd] @eval $f(x::AbstractQuantity) = _dimerr($f) @eval $f(x::DimensionlessQuantity) = $f(uconvert(NoUnits, x)) end _rounderr() = error("specify the type of the quantity to convert to ", "when rounding quantities. Example: round(typeof(1u\"m\"), 137u\"cm\").") # convenience methods round(u::Units, q::AbstractQuantity, r::RoundingMode=RoundNearest; kwargs...) = Quantity(round(ustrip(u, q), r; kwargs...), u) round(::Type{T}, u::Units, q::AbstractQuantity, r::RoundingMode=RoundNearest; kwargs...) where {T<:Number} = round(Quantity{T, dimension(u), typeof(u)}, q, r; kwargs...) # workhorse methods round(x::AbstractQuantity, r::RoundingMode=RoundNearest; kwargs...) = _rounderr() round(x::DimensionlessQuantity; kwargs...) = round(uconvert(NoUnits, x); kwargs...) round(x::DimensionlessQuantity, r::RoundingMode; kwargs...) = round(uconvert(NoUnits, x), r; kwargs...) round(::Type{T}, x::AbstractQuantity, r::RoundingMode=RoundNearest; kwargs...) where {T<:Number} = _dimerr(:round) round(::Type{T}, x::DimensionlessQuantity, r::RoundingMode=RoundNearest; kwargs...) where {T<:Number} = round(T, uconvert(NoUnits, x), r; kwargs...) function round(::Type{T}, x::AbstractQuantity; kwargs...) where {S, T <: Quantity{S}} u = unit(T) unitless = ustrip(u, x) return Quantity{S, dimension(T), typeof(u)}(round(unitless; kwargs...)) end function round(::Type{T}, x::AbstractQuantity, r::RoundingMode; kwargs...) where {S, T <: Quantity{S}} u = unit(T) unitless = ustrip(u, x) return Quantity{S, dimension(T), typeof(u)}(round(unitless, r; kwargs...)) end round(::Type{T}, x::DimensionlessQuantity; kwargs...) where {S, T <: Quantity{S}} = invoke(round, Tuple{Type{T},AbstractQuantity}, T, x; kwargs...) # for ambiguity resolution round(::Type{T}, x::DimensionlessQuantity, r::RoundingMode; kwargs...) where {S, T <: Quantity{S}} = invoke(round, Tuple{Type{T},AbstractQuantity,RoundingMode}, T, x, r; kwargs...) # for ambiguity resolution # that should actually be fixed in Base ↓ for (f,r) = ((:trunc, :RoundToZero), (:floor, :RoundDown), (:ceil, :RoundUp)) @eval $f(x::AbstractQuantity; kwargs...) = round(x, $r; kwargs...) @eval $f(::Type{T}, x::AbstractQuantity; kwargs...) where {T<:Number} = round(T, x, $r; kwargs...) @eval $f(u::Units, x::AbstractQuantity; kwargs...) = round(u, x, $r; kwargs...) end zero(x::AbstractQuantity) = Quantity(zero(x.val), unit(x)) zero(x::AffineQuantity) = Quantity(zero(x.val), absoluteunit(x)) zero(x::Type{<:AbstractQuantity{T}}) where {T} = throw(ArgumentError("zero($x) not defined.")) zero(x::Type{<:AbstractQuantity{T,D}}) where {T,D} = zero(T) * upreferred(D) zero(x::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U<:ScalarUnits} = zero(T)*U() zero(x::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U<:AffineUnits} = zero(T)*absoluteunit(U()) function zero(x::AbstractArray{T}) where T<:AbstractQuantity if isconcretetype(T) z = zero(T) fill!(similar(x, typeof(z)), z) else dest = similar(x) for i = eachindex(x) if isassigned(x, i...) dest[i] = zero(x[i]) else dest[i] = zero(T) end end dest end end @static if VERSION < v"1.8.0-DEV.107" function zero(x::AbstractArray{Union{T,Missing}}) where T<:AbstractQuantity # only matches _concrete_ T ... @assert isconcretetype(T) # ... but check anyway z = zero(T) fill!(similar(x, typeof(z)), z) end end one(x::AbstractQuantity) = one(x.val) one(x::AffineQuantity) = throw(AffineError("no multiplicative identity for affine quantity $x.")) oneunit(x::AffineQuantity) = Quantity(one(x.val), absoluteunit(x)) oneunit(x::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U<:AffineUnits} = Quantity(one(T), absoluteunit(U())) get_T(::Type{<:AbstractQuantity{T}}) where T = T get_T(::Type{<:AbstractQuantity{T,D}}) where {T,D} = T get_T(::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U} = T one(x::Type{<:AbstractQuantity}) = one(get_T(x)) one(x::Type{<:AffineQuantity}) = throw(AffineError("no multiplicative identity for affine quantity type $x.")) isreal(x::AbstractQuantity) = isreal(x.val) isfinite(x::AbstractQuantity) = isfinite(x.val) isinf(x::AbstractQuantity) = isinf(x.val) isnan(x::AbstractQuantity) = isnan(x.val) @static if VERSION ≥ v"1.7.0-DEV.119" isunordered(x::AbstractQuantity) = isunordered(x.val) end eps(x::T) where {T<:AbstractQuantity} = T(eps(x.val)) eps(x::Type{T}) where {T<:AbstractQuantity} = eps(Unitful.numtype(T)) unsigned(x::AbstractQuantity) = Quantity(unsigned(x.val), unit(x)) for f in (:exp, :exp10, :exp2, :expm1, :log, :log10, :log1p, :log2) @eval ($f)(x::DimensionlessQuantity) = ($f)(uconvert(NoUnits, x)) end real(x::AbstractQuantity) = Quantity(real(x.val), unit(x)) imag(x::AbstractQuantity) = Quantity(imag(x.val), unit(x)) conj(x::AbstractQuantity) = Quantity(conj(x.val), unit(x)) @inline norm(x::AbstractQuantity, p::Real=2) = Quantity(norm(x.val, p), unit(x)) """ sign(x::AbstractQuantity) Returns the sign of `x`. """ sign(x::AbstractQuantity) = sign(x.val) """ signbit(x::AbstractQuantity) Returns the sign bit of the underlying numeric value of `x`. """ signbit(x::AbstractQuantity) = signbit(x.val) prevfloat(x::AbstractQuantity{T}, d::Integer) where {T <: AbstractFloat} = Quantity(prevfloat(x.val, d), unit(x)) prevfloat(x::AbstractQuantity{T}) where {T <: AbstractFloat} = prevfloat(x, 1) nextfloat(x::AbstractQuantity{T}, d::Integer) where {T <: AbstractFloat} = Quantity(nextfloat(x.val, d), unit(x)) nextfloat(x::AbstractQuantity{T}) where {T <: AbstractFloat} = nextfloat(x, 1) function frexp(x::AbstractQuantity{T}) where {T <: AbstractFloat} a,b = frexp(x.val) a*unit(x), b end for f in (:float, :BigFloat, :Float64, :Float32, :Float16) @eval begin """ $($f)(x::AbstractQuantity) Convert the numeric backing type of `x` to a floating-point representation. Returns a `Quantity` with the same units. """ (Base.$f)(x::AbstractQuantity) = Quantity($f(x.val), unit(x)) end end """ Integer(x::AbstractQuantity) Convert the numeric backing type of `x` to an integer representation. Returns a `Quantity` with the same units. """ Integer(x::AbstractQuantity) = Quantity(Integer(x.val), unit(x)) """ Rational(x::AbstractQuantity) Convert the numeric backing type of `x` to a rational number representation. Returns a `Quantity` with the same units. """ Rational(x::AbstractQuantity) = Quantity(Rational(x.val), unit(x)) big(x::AbstractQuantity{T,D,U}) where {T,D,U} = Quantity{big(T),D,U}(big(x.val)) big(::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U} = Quantity{big(T),D,U} Base.hastypemax(::Type{<:AbstractQuantity{T}}) where {T} = Base.hastypemax(T) typemin(::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U} = typemin(T)*U() typemin(x::AbstractQuantity{T}) where {T} = typemin(T)*unit(x) typemax(::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U} = typemax(T)*U() typemax(x::AbstractQuantity{T}) where {T} = typemax(T)*unit(x) Base.literal_pow(::typeof(^), x::AbstractQuantity, ::Val{v}) where {v} = Quantity(Base.literal_pow(^, x.val, Val(v)), Base.literal_pow(^, unit(x), Val(v))) # All of these are needed for ambiguity resolution ^(x::AbstractQuantity, y::Integer) = Quantity((x.val)^y, unit(x)^y) @static if VERSION ≥ v"1.8.0-DEV.501" Base.@constprop(:aggressive, ^(x::AbstractQuantity, y::Rational) = Quantity((x.val)^y, unit(x)^y)) else ^(x::AbstractQuantity, y::Rational) = Quantity((x.val)^y, unit(x)^y) end ^(x::AbstractQuantity, y::Real) = Quantity((x.val)^y, unit(x)^y) Base.rand(r::Random.AbstractRNG, ::Random.SamplerType{<:AbstractQuantity{T,D,U}}) where {T,D,U} = rand(r, T) * U() Base.ones(Q::Type{<:AbstractQuantity}, dims::NTuple{N,Integer}) where {N} = fill!(Array{Q,N}(undef, map(Base.to_dim, dims)), oneunit(Q)) Base.ones(Q::Type{<:AbstractQuantity}, dims::Tuple{}) = fill!(Array{Q}(undef), oneunit(Q)) Base.ones(a::AbstractArray, Q::Type{<:AbstractQuantity}) = fill!(similar(a,Q), oneunit(Q)) ================================================ FILE: src/range.jl ================================================ const colon = Base.:(:) import Base: ArithmeticRounds import Base: OrderStyle, Ordered, ArithmeticStyle, ArithmeticWraps import Base.Broadcast: DefaultArrayStyle, broadcasted *(y::Units, r::AbstractRange) = *(r,y) *(r::AbstractRange, y::Units, z::Units...) = *(r, *(y,z...)) # start, stop, length Base._range(start::Quantity, ::Nothing, stop, len::Integer) = _unitful_start_stop_length(start, stop, len) Base._range(start, ::Nothing, stop::Quantity, len::Integer) = _unitful_start_stop_length(start, stop, len) Base._range(start::Quantity, ::Nothing, stop::Quantity, len::Integer) = _unitful_start_stop_length(start, stop, len) function _unitful_start_stop_length(start, stop, len) dimension(start) != dimension(stop) && throw(DimensionError(start, stop)) a, b = promote(start, stop) Base._range(a, nothing, b, len) end Base._range(start::T, ::Nothing, stop::T, len::Integer) where {T<:Quantity} = LinRange{T}(start, stop, len) Base._range(start::T, ::Nothing, stop::T, len::Integer) where {T<:Quantity{<:Integer}} = Base._linspace(Float64, ustrip(start), ustrip(stop), len, 1)*unit(T) Base._range(start::T, ::Nothing, stop::T, len::Integer) where {T<:Quantity{<:Base.IEEEFloat}} = Base._range(ustrip(start), nothing, ustrip(stop), len) * unit(T) # start, step, length Base._range(a::T, step::T, ::Nothing, len::Integer) where {T<:Quantity{<:Base.IEEEFloat}} = Base._range(ustrip(a), ustrip(step), nothing, len) * unit(T) Base._range(a::T, step::T, ::Nothing, len::Integer) where {T<:Quantity{<:AbstractFloat}} = StepRangeLen{typeof(step*len),typeof(a),typeof(step)}(a, step, len) Base._range(a::T, step::T, ::Nothing, len::Integer) where {T<:Quantity} = @static if VERSION ≥ v"1.8.0-DEV" Base.range_start_step_length(a, step, len) else Base._rangestyle(OrderStyle(a), ArithmeticStyle(a), a, step, len) end Base._range(a::Quantity{<:Real}, step::Quantity{<:AbstractFloat}, ::Nothing, len::Integer) = _unitful_start_step_length(float(a), step, len) Base._range(a::Quantity{<:AbstractFloat}, step::Quantity{<:Real}, ::Nothing, len::Integer) = _unitful_start_step_length(a, float(step), len) Base._range(a::Quantity{<:AbstractFloat}, step::Quantity{<:AbstractFloat}, ::Nothing, len::Integer) = _unitful_start_step_length(a, step, len) Base._range(a, step::Quantity, ::Nothing, len::Integer) = _unitful_start_step_length(a, step, len) Base._range(a::Quantity, step, ::Nothing, len::Integer) = _unitful_start_step_length(a, step, len) Base._range(a::Quantity, step::Quantity, ::Nothing, len::Integer) = _unitful_start_step_length(a, step, len) function _unitful_start_step_length(start, step, len) dimension(start) != dimension(step) && throw(DimensionError(start,step)) Base._range(promote(start, uconvert(unit(start), step))..., nothing, len) end # start, length (step defaults to 1) Base._range(a::Quantity, ::Nothing, ::Nothing, len::Integer) = Base._range(a, one(a), nothing, len) # step, stop, length @static if VERSION ≥ v"1.7" Base._range(::Nothing, step, stop::Quantity, len::Integer) = _unitful_step_stop_length(step, stop, len) Base._range(::Nothing, step::Quantity, stop, len::Integer) = _unitful_step_stop_length(step, stop, len) Base._range(::Nothing, step::Quantity, stop::Quantity, len::Integer) = _unitful_step_stop_length(step, stop, len) Base._range(::Nothing, step::Quantity, ::Nothing, len::Integer) = Base.range_error(nothing, step, nothing, len) function _unitful_step_stop_length(step, stop, len) dimension(stop) != dimension(step) && throw(DimensionError(stop,step)) Base.range_step_stop_length(promote(uconvert(unit(stop), step), stop)..., len) end end # stop, length (step defaults to 1) @static if VERSION ≥ v"1.7" Base._range(::Nothing, ::Nothing, stop::Quantity, len::Integer) = Base._range(nothing, one(stop), stop, len) end *(r::AbstractRange, y::Units) = range(first(r)*y, step=step(r)*y, length=length(r)) # first promote start and stop, leaving step alone colon(start::A, step, stop::C) where {A<:Real,C<:Quantity} = colonstartstop(start,step,stop) colon(start::A, step, stop::C) where {A<:Quantity,C<:Real} = colonstartstop(start,step,stop) colon(a::T, b::Quantity, c::T) where {T<:Real} = colon(promote(a,b,c)...) colon(start::Quantity{<:Real}, step, stop::Quantity{<:Real}) = colon(promote(start, step, stop)...) # promotes start and stop function colonstartstop(start::A, step, stop::C) where {A,C} dimension(start) != dimension(stop) && throw(DimensionError(start, stop)) colon(convert(promote_type(A,C),start), step, convert(promote_type(A,C),stop)) end function colon(start::A, step::B, stop::A) where A<:Quantity{<:Real} where B<:Quantity{<:Real} dimension(start) != dimension(step) && throw(DimensionError(start, step)) colon(promote(start, step, stop)...) end OrderStyle(::Type{<:AbstractQuantity{T}}) where T = OrderStyle(T) ArithmeticStyle(::Type{<:AbstractQuantity{T}}) where T = ArithmeticStyle(T) colon(start::T, step::T, stop::T) where {T<:Quantity{<:Real}} = _colon(OrderStyle(T), ArithmeticStyle(T), start, step, stop) _colon(::Ordered, ::Any, start::T, step, stop::T) where {T} = StepRange(start, step, stop) _colon(::Ordered, ::ArithmeticRounds, start::T, step, stop::T) where {T} = StepRangeLen(start, step, floor(Int, (stop-start)/step)+1) _colon(::Any, ::Any, start::T, step, stop::T) where {T} = StepRangeLen(start, step, floor(Int, (stop-start)/step)+1) # Opt into TwicePrecision functionality *(x::Base.TwicePrecision, y::Units) = Base.TwicePrecision(x.hi*y, x.lo*y) *(x::Base.TwicePrecision, y::Quantity) = (x * ustrip(y)) * unit(y) uconvert(y, x::Base.TwicePrecision) = Base.TwicePrecision(uconvert(y, x.hi), uconvert(y, x.lo)) ustrip(x::Base.TwicePrecision) = Base.TwicePrecision(ustrip(x.hi), ustrip(x.lo)) @inline upreferred(x::Base.TwicePrecision{T}) where T<:Number = x @inline upreferred(x::Base.TwicePrecision{T}) where T<:AbstractQuantity = uconvert(upreferred(unit(x)), x) colon(start::T, step::T, stop::T) where {T<:Quantity{<:Base.IEEEFloat}} = colon(ustrip(start), ustrip(step), ustrip(stop)) * unit(T) # This will always return a StepRangeLen # two-argument colon colon(start, stop::Quantity) = _unitful_start_stop(start, stop) colon(start::Quantity, stop) = _unitful_start_stop(start, stop) colon(start::Quantity, stop::Quantity) = _unitful_start_stop(start, stop) function _unitful_start_stop(start, stop) dimension(start) != dimension(stop) && throw(DimensionError(start, stop)) colon(promote(start, stop)...) end function colon(start::T, stop::T) where {T<:Quantity} step = uconvert(unit(start), one(start)) colon(promote(start, step, stop)...) end # No need to confuse things by changing the type once units are on there, # if we can help it. *(r::StepRangeLen, y::Units) = StepRangeLen{typeof(zero(eltype(r))*y)}(r.ref*y, r.step*y, length(r), r.offset) *(r::LinRange, y::Units) = LinRange(r.start*y, r.stop*y, length(r)) *(r::StepRange, y::Units) = StepRange(r.start*y, r.step*y, r.stop*y) *(r::AbstractUnitRange, y::Units) = StepRange(first(r)*y, oneunit(first(r))*y, last(r)*y) function /(x::Base.TwicePrecision, v::Quantity) x / Base.TwicePrecision(oftype(ustrip(x.hi)/ustrip(v)*unit(v), v)) end # These can be removed (I think) if `range_start_step_length()` returns a `StepRangeLen` for # non-floats, cf. https://github.com/JuliaLang/julia/issues/40672 broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::AbstractRange, x::AbstractQuantity) = broadcasted(DefaultArrayStyle{1}(), *, r, ustrip(x)) * unit(x) broadcasted(::DefaultArrayStyle{1}, ::typeof(*), x::AbstractQuantity, r::AbstractRange) = broadcasted(DefaultArrayStyle{1}(), *, ustrip(x), r) * unit(x) const BCAST_PROPAGATE_CALLS = Union{typeof(upreferred), Units} broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::AbstractRange, x::Ref{<:Units}) = r * x[] broadcasted(::DefaultArrayStyle{1}, ::typeof(*), x::Ref{<:Units}, r::AbstractRange) = x[] * r broadcasted(::DefaultArrayStyle{1}, x::BCAST_PROPAGATE_CALLS, r::StepRangeLen) = StepRangeLen{typeof(x(zero(eltype(r))))}(x(r.ref), x(r.step), r.len, r.offset) function broadcasted(::DefaultArrayStyle{1}, x::BCAST_PROPAGATE_CALLS, r::StepRange) start = x(r.start) au_to = absoluteunit(unit(start)) step = uconvert(au_to, r.step) if Base.ArithmeticStyle(start) == Base.ArithmeticRounds() || Base.ArithmeticStyle(step) == Base.ArithmeticRounds() au_from = absoluteunit(unit(r.start)) astart = ustrip(au_from, r.start) astop = ustrip(au_from, r.stop) len = length(r) offset = _offset_for_steprangelen(astart, astop, len) nb = ndigits(max(offset-1, len-offset), base=2, pad=0) T = promote_type(typeof(start/unit(start)), typeof(step/unit(step))) unitless_range = Base.steprangelen_hp(T, ustrip(au_to, r[offset]), ustrip(au_to, step), nb, len, offset) return unitless_range * unit(start) else return StepRange(start, step, x(r.stop)) end end broadcasted(::DefaultArrayStyle{1}, x::BCAST_PROPAGATE_CALLS, r::LinRange) = LinRange(x(r.start), x(r.stop), r.len) broadcasted(::DefaultArrayStyle{1}, ::typeof(|>), r::AbstractRange, x::Ref{<:BCAST_PROPAGATE_CALLS}) = broadcasted(DefaultArrayStyle{1}(), x[], r) function _offset_for_steprangelen(start, stop, len) if iszero(start) return oneunit(len) elseif iszero(stop) return len elseif signbit(start) == signbit(stop) return abs(start) < abs(stop) ? oneunit(len) : len else fstart = Float64(start) fstop = Float64(stop) return round(typeof(len), (fstop-len*fstart)/(fstop-fstart)) end end broadcasted(::DefaultArrayStyle{1}, ::typeof(ustrip), r::StepRangeLen) = StepRangeLen{typeof(ustrip(zero(eltype(r))))}(ustrip(unit(eltype(r)), r.ref), ustrip(unit(eltype(r)), r.step), r.len, r.offset) broadcasted(::DefaultArrayStyle{1}, ::typeof(ustrip), r::StepRange) = ustrip(unit(eltype(r)), r.start):ustrip(unit(eltype(r)), r.step):ustrip(unit(eltype(r)), r.stop) broadcasted(::DefaultArrayStyle{1}, ::typeof(ustrip), r::LinRange) = LinRange(ustrip(unit(eltype(r)), r.start), ustrip(unit(eltype(r)), r.stop), r.len) broadcasted(::DefaultArrayStyle{1}, ::typeof(|>), r::AbstractRange, ::Ref{typeof(ustrip)}) = broadcasted(DefaultArrayStyle{1}(), ustrip, r) # for ambiguity resolution broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::StepRangeLen{T}, x::AbstractQuantity) where T = broadcasted(DefaultArrayStyle{1}(), *, r, ustrip(x)) * unit(x) broadcasted(::DefaultArrayStyle{1}, ::typeof(*), x::AbstractQuantity, r::StepRangeLen{T}) where T = broadcasted(DefaultArrayStyle{1}(), *, ustrip(x), r) * unit(x) broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::LinRange, x::AbstractQuantity) = LinRange(r.start*x, r.stop*x, r.len) broadcasted(::DefaultArrayStyle{1}, ::typeof(*), x::AbstractQuantity, r::LinRange) = LinRange(x*r.start, x*r.stop, r.len) ================================================ FILE: src/types.jl ================================================ """ abstract type Unitlike end Represents units or dimensions. Dimensions are unit-like in the sense that they are not numbers but you can multiply or divide them and exponentiate by rationals. """ abstract type Unitlike end """ struct Dimension{D} power::Rational{Int} end Description of a dimension. The name of the dimension `D` is a symbol, e.g. `:Length`, `:Time`, `:Mass`, etc. `Dimension{D}` objects are collected in a tuple, which is used for the type parameter `N` of a [`Dimensions{N}`](@ref) object. """ struct Dimension{D} power::Rational{Int} end @inline name(x::Dimension{D}) where {D} = D @inline power(x::Dimension) = x.power """ struct Dimensions{N} <: Unitlike Instances of this object represent dimensions, possibly combinations thereof. """ struct Dimensions{N} <: Unitlike end " Unitful.NoDims \nA dimension representing quantities without dimensions." const NoDims = Dimensions{()}() """ struct Unit{U,D} tens::Int power::Rational{Int} end Description of a physical unit, including powers-of-ten prefixes and powers of the unit. The name of the unit is encoded in the type parameter `U` as a symbol, e.g. `:Meter`, `:Second`, `:Gram`, etc. The type parameter `D` is a [`Dimensions{N}`](@ref) object, for instance `Unit{:Meter, 𝐋}` or `Unit{:Liter, 𝐋^3}`. Note that the dimension information refers to the unit, not powers of the unit. `Unit{U,D}` objects are almost never explicitly manipulated by the user. They are collected in a tuple, which is used for the type parameter `N` of a [`Units{N,D,A}`](@ref) object. """ struct Unit{U,D} tens::Int power::Rational{Int} end @inline name(x::Unit{U}) where {U} = U @inline tens(x::Unit) = x.tens @inline power(x::Unit) = x.power """ dimension(x::Unit) Returns a [`Unitful.Dimensions`](@ref) object describing the given unit `x`. """ @inline dimension(u::Unit{U,D}) where {U,D} = D^u.power struct Affine{T} end """ abstract type Units{N,D,A} <: Unitlike end Abstract supertype of all units objects, which can differ in their implementation details. `A` is a translation for affine quantities; for non-affine quantities it is `nothing`. """ abstract type Units{N,D,A} <: Unitlike end affinetranslation(::Units{N,D,Affine{T}}) where {N,D,T} = T affinetranslation(::Units{N,D,nothing}) where {N,D} = false """ genericunit(::Units) Given e.g. a `FreeUnits{N,D,A}`, `ContextUnits{N,D,P,A}`, or `FixedUnits{N,D,A}` object, return the type `Units{N,D,A}`. """ genericunit(::Units{N,D,A}) where {N,D,A} = Units{N,D,A} """ struct FreeUnits{N,D,A} <: Units{N,D,A} Instances of this object represent units, possibly combinations thereof. These behave like units have behaved in previous versions of Unitful, and provide a basic level of functionality that should be acceptable to most users. See [Basic promotion mechanisms](@ref) in the docs for details. Example: the unit `m` is actually a singleton of type `Unitful.FreeUnits{(Unitful.Unit{:Meter, 𝐋}(0, 1//1),), 𝐋, nothing}`. After dividing by `s`, a singleton of type `Unitful.FreeUnits{(Unitful.Unit{:Meter, 𝐋}(0, 1//1), Unitful.Unit{:Second, 𝐓}(0, -1//1)), 𝐋/𝐓, nothing}` is returned. """ struct FreeUnits{N,D,A} <: Units{N,D,A} end FreeUnits{N,D}() where {N,D} = FreeUnits{N,D,nothing}() FreeUnits(::Units{N,D,A}) where {N,D,A} = FreeUnits{N,D,A}() """ NoUnits An object that represents "no units", i.e., the units of a unitless number. The type of the object is `Unitful.FreeUnits{(), NoDims}`. It is displayed as an empty string. Example: ```jldoctest julia> unit(1.0) == NoUnits true ``` """ const NoUnits = FreeUnits{(), NoDims}() (y::FreeUnits)(x) = uconvert(y,x) """ struct ContextUnits{N,D,P,A} <: Units{N,D,A} Instances of this object represent units, possibly combinations thereof. It is in most respects like `FreeUnits{N,D,A}`, except that the type parameter `P` is again a `FreeUnits{M,D}` type that specifies a preferred unit for promotion. See [Advanced promotion mechanisms](@ref) in the docs for details. """ struct ContextUnits{N,D,P,A} <: Units{N,D,A} end function ContextUnits(x::Units{N,D,A}, y::Units) where {N,D,A} D !== dimension(y) && throw(DimensionError(x,y)) ContextUnits{N,D,typeof(FreeUnits(y)),A}() end ContextUnits{N,D,P}() where {N,D,P} = ContextUnits{N,D,P,nothing}() ContextUnits(u::Units{N,D,A}) where {N,D,A} = ContextUnits{N,D,typeof(FreeUnits(upreferred(u))),A}() (y::ContextUnits)(x) = uconvert(y,x) """ struct FixedUnits{N,D,A} <: Units{N,D,A} end Instances of this object represent units, possibly combinations thereof. These are primarily intended for use when you would like to disable automatic unit conversions. See [Advanced promotion mechanisms](@ref) in the docs for details. """ struct FixedUnits{N,D,A} <: Units{N,D,A} end FixedUnits{N,D}() where {N,D} = FixedUnits{N,D,nothing}() FixedUnits(::Units{N,D,A}) where {N,D,A} = FixedUnits{N,D,A}() """ abstract type AbstractQuantity{T,D,U} <: Number end Represents a generic quantity type, whose dimensions and units are specified in the type signature. The dimensions and units are allowed to be the empty set, in which case a dimensionless, unitless number results. The type parameter `T` represents the numeric backing type. The type parameters `D :: ` [`Unitful.Dimensions`](@ref) and `U <: ` [`Unitful.Units`](@ref). Of course, the dimensions follow from the units, but the type parameters are kept separate to permit convenient dispatch on dimensions. """ abstract type AbstractQuantity{T,D,U} <: Number end """ struct Quantity{T,D,U} <: AbstractQuantity{T,D,U} A concrete subtype of [`Unitful.AbstractQuantity`](@ref). The type parameter `T` represents the numeric backing type. The type parameters `D :: ` [`Unitful.Dimensions`](@ref) and `U <: ` [`Unitful.Units`](@ref). """ struct Quantity{T,D,U} <: AbstractQuantity{T,D,U} val::T Quantity{T,D,U}(v::Number) where {T,D,U} = new{T,D,U}(v) Quantity{T,D,U}(v::Quantity) where {T,D,U} = convert(Quantity{T,D,U}, v) end # Field-only constructor Quantity{<:Any,D,U}(val::Number) where {D,U} = Quantity{typeof(val),D,U}(val) """ DimensionlessUnits{U} Useful for dispatching on [`Unitful.Units`](@ref) types that have no dimensions. Example: ```jldoctest julia> isa(Unitful.rad, DimensionlessUnits) true """ const DimensionlessUnits{U} = Units{U, NoDims} """ AffineUnits{N,D,A} = Units{N,D,A} where A<:Affine Useful for dispatching on unit objects that indicate a quantity should affine-transform under unit conversion, like absolute temperatures. Not exported. """ const AffineUnits{N,D,A} = Units{N,D,A} where A<:Affine """ ScalarUnits{N,D} = Units{N,D,nothing} Useful for dispatching on unit objects that indicate a quantity should transform in the usual scalar way under unit conversion. Not exported. """ const ScalarUnits{N,D} = Units{N,D,nothing} """ DimensionlessQuantity{T,U} = AbstractQuantity{T, NoDims, U} Useful for dispatching on [`Unitful.Quantity`](@ref) types that may have units but no dimensions. (Units with differing power-of-ten prefixes are not canceled out.) Example: ```jldoctest julia> isa(1.0u"mV/V", DimensionlessQuantity) true ``` """ const DimensionlessQuantity{T,U} = AbstractQuantity{T, NoDims, U} """ AffineQuantity{T,D,U} = AbstractQuantity{T,D,U} where U<:AffineUnits Useful for dispatching on quantities that affine-transform under unit conversion, like absolute temperatures. Not exported. """ const AffineQuantity{T,D,U} = AbstractQuantity{T,D,U} where U<:AffineUnits """ ScalarQuantity{T,D,U} = AbstractQuantity{T,D,U} where U<:ScalarUnits Useful for dispatching on quantities that transform in the usual scalar way under unit conversion. Not exported. """ const ScalarQuantity{T,D,U} = AbstractQuantity{T,D,U} where U<:ScalarUnits """ struct LogInfo{N,B,P} Describes a logarithmic unit. Type parameters include: - `N`: The name of the logarithmic unit, e.g. `:Decibel`, `:Neper`. - `B`: The base of the logarithm. - `P`: A prefactor to multiply the logarithm when the log is of a power ratio. """ struct LogInfo{N,B,P} end """ abstract type LogScaled{L<:LogInfo} <: Number end Abstract supertype of [`Unitful.Level`](@ref) and [`Unitful.Gain`](@ref). It is only used in promotion to put levels and gains onto a common log scale. """ abstract type LogScaled{L<:LogInfo} <: Number end const RealOrRealQuantity = Union{Real, AbstractQuantity{<:Real}} """ struct Level{L, S, T<:Union{Real, AbstractQuantity{<:Real}}} <: LogScaled{L} A logarithmic scale-based level. Details about the logarithmic scale are encoded in `L <: LogInfo`. `S` is a reference quantity for the level, not a type. This type has one field, `val::T`, and the log of the ratio `val/S` is taken. This type differs from [`Unitful.Gain`](@ref) in that `val` is a linear quantity. """ struct Level{L, S, T<:RealOrRealQuantity} <: LogScaled{L} val::T function Level{L,S,T}(x::Number) where {L,S,T} S isa ReferenceQuantity || throw(DomainError(S, "Reference quantity must be real.")) dimension(S) != dimension(x) && throw(DimensionError(S,x)) return new{L,S,T}(x) end end Level{L,S}(val::Number) where {L,S} = Level{L,S,real(typeof(val))}(val) Level{L,S}(val::RealOrRealQuantity) where {L,S} = Level{L,S,typeof(val)}(val) """ struct Gain{L, S, T<:Real} <: LogScaled{L} A logarithmic scale-based gain or attenuation factor. This type has one field, `val::T`. For example, given a gain of `20dB`, we have `val===20`. This type differs from [`Unitful.Level`](@ref) in that `val` is stored after computing the logarithm. """ struct Gain{L, S, T<:Real} <: LogScaled{L} val::T Gain{L, S, T}(x::Number) where {L,S,T<:Real} = new{L,S,T}(x) end """ struct MixedUnits{T<:LogScaled, U<:Units} Struct for representing mixed logarithmic / linear units. Primarily useful as an intermediate for `uconvert`. `T` is `<: Level` or `<: Gain`. """ struct MixedUnits{T<:LogScaled, U<:Units} units::U end MixedUnits{T}() where {T} = MixedUnits{T, typeof(NoUnits)}(NoUnits) MixedUnits{T}(u::Units) where {T} = MixedUnits{T,typeof(u)}(u) (y::MixedUnits)(x) = uconvert(y,x) # For logarithmic quantities struct IsRootPowerRatio{S,T} val::T end IsRootPowerRatio{S}(x) where {S} = IsRootPowerRatio{S, typeof(x)}(x) Base.show(io::IO, x::IsRootPowerRatio{S}) where {S} = print(io, ifelse(S, "root-power ratio", "power ratio"), " with reference ", x.val) const PowerRatio{T} = IsRootPowerRatio{false,T} const RootPowerRatio{T} = IsRootPowerRatio{true,T} @inline unwrap(x::IsRootPowerRatio) = x.val @inline unwrap(x) = x const ReferenceQuantity = Union{RealOrRealQuantity, IsRootPowerRatio{S, <:RealOrRealQuantity} where S} """ reflevel(x::Level{L,S}) reflevel(::Type{Level{L,S}}) reflevel(::Type{Level{L,S,T}}) Returns the reference level, e.g. ```jldoctest julia> reflevel(3u"dBm") 1 mW ``` """ function reflevel end @inline reflevel(x::Level{L,S}) where {L,S} = unwrap(S) @inline reflevel(::Type{Level{L,S}}) where {L,S} = unwrap(S) @inline reflevel(::Type{Level{L,S,T}}) where {L,S,T} = unwrap(S) ================================================ FILE: src/units.jl ================================================ @generated function *(a0::FreeUnits, a::FreeUnits...) # Sort the units uniquely. This is a generated function so that we # don't have to figure out the units each time. linunits = Vector{Unit}() nunits = length(a) + 1 for x in (a0, a...) (x.parameters[3] !== nothing) && (nunits > 1) && throw(AffineError("an invalid operation was attempted with affine units: $(x())")) xp = x.parameters[1] append!(linunits, xp[1:end]) end # linunits is an Array containing all of the Unit objects that were # found in the type parameters of the FreeUnits objects (a0, a...) sort!(linunits, by=power) sort!(linunits, by=tens) sort!(linunits, by=name) # [m,m,cm,cm^2,cm^3,nm,m^4,μs,μs^2,s] # reordered as: # [nm,cm,cm^2,cm^3,m,m,m^4,μs,μs^2,s] # Collect powers of a given unit into `c` c = Vector{Unit}() if !isempty(linunits) next = iterate(linunits) p = 0//1 oldvalue = next[1] while next !== nothing (value, state) = next if tens(value) == tens(oldvalue) && name(value) == name(oldvalue) p += power(value) else if p != 0 push!(c, Unit{name(oldvalue), dimtype(oldvalue)}(tens(oldvalue), p)) end p = power(value) end oldvalue = value next = iterate(linunits, state) end if p != 0 push!(c, Unit{name(oldvalue), dimtype(oldvalue)}(tens(oldvalue), p)) end end # results in: # [nm,cm^6,m^6,μs^3,s] d = (c...,) f = mapreduce(dimension, *, d; init=NoDims) :(FreeUnits{$d,$f,$(a0.parameters[3])}()) end *(a0::ContextUnits, a::ContextUnits...) = ContextUnits(*(FreeUnits(a0), FreeUnits.(a)...), *(FreeUnits(upreferred(a0)), FreeUnits.((upreferred).(a))...)) FreeOrContextUnits = Union{FreeUnits, ContextUnits} *(a0::FreeOrContextUnits, a::FreeOrContextUnits...) = *(ContextUnits(a0), ContextUnits.(a)...) *(a0::FixedUnits, a::FixedUnits...) = FixedUnits(*(FreeUnits(a0), FreeUnits.(a)...)) """ ``` *(a0::Units, a::Units...) ``` Given however many units, multiply them together. This is actually handled by a few different methods, since we have `FreeUnits`, `ContextUnits`, and `FixedUnits`. Collect [`Unitful.Unit`](@ref) objects from the type parameter of the [`Unitful.Units`](@ref) objects. For identical units including SI prefixes (i.e. `cm` ≠ `m`), collect powers and sort uniquely by the name of the `Unit`. The unique sorting permits easy unit comparisons. Examples: ```jldoctest julia> u"kg*m/s^2" kg m s^-2 julia> u"m/s*kg/s" kg m s^-2 julia> typeof(u"m/s*kg/s") == typeof(u"kg*m/s^2") true ``` """ *(a0::Units, a::Units...) = FixedUnits(*(FreeUnits(a0), FreeUnits.(a)...)) # Logic above is that if we're not using FreeOrContextUnits, at least one is FixedUnits. *(a0::Units, a::Missing) = missing *(a0::Missing, a::Units) = missing *(a0::MixedUnits, a::Missing) = missing *(a0::Missing, a::MixedUnits) = missing /(x::Units, y::Units) = *(x,inv(y)) /(x::Units, y::Missing) = missing /(x::Missing, y::Units) = missing /(x::MixedUnits, y::Missing) = missing /(x::Missing, y::MixedUnits) = missing //(x::Units, y::Units) = x/y # Both methods needed for ambiguity resolution ^(x::Unit{U,D}, y::Integer) where {U,D} = Unit{U,D}(tens(x), power(x)*y) ^(x::Unit{U,D}, y::Number) where {U,D} = Unit{U,D}(tens(x), power(x)*y) # A word of caution: # Exponentiation is not type-stable for `Units` objects. # Dimensions get reconstructed anyway so we pass () for the D type parameter... ^(x::AffineUnits, y::Integer) = throw(AffineError("an invalid operation was attempted with affine units: $x")) ^(x::AffineUnits, y::Number) = throw(AffineError("an invalid operation was attempted with affine units: $x")) ^(x::FreeUnits{N,D,nothing}, y::Integer) where {N,D} = *(FreeUnits{map(a->a^y, N), ()}()) ^(x::FreeUnits{N,D,nothing}, y::Number) where {N,D} = *(FreeUnits{map(a->a^y, N), ()}()) ^(x::ContextUnits{N,D,P,nothing}, y::Integer) where {N,D,P} = *(ContextUnits{map(a->a^y, N), (), typeof(P()^y)}()) ^(x::ContextUnits{N,D,P,nothing}, y::Number) where {N,D,P} = *(ContextUnits{map(a->a^y, N), (), typeof(P()^y)}()) ^(x::FixedUnits{N,D,nothing}, y::Integer) where {N,D} = *(FixedUnits{map(a->a^y, N), ()}()) ^(x::FixedUnits{N,D,nothing}, y::Number) where {N,D} = *(FixedUnits{map(a->a^y, N), ()}()) ^(x::Units, y::Missing) = missing ^(x::Missing, y::Units) = missing Base.literal_pow(::typeof(^), x::AffineUnits, ::Val{p}) where p = throw(AffineError("an invalid operation was attempted with affine units: $x")) @generated function Base.literal_pow(::typeof(^), x::FreeUnits{N,D,nothing}, ::Val{p}) where {N,D,p} y = *(FreeUnits{map(a->a^p, N), ()}()) :($y) end @generated function Base.literal_pow(::typeof(^), x::ContextUnits{N,D,P,nothing}, ::Val{p}) where {N,D,P,p} y = *(ContextUnits{map(a->a^p, N), (), typeof(P()^p)}()) :($y) end @generated function Base.literal_pow(::typeof(^), x::FixedUnits{N,D,nothing}, ::Val{p}) where {N,D,p} y = *(FixedUnits{map(a->a^p, N), ()}()) :($y) end # Since exponentiation is not type stable, we define a special `inv` method to enable fast # division. For julia 0.6.0, the appropriate methods for ^ and * need to be defined before # this one! for (fun,pow) in ((:inv, -1//1), (:sqrt, 1//2), (:cbrt, 1//3)) # The following are generated functions to ensure type stability. @eval @generated function ($fun)(x::FreeUnits) (x <: AffineUnits) && throw( AffineError("an invalid operation was attempted with affine units: $(x())")) unittuple = map(x->x^($pow), x.parameters[1]) y = *(FreeUnits{unittuple,()}()) # sort appropriately :($y) end @eval @generated function ($fun)(x::ContextUnits) (x <: AffineUnits) && throw( AffineError("an invalid operation was attempted with affine units: $(x())")) unittuple = map(x->x^($pow), x.parameters[1]) promounit = ($fun)(x.parameters[3]()) y = *(ContextUnits{unittuple,(),typeof(promounit)}()) # sort appropriately :($y) end @eval @generated function ($fun)(x::FixedUnits) (x <: AffineUnits) && throw( AffineError("an invalid operation was attempted with affine units: $(x())")) unittuple = map(x->x^($pow), x.parameters[1]) y = *(FixedUnits{unittuple,()}()) # sort appropriately :($y) end end function tensfactor(x::Unit) p = power(x) if isinteger(p) p = Integer(p) end tens(x)*p end @generated function tensfactor(x::Units) tunits = x.parameters[1] a = mapreduce(tensfactor, +, tunits; init=0) :($a) end # This is type unstable but # a) this method is not called by the user # b) ultimately the instability will only be present at compile time as it is # hidden behind a "generated function barrier" function basefactor(inex, ex, eq, tens, p) # Sometimes (x::Rational)^1 can fail for large rationals because the result # is of type x*x so we do a hack here function dpow(x, p) if p == 0 1 elseif p == 1 x elseif p == -1 1//x else x^p end end if isinteger(p) p = Integer(p) end if isinteger(tens) tens = Integer(tens) end eq_is_exact = false output_ex_float = (10.0^tens * float(ex))^p eq_raised = float(eq)^p if isa(eq, Integer) || isa(eq, Rational) output_ex_float *= eq_raised eq_is_exact = true end can_exact = (output_ex_float < typemax(Int)) can_exact &= (1/output_ex_float < typemax(Int)) can_exact &= isinteger(p) can_exact2 = (eq_raised < typemax(Int)) can_exact2 &= (1/eq_raised < typemax(Int)) can_exact2 &= isinteger(p) if can_exact && isinteger(tens) if eq_is_exact # If we got here then p is an integer. # Note that sometimes x^1 can cause an overflow error if x is large because # of how power_by_squaring is implemented for Rationals, so we use dpow. x = dpow(eq*ex*(10//1)^tens, p) result = (inex^p, isinteger(x) ? Int(x) : x) else x = dpow(ex*(10//1)^tens, p) result = ((inex * eq)^p, isinteger(x) ? Int(x) : x) end else if eq_is_exact && can_exact2 x = dpow(eq, p) result = ((inex * ex * 10.0^tens)^p, isinteger(x) ? Int(x) : x) else result = ((inex * ex * 10.0^tens * eq)^p, 1) end end if fp_overflow_underflow(inex, first(result)) throw(ArgumentError("Floating point overflow/underflow, probably due to large exponent ($p)")) end return result end """ basefactor(x::Unit) Specifies conversion factors to reference units. It returns a tuple. The first value is any irrational part of the conversion, and the second value is a rational component. This segregation permits exact conversions within unit systems that have no rational conversion to the reference units. """ @inline basefactor(x::Unit{U}) where {U} = basefactor(basefactors[U]..., 1, 0, power(x)) function basefactor(::Units{U}) where {U} fact1 = map(basefactor, U) inex1 = mapreduce(first, *, fact1, init=1.0) float_num = mapreduce(x -> float(numerator(last(x))), *, fact1, init=1.0) float_den = mapreduce(x -> float(denominator(last(x))), *, fact1, init=1.0) can_exact = float_num < typemax(Int) && float_den < typemax(Int) if can_exact result = (inex1, mapreduce(last, *, fact1, init=1)) else result = (inex1 * (float_num / float_den), 1) end if any(fp_overflow_underflow(first(x), first(result)) for x in fact1) throw(ArgumentError("Floating point overflow/underflow, probably due to a large exponent in some of the units")) end return result end Base.broadcastable(x::Units) = Ref(x) Base.nbitslen(::Type{Q}, len, offset) where Q<:Quantity = Base.nbitslen(numtype(Q), len, offset) ustrip(x::Base.TwicePrecision{Q}) where Q<:Quantity = Base.TwicePrecision(ustrip(x.hi), ustrip(x.lo)) unit(x::Base.TwicePrecision{Q}) where Q<:Quantity = unit(x.hi) function Base.twiceprecision(x::Union{Q,Base.TwicePrecision{Q}}, nb::Integer) where Q<:Quantity xt = Base.twiceprecision(ustrip(x), nb) return Base.TwicePrecision(xt.hi*unit(x), xt.lo*unit(x)) end function *(x::Base.TwicePrecision{Q}, v::Real) where Q<:Quantity v == 0 && return Base.TwicePrecision(x.hi*v, x.lo*v) (ustrip(x) * Base.TwicePrecision(oftype(ustrip(x.hi)*v, v))) * unit(x) end Base.mul12(x::Quantity, y::Quantity) = Base.mul12(ustrip(x), ustrip(y)) .* (unit(x) * unit(y)) Base.mul12(x::Quantity, y::Real) = Base.mul12(ustrip(x), y) .* unit(x) Base.mul12(x::Real, y::Quantity) = Base.mul12(x, ustrip(y)) .* unit(y) # The following method must not be defined before `*(a0::FreeUnits, a::FreeUnits...)` """ upreferred(x::Dimensions) Return units which are preferred for dimensions `x`. If you are using the factory defaults, this function will return a product of powers of base SI units (as [`Unitful.FreeUnits`](@ref)). """ @generated function upreferred(x::Dimensions{D}) where {D} u = prod((NoUnits, (promotion[name(z)]^z.power for z in D)...)) :($u) end ================================================ FILE: src/user.jl ================================================ """ register(unit_module::Module) Makes Unitful aware of units defined in a new unit module, including making the [`@u_str`](@ref) macro work with these units. By default, Unitful is itself a registered module. Note that Main is not, so if you define new units at the REPL, you will probably want to do `Unitful.register(Main)`. Example: ```julia # somewhere in a custom units package... module MyUnitsPackage using Unitful function __init__() ... Unitful.register(MyUnitsPackage) end end #module ``` """ function register(unit_module::Module) push!(Unitful.unitmodules, unit_module) if unit_module !== Unitful merge!(Unitful.basefactors, _basefactors(unit_module)) end end """ @dimension(symb, abbr, name, autodocs=false) Creates new dimensions. `name` will be used like an identifier in the type parameter for a [`Unitful.Dimension`](@ref) object. `symb` will be a symbol defined in the namespace from which this macro is called that is bound to a [`Unitful.Dimensions`](@ref) object. For most intents and purposes it is this object that the user would manipulate in doing dimensional analysis. The symbol is not exported. This macro extends [`Unitful.abbr`](@ref) to display the new dimension in an abbreviated format using the string `abbr`. Type aliases are created that allow the user to dispatch on [`Unitful.Quantity`](@ref), [`Unitful.Level`](@ref) and [`Unitful.Units`](@ref) objects of the newly defined dimension. The type alias for quantities or levels is simply given by `name`, and the type alias for units is given by `name*"Units"`, e.g. `LengthUnits`. Note that there is also `LengthFreeUnits`, for example, which is an alias for dispatching on `FreeUnits` with length dimensions. The aliases are not exported. If `autodocs == true`, docstrings will be automatically generated for these aliases. !!! compat "Unitful 1.10" Documenting the resulting dimension symbol by adding a docstring before the `@dimension` call requires Unitful 1.10 or later. The `autodocs` argument also requires Unitful 1.10 or later. Finally, if you define new dimensions with [`@dimension`](@ref) you will need to specify a preferred unit for that dimension with [`Unitful.preferunits`](@ref), otherwise promotion will not work with that dimension. This is done automatically in the [`@refunit`](@ref) macro. Returns the `Dimensions` object to which `symb` is bound. Usage example from `src/pkgdefaults.jl`: `@dimension 𝐋 "𝐋" Length` """ macro dimension(symb, abbr, name, autodocs=false) s = Symbol(symb) x = Expr(:quote, name) uname = Symbol(name,"Units") funame = Symbol(name,"FreeUnits") name_links = __module__ == Unitful ? "[`Unitful.Quantity`](@ref), [`Unitful.Level`](@ref)" : "`Unitful.Quantity`, `Unitful.Level`" unit_links = __module__ == Unitful ? "[`Unitful.Units`](@ref)" : "`Unitful.Units`" funit_links = __module__ == Unitful ? "[`Unitful.FreeUnits`](@ref)" : "`Unitful.FreeUnits`" name_doc = """ $__module__.$name{T, U} A supertype for quantities and levels of dimension [`$__module__.$s`](@ref) with a value of type `T` and units `U`. See also: [`$__module__.$s`](@ref), $name_links. """ unit_doc = """ $__module__.$uname{U} A supertype for units of dimension [`$__module__.$s`](@ref). Equivalent to `Unitful.Units{U, $__module__.$s}`. See also: [`$__module__.$s`](@ref), $unit_links. """ funit_doc = """ $__module__.$funame{U} A supertype for $funit_links of dimension [`$__module__.$s`](@ref). Equivalent to `Unitful.FreeUnits{U, $__module__.$s}`. See also: [`$__module__.$s`](@ref). """ esc(quote $Unitful.abbr(::$Dimension{$x}) = $abbr Base.@__doc__ const global $s = $Dimensions{($Dimension{$x}(1),)}() const global ($name){T,U} = Union{ $Quantity{T,$s,U}, $Level{L,S,$Quantity{T,$s,U}} where {L,S}} const global ($uname){U} = $Units{U,$s} const global ($funame){U} = $FreeUnits{U,$s} if $autodocs @doc $name_doc $name @doc $unit_doc $uname @doc $funit_doc $funame end $s end) end """ @derived_dimension(name, dims, autodocs=false) Creates type aliases to allow dispatch on [`Unitful.Quantity`](@ref), [`Unitful.Level`](@ref), and [`Unitful.Units`](@ref) objects of a derived dimension, like area, which is just length squared. The type aliases are not exported. If `autodocs == true`, docstrings will be automatically generated for these aliases. !!! compat "Unitful 1.10" The `autodocs` argument requires Unitful 1.10 or later. `dims` is a [`Unitful.Dimensions`](@ref) object. Returns `nothing`. Usage examples: - `@derived_dimension Area 𝐋^2` gives `Area` and `AreaUnit` type aliases - `@derived_dimension Speed 𝐋/𝐓` gives `Speed` and `SpeedUnit` type aliases """ macro derived_dimension(name, dims, autodocs=false) uname = Symbol(name,"Units") funame = Symbol(name,"FreeUnits") name_links = __module__ == Unitful ? "[`Unitful.Quantity`](@ref), [`Unitful.Level`](@ref)" : "`Unitful.Quantity`, `Unitful.Level`" unit_links = __module__ == Unitful ? "[`Unitful.Units`](@ref)" : "`Unitful.Units`" funit_links = __module__ == Unitful ? "[`Unitful.FreeUnits`](@ref)" : "`Unitful.FreeUnits`" name_doc = """ $__module__.$name{T, U} A supertype for quantities and levels of dimension `$dims` with a value of type `T` and units `U`. See also: $name_links. """ unit_doc = """ $__module__.$uname{U} A supertype for units of dimension `$dims`. Equivalent to `Unitful.Units{U, $dims}`. See also: $unit_links. """ funit_doc = """ $__module__.$funame{U} A supertype for $funit_links of dimension `$dims`. Equivalent to `Unitful.FreeUnits{U, $dims}`. """ esc(quote const global ($name){T,U} = Union{ $Quantity{T,$dims,U}, $Level{L,S,$Quantity{T,$dims,U}} where {L,S}} const global ($uname){U} = $Units{U,$dims} const global ($funame){U} = $FreeUnits{U,$dims} if $autodocs @doc $name_doc $name @doc $unit_doc $uname @doc $funit_doc $funame end nothing end) end """ @refunit(symb, abbr, name, dimension, tf, autodocs=false) Define a reference unit, typically SI. Rather than define conversion factors between each and every unit of a given dimension, conversion factors are given between each unit and a reference unit, defined by this macro. This macro extends [`Unitful.abbr`](@ref) so that the reference unit can be displayed in an abbreviated format. If `tf == true`, this macro generates symbols for every power of ten of the unit, using the standard SI prefixes. A `dimension` must be given ([`Unitful.Dimensions`](@ref) object) that specifies the dimension of the reference unit. If `autodocs == true`, autogenerated docstrings for SI-prefixed units will be added. This option has no effect when `tf == false`. !!! compat "Unitful 1.10" Documenting the resulting unit by adding a docstring before the `@refunit` call requires Unitful 1.10 or later. The `autodocs` argument also requires Unitful 1.10 or later. In principle, users can use this macro, but it probably does not make much sense to do so. If you define a new (probably unphysical) dimension using [`@dimension`](@ref), then this macro will be necessary. With existing dimensions, you will almost certainly cause confusion if you use this macro. One potential use case would be to define a unit system without reference to SI. However, there's no explicit barrier to prevent attempting conversions between SI and this hypothetical unit system, which could yield unexpected results. Note that this macro will also choose the new unit (no power-of-ten prefix) as the default unit for promotion given this dimension. Returns the [`Unitful.FreeUnits`](@ref) object to which `symb` is bound. Usage example: `@refunit m "m" Meter 𝐋 true` This example, found in `src/pkgdefaults.jl`, generates `km`, `m`, `cm`, ... """ macro refunit(symb, abbr, name, dimension, tf, autodocs=false) expr = Expr(:block) n = Meta.quot(Symbol(name)) push!(expr.args, quote $Unitful.abbr(::$Unit{$n, $dimension}) = $abbr end) if tf push!(expr.args, quote Base.@__doc__ $Unitful.@prefixed_unit_symbols $symb $name $dimension (1.0, 1) $autodocs end) else push!(expr.args, quote Base.@__doc__ $Unitful.@unit_symbols $symb $name $dimension (1.0, 1) end) end push!(expr.args, quote $preferunits($symb) $symb end) esc(expr) end """ @unit(symb,abbr,name,equals,tf,autodocs=false) Define a unit. Rather than specifying a dimension like in [`@refunit`](@ref), `equals` should be a [`Unitful.Quantity`](@ref) equal to one of the unit being defined. If `tf == true`, symbols will be made for each power-of-ten prefix. If `autodocs == true`, autogenerated docstrings for SI-prefixed units will be added. This option has no effect when `tf == false`. !!! compat "Unitful 1.10" Documenting the resulting unit by adding a docstring before the `@unit` call requires Unitful 1.10 or later. The `autodocs` argument also requires Unitful 1.10 or later. Returns the [`Unitful.FreeUnits`](@ref) object to which `symb` is bound. Usage example: `@unit mi "mi" Mile (201168//125)*m false` This example will *not* generate `kmi` (kilomiles). """ macro unit(symb,abbr,name,equals,tf,autodocs=false) expr = Expr(:block) n = Meta.quot(Symbol(name)) d = :($dimension($equals)) basef = :($basefactor($basefactor($unit($equals))..., ($equals)/$unit($equals), $tensfactor($unit($equals)), 1)) push!(expr.args, quote $Unitful.abbr(::$Unit{$n, $d}) = $abbr end) if tf push!(expr.args, quote Base.@__doc__ $Unitful.@prefixed_unit_symbols $symb $name $d $basef $autodocs end) else push!(expr.args, quote Base.@__doc__ $Unitful.@unit_symbols $symb $name $d $basef end) end push!(expr.args, quote $symb end) esc(expr) end """ @affineunit(symb, abbr, offset) Macro for easily defining affine units. `offset` gives the zero of the relative scale in terms of an absolute scale; the scaling is the same as the absolute scale. Example: `@affineunit °C "°C" (27315//100)K` is used internally to define degrees Celsius. !!! compat "Unitful 1.10" Documenting the resulting unit by adding a docstring before the `@affineunit` call requires Unitful 1.10 or later. """ macro affineunit(symb, abbr, offset) s = Symbol(symb) return esc(quote Base.@__doc__ const global $s = $affineunit($offset) $Base.show(io::$IO, ::$genericunit($s)) = $print(io, $abbr) end) end function basefactors_expr(m::Module, n, basefactor) if m === Unitful :($(_basefactors(Unitful))[$n] = $basefactor) else # We add the base factor to dictionaries both in Unitful and the other # module so that the factor is available both interactively and with # precompilation. quote $(_basefactors(m))[$n] = $basefactor $(_basefactors(Unitful))[$n] = $basefactor end end end """ @prefixed_unit_symbols(symb,name,dimension,basefactor,autodocs=false) Not called directly by the user. Given a unit symbol and a unit's name, will define units for each possible SI power-of-ten prefix on that unit. If `autodocs == true`, it will automatically generate docstrings for these units. !!! compat "Unitful 1.10" Documenting the resulting unit by adding a docstring before the `@prefixed_unit_symbols` call requires Unitful 1.10 or later. The `autodocs` argument also requires Unitful 1.10 or later. Example: `@prefixed_unit_symbols m Meter 𝐋 (1.0,1) true` results in `nm`, `cm`, `m`, `km`, ... all getting defined in the calling namespace, with docstrings automatically defined for SI-prefixed units. """ macro prefixed_unit_symbols(symb,name,user_dimension,basefactor,autodocs=false) expr = Expr(:block) n = Meta.quot(Symbol(name)) for (k,v) in prefixdict s = Symbol(v,symb) u = :($Unit{$n, $user_dimension}($k,1//1)) if k == 0 ea = quote $(basefactors_expr(__module__, n, basefactor)) Base.@__doc__ const global $s = $FreeUnits{($u,), $dimension($u), $nothing}() end else docstring1 = """ $__module__.$s A prefixed unit, equal to 10^$k $symb. Dimension: """ docstring2 = "\n\nSee also: [`$__module__.$symb`](@ref)." ea = quote $(basefactors_expr(__module__, n, basefactor)) const global $s = $FreeUnits{($u,), $dimension($u), $nothing}() if $autodocs @doc $docstring1*string($user_dimension)*$docstring2 $s end end end push!(expr.args, ea) end esc(expr) end """ @unit_symbols(symb,name) Not called directly by the user. Given a unit symbol and a unit's name, will define units without SI power-of-ten prefixes. !!! compat "Unitful 1.10" Documenting the resulting unit by adding a docstring before the `@unit_symbols` call requires Unitful 1.10 or later. Example: `@unit_symbols ft Foot 𝐋` results in `ft` getting defined but not `kft`. """ macro unit_symbols(symb,name,user_dimension,basefactor) s = Symbol(symb) n = Meta.quot(Symbol(name)) u = :($Unit{$n, $user_dimension}(0,1//1)) esc(quote $(basefactors_expr(__module__, n, basefactor)) Base.@__doc__ const global $s = $FreeUnits{($u,), $dimension($u), $nothing}() end) end """ preferunits(u0::Units, u::Units...) This function specifies the default fallback units for promotion. Units provided to this function must have a pure dimension of power 1, like `𝐋` or `𝐓` but not `𝐋/𝐓` or `𝐋^2`. The function will complain if this is not the case. Additionally, the function will complain if you provide two units with the same dimension, as a courtesy to the user. Finally, you cannot use affine units such as `°C` with this function. Once [`Unitful.upreferred`](@ref) has been called or quantities have been promoted, this function will appear to have no effect. Usage example: `preferunits(u"m,s,A,K,cd,kg,mol"...)` """ function preferunits(u0::Units, u::Units...) units = (u0, u...) any(x->x isa AffineUnits, units) && error("cannot use `Unitful.preferunits` with affine units; try `Unitful.ContextUnits`.") dims = map(dimension, units) if length(union(dims)) != length(dims) error("preferunits received more than one unit of a given ", "dimension.") end for i in eachindex(units) unit, dim = units[i], dims[i] if length(typeof(dim).parameters[1]) > 1 error("preferunits can only be used with a unit that has a pure ", "dimension, like 𝐋 or 𝐓 but not 𝐋/𝐓.") end if length(typeof(dim).parameters[1]) == 1 && typeof(dim).parameters[1][1].power != 1 error("preferunits cannot handle powers of pure dimensions except 1. ", "For instance, it should not be used with units of dimension 𝐋^2.") end y = typeof(dim).parameters[1][1] promotion[name(y)] = unit end # Let's run a quick check for anything where, for instance, current was defined is C/ms and time # was defined in ns. We'll give a warning if this is the case so the user knows they may get # unexpected (though still correct) results. There may be cases where people would want to do this, # so we won't stop them, just let them know they're doing it. ulist = (typeof(i[2]).parameters[1] for i in promotion) check = Dict{Dimensions, Unit}() for a in ulist ulistA = (i^(1/i.power) for i in a) for i in ulistA k = dimension(i) if haskey(check, k) if i!=check[k] @warn "Preferred units contain complex units based on units of the same dimension but different scales: "*string(i)*" and "*string(check[k])* "\nThis may be intentional, but otherwise could lead to redundant units, such as ms/s or kg/g" end else check[k] = i end end end nothing end """ upreferred(x::Number) upreferred(x::Quantity) Unit-convert `x` to units which are preferred for the dimensions of `x`. If you are using the factory defaults, this function will unit-convert to a product of powers of base SI units. If quantity `x` has [`Unitful.ContextUnits`](@ref)`(y,z)`, the resulting quantity will have units `ContextUnits(z,z)`. """ @inline upreferred(x::Number) = x @inline upreferred(x::AbstractQuantity) = uconvert(upreferred(unit(x)), x) @inline upreferred(::Missing) = missing """ upreferred(x::Units) Return units which are preferred for the dimensions of `x`, which may or may not be equal to `x`, as specified by the [`preferunits`](@ref) function. If you are using the factory defaults, this function will return a product of powers of base SI units. """ @inline upreferred(x::FreeUnits) = upreferred(dimension(x)) @inline upreferred(::ContextUnits{N,D,P}) where {N,D,P} = ContextUnits(P(),P()) @inline upreferred(x::FixedUnits) = x """ @logscale(symb,abbr,name,base,prefactor,irp) Define a logarithmic scale. Unlike with units, there is no special treatment for power-of-ten prefixes (decibels and bels are defined separately). However, arbitrary bases are possible, and computationally appropriate `log` and `exp` functions are used in calculations when available (e.g. `log2`, `log10` for base 2 and base 10, respectively). This macro defines a `MixedUnits` object identified by symbol `symb`. This can be used to construct `Gain` types, e.g. `3*dB`. Additionally, two other `MixedUnits` objects are defined, with symbols `Symbol(symb,"_rp")` and `Symbol(symb,"_p")`, e.g. `dB_rp` and `dB_p`, respectively. These objects serve nearly the same purpose, but have extra information in their type that indicates whether they should be considered as root-power ratios or power ratios upon conversion to pure numbers. This macro also defines another macro available as `@symb`. For example, `@dB` in the case of decibels. This can be used to construct `Level` objects at parse time. Usage is like `@dB 3V/1V`. `prefactor` is the prefactor out in front of the logarithm for this log scale. In all cases it is defined with respect to taking ratios of power quantities. Just divide by two if you want to refer to root-power / field quantities instead. `irp` (short for "is root power?") specifies whether the logarithmic scale is defined with respect to ratios of power or root-power quantities. In short: use `false` if your scale is decibel-like, or `true` if your scale is neper-like. Examples: ```jldoctest julia> using Unitful: V, W julia> @logscale dΠ "dΠ" Decipies π 10 false dΠ julia> @dΠ π*V/1V 20.0 dΠ (1 V) julia> dΠ(π*V, 1V) 20.0 dΠ (1 V) julia> @dΠ π^2*V/1V 40.0 dΠ (1 V) julia> @dΠ π*W/1W 10.0 dΠ (1 W) ``` """ macro logscale(symb,abbr,name,base,prefactor,irp) quote $Unitful.abbr(::LogInfo{$(QuoteNode(name))}) = $abbr const global $(esc(name)) = LogInfo{$(QuoteNode(name)), $base, $prefactor} $Unitful.isrootpower(::Type{$(esc(name))}) = $irp const global $(esc(symb)) = MixedUnits{Gain{$(esc(name)), :?}}() const global $(esc(Symbol(symb,"_rp"))) = MixedUnits{Gain{$(esc(name)), :rp}}() const global $(esc(Symbol(symb,"_p"))) = MixedUnits{Gain{$(esc(name)), :p}}() macro $(esc(symb))(::Union{Real,Symbol}) throw(ArgumentError(join(["usage: `@", $(String(symb)), " (a)/(b)`"]))) end macro $(esc(symb))(expr::Expr) expr.args[1] != :/ && throw(ArgumentError(join(["usage: `@", $(String(symb)), " (a)/(b)`"]))) length(expr.args) != 3 && throw(ArgumentError(join(["usage: `@", $(String(symb)), " (a)/(b)`"]))) return Expr(:call, $(esc(symb)), expr.args[2], expr.args[3]) end macro $(esc(symb))(expr::Expr, tf::Bool) expr.args[1] != :/ && throw(ArgumentError(join(["usage: `@", $(String(symb)), " (a)/(b)`"]))) length(expr.args) != 3 && throw(ArgumentError(join(["usage: `@", $(String(symb)), " (a)/(b)`"]))) return Expr(:call, $(esc(symb)), expr.args[2], expr.args[3], tf) end function (::$(esc(:typeof))($(esc(symb))))(num::Number, den::Number) dimension(num) != dimension(den) && throw(DimensionError(num,den)) dimension(num) == NoDims && throw(ArgumentError(string("to use with dimensionless numbers, pass a ", "final `Bool` argument: true if the ratio is a root-power ratio, ", "false otherwise."))) return Level{$(esc(name)), den}(num) end function (::$(esc(:typeof))($(esc(symb))))(num::Number, den::Number, irp::Bool) dimension(num) != dimension(den) && throw(DimensionError(num,den)) dimension(num) != NoDims && throw(ArgumentError(string("when passing a final Bool argument, ", "this can only be used with dimensionless numbers."))) T = ifelse(irp, RootPowerRatio, PowerRatio) return Level{$(esc(name)), T(den)}(num) end function (::$(esc(:typeof))($(esc(symb))))(num::Number, den::Units) $(esc(symb))(num, 1*den) end function (::$(esc(:typeof))($(esc(symb))))(num::Number, den::Units, irp::Bool) $(esc(symb))(num, 1*den, irp) end $(esc(symb)) end end """ @logunit(symb, abbr, logscale, reflevel) Defines a logarithmic unit. For examples see `src/pkgdefaults.jl`. """ macro logunit(symb, abbr, logscale, reflevel) quote $Unitful.abbr(::Level{$(esc(logscale)), $(esc(reflevel))}) = $abbr const global $(esc(symb)) = MixedUnits{Level{$(esc(logscale)), $(esc(reflevel))}}() end end """ affineunit(x::Quantity) Returns a [`Unitful.Units`](@ref) object that can be used to construct affine quantities. Primarily, this is for relative temperatures (as opposed to absolute temperatures, which transform as usual under unit conversion). To use this function, pass the scale offset, e.g. `affineunit(273.15K)` yields a Celsius unit. """ affineunit(x::Quantity{T,D,FreeUnits{N,D,nothing}}) where {N,D,T} = FreeUnits{N,D,Affine{-ustrip(x)}}() """ @u_str(unit) String macro to easily recall units, dimensions, or quantities defined in unit modules that have been registered with [`Unitful.register`](@ref). If the same symbol is used for a [`Unitful.Units`](@ref) object defined in different modules, then the symbol found in the most recently registered module will be used. Note that what goes inside must be parsable as a valid Julia expression. In other words, `u"N m"` will fail if you intended to write `u"N*m"`. Examples: ```jldoctest julia> 1.0u"m/s" 1.0 m s^-1 julia> 1.0u"N*m" 1.0 m N julia> u"m,kg,s" (m, kg, s) julia> typeof(1.0u"m/s") Quantity{Float64, 𝐋 𝐓^-1, Unitful.FreeUnits{(m, s^-1), 𝐋 𝐓^-1, nothing}} julia> u"ħ" 1.0545718176461565e-34 J s ``` """ macro u_str(unit) ex = Meta.parse(unit) unitmods = [Unitful] for m in Unitful.unitmodules # Find registered unit extension modules which are also loaded by # __module__ (required so that precompilation will work). if isdefined(__module__, nameof(m)) && getfield(__module__, nameof(m)) === m push!(unitmods, m) end end esc(lookup_units(unitmods, ex)) end """ uparse(string [; unit_context=ctx]) Parse a string as a unit or quantity. The format for `string` must be a valid Julia expression, and any identifiers will be looked up in the context `ctx`, which may be a `Module` or a vector of `Module`s. By default `unit_context=Unitful`. Examples: ```jldoctest julia> uparse("m/s") m s^-1 julia> uparse("1.0*dB") 1.0 dB ``` """ function uparse(str; unit_context=Unitful) ex = Meta.parse(str) eval(lookup_units(unit_context, ex)) end const allowed_funcs = [:*, :/, :^, :sqrt, :√, :+, :-, ://] function lookup_units(unitmods, ex::Expr) if ex.head == :call ex.args[1] in allowed_funcs || throw(ArgumentError( """$(ex.args[1]) is not a valid function call when parsing a unit. Only the following functions are allowed: $allowed_funcs""")) for i=2:length(ex.args) if typeof(ex.args[i])==Symbol || typeof(ex.args[i])==Expr ex.args[i]=lookup_units(unitmods, ex.args[i]) end end return ex elseif ex.head == :tuple for i=1:length(ex.args) if typeof(ex.args[i])==Symbol ex.args[i]=lookup_units(unitmods, ex.args[i]) else throw(ArgumentError("Only use symbols inside the tuple.")) end end return ex else throw(ArgumentError("Expr head $(ex.head) must equal :call or :tuple")) end end function lookup_units(unitmods, sym::Symbol) has_unit = m->(isdefined(m,sym) && ustrcheck_bool(getfield(m, sym))) inds = findall(has_unit, unitmods) if isempty(inds) # Check whether unit exists in the global list to give an improved # error message. hintidx = findfirst(has_unit, unitmodules) if hintidx !== nothing hintmod = unitmodules[hintidx] throw(ArgumentError( """Symbol `$sym` was found in the globally registered unit module $hintmod but was not in the provided list of unit modules $(join(unitmods, ", ")). (Consider `using $hintmod` in your module if you are using `@u_str`?)""")) else throw(ArgumentError("Symbol $sym could not be found in unit modules $unitmods")) end end m = unitmods[inds[end]] u = getfield(m, sym) any(u != u1 for u1 in getfield.(unitmods[inds[1:(end-1)]], sym)) && @warn """Symbol $sym was found in multiple registered unit modules. We will use the one from $m.""" return u end lookup_units(unitmod::Module, ex::Symbol) = lookup_units([unitmod], ex) lookup_units(unitmods, literal::Number) = literal ustrcheck_bool(::Number) = true ustrcheck_bool(::MixedUnits) = true ustrcheck_bool(::Units) = true ustrcheck_bool(::Dimensions) = true ustrcheck_bool(::Quantity) = true ustrcheck_bool(::Any) = false ================================================ FILE: src/utils.jl ================================================ @inline isunitless(::Units) = false @inline isunitless(::Units{()}) = true @inline numtype(::AbstractQuantity{T}) where {T} = T @inline numtype(::Type{Q}) where {T, Q<:AbstractQuantity{T}} = T @inline dimtype(u::Unit{U,D}) where {U,D} = D """ ustrip(u::Units, x::Quantity) ustrip(T::Type, u::Units, x::Quantity) Convert `x` to units `u` using [`uconvert`](@ref) and return the number out the front of the resulting quantity. If `T` is supplied, also `convert` the resulting number into type `T`. This function is mainly intended for compatibility with packages that don't know how to handle quantities. ```jldoctest julia> ustrip(u"m", 1u"mm") == 1//1000 true julia> ustrip(Float64, u"m", 2u"mm") == 0.002 true ``` `ustrip` supports `InverseFunctions.inverse`: ```jldoctest julia> using InverseFunctions: inverse julia> inverse(Base.Fix1(ustrip, u"m"))(5) 5 m ``` """ @inline ustrip(u::Units, x) = ustrip(uconvert(u, x)) @inline ustrip(T::Type, u::Units, x) = convert(T, ustrip(u, x)) """ ustrip(x::Number) ustrip(x::Quantity) Returns the number out in front of any units. The value of `x` may differ from the number out front of the units in the case of dimensionless quantities, e.g. `1m/mm != 1`. See [`uconvert`](@ref) and the example below. Because the units are removed, information may be lost and this should be used with some care — see `ustrip(u,x)` for a safer alternative. ```jldoctest julia> ustrip(2u"μm/m") == 2 true julia> uconvert(NoUnits, 2u"μm/m") == 2//1000000 true ``` """ @inline ustrip(x::Number) = x / unit(x) @inline ustrip(x::Quantity) = ustrip(x.val) @inline ustrip(x::Missing) = missing """ ustrip(x::Array{Q}) where {Q <: Quantity} Strip units from an `Array` by reinterpreting to type `T`. The resulting `Array` is a not a copy, but rather a unit-stripped view into array `x`. Because the units are removed, information may be lost and this should be used with some care. This function is provided primarily for compatibility purposes; you could pass the result to PyPlot, for example. ```jldoctest julia> a = [1u"m", 2u"m"] 2-element Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}: 1 m 2 m julia> b = ustrip(a) 2-element reinterpret(Int64, ::Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}): 1 2 julia> a[1] = 3u"m"; b 2-element reinterpret(Int64, ::Vector{Quantity{Int64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}): 3 2 ``` """ @inline ustrip(A::Array{Q}) where {Q <: Quantity} = reinterpret(numtype(Q), A) @deprecate(ustrip(A::AbstractArray{T}) where {T<:Number}, ustrip.(A)) """ ustrip(A::Diagonal) ustrip(A::Bidiagonal) ustrip(A::Tridiagonal) ustrip(A::SymTridiagonal) Strip units from various kinds of matrices by calling `ustrip` on the underlying vectors. """ ustrip(A::Diagonal) = Diagonal(ustrip(A.diag)) ustrip(A::Bidiagonal) = Bidiagonal(ustrip(A.dv), ustrip(A.ev), ifelse(istriu(A), :U, :L)) ustrip(A::Tridiagonal) = Tridiagonal(ustrip(A.dl), ustrip(A.d), ustrip(A.du)) ustrip(A::SymTridiagonal) = SymTridiagonal(ustrip(A.dv), ustrip(A.ev)) """ unit(x::Quantity{T,D,U}) where {T,D,U} unit(x::Type{Quantity{T,D,U}}) where {T,D,U} Returns the units associated with a `Quantity` or `Quantity` type. Examples: ```jldoctest julia> unit(1.0u"m") == u"m" true julia> unit(typeof(1.0u"m")) == u"m" true ``` See also: [`logunit`](@ref). """ @inline unit(x::AbstractQuantity{T,D,U}) where {T,D,U} = U() @inline unit(::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U} = U() """ unit(x::Number) Returns the [`NoUnits`](@ref) object to indicate that ordinary numbers have no units. The unit is displayed as an empty string. Examples: ```jldoctest julia> typeof(unit(1.0)) Unitful.FreeUnits{(), NoDims, nothing} julia> typeof(unit(Float64)) Unitful.FreeUnits{(), NoDims, nothing} julia> unit(1.0) == NoUnits true ``` """ @inline unit(x::Number) = NoUnits @inline unit(x::Type{T}) where {T <: Number} = NoUnits @inline unit(x::Type{Union{Missing, T}}) where T = unit(T) @inline unit(x::Type{Missing}) = missing @inline unit(x::Missing) = missing # Prevent infinite recursion, in case unit(Any) is called. @inline unit(x::Type{Any}) = throw(ArgumentError( "unit(Any) was called, which has no meaningful result. This may be due to calling unit(eltype(…)), since eltype has a generic fallback method that returns Any." )) """ absoluteunit(::Units) absoluteunit(::Quantity) Given a unit or quantity, which may or may not be affine (e.g. `°C`), return the corresponding unit on the absolute temperature scale (e.g. `K`). Passing a [`Unitful.ContextUnits`](@ref) object will return another `ContextUnits` object with the same promotion unit, which may be an affine unit, so take care. """ function absoluteunit end absoluteunit(x::AbstractQuantity{T,D,U}) where {T,D,U} = absoluteunit(U()) absoluteunit(::FreeUnits{N,D,A}) where {N,D,A} = FreeUnits{N,D}() absoluteunit(::ContextUnits{N,D,P,A}) where {N,D,P,A} = ContextUnits{N,D,P}() absoluteunit(::FixedUnits{N,D,A}) where {N,D,A} = FixedUnits{N,D}() """ dimension(x::Number) dimension(x::Type{T}) where {T<:Number} Returns a `Unitful.Dimensions{()}` object to indicate that ordinary numbers are dimensionless. This is a singleton, which we export as `NoDims`. The dimension is displayed as an empty string. Examples: ```jldoctest julia> typeof(dimension(1.0)) Unitful.Dimensions{()} julia> typeof(dimension(Float64)) Unitful.Dimensions{()} julia> dimension(1.0) == NoDims true ``` """ @inline dimension(x::Number) = NoDims @inline dimension(x::Type{T}) where {T <: Number} = NoDims @inline dimension(x::Missing) = missing @inline dimension(x::Type{Missing}) = missing @inline dimension(x::IsRootPowerRatio{S,T}) where {S,T} = dimension(T) @inline dimension(x::Level) = dimension(reflevel(x)) @inline dimension(x::Type{T}) where {T<:Level} = dimension(reflevel(T)) @inline dimension(x::Gain) = NoDims @inline dimension(x::Type{<:Gain}) = NoDims dimension(a::MixedUnits{L}) where {L} = dimension(L) * dimension(a.units) """ dimension(u::Units{U,D}) where {U,D} Returns a [`Unitful.Dimensions`](@ref) object corresponding to the dimensions of the units, `D`. For a dimensionless combination of units, a `Unitful.Dimensions{()}` object is returned (`NoDims`). Examples: ```jldoctest julia> dimension(u"m") 𝐋 julia> typeof(dimension(u"m")) Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)} julia> dimension(u"m/km") NoDims ``` """ @inline dimension(u::Units{U,D}) where {U,D} = D """ dimension(x::Quantity{T,D}) where {T,D} dimension(::Type{Quantity{T,D,U}}) where {T,D,U} Returns a [`Unitful.Dimensions`](@ref) object `D` corresponding to the dimensions of quantity `x`. For a dimensionless [`Unitful.Quantity`](@ref), a `Unitful.Dimensions{()}` object is returned (`NoDims`). Examples: ```jldoctest julia> dimension(1.0u"m") 𝐋 julia> typeof(dimension(1.0u"m/μm")) Unitful.Dimensions{()} ``` """ @inline dimension(x::AbstractQuantity{T,D}) where {T,D} = D @inline dimension(::Type{<:AbstractQuantity{T,D,U}}) where {T,D,U} = D @deprecate(dimension(x::AbstractArray{T}) where {T<:Number}, dimension.(x)) @deprecate(dimension(x::AbstractArray{T}) where {T<:Units}, dimension.(x)) """ struct DimensionError <: Exception Physical dimensions are inconsistent for the attempted operation. """ struct DimensionError <: Exception x y end Base.showerror(io::IO, e::DimensionError) = print(io, "DimensionError: $(e.x) and $(e.y) are not dimensionally compatible."); """ struct AffineError <: Exception An invalid operation was attempted with affine units / quantities. """ struct AffineError <: Exception x end Base.showerror(io::IO, e::AffineError) = print(io, "AffineError: $(e.x)") fp_overflow_underflow(input, result) = isfinite(input) && !isfinite(result) || !iszero(input) && iszero(result) ================================================ FILE: test/dates.jl ================================================ @testset "Dates stdlib" begin @testset "> dimension, numtype, unit" begin for (T,u) = ((Nanosecond, u"ns"), (Microsecond, u"μs"), (Millisecond, u"ms"), (Second, u"s"), (Minute, u"minute"), (Hour, u"hr"), (Day, u"d"), (Week, u"wk")) @test dimension(T) === dimension(T(1)) === 𝐓 @test Unitful.numtype(T) === Unitful.numtype(T(1)) === typeof(Dates.value(T(1))) @test unit(T) === unit(T(1)) === u end for T = (Month, Year) @test_throws MethodError dimension(T) @test_throws MethodError dimension(T(1)) @test_throws MethodError Unitful.numtype(T) @test_throws MethodError Unitful.numtype(T(1)) @test_throws MethodError unit(T) @test_throws MethodError unit(T(1)) end for p = (CompoundPeriod, CompoundPeriod(), CompoundPeriod(Day(1)), CompoundPeriod(Day(1), Hour(-1))) @test dimension(p) === 𝐓 @test_throws MethodError Unitful.numtype(p) @test_throws MethodError unit(p) end end @testset "> Arithmetic" begin @testset ">> Addition/subtraction" begin @test Second(3) + 5u"ms" === Int64(3)u"s" + 5u"ms" @test Second(3) - 5u"ms" === Int64(3)u"s" - 5u"ms" @test 1.0u"wk" + Day(3) === 1.0u"wk" + Int64(3)u"d" @test 1.0u"wk" - Day(3) === 1.0u"wk" - Int64(3)u"d" @test_throws DimensionError 1u"m" + Second(1) @test_throws DimensionError 1u"m" - Second(1) @test Dates.Date(2000, 2, 3) + 24.0u"hr" == Dates.Date(2000, 2, 4) @test Dates.Date(2000, 2, 3) + 48.0u"hr" == Dates.Date(2000, 2, 5) @test_throws Exception Dates.Date(2000, 2, 3) + 1.0u"hr" @test Dates.DateTime(2000, 2, 3, 4, 5) + 1.0u"ns" == Dates.DateTime(2000, 2, 3, 4, 5) @test Dates.DateTime(2000, 2, 3, 4, 5) + 23.0u"hr" == Dates.DateTime(2000, 2, 4, 3, 5) @test Dates.DateTime(2000, 2, 3, 4, 5) + 100.0u"hr" == Dates.DateTime(2000, 2, 7, 8, 5) @test Dates.Time(4, 5) + 1.0u"ps" == Dates.Time(4, 5) @test Dates.Time(4, 5) + 1.0u"ms" == Dates.Time(4, 5, 0, 1) @test Dates.Time(4, 5) + 1.5u"hr" == Dates.Time(5, 35) @test Dates.Date(2000, 2, 5) - 48.0u"hr" == Dates.Date(2000, 2, 3) @test 24.0u"hr" + Dates.Date(2000, 2, 5) == Dates.Date(2000, 2, 6) end @testset ">> Multiplication" begin # Multiplication with quantity @test Int64(3)u"m" * Day(1) === Int64(3)u"m*d" @test 1.0f0u"m" * Microsecond(-2) === -2.0f0u"m*μs" @test Hour(4) * Int32(2)u"m" === Int64(8)u"hr*m" @test Second(5) * (3//2)u"Hz" === Rational{Int64}(15,2)u"s*Hz" @test Second(5) * 2u"1/s" === Int64(10) @test 2.5u"1/s^2" * Second(2) === 5.0u"1/s" @test_throws AffineError Second(1) * 1u"°C" @test_throws AffineError 1u"°C" * Second(1) # Multiplication with unit @test Week(5) * u"Hz" === Int64(5)u"wk*Hz" @test u"mm" * Millisecond(20) === Int64(20)u"mm*ms" @test u"ms^-1" * Millisecond(20) === Int64(20) @test_throws AffineError Second(1) * u"°C" @test_throws AffineError u"°C" * Second(1) # Multiple factors @test 3.0u"s" * Second(3) * (3//1)u"s" === 27.0u"s^3" @test 3.0u"s" * Second(3) * Minute(3) === 27.0u"s^2*minute" @test u"s" * Second(3) * u"minute" === Int64(3)u"s^2*minute" @test Second(3) * u"m" * u"m" === Int64(3)u"s*m^2" end @testset ">> Division" begin @testset ">>> /, //" begin @test Nanosecond(10) / 2u"m" === 5.0u"ns/m" @test Nanosecond(10) / 2.0f0u"m" === 5.0f0u"ns/m" @test 5u"m" / Hour(2) === 2.5u"m/hr" @test 5.0f0u"m" / Hour(2) === 2.5f0u"m/hr" @test Nanosecond(10) // 2u"m" === Rational{Int64}(5,1)u"ns/m" @test 5u"m" // Hour(2) === Rational{Int64}(5,2)u"m/hr" end @testset ">>> div, fld, cld" begin @test div(Second(1), 2u"ms") == div(1u"s", Millisecond(2)) == div(1000, 2) @test div(Second(11), 2u"s") == div(11u"s", Second(2)) == div(11, 2) @test div(Second(-5), 2u"s") == div(-5u"s", Second(2)) == div(-5, 2) @test_throws DimensionError div(Second(1), 1u"m") @test_throws DimensionError div(1u"m", Second(1)) @test div(4u"minute", CompoundPeriod(Minute(1), Second(30))) == div(8, 3) @test div(CompoundPeriod(Minute(4)), 90u"s") == div(8, 3) @test_throws DimensionError div(4u"m", CompoundPeriod(Minute(1), Second(30))) @test_throws DimensionError div(CompoundPeriod(Minute(4)), 90u"m") for r = (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp, RoundToZero, RoundUp, RoundDown) @test div(Second(11), 2u"s", r) == div(11u"s", Second(2), r) == div(11, 2, r) @test div(Second(-5), 2u"s", r) == div(-5u"s", Second(2), r) == div(-5, 2, r) @test_throws DimensionError div(Second(1), 1u"m", r) @test_throws DimensionError div(1u"m", Second(1), r) if Sys.WORD_SIZE == 32 && r in (RoundNearestTiesAway, RoundNearestTiesUp) @test_broken div(4u"minute", CompoundPeriod(Minute(1), Second(30)), r) == div(8, 3, r) else @test div(4u"minute", CompoundPeriod(Minute(1), Second(30)), r) == div(8, 3, r) end @test div(CompoundPeriod(Minute(4)), 90u"s", r) == div(8, 3, r) @test_throws DimensionError div(4u"m", CompoundPeriod(Minute(1), Second(30)), r) @test_throws DimensionError div(CompoundPeriod(Minute(4)), 90u"m", r) end @test fld(Second(1), 2u"ms") == fld(1u"s", Millisecond(2)) == fld(1000, 2) @test fld(Second(11), 2u"s") == fld(11u"s", Second(2)) == fld(11, 2) @test fld(Second(-5), 2u"s") == fld(-5u"s", Second(2)) == fld(-5, 2) @test_throws DimensionError fld(Second(1), 1u"m") @test_throws DimensionError fld(1u"m", Second(1)) @test fld(4u"minute", CompoundPeriod(Minute(1), Second(30))) == fld(8, 3) @test fld(CompoundPeriod(Minute(4)), 90u"s") == fld(8, 3) @test_throws DimensionError fld(4u"m", CompoundPeriod(Minute(1), Second(30))) @test_throws DimensionError fld(CompoundPeriod(Minute(4)), 90u"m") @test cld(Second(1), 2u"ms") == cld(1u"s", Millisecond(2)) == cld(1000, 2) @test cld(Second(11), 2u"s") == cld(11u"s", Second(2)) == cld(11, 2) @test cld(Second(-5), 2u"s") == cld(-5u"s", Second(2)) == cld(-5, 2) @test_throws DimensionError cld(Second(1), 1u"m") @test_throws DimensionError cld(1u"m", Second(1)) @test cld(4u"minute", CompoundPeriod(Minute(1), Second(30))) == cld(8, 3) @test cld(CompoundPeriod(Minute(4)), 90u"s") == cld(8, 3) @test_throws DimensionError cld(4u"m", CompoundPeriod(Minute(1), Second(30))) @test_throws DimensionError cld(CompoundPeriod(Minute(4)), 90u"m") end @testset ">>> mod, rem" begin @test mod(Second(11), 3_000u"ms") == mod(11u"s", Millisecond(3_000)) == mod(11, 3)u"s" @test mod(Second(11), -3u"s") == mod(11u"s", Second(-3)) == mod(11, -3)u"s" @test_throws DimensionError mod(Second(1), 1u"m") @test_throws DimensionError mod(1u"m", Second(1)) @test mod(CompoundPeriod(Minute(4)), 90u"s") == mod(240, 90)u"s" @test_throws MethodError mod(4u"minute", CompoundPeriod(Minute(1), Second(30))) @test_throws MethodError mod(4u"m", CompoundPeriod(Minute(1), Second(30))) @test_throws DimensionError mod(CompoundPeriod(Minute(4)), 90u"m") @test rem(Second(11), 3_000u"ms") == rem(11u"s", Millisecond(3_000)) == rem(11, 3)u"s" @test rem(Second(11), -3u"s") == rem(11u"s", Second(-3)) == rem(11, -3)u"s" @test_throws DimensionError rem(Second(1), 1u"m") @test_throws DimensionError rem(1u"m", Second(1)) @test rem(CompoundPeriod(Minute(4)), 90u"s") == rem(240, 90)u"s" @test_throws MethodError rem(4u"minute", CompoundPeriod(Minute(1), Second(30))) @test_throws MethodError rem(4u"m", CompoundPeriod(Minute(1), Second(30))) @test_throws DimensionError rem(CompoundPeriod(Minute(4)), 90u"m") for r = (RoundToZero, RoundUp, RoundDown) @test rem(Second(11), 2u"s", r) == rem(11u"s", Second(2), r) == rem(11, 2, r)u"s" @test rem(Second(-5), 2u"s", r) == rem(-5u"s", Second(2), r) == rem(-5, 2, r)u"s" @test_throws DimensionError rem(Second(1), 1u"m", r) @test_throws DimensionError rem(1u"m", Second(1), r) end end end @testset ">> atan" begin @test atan(Minute(1), 30u"s") == atan(2,1) @test atan(1u"ms", Millisecond(5)) == atan(1,5) @test_throws DimensionError atan(Second(1), 1u"m") @test_throws DimensionError atan(1u"m", Second(1)) @test atan(CompoundPeriod(Minute(1), Second(30)), 10u"s") == atan(9,1) @test atan(1u"yr", CompoundPeriod(Day(365), Hour(6))) == atan(1,1) @test_throws DimensionError atan(1u"m", CompoundPeriod(Day(365), Hour(6))) @test_throws DimensionError atan(CompoundPeriod(Day(365), Hour(6)), 1u"m") @test_throws MethodError atan(1u"s", CompoundPeriod(Year(1))) @test_throws MethodError atan(CompoundPeriod(Month(6)), 1u"s") end end @testset "> Conversion" begin @testset ">> uconvert" begin @test uconvert(u"s", Second(3)) === u"s"(Second(3)) === Int64(3)u"s" @test uconvert(u"hr", Minute(90)) === u"hr"(Minute(90)) === Rational{Int64}(3,2)u"hr" @test uconvert(u"ns", Millisecond(-2)) === u"ns"(Millisecond(-2)) === Int64(-2_000_000)u"ns" @test uconvert(u"wk", Hour(1)) === u"wk"(Hour(1)) === Rational{Int64}(1,168)u"wk" @test_throws DimensionError uconvert(u"m", Second(1)) @test_throws DimensionError u"m"(Second(1)) @static if Sys.WORD_SIZE == 32 @test uconvert(u"yr", CompoundPeriod()) === u"yr"(CompoundPeriod()) === 0.0u"yr" else @test uconvert(u"yr", CompoundPeriod()) === u"yr"(CompoundPeriod()) === Rational{Int64}(0,1)u"yr" end @test uconvert(u"μs", CompoundPeriod()) === u"μs"(CompoundPeriod()) === Rational{Int64}(0,1)u"μs" @test uconvert(u"ns", CompoundPeriod()) === u"ns"(CompoundPeriod()) === Int64(0)u"ns" @test uconvert(u"ps", CompoundPeriod()) === u"ps"(CompoundPeriod()) === Int64(0)u"ps" @static if Sys.WORD_SIZE == 32 @test uconvert(u"yr", CompoundPeriod(Day(365),Hour(6))) === 1.0u"yr" @test u"yr"(CompoundPeriod(Day(365),Hour(6))) === 1.0u"yr" else @test uconvert(u"yr", CompoundPeriod(Day(365),Hour(6))) === Rational{Int64}(1,1)u"yr" @test u"yr"(CompoundPeriod(Day(365),Hour(6))) === Rational{Int64}(1,1)u"yr" end @test uconvert(u"μs", CompoundPeriod(Day(365),Hour(6))) === Rational{Int64}(31_557_600_000_000,1)u"μs" @test u"μs"(CompoundPeriod(Day(365),Hour(6))) === Rational{Int64}(31_557_600_000_000,1)u"μs" @test uconvert(u"ns", CompoundPeriod(Day(365),Hour(6))) === Int64(31_557_600_000_000_000)u"ns" @test u"ns"(CompoundPeriod(Day(365),Hour(6))) === Int64(31_557_600_000_000_000)u"ns" @test uconvert(u"ps", CompoundPeriod(Week(1),Hour(-1))) === Int64(601_200_000_000_000_000)u"ps" @test u"ps"(CompoundPeriod(Week(1),Hour(-1))) === Int64(601_200_000_000_000_000)u"ps" @test_throws DimensionError uconvert(u"m", CompoundPeriod(Day(365),Hour(6))) @test_throws DimensionError u"m"(CompoundPeriod(Day(365),Hour(6))) @test_throws MethodError uconvert(u"yr", CompoundPeriod(Year(1),Day(1))) @test_throws MethodError u"yr"(CompoundPeriod(Year(1),Day(1))) @test_throws MethodError uconvert(u"s", CompoundPeriod(Month(1),Day(1))) @test_throws MethodError u"s"(CompoundPeriod(Month(1),Day(1))) end @testset ">> ustrip" begin for (T,u) = ((Nanosecond, u"ns"), (Microsecond, u"μs"), (Millisecond, u"ms"), (Second, u"s"), (Minute, u"minute"), (Hour, u"hr"), (Day, u"d"), (Week, u"wk")) @test ustrip(T(5)) === ustrip(u, T(5)) === Int64(5) end @test ustrip(u"ms", Second(1)) === Int64(1000) @test ustrip(u"wk", Day(1)) === Rational{Int64}(1,7) @test_throws DimensionError ustrip(u"m", Nanosecond(1)) @test_throws MethodError ustrip(Month(1)) @test_throws MethodError ustrip(Year(1)) @test_throws MethodError ustrip(u"s", Month(1)) @test_throws MethodError ustrip(u"yr", Year(1)) @static if Sys.WORD_SIZE == 32 @test ustrip(u"yr", CompoundPeriod()) === 0.0 else @test ustrip(u"yr", CompoundPeriod()) === Rational{Int64}(0,1) end @test ustrip(u"μs", CompoundPeriod()) === Rational{Int64}(0,1) @test ustrip(u"ns", CompoundPeriod()) === Int64(0) @test ustrip(u"ps", CompoundPeriod()) === Int64(0) @static if Sys.WORD_SIZE == 32 @test ustrip(u"yr", CompoundPeriod(Day(365),Hour(6))) === 1.0 else @test ustrip(u"yr", CompoundPeriod(Day(365),Hour(6))) === Rational{Int64}(1,1) end @test ustrip(u"μs", CompoundPeriod(Day(365),Hour(6))) === Rational{Int64}(31_557_600_000_000,1) @test ustrip(u"ns", CompoundPeriod(Day(365),Hour(6))) === Int64(31_557_600_000_000_000) @test ustrip(u"ps", CompoundPeriod(Week(1),Hour(-1))) === Int64(601_200_000_000_000_000) @test_throws DimensionError ustrip(u"m", CompoundPeriod(Day(365),Hour(6))) @test_throws MethodError ustrip(CompoundPeriod()) @test_throws MethodError ustrip(CompoundPeriod(Second(1))) @test_throws MethodError ustrip(CompoundPeriod(Week(1), Hour(-1))) @test_throws MethodError ustrip(u"yr", CompoundPeriod(Year(1))) @test_throws MethodError ustrip(u"yr", CompoundPeriod(Month(1))) end @testset ">> Constructors/convert" begin for (T,u) = ((Nanosecond, u"ns"), (Microsecond, u"μs"), (Millisecond, u"ms"), (Second, u"s"), (Minute, u"minute"), (Hour, u"hr"), (Day, u"d"), (Week, u"wk")) @test Quantity(T(1)) === convert(Quantity, T(1)) === Int64(1)*u @test Quantity{Float64,𝐓,typeof(u)}(T(2)) === convert(Quantity{Float64,𝐓,typeof(u)}, T(2)) === 2.0u @test Quantity{Rational{Int64},𝐓,typeof(u)}(T(3)) === convert(Quantity{Rational{Int64},𝐓,typeof(u)}, T(3)) === Rational{Int64}(3,1)u end @test Quantity{Float64,𝐓,typeof(u"d")}(Hour(6)) === convert(typeof(1.0u"d"), Hour(6)) === 0.25u"d" @test_throws InexactError Quantity{Int64,𝐓,typeof(u"d")}(Hour(6)) @test_throws InexactError convert(typeof(1u"d"), Hour(6)) @test_throws DimensionError Quantity{Float64,𝐋,typeof(u"m")}(Hour(6)) @test_throws DimensionError convert(typeof(1.0u"m"), Week(1)) @test_throws MethodError Quantity{Float64,𝐓,typeof(u"d")}(Month(1)) @test_throws MethodError Quantity{Float64,𝐓,typeof(u"d")}(Year(1)) @test_throws MethodError convert(typeof(1u"d"), Month(1)) @test_throws MethodError convert(typeof(1u"d"), Year(1)) @test Week(4u"wk") === convert(Week, 4u"wk") === Week(4) @test Microsecond((3//2)u"ms") === convert(Microsecond, (3//2)u"ms") === Microsecond(1500) @test Millisecond(1.0u"s") === convert(Millisecond, 1.0u"s") === Millisecond(1000) @test Second(1.0u"s") === convert(Second, 1.0u"s") === Second(1) @test Day(3u"wk") === convert(Day, 3u"wk") === Day(21) @test_throws InexactError Second(1.5u"s") @test_throws InexactError convert(Second, 1.5u"s") @test_throws InexactError Second(1u"ms") @test_throws InexactError convert(Second, 1u"ms") @test_throws DimensionError Second(1u"m") @test_throws DimensionError convert(Second, 1u"m") @test_throws DimensionError Month(1u"s") # Doesn't throw MethodError because Month(::Number) exists @test_throws DimensionError Year(1u"s") # Doesn't throw MethodError because Year(::Number) exists @test_throws MethodError convert(Month, 1u"s") @test_throws MethodError convert(Year, 1u"s") for T = (Quantity{Rational{Int64},𝐓,typeof(u"yr")}, Quantity{Float64,𝐓,typeof(u"s")}, Quantity{Int64,𝐓,typeof(u"ns")}) @test T(CompoundPeriod()) === convert(T, CompoundPeriod()) === T(0u"s") @test T(CompoundPeriod(Day(365), Hour(6))) === convert(T, CompoundPeriod(Day(365), Hour(6))) === T(1u"yr") @test T(CompoundPeriod(Week(1), Hour(-1))) === convert(T, CompoundPeriod(Week(1), Hour(-1))) === T(167u"hr") @test_throws MethodError T(CompoundPeriod(Month(1))) @test_throws MethodError T(CompoundPeriod(Year(1))) @test_throws MethodError convert(T, CompoundPeriod(Month(1))) @test_throws MethodError convert(T, CompoundPeriod(Year(1))) end @test_throws InexactError Quantity{Int64,𝐓,typeof(u"d")}(CompoundPeriod(Day(1),Hour(6))) @test_throws InexactError convert(typeof(1u"d"), CompoundPeriod(Day(1),Hour(1))) @test_throws DimensionError Quantity{Float64,𝐋,typeof(u"m")}(CompoundPeriod(Day(365), Hour(6))) @test_throws DimensionError convert(typeof(1.0u"m"), CompoundPeriod(Day(365), Hour(6))) end end @testset "> Rounding" begin @test round(Second, -1.2u"s") === round(Second, -1.2u"s", RoundNearest) === Second(-1) @test round(Second, -1.5u"s") === round(Second, -1.5u"s", RoundNearest) === Second(-2) @test round(Second, -0.5u"s") === round(Second, -0.5u"s", RoundNearest) === Second(0) @test round(Minute, 45u"s") === round(Minute, 45u"s", RoundNearest) === Minute(1) @test round(Minute, 90u"s") === round(Minute, 90u"s", RoundNearest) === Minute(2) @test round(Minute, 150u"s") === round(Minute, 150u"s", RoundNearest) === Minute(2) @test round(Second, -1.2u"s", RoundNearestTiesAway) === Second(-1) @test round(Second, -1.5u"s", RoundNearestTiesAway) === Second(-2) @test round(Second, -0.5u"s", RoundNearestTiesAway) === Second(-1) @test round(Minute, 45u"s", RoundNearestTiesAway) === Minute(1) @test round(Minute, 90u"s", RoundNearestTiesAway) === Minute(2) @test round(Minute, 150u"s", RoundNearestTiesAway) === Minute(3) @test round(Second, -1.2u"s", RoundNearestTiesUp) === Second(-1) @test round(Second, -1.5u"s", RoundNearestTiesUp) === Second(-1) @test round(Second, -0.5u"s", RoundNearestTiesUp) === Second(0) @test round(Minute, 45u"s", RoundNearestTiesUp) === Minute(1) @test round(Minute, 90u"s", RoundNearestTiesUp) === Minute(2) @test round(Minute, 150u"s", RoundNearestTiesUp) === Minute(3) @test trunc(Second, -1.2u"s") === round(Second, -1.2u"s", RoundToZero) === Second(-1) @test trunc(Second, -1.5u"s") === round(Second, -1.5u"s", RoundToZero) === Second(-1) @test trunc(Second, -0.5u"s") === round(Second, -0.5u"s", RoundToZero) === Second(0) @test ceil(Second, -1.2u"s") === round(Second, -1.2u"s", RoundUp) === Second(-1) @test ceil(Second, -1.5u"s") === round(Second, -1.5u"s", RoundUp) === Second(-1) @test ceil(Second, -0.5u"s") === round(Second, -0.5u"s", RoundUp) === Second(0) @test floor(Second, -1.2u"s") === round(Second, -1.2u"s", RoundDown) === Second(-2) @test floor(Second, -1.5u"s") === round(Second, -1.5u"s", RoundDown) === Second(-2) @test floor(Second, -0.5u"s") === round(Second, -0.5u"s", RoundDown) === Second(-1) @test trunc(Minute, 45u"s") === round(Minute, 45u"s", RoundToZero) === Minute(0) @test trunc(Minute, 90u"s") === round(Minute, 90u"s", RoundToZero) === Minute(1) @test trunc(Minute, 150u"s") === round(Minute, 150u"s", RoundToZero) === Minute(2) @test ceil(Minute, 45u"s") === round(Minute, 45u"s", RoundUp) === Minute(1) @test ceil(Minute, 90u"s") === round(Minute, 90u"s", RoundUp) === Minute(2) @test ceil(Minute, 150u"s") === round(Minute, 150u"s", RoundUp) === Minute(3) @test floor(Minute, 45u"s") === round(Minute, 45u"s", RoundDown) === Minute(0) @test floor(Minute, 90u"s") === round(Minute, 90u"s", RoundDown) === Minute(1) @test floor(Minute, 150u"s") === round(Minute, 150u"s", RoundDown) === Minute(2) @test_throws DimensionError round(Second, 1u"m") @test_throws DimensionError round(Second, 1u"m", RoundNearestTiesUp) @test_throws DimensionError trunc(Second, 1u"m") @test_throws DimensionError ceil(Second, 1u"m") @test_throws DimensionError floor(Second, 1u"m") @test round(u"minute", Second(-50)) === Rational{Int64}(-1,1)u"minute" @test round(u"minute", Second(-90)) === Rational{Int64}(-2,1)u"minute" @test round(u"minute", Second(150)) === Rational{Int64}(2,1)u"minute" @test round(u"minute", Second(-50), RoundNearest) === Rational{Int64}(-1,1)u"minute" @test round(u"minute", Second(-90), RoundNearest) === Rational{Int64}(-2,1)u"minute" @test round(u"minute", Second(150), RoundNearest) === Rational{Int64}(2,1)u"minute" @test round(u"minute", Second(-50), RoundNearestTiesAway) === Rational{Int64}(-1,1)u"minute" @test round(u"minute", Second(-90), RoundNearestTiesAway) === Rational{Int64}(-2,1)u"minute" @test round(u"minute", Second(150), RoundNearestTiesAway) === Rational{Int64}(3,1)u"minute" @test round(u"minute", Second(-50), RoundNearestTiesUp) === Rational{Int64}(-1,1)u"minute" @test round(u"minute", Second(-90), RoundNearestTiesUp) === Rational{Int64}(-1,1)u"minute" @test round(u"minute", Second(150), RoundNearestTiesUp) === Rational{Int64}(3,1)u"minute" @test trunc(u"minute", Second(-50)) === round(u"minute", Second(-50), RoundToZero) === Rational{Int64}(0,1)u"minute" @test trunc(u"minute", Second(-90)) === round(u"minute", Second(-90), RoundToZero) === Rational{Int64}(-1,1)u"minute" @test trunc(u"minute", Second(150)) === round(u"minute", Second(150), RoundToZero) === Rational{Int64}(2,1)u"minute" @test ceil(u"minute", Second(-50)) === round(u"minute", Second(-50), RoundUp) === Rational{Int64}(0,1)u"minute" @test ceil(u"minute", Second(-90)) === round(u"minute", Second(-90), RoundUp) === Rational{Int64}(-1,1)u"minute" @test ceil(u"minute", Second(150)) === round(u"minute", Second(150), RoundUp) === Rational{Int64}(3,1)u"minute" @test floor(u"minute", Second(-50)) === round(u"minute", Second(-50), RoundDown) === Rational{Int64}(-1,1)u"minute" @test floor(u"minute", Second(-90)) === round(u"minute", Second(-90), RoundDown) === Rational{Int64}(-2,1)u"minute" @test floor(u"minute", Second(150)) === round(u"minute", Second(150), RoundDown) === Rational{Int64}(2,1)u"minute" @test_throws DimensionError round(u"m", Second(1)) @test_throws DimensionError round(u"m", Second(1), RoundNearestTiesAway) @test_throws DimensionError trunc(u"m", Second(1)) @test_throws DimensionError ceil(u"m", Second(1)) @test_throws DimensionError floor(u"m", Second(1)) T = @static Sys.WORD_SIZE == 32 ? Float64 : Rational{Int64} @test round(u"minute", CompoundPeriod(Minute(-1), Second(10))) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(-2), Second(30))) === T(-2)u"minute" @test round(u"minute", CompoundPeriod(Minute(3), Second(-30))) === T(2)u"minute" @test round(u"minute", CompoundPeriod(Minute(-1), Second(10)), RoundNearest) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(-2), Second(30)), RoundNearest) === T(-2)u"minute" @test round(u"minute", CompoundPeriod(Minute(3), Second(-30)), RoundNearest) === T(2)u"minute" @test round(u"minute", CompoundPeriod(Minute(-1), Second(10)), RoundNearestTiesAway) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(-2), Second(30)), RoundNearestTiesAway) === T(-2)u"minute" @test round(u"minute", CompoundPeriod(Minute(3), Second(-30)), RoundNearestTiesAway) === T(3)u"minute" @test round(u"minute", CompoundPeriod(Minute(-1), Second(10)), RoundNearestTiesUp) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(-2), Second(30)), RoundNearestTiesUp) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(3), Second(-30)), RoundNearestTiesUp) === T(3)u"minute" @test trunc(u"minute", CompoundPeriod(Minute(-1), Second(10))) === -T(0)u"minute" @test trunc(u"minute", CompoundPeriod(Minute(-2), Second(30))) === T(-1)u"minute" @test trunc(u"minute", CompoundPeriod(Minute(3), Second(-30))) === T(2)u"minute" @test round(u"minute", CompoundPeriod(Minute(-1), Second(10)), RoundToZero) === -T(0)u"minute" @test round(u"minute", CompoundPeriod(Minute(-2), Second(30)), RoundToZero) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(3), Second(-30)), RoundToZero) === T(2)u"minute" @test ceil(u"minute", CompoundPeriod(Minute(-1), Second(10))) === -T(0)u"minute" @test ceil(u"minute", CompoundPeriod(Minute(-2), Second(30))) === T(-1)u"minute" @test ceil(u"minute", CompoundPeriod(Minute(3), Second(-30))) === T(3)u"minute" @test round(u"minute", CompoundPeriod(Minute(-1), Second(10)), RoundUp) === -T(0)u"minute" @test round(u"minute", CompoundPeriod(Minute(-2), Second(30)), RoundUp) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(3), Second(-30)), RoundUp) === T(3)u"minute" @test floor(u"minute", CompoundPeriod(Minute(-1), Second(10))) === T(-1)u"minute" @test floor(u"minute", CompoundPeriod(Minute(-2), Second(30))) === T(-2)u"minute" @test floor(u"minute", CompoundPeriod(Minute(3), Second(-30))) === T(2)u"minute" @test round(u"minute", CompoundPeriod(Minute(-1), Second(10)), RoundDown) === T(-1)u"minute" @test round(u"minute", CompoundPeriod(Minute(-2), Second(30)), RoundDown) === T(-2)u"minute" @test round(u"minute", CompoundPeriod(Minute(3), Second(-30)), RoundDown) === T(2)u"minute" @test_throws MethodError round(u"s", CompoundPeriod(Year(1))) @test_throws MethodError round(u"s", CompoundPeriod(Year(1)), RoundNearestTiesAway) @test_throws MethodError trunc(u"s", CompoundPeriod(Year(1))) @test_throws MethodError ceil(u"s", CompoundPeriod(Year(1))) @test_throws MethodError floor(u"s", CompoundPeriod(Year(1))) @test_throws MethodError round(u"s", CompoundPeriod(Month(1))) @test_throws MethodError round(u"s", CompoundPeriod(Month(1)), RoundNearestTiesAway) @test_throws MethodError trunc(u"s", CompoundPeriod(Month(1))) @test_throws MethodError ceil(u"s", CompoundPeriod(Month(1))) @test_throws MethodError floor(u"s", CompoundPeriod(Month(1))) @test_throws DimensionError round(u"m", CompoundPeriod(Second(1))) @test_throws DimensionError round(u"m", CompoundPeriod(Second(1)), RoundNearestTiesAway) @test_throws DimensionError trunc(u"m", CompoundPeriod(Second(1))) @test_throws DimensionError ceil(u"m", CompoundPeriod(Second(1))) @test_throws DimensionError floor(u"m", CompoundPeriod(Second(1))) @test round(u"wk", Day(10), digits=1) === 1.4u"wk" @test round(u"wk", Day(10), sigdigits=3) === 1.43u"wk" @test round(u"wk", Day(10), RoundUp, digits=1) === 1.5u"wk" @test round(u"wk", Day(10), RoundDown, sigdigits=3) === 1.42u"wk" @test round(u"wk", Day(10), RoundUp, digits=2, base=2) === 1.5u"wk" @test round(u"wk", Day(10), RoundToZero, digits=2, base=2) === 1.25u"wk" @test floor(u"wk", Day(10), sigdigits=3) === 1.42u"wk" @test ceil(u"wk", Day(10), digits=2, base=2) === 1.5u"wk" @test trunc(u"wk", Day(10), digits=2, base=2) === 1.25u"wk" @test_throws DimensionError round(u"m", Day(10), digits=1) @test_throws DimensionError round(u"m", Day(10), sigdigits=3, base=2) @test_throws DimensionError round(u"m", Day(10), RoundUp, digits=1) @test_throws DimensionError trunc(u"m", Day(10), digits=1) @test_throws DimensionError ceil(u"m", Day(10), sigdigits=3) @test_throws DimensionError floor(u"m", Day(10), digits=1, base=2) @test round(u"wk", CompoundPeriod(Week(1), Day(3)), digits=1) === 1.4u"wk" @test round(u"wk", CompoundPeriod(Week(1), Day(3)), sigdigits=3) === 1.43u"wk" @test round(u"wk", CompoundPeriod(Week(1), Day(3)), RoundUp, digits=1) === 1.5u"wk" @test round(u"wk", CompoundPeriod(Week(1), Day(3)), RoundDown, sigdigits=3) === 1.42u"wk" @test round(u"wk", CompoundPeriod(Week(1), Day(3)), RoundUp, digits=2, base=2) === 1.5u"wk" @test round(u"wk", CompoundPeriod(Week(1), Day(3)), RoundToZero, digits=2, base=2) === 1.25u"wk" @test floor(u"wk", CompoundPeriod(Week(1), Day(3)), sigdigits=3) === 1.42u"wk" @test ceil(u"wk", CompoundPeriod(Week(1), Day(3)), digits=2, base=2) === 1.5u"wk" @test trunc(u"wk", CompoundPeriod(Week(1), Day(3)), digits=2, base=2) === 1.25u"wk" @test_throws MethodError round(u"wk", CompoundPeriod(Year(1)), digits=1) @test_throws MethodError floor(u"wk", CompoundPeriod(Year(1)), sigdigits=3) @test_throws MethodError ceil(u"wk", CompoundPeriod(Year(1)), digits=2, base=2) @test_throws MethodError trunc(u"wk", CompoundPeriod(Year(1)), digits=2, base=2) @test_throws MethodError round(u"wk", CompoundPeriod(Month(1)), digits=1) @test_throws MethodError floor(u"wk", CompoundPeriod(Month(1)), sigdigits=3) @test_throws MethodError ceil(u"wk", CompoundPeriod(Month(1)), digits=2, base=2) @test_throws MethodError trunc(u"wk", CompoundPeriod(Month(1)), digits=2, base=2) @test_throws DimensionError round(u"m", CompoundPeriod(Week(1), Day(3)), digits=1) @test_throws DimensionError round(u"m", CompoundPeriod(Week(1), Day(3)), sigdigits=3, base=2) @test_throws DimensionError round(u"m", CompoundPeriod(Week(1), Day(3)), RoundUp, digits=1) @test_throws DimensionError trunc(u"m", CompoundPeriod(Week(1), Day(3)), digits=1) @test_throws DimensionError ceil(u"m", CompoundPeriod(Week(1), Day(3)), sigdigits=3) @test_throws DimensionError floor(u"m", CompoundPeriod(Week(1), Day(3)), digits=1, base=2) @test round(Int, u"minute", Second(-50)) === -1u"minute" @test round(Float32, u"minute", Second(-50)) === -1.0f0u"minute" @test round(Int, u"minute", Second(50), RoundDown) === 0u"minute" @test round(Int, u"minute", Second(50), RoundUp) === 1u"minute" @test round(Int, u"minute", Second(50), RoundToZero) === 0u"minute" @test floor(Int, u"minute", Second(50)) === 0u"minute" @test ceil(Int, u"minute", Second(50)) === 1u"minute" @test trunc(Int, u"minute", Second(50)) === 0u"minute" @test round(Float32, u"minute", Second(50), RoundDown) === 0.0f0u"minute" @test round(Float32, u"minute", Second(50), RoundUp) === 1.0f0u"minute" @test round(Float32, u"minute", Second(50), RoundToZero) === 0.0f0u"minute" @test floor(Float32, u"minute", Second(50)) === 0.0f0u"minute" @test ceil(Float32, u"minute", Second(50)) === 1.0f0u"minute" @test trunc(Float32, u"minute", Second(50)) === 0.0f0u"minute" @test round(Float32, u"wk", Day(10), digits=1) === 1.4f0u"wk" @test round(Float32, u"wk", Day(10), sigdigits=3) === 1.43f0u"wk" @test round(Float32, u"wk", Day(10), RoundUp, digits=1) === 1.5f0u"wk" @test round(Float32, u"wk", Day(10), RoundDown, sigdigits=3) === 1.42f0u"wk" @test round(Float32, u"wk", Day(10), RoundUp, digits=2, base=2) === 1.5f0u"wk" @test round(Float32, u"wk", Day(10), RoundToZero, digits=2, base=2) === 1.25f0u"wk" @test floor(Float32, u"wk", Day(10), sigdigits=3) === 1.42f0u"wk" @test ceil(Float32, u"wk", Day(10), digits=2, base=2) === 1.5f0u"wk" @test trunc(Float32, u"wk", Day(10), digits=2, base=2) === 1.25f0u"wk" @test_throws DimensionError round(Float32, u"m", Second(-50)) @test_throws DimensionError round(Float32, u"m", Second(-50), RoundDown) @test_throws DimensionError round(Float32, u"m", Second(-50), digits=1) @test_throws DimensionError round(Float32, u"m", Second(-50), RoundDown, sigdigits=3) @test_throws DimensionError floor(Float32, u"m", Second(-50)) @test_throws DimensionError ceil(Float32, u"m", Second(-50), digits=1) @test_throws DimensionError trunc(Float32, u"m", Second(-50), sigdigits=3, base=2) @test round(Int, u"minute", CompoundPeriod(Minute(-1), Second(10))) === -1u"minute" @test round(Float32, u"minute", CompoundPeriod(Minute(-1), Second(10))) === -1.0f0u"minute" @test round(Int, u"minute", CompoundPeriod(Minute(1), Second(-10)), RoundDown) === 0u"minute" @test round(Int, u"minute", CompoundPeriod(Minute(1), Second(-10)), RoundUp) === 1u"minute" @test round(Int, u"minute", CompoundPeriod(Minute(1), Second(-10)), RoundToZero) === 0u"minute" @test floor(Int, u"minute", CompoundPeriod(Minute(1), Second(-10))) === 0u"minute" @test ceil(Int, u"minute", CompoundPeriod(Minute(1), Second(-10))) === 1u"minute" @test trunc(Int, u"minute", CompoundPeriod(Minute(1), Second(-10))) === 0u"minute" @test round(Float32, u"minute", CompoundPeriod(Minute(1), Second(-10)), RoundDown) === 0.0f0u"minute" @test round(Float32, u"minute", CompoundPeriod(Minute(1), Second(-10)), RoundUp) === 1.0f0u"minute" @test round(Float32, u"minute", CompoundPeriod(Minute(1), Second(-10)), RoundToZero) === 0.0f0u"minute" @test floor(Float32, u"minute", CompoundPeriod(Minute(1), Second(-10))) === 0.0f0u"minute" @test ceil(Float32, u"minute", CompoundPeriod(Minute(1), Second(-10))) === 1.0f0u"minute" @test trunc(Float32, u"minute", CompoundPeriod(Minute(1), Second(-10))) === 0.0f0u"minute" @test round(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), digits=1) === 1.4f0u"wk" @test round(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), sigdigits=3) === 1.43f0u"wk" @test round(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), RoundUp, digits=1) === 1.5f0u"wk" @test round(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), RoundDown, sigdigits=3) === 1.42f0u"wk" @test round(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), RoundUp, digits=2, base=2) === 1.5f0u"wk" @test round(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), RoundToZero, digits=2, base=2) === 1.25f0u"wk" @test floor(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), sigdigits=3) === 1.42f0u"wk" @test ceil(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), digits=2, base=2) === 1.5f0u"wk" @test trunc(Float32, u"wk", CompoundPeriod(Week(1), Day(3)), digits=2, base=2) === 1.25f0u"wk" @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Year(1))) @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Year(1)), RoundDown) @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Year(1)), digits=1) @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Year(1)), RoundDown, sigdigits=3) @test_throws MethodError floor(Float32, u"yr", CompoundPeriod(Year(1))) @test_throws MethodError ceil(Float32, u"yr", CompoundPeriod(Year(1)), digits=1) @test_throws MethodError trunc(Float32, u"yr", CompoundPeriod(Year(1)), sigdigits=3, base=2) @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Month(1))) @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Month(1)), RoundDown) @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Month(1)), digits=1) @test_throws MethodError round(Float32, u"yr", CompoundPeriod(Month(1)), RoundDown, sigdigits=3) @test_throws MethodError floor(Float32, u"yr", CompoundPeriod(Month(1))) @test_throws MethodError ceil(Float32, u"yr", CompoundPeriod(Month(1)), digits=1) @test_throws MethodError trunc(Float32, u"yr", CompoundPeriod(Month(1)), sigdigits=3, base=2) @test_throws DimensionError round(Float32, u"m", CompoundPeriod(Minute(-1), Second(10))) @test_throws DimensionError round(Float32, u"m", CompoundPeriod(Minute(-1), Second(10)), RoundDown) @test_throws DimensionError round(Float32, u"m", CompoundPeriod(Minute(-1), Second(10)), digits=1) @test_throws DimensionError round(Float32, u"m", CompoundPeriod(Minute(-1), Second(10)), RoundDown, sigdigits=3) @test_throws DimensionError floor(Float32, u"m", CompoundPeriod(Minute(-1), Second(10))) @test_throws DimensionError ceil(Float32, u"m", CompoundPeriod(Minute(-1), Second(10)), digits=1) @test_throws DimensionError trunc(Float32, u"m", CompoundPeriod(Minute(-1), Second(10)), sigdigits=3, base=2) @test round(typeof(1.0f0u"minute"), Second(-50)) === -1.0f0u"minute" @test round(typeof(1.0f0u"minute"), Second(50), RoundToZero) === 0.0f0u"minute" @test round(typeof(1.0f0u"wk"), Day(10), digits=1) === 1.4f0u"wk" @test round(typeof(1.0f0u"wk"), Day(10), sigdigits=3) === 1.43f0u"wk" @test round(typeof(1.0f0u"wk"), Day(10), RoundUp, digits=1) === 1.5f0u"wk" @test round(typeof(1.0f0u"wk"), Day(10), RoundDown, sigdigits=3) === 1.42f0u"wk" @test round(typeof(1.0f0u"wk"), Day(10), RoundUp, digits=2, base=2) === 1.5f0u"wk" @test round(typeof(1.0f0u"wk"), Day(10), RoundToZero, digits=2, base=2) === 1.25f0u"wk" @test floor(typeof(1.0f0u"wk"), Day(10), sigdigits=3) === 1.42f0u"wk" @test ceil(typeof(1.0f0u"wk"), Day(10), digits=2, base=2) === 1.5f0u"wk" @test trunc(typeof(1.0f0u"wk"), Day(10), digits=2, base=2) === 1.25f0u"wk" @test_throws DimensionError round(typeof(1.0u"m"), Second(1)) @test_throws DimensionError round(typeof(1.0u"m"), Second(1), RoundToZero) @test_throws DimensionError round(typeof(1.0u"m"), Second(1), digits=1) @test_throws DimensionError round(typeof(1.0u"m"), Second(1), RoundToZero, sigdigits=2) @test_throws DimensionError floor(typeof(1.0u"m"), Second(1)) @test_throws DimensionError ceil(typeof(1.0u"m"), Second(1), sigdigits=2, base=2) @test_throws DimensionError trunc(typeof(1.0u"m"), Second(1), digits=1) @test round(typeof(1.0f0u"minute"), CompoundPeriod(Minute(-1), Second(10))) === -1.0f0u"minute" @test round(typeof(1.0f0u"minute"), CompoundPeriod(Minute(1), Second(-10)), RoundToZero) === 0.0f0u"minute" @test round(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), digits=1) === 1.4f0u"wk" @test round(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), sigdigits=3) === 1.43f0u"wk" @test round(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), RoundUp, digits=1) === 1.5f0u"wk" @test round(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), RoundDown, sigdigits=3) === 1.42f0u"wk" @test round(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), RoundUp, digits=2, base=2) === 1.5f0u"wk" @test round(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), RoundToZero, digits=2, base=2) === 1.25f0u"wk" @test floor(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), sigdigits=3) === 1.42f0u"wk" @test ceil(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), digits=2, base=2) === 1.5f0u"wk" @test trunc(typeof(1.0f0u"wk"), CompoundPeriod(Week(1), Day(3)), digits=2, base=2) === 1.25f0u"wk" @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Year(1))) @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Year(1)), RoundToZero) @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Year(1)), digits=1) @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Year(1)), RoundToZero, sigdigits=2) @test_throws MethodError floor(typeof(1.0u"s"), CompoundPeriod(Year(1))) @test_throws MethodError ceil(typeof(1.0u"s"), CompoundPeriod(Year(1)), sigdigits=2, base=2) @test_throws MethodError trunc(typeof(1.0u"s"), CompoundPeriod(Year(1)), digits=1) @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Month(1))) @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Month(1)), RoundToZero) @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Month(1)), digits=1) @test_throws MethodError round(typeof(1.0u"s"), CompoundPeriod(Month(1)), RoundToZero, sigdigits=2) @test_throws MethodError floor(typeof(1.0u"s"), CompoundPeriod(Month(1))) @test_throws MethodError ceil(typeof(1.0u"s"), CompoundPeriod(Month(1)), sigdigits=2, base=2) @test_throws MethodError trunc(typeof(1.0u"s"), CompoundPeriod(Month(1)), digits=1) @test_throws DimensionError round(typeof(1.0u"m"), CompoundPeriod(Second(1))) @test_throws DimensionError round(typeof(1.0u"m"), CompoundPeriod(Second(1)), RoundToZero) @test_throws DimensionError round(typeof(1.0u"m"), CompoundPeriod(Second(1)), digits=1) @test_throws DimensionError round(typeof(1.0u"m"), CompoundPeriod(Second(1)), RoundToZero, sigdigits=2) @test_throws DimensionError floor(typeof(1.0u"m"), CompoundPeriod(Second(1))) @test_throws DimensionError ceil(typeof(1.0u"m"), CompoundPeriod(Second(1)), sigdigits=2, base=2) @test_throws DimensionError trunc(typeof(1.0u"m"), CompoundPeriod(Second(1)), digits=1) end @testset "> Comparison" begin # == @test Second(2) == 2.0u"s" @test 72u"hr" == Day(3) @test Millisecond(0) == -0.0u"ms" @test !(Day(4) == 4u"hr") @test !(4u"cm" == Day(4)) @test CompoundPeriod(Day(365), Hour(6)) == 1u"yr" @test 1u"yr" == CompoundPeriod(Day(365), Hour(6)) @test CompoundPeriod() == -0.0u"s" @test !(1u"m" == CompoundPeriod(Day(1))) @test !(CompoundPeriod(Day(1)) == 1u"m") @test !(1u"yr" == CompoundPeriod(Year(1))) @test !(1u"yr" == CompoundPeriod(Month(12))) @test !(CompoundPeriod(Year(1)) == 1u"yr") @test !(CompoundPeriod(Month(12)) == 1u"yr") # isequal @test isequal(Second(2), 2.0u"s") @test isequal(72u"hr", Day(3)) @test !isequal(Millisecond(0), -0.0u"ms") # !isequal(0.0, -0.0) @test !isequal(Day(4), 4u"hr") @test !isequal(4u"cm", Day(4)) @test isequal(CompoundPeriod(), 0s) @test isequal(CompoundPeriod(Day(365), Hour(6)), 1u"yr") @test isequal(1u"yr", CompoundPeriod(Day(365), Hour(6))) @test !isequal(CompoundPeriod(), -0.0u"s") # !isequal(0.0, -0.0) @test !isequal(1u"m", CompoundPeriod(Day(1))) @test !isequal(CompoundPeriod(Day(1)), 1u"m") @test !isequal(1u"yr", CompoundPeriod(Year(1))) @test !isequal(1u"yr", CompoundPeriod(Month(12))) @test !isequal(CompoundPeriod(Year(1)), 1u"yr") @test !isequal(CompoundPeriod(Month(12)), 1u"yr") # hash @test_broken hash(Second(2)) === hash(2.0s) @test_broken hash(72hr) === hash(Day(3)) @test_broken hash(CompoundPeriod()) === hash(0s) @test_broken hash(CompoundPeriod(Hour(6))) === hash(6hr) # < @test Second(1) < 1001u"ms" @test 3u"minute" < Minute(4) @test !(Minute(3) < 3u"minute") @test !(7u"d" < Week(1)) @test !(-0.0u"d" < Day(0)) @test_throws DimensionError 7u"kg" < Day(1) @test_throws DimensionError Day(1) < 7u"kg" @test CompoundPeriod(Day(365)) < 1u"yr" @test 1u"s" < CompoundPeriod(Second(1), Nanosecond(1)) @test !(CompoundPeriod(Day(365), Hour(6)) < 1u"yr") @test !(1u"s" < CompoundPeriod(Second(1))) @test !(-0.0u"s" < CompoundPeriod()) @test_throws DimensionError 7u"kg" < CompoundPeriod(Day(1)) @test_throws DimensionError CompoundPeriod() < 1u"m" @test_throws MethodError 1u"s" < CompoundPeriod(Year(1)) @test_throws MethodError 1u"s" < CompoundPeriod(Month(1)) @test_throws MethodError CompoundPeriod(Year(1)) < 2u"yr" @test_throws MethodError CompoundPeriod(Month(1)) < 1u"yr" # isless @test isless(Second(1), 1001u"ms") @test isless(3u"minute", Minute(4)) @test !isless(Minute(3), 3u"minute") @test !isless(7u"d", Week(1)) @test isless(-0.0u"d", Day(0)) @test_throws DimensionError isless(7u"kg", Day(1)) @test_throws DimensionError isless(Day(1), 7u"kg") @test isless(CompoundPeriod(Day(365)), 1u"yr") @test isless(1u"s", CompoundPeriod(Second(1), Nanosecond(1))) @test !isless(CompoundPeriod(Day(365), Hour(6)), 1u"yr") @test !isless(1u"s", CompoundPeriod(Second(1))) @test isless(-0.0u"s", CompoundPeriod()) @test_throws DimensionError isless(7u"kg", CompoundPeriod(Day(1))) @test_throws DimensionError isless(CompoundPeriod(), 1u"m") @test_throws MethodError isless(1u"s", CompoundPeriod(Year(1))) @test_throws MethodError isless(1u"s", CompoundPeriod(Month(1))) @test_throws MethodError isless(CompoundPeriod(Year(1)), 2u"yr") @test_throws MethodError isless(CompoundPeriod(Month(1)), 1u"yr") # ≤ @test Second(1) ≤ 1001u"ms" @test 7u"d" ≤ Week(1) @test !(Minute(4) ≤ 3u"minute") @test_throws DimensionError 7u"kg" ≤ Day(1) @test_throws DimensionError Day(1) ≤ 7u"kg" @test CompoundPeriod(Day(365), Hour(6)) ≤ 1u"yr" @test 1u"s" ≤ CompoundPeriod(Second(1), Nanosecond(1)) @test !(1u"s" ≤ CompoundPeriod(Millisecond(999))) @test_throws DimensionError 7u"kg" ≤ CompoundPeriod(Day(1)) @test_throws DimensionError CompoundPeriod() ≤ 1u"m" @test_throws MethodError 1u"s" ≤ CompoundPeriod(Year(1)) @test_throws MethodError 1u"s" ≤ CompoundPeriod(Month(1)) @test_throws MethodError CompoundPeriod(Year(1)) ≤ 2u"yr" @test_throws MethodError CompoundPeriod(Month(1)) ≤ 1u"yr" # min, max @test min(1u"s", Microsecond(100)) == Microsecond(100) @test min(Day(1), 1u"hr") == 1u"hr" @test_throws DimensionError min(1u"kg", Second(1)) @test_throws DimensionError min(Second(1), 1u"kg") @test max(1u"s", Microsecond(100)) == 1u"s" @test max(Day(1), 1u"hr") == Day(1) @test_throws DimensionError max(1u"kg", Second(1)) @test_throws DimensionError max(Second(1), 1u"kg") @test min(1u"s", CompoundPeriod()) == CompoundPeriod() @test min(1u"yr", CompoundPeriod(Day(365), Hour(7))) == 1u"yr" @test_throws DimensionError min(CompoundPeriod(), 1u"m") @test_throws DimensionError min(1u"m", CompoundPeriod()) @test_throws MethodError min(1u"yr", CompoundPeriod(Year(1))) @test_throws MethodError min(CompoundPeriod(Month(1)), 1u"yr") @test max(1u"s", CompoundPeriod()) == 1u"s" @test max(1u"yr", CompoundPeriod(Day(365), Hour(7))) == CompoundPeriod(Day(365), Hour(7)) @test_throws DimensionError max(CompoundPeriod(), 1u"m") @test_throws DimensionError max(1u"m", CompoundPeriod()) @test_throws MethodError max(1u"yr", CompoundPeriod(Year(1))) @test_throws MethodError max(CompoundPeriod(Month(1)), 1u"yr") end @testset "> isapprox" begin # scalar arguments @test isapprox(nextfloat(1.0)u"s", Second(1)) @test isapprox(1.0u"s", Second(1), rtol=0) @test isapprox(Second(2), 2500u"ms", atol=1u"s") @test isapprox(Second(2), 2500u"ms", atol=Second(1)) @test isapprox(2500u"ms", Second(2), rtol=0.5) @test !isapprox(2500u"ms", Second(2), rtol=0.1) @test !isapprox(nextfloat(1.0)u"s", Second(1), rtol=0) @test !isapprox(Second(1), 1u"m") @test !isapprox(1u"m", Second(1)) @test !isapprox(Second(1), 1u"m", atol=1u"kg") @test !isapprox(1u"m", Second(1), atol=Second(1)) @test_throws DimensionError isapprox(Second(2), 2500u"ms", atol=0.5) @test_throws DimensionError isapprox(Second(2), 2500u"ms", atol=0.5u"m") @test isapprox(nextfloat(1.0)u"yr", CompoundPeriod(Day(365), Hour(6))) @test isapprox(1.0u"yr", CompoundPeriod(Day(365), Hour(6)), rtol=0) @test isapprox(2u"s", CompoundPeriod(Second(2), Millisecond(500)), atol=1u"s") @test isapprox(CompoundPeriod(Second(2), Millisecond(500)), 2u"s", atol=CompoundPeriod(Second(1))) @test isapprox(CompoundPeriod(Second(2), Millisecond(500)), 2u"s", rtol=0.5) @test !isapprox(CompoundPeriod(Second(2), Millisecond(500)), 2u"s", rtol=0.1) @test !isapprox(CompoundPeriod(Week(1), Nanosecond(1)), 1.0u"wk", rtol=0) @test !isapprox(CompoundPeriod(Second(1)), 1u"m") @test !isapprox(1u"m", CompoundPeriod(Second(1))) @test !isapprox(CompoundPeriod(Second(1)), 1u"m", atol=1u"kg") @test !isapprox(1u"m", CompoundPeriod(Second(1)), atol=CompoundPeriod(Second(1))) @test_throws MethodError isapprox(CompoundPeriod(Year(1)), 1u"s") @test_throws MethodError isapprox(1u"s", CompoundPeriod(Year(1)), rtol=1) @test_throws MethodError isapprox(1u"s", 1u"s", atol=CompoundPeriod(Year(1))) @test_throws MethodError isapprox(CompoundPeriod(Month(1)), 1u"s") @test_throws MethodError isapprox(1u"s", CompoundPeriod(Month(1)), rtol=1) @test_throws MethodError isapprox(1u"s", 1u"s", atol=CompoundPeriod(Month(1))) @test_throws DimensionError isapprox(CompoundPeriod(Day(1)), 1u"d", atol=0.1) @test_throws DimensionError isapprox(1u"d", CompoundPeriod(Day(1)), atol=0.1u"m") # array arguments @test isapprox([Second(-5), Second(5)], [-5.0u"s", 5.0u"s"]) @test isapprox([Second(-5), Second(5)], [-4.99u"s", 5.01u"s"], rtol=1e-2) @test_broken isapprox([1u"s", 60u"s"], Period[Second(1), Minute(1)], rtol=0) @test !isapprox([1.0u"kg"], [Second(1)]) @test isapprox([CompoundPeriod(Day(2), Hour(12)), CompoundPeriod(Day(-3), Hour(12))], [2.5u"d", -2.5u"d"]) @test isapprox([2.51u"d", -2.49u"d"], [CompoundPeriod(Day(2), Hour(12)), CompoundPeriod(Day(-3), Hour(12))], rtol=1e-2) @test isapprox([1u"s", 60u"s"], CompoundPeriod[Second(1), Minute(1)], rtol=0) @test !isapprox([CompoundPeriod(Day(1))], [1.0u"kg"]) @test_throws MethodError isapprox([CompoundPeriod(Year(1))], [1.0u"yr"]) @test_throws MethodError isapprox([1.0u"yr"], [CompoundPeriod(Month(12))], rtol=1) end @testset "> promote" begin @test promote(1u"d", Minute(1)) === promote(1u"d", Int64(1)u"minute") @test promote(Second(10), 2.0u"fs") === promote(Int64(10)u"s", 2.0u"fs") @test_throws ErrorException promote(1u"m", Second(1)) @test_throws ErrorException promote(Day(1), 3u"T") end sleep(10u"ms") # not tested explicitly, because sleep doesn't come with guarantees end ================================================ FILE: test/runtests.jl ================================================ using Unitful using Test, LinearAlgebra, Random, ConstructionBase, InverseFunctions, Printf import Unitful: DimensionError, AffineError import Unitful: LogScaled, LogInfo, Level, Gain, MixedUnits, Decibel import Unitful: FreeUnits, ContextUnits, FixedUnits, AffineUnits, AffineQuantity import ForwardDiff import NaNMath import Latexify: Latexify, latexify, @latexify, FancyNumberFormatter, SiunitxNumberFormatter import LaTeXStrings: LaTeXString, @L_str import Unitful: nm, μm, mm, cm, m, km, inch, ft, mi, ac, mg, g, kg, Ra, °F, °C, K, rad, mrad, °, deg, ms, s, minute, hr, d, yr, Hz, J, A, N, mol, V, mJ, eV, dyn, mN, mW, W, dB, dB_rp, dB_p, dBm, dBV, dBSPL, Decibel, Np, Np_rp, Np_p, Neper, C import Unitful: 𝐋, 𝐓, 𝐍, 𝚯 import Unitful: Length, Area, Volume, Luminosity, Time, Frequency, Mass, Current, Temperature, AbsoluteScaleTemperature, RelativeScaleTemperature, Action, Power, MassFlow, MolarFlow, VolumeFlow import Unitful: LengthUnits, AreaUnits, MassUnits, TemperatureUnits using Dates: Dates, Nanosecond, Microsecond, Millisecond, Second, Minute, Hour, Day, Week, Month, Year, CompoundPeriod const colon = Base.:(:) macro test_or_throws(extype, ex) return :( try # if the first line throws, go to @test_throws in catch clause # if not: test expression normally result = $ex @test result catch @test_throws $extype $ex end ) end is_finite_nonzero(x) = isfinite(x) && !iszero(x) @testset "Construction" begin @test isa(NoUnits, FreeUnits) @test typeof(𝐋) === Unitful.Dimensions{(Unitful.Dimension{:Length}(1),)} @test 𝐋*𝐋 === 𝐋^2 @test typeof(1.0m) === Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(Unitful.Unit{:Meter, 𝐋}(0,1),), 𝐋, nothing}} @test typeof(1m^2) === Unitful.Quantity{Int, 𝐋^2, Unitful.FreeUnits{(Unitful.Unit{:Meter, 𝐋}(0,2),), 𝐋^2, nothing}} @test typeof(1ac) === Unitful.Quantity{Int, 𝐋^2, Unitful.FreeUnits{(Unitful.Unit{:Acre, 𝐋^2}(0,1),), 𝐋^2, nothing}} @test typeof(ContextUnits(m,μm)) === ContextUnits{(Unitful.Unit{:Meter, 𝐋}(0,1),), 𝐋, typeof(μm), nothing} @test typeof(1.0*ContextUnits(m,μm)) === Unitful.Quantity{Float64, 𝐋, ContextUnits{(Unitful.Unit{:Meter, 𝐋}(0,1),), 𝐋, typeof(μm), nothing}} @test typeof(1.0*FixedUnits(m)) === Unitful.Quantity{Float64, 𝐋, FixedUnits{(Unitful.Unit{:Meter, 𝐋}(0,1),), 𝐋, nothing}} @test 3mm != 3*(m*m) # mm not interpreted as m*m @test (3+4im)*V === V*(3+4im) === (3V+4V*im) # Complex quantity construction @test !isreal(Base.complex(3.0/m, 4.0/m)) @test !isreal(Base.complex((3.0+4.0im)/m)) @test Base.reim(Base.complex((3.0+4.0im)/m)) == (3.0/m, 4.0/m) @test Base.complex(1m, 1.5m) == Base.complex(1.0m, 1.5m) @test Base.widen(Base.complex(Float32(3.0)/m)) == Base.complex(Float64(3.0)/m) @test Base.complex(1.0/m) == (1.0/m + (0.0/m)*im) @test Base.complex(1.0/m + (1.5/m)*im) == (1.0/m + (1.5/m)*im) @test 3*NoUnits === 3 @test 3*(FreeUnits(m)/FreeUnits(m)) === 3 @test 3*(ContextUnits(m)/ContextUnits(m)) === 3 @test 3*(FixedUnits(m)/FixedUnits(m)) === 3 @test ContextUnits(mm) === ContextUnits(mm,m) @test Quantity(3, NoUnits) === 3 @test FreeUnits(ContextUnits(mm,m)) === FreeUnits(mm) @test FreeUnits(FixedUnits(mm)) === FreeUnits(mm) @test isa(FreeUnits(m), FreeUnits) @test isa(ContextUnits(m), ContextUnits) @test isa(ContextUnits(m,mm), ContextUnits) @test isa(FixedUnits(m), FixedUnits) @test ContextUnits(m, FixedUnits(mm)) === ContextUnits(m, mm) @test ContextUnits(m, ContextUnits(mm, mm)) === ContextUnits(m, mm) @test_throws DimensionError ContextUnits(m,kg) @test ConstructionBase.constructorof(typeof(1.0m))(2) === 2m end @testset "inverse" begin InverseFunctions.test_inverse(Base.Fix1(ustrip, m), 2m) InverseFunctions.test_inverse(Base.Fix1(ustrip, m), 2mm) end @testset "Types" begin @test Base.complex(Quantity{Float64,NoDims,NoUnits}) == Quantity{Complex{Float64},NoDims,NoUnits} end # A number type for which the results of `real` and `float` # are not `<:Real` or `<:AbstractFloat`, respectively struct NonReal <: Number num::Int end Base.:*(x::NonReal, y::Float64) = x.num * y Base.real(x::NonReal) = x Base.float(x::NonReal) = x # A number type for which `real` and `float` throw an error struct ErrReal <: Number num::Int end Base.:*(x::ErrReal, y::Float64) = x.num * y Base.real(x::ErrReal) = error("real not defined") Base.float(x::ErrReal) = error("float not defined") # A number type for which `real` and `float` do not return a built-in type struct MyFloat64 <: AbstractFloat num::Float64 end Base.:*(x::MyFloat64, y::Float64) = x.num * y # Base.real(x::MyFloat) = x # Base.float(x::MyFloat) = x @testset "Conversion" begin @testset "> Unitless ↔ unitful conversion" begin @test_throws DimensionError convert(typeof(3m), 1) @test_throws DimensionError convert(Quantity{Float64, typeof(𝐋)}, 1) @test_throws DimensionError convert(Float64, 3m) @test @inferred(3m/unit(3m)) === 3 @test @inferred(3.0g/unit(3.0g)) === 3.0 # 1-arg ustrip @test @inferred(ustrip(3*FreeUnits(m))) === 3 @test @inferred(ustrip(3*ContextUnits(m,μm))) === 3 @test @inferred(ustrip(3*FixedUnits(m))) === 3 @test @inferred(ustrip(3)) === 3 @test @inferred(ustrip(3.0m)) === 3.0 # ustrip with type and unit arguments @test @inferred(ustrip(m, 3.0m)) === 3.0 @test @inferred(ustrip(m, 2mm)) === 1//500 @test @inferred(ustrip(mm, 3.0m)) === 3000.0 @test @inferred(ustrip(NoUnits, 3.0m/1.0m)) === 3.0 @test @inferred(ustrip(NoUnits, 3.0m/1.0cm)) === 300.0 @test @inferred(ustrip(cm, missing)) === missing @test @inferred(ustrip(NoUnits, missing)) === missing @test_throws DimensionError ustrip(NoUnits, 3.0m/1.0s) @test @inferred(ustrip(Float64, m, 2mm)) === 0.002 @test @inferred(ustrip(Int, mm, 2.0m)) === 2000 @test @inferred(ustrip(Float32, NoUnits, 5.0u"m"/2.0u"m")) === Float32(2.5) @test @inferred(ustrip(Int, NoUnits, 3.0u"m"/1.0u"cm")) === 300 # convert @test convert(typeof(1mm/m), 3) == 3000mm/m @test convert(typeof(1mm/m), 3*NoUnits) == 3000mm/m @test convert(typeof(1*ContextUnits(mm/m, NoUnits)), 3) == 3000mm/m @test convert(typeof(1*FixedUnits(mm/m)), 3) == 3000*FixedUnits(mm/m) @test convert(Int, 1*FreeUnits(m/mm)) === 1000 @test convert(Int, 1*FixedUnits(m/mm)) === 1000 @test convert(Int, 1*ContextUnits(m/mm, NoUnits)) === 1000 for U = (NoUnits, FixedUnits(NoUnits), ContextUnits(NoUnits, m/mm)) @test convert(Quantity{Int,NoDims,typeof(U)}, 1*FreeUnits(m/mm)) === Quantity{Int,NoDims,typeof(U)}(1000) @test convert(Quantity{Int,NoDims,typeof(U)}, 1*FixedUnits(m/mm)) === Quantity{Int,NoDims,typeof(U)}(1000) @test convert(Quantity{Int,NoDims,typeof(U)}, 1*ContextUnits(m/mm, NoUnits)) === Quantity{Int,NoDims,typeof(U)}(1000) @test convert(Quantity{Int,NoDims,typeof(U)}, 1) === Quantity{Int,NoDims,typeof(U)}(1) end @test convert(Quantity{Int}, 1) === Quantity{Int,NoDims,typeof(NoUnits)}(1) @test convert(Quantity{Int,NoDims}, 1) === Quantity{Int,NoDims,typeof(NoUnits)}(1) # w/ units distinct from w/o units @test 1m != 1 @test 1 != 1m @test (3V+4V*im) != (3+4im) # Issue 26 @unit altL "altL" altLiter 1000*cm^3 true @test convert(Float64, 1altL/cm^3) === 1000.0 # Issue 327 @test uconvert(u"√cm", 1u"√m") == 10u"√cm" end @testset "> Unitful ↔ unitful conversion" begin @testset ">> Numeric conversion" begin @test @inferred(float(3m)) === 3.0m @test @inferred(Float32(3m)) === 3.0f0m @test @inferred(Integer(3.0A)) === 3A @test Rational(3.0m) === (Int64(3)//1)*m @test typeof(convert(typeof(0.0°), 90°)) == typeof(0.0°) @test (3.0+4.0im)*V == (3+4im)*V @test im*V == Complex(0,1)*V for x = (3, 3.0, 3//1, 1+2im, 1.0+2.0im, (1//2)+(3//4)im) for u = (m, °C) @test @inferred(big(x*u)) == big(x)*u @test typeof(big(x*u)) == typeof(big(x)*u) @test big(typeof(x*u)) == typeof(big(x)*u) end q = Quantity{typeof(x),NoDims,typeof(NoUnits)}(x) big_q = Quantity{big(typeof(x)),NoDims,typeof(NoUnits)}(big(x)) @test @inferred(big(q)) == big_q @test typeof(big(q)) == typeof(big_q) @test big(typeof(q)) == typeof(big_q) end end @testset ">> Intra-unit conversion" begin # an essentially no-op uconvert should not disturb numeric type @test @inferred(uconvert(g,1g)) === 1g @test @inferred(uconvert(m,0x01*m)) === 0x01*m @test @inferred(convert(Quantity{Float64, 𝐋}, 1m)) === 1.0m @test 1kg === 1kg @test typeof(1m)(1m) === 1m @test 1*FreeUnits(kg) == 1*ContextUnits(kg,g) @test 1*ContextUnits(kg,g) == 1*FreeUnits(kg) @test 1*FreeUnits(kg) == 1*FixedUnits(kg) @test 1*FixedUnits(kg) == 1*FreeUnits(kg) @test 1*ContextUnits(kg,g) == 1*FixedUnits(kg) @test 1*FixedUnits(kg) == 1*ContextUnits(kg,g) # No auto conversion when working with FixedUnits exclusively. @test_throws ErrorException 1*FixedUnits(kg) == 1000*FixedUnits(g) end @testset ">> Inter-unit conversion" begin @test 1g == 0.001kg # Issue 56 @test 0.001kg == 1g # Issue 56 @test 1kg == 1000g @test !(1kg === 1000g) @test 1inch == (254//100)*cm @test 1ft == 12inch @test 1/mi == 1//(5280ft) @test 1minute == 60s @test 1hr == 60minute @test 1d == 24hr @test 1yr == 365.25d @test 1J == 1kg*m^2/s^2 @test typeof(1cm)(1m) === 100cm @test (3V+4V*im) != (3m+4m*im) @test_throws DimensionError uconvert(m, 1kg) @test_throws DimensionError uconvert(m, 1*ContextUnits(kg,g)) @test_throws DimensionError uconvert(ContextUnits(m,mm), 1kg) @test_throws DimensionError uconvert(m, 1*FixedUnits(kg)) @test uconvert(g, 1*FixedUnits(kg)) == 1000g # manual conversion okay @test (1kg, 2g, 3mg, missing) .|> g === (1000g, 2g, (3//1000)g, missing) # Issue 79: @test isapprox(upreferred(Unitful.ε0), 8.85e-12u"F/m", atol=0.01e-12u"F/m") # Issue 261: @test 1u"rps" == 360°/s @test 1u"rps" == 2π/s @test 1u"rpm" == 360°/minute @test 1u"rpm" == 2π/minute # Issue 430: # definition for arcsecond taken from UnitfulAngles.jl # definition for Jy arcsecond taken from UnitfulAstro.jl @unit angles_arcsecond "angles_″" angles_Arcsecond °//3600 false @unit astro_Jy "astro_Jy" astro_Jansky 1e-23u"erg*s^-1*cm^-2*Hz^-1" true @test uconvert( astro_Jy / angles_arcsecond^2, 1.0u"GHz^2 * J * c^-2" ) ≈ 2.6152205956835644e16 * astro_Jy * angles_arcsecond^-2 # Issue 458: @test deg2rad(360°) ≈ 2π * rad @test rad2deg(2π * rad) ≈ 360° # Issue 647: @test uconvert(u"kb^1000", 1u"kb^1001 * b^-1") === 1000u"kb^1000" @test uconvert(u"kOe^1000", 1u"kOe^1001 * Oe^-1") === 1000u"kOe^1000" # Issue 753: # preserve the floating point precision of quantities for T = [Float16, Float32, Float64, BigFloat] @test Unitful.numtype(uconvert(m, T(100)cm)) === T @test Unitful.numtype(uconvert(cm, (T(1)π + im) * m)) === Complex{T} @test Unitful.numtype(uconvert(rad, T(360)°)) === T @test Unitful.numtype(uconvert(°, (T(2)π + im) * rad)) === Complex{T} @test typeof(upreferred(T(360)°)) === T end @test uconvert(rad, NonReal(360)°) == uconvert(rad, 360°) @test uconvert(rad, ErrReal(360)°) == uconvert(rad, 360°) @test uconvert(rad, MyFloat64(360)°) == uconvert(rad, 360°) @test upreferred(NonReal(360)°) == upreferred(360°) @test upreferred(ErrReal(360)°) == upreferred(360°) @test upreferred(MyFloat64(360)°) == upreferred(360°) # Floating point overflow/underflow in uconvert can happen if the # conversion factor is large, because uconvert does not cancel # common basefactors (or just for really large exponents and/or # SI prefixes). This test makes sure that uconvert does not silently # return NaN, Inf, or 0 in these cases, i.e. either returns a finite # result or throws an error indicating that it cannot handle the # conversion. @test_or_throws ArgumentError is_finite_nonzero(uconvert(u"kb^12", 1u"b^12")) @test_or_throws ArgumentError is_finite_nonzero(uconvert(u"ab^11", 1u"Tb^11")) @test_or_throws ArgumentError is_finite_nonzero(uconvert(u"Tb^11", 1u"ab^11")) @test_or_throws ArgumentError is_finite_nonzero(uconvert(u"b^11 * eV", 1u"m^22 * J")) @test_or_throws ArgumentError is_finite_nonzero(uconvert(u"m^22 * J", 1u"b^11 * eV")) # min/max had code doing the equivalent of uconvert, and suffering # from similar problems as issue 647 (see above) @test_or_throws ArgumentError max(1u"Ym^18", 1u"Em^18") === 1u"Ym^18" @test_or_throws ArgumentError max(1u"Em^18", 1u"Ym^18") === 1u"Ym^18" @test_or_throws ArgumentError min(1u"Ym^18", 1u"Em^18") === 1u"Em^18" @test_or_throws ArgumentError min(1u"Em^18", 1u"Ym^18") === 1u"Em^18" @test_or_throws ArgumentError minmax(1u"Ym^18", 1u"Em^18") === (1u"Em^18", 1u"Em^18") @test_or_throws ArgumentError max(1u"fb^8", 1u"ab^8") === 1u"fb^8" @test_or_throws ArgumentError max(1u"ab^8", 1u"fb^8") === 1u"fb^8" @test_or_throws ArgumentError min(1u"fb^8", 1u"ab^8") === 1u"ab^8" @test_or_throws ArgumentError min(1u"ab^8", 1u"fb^8") === 1u"ab^8" @test_or_throws ArgumentError minmax(1u"fb^8", 1u"ab^8") === (1u"ab^8", 1u"fb^8") # Issue 660: @test uconvert(u"Å * ps^-2", 1.0u"kcal*Å^-1*g^-1") ≈ 418.4u"Å * ps^-2" # Issue 780: @unit Fr "Fr" franklin 1sqrt(dyn)*cm false @unit Test780 "Test780" test780 1sqrt(mN)*cm false @test uconvert(dyn, 1Fr^2/cm^2) ≈ 1dyn @test uconvert(mN, 1Test780^2/cm^2) ≈ 1mN @test_broken uconvert(dyn, 1Fr^2/cm^2) === 1dyn @test_broken uconvert(mN, 1Test780^2/cm^2) === 1mN end end end include("dates.jl") @testset "Temperature and affine quantities" begin @testset "Affine transforms and quantities" begin @test 1°C isa RelativeScaleTemperature @test !isa(1°C, AbsoluteScaleTemperature) @test 1K isa AbsoluteScaleTemperature @test !isa(1K, RelativeScaleTemperature) @test_throws AffineError °C*°C @test_throws AffineError °C*K @test_throws AffineError (0°C)*(0°C) @test_throws AffineError (1°C)/(1°C) @test_throws AffineError °C^2 let x = 2 @test_throws AffineError °C^x end @test_throws AffineError inv(°C) @test_throws AffineError inv(0°C) @test_throws AffineError sqrt(°C) @test_throws AffineError sqrt(0°C) @test_throws AffineError cbrt(°C) @test_throws AffineError cbrt(0°C) @test_throws AffineError 32°F + 1°F @test_throws AffineError (32°F) * 2 @test_throws AffineError 2 * (32°F) @test_throws AffineError (32°F) / 2 @test_throws AffineError 2 / (32°F) for f = (:div, :rem, :divrem) @eval for r = (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp, RoundToZero, RoundUp, RoundDown) @test_throws AffineError $f(32°F, 2°F, r) @test_throws AffineError $f(32°F, 2K, r) @test_throws AffineError $f(32K, 2°F, r) end end for f = (:div, :cld, :fld, :rem, :mod, :divrem, :fldmod) @eval begin @test_throws AffineError $f(32°F, 2°F) @test_throws AffineError $f(32°F, 2K) @test_throws AffineError $f(32K, 2°F) end end @test zero(100°C) === 0K @test zero(typeof(100°C)) === 0K @test oneunit(100°C) === 1K @test oneunit(typeof(100°C)) === 1K @test_throws AffineError one(100°C) @test_throws AffineError one(typeof(100°C)) @test 0°C isa AffineQuantity{T, 𝚯} where T # is "relative temperature" @test 0°C isa Temperature # dimensional correctness @test °C isa AffineUnits{N, 𝚯} where N @test °C isa TemperatureUnits @test @inferred(uconvert(°F, 0°C)) === (32//1)°F # Some known conversions... @test @inferred(uconvert(°C, 32°F)) === (0//1)°C # ⋮ @test @inferred(uconvert(°C, 212°F)) === (100//1)°C # ⋮ @test @inferred(uconvert(°C, 0x01*°C)) === 0x01*°C # Preserve numeric type # The next test is a little funky but checks the `affineunit` functionality @test @inferred(uconvert(°F, 0*Unitful.affineunit(27315K//100 + 5K//9))) === (33//1)°F end @testset "Temperature differences" begin @test @inferred(uconvert(Ra, 0K)) === 0Ra//1 @test @inferred(uconvert(K, 1Ra)) === 5K//9 @test @inferred(uconvert(μm/(m*Ra), 9μm/(m*K))) === 5μm/(m*Ra)//1 @test @inferred(uconvert(FreeUnits(Ra), 4.2K)) ≈ 7.56Ra @test @inferred(unit(uconvert(FreeUnits(Ra), 4.2K))) === FreeUnits(Ra) @test @inferred(uconvert(FreeUnits(Ra), 4.2*ContextUnits(K))) ≈ 7.56Ra @test @inferred(unit(uconvert(FreeUnits(Ra), 4.2*ContextUnits(K)))) === FreeUnits(Ra) @test @inferred(unit(uconvert(ContextUnits(Ra), 4.2K))) === ContextUnits(Ra) let cc = ContextUnits(°C, °C), kc = ContextUnits(K, °C), rac = ContextUnits(Ra, °C) @test 100°C + 1K === (7483//20)K @test 100cc + 1K === (101//1)cc @test 100cc + 1K == (101//1)°C @test 1K + 100cc === (101//1)cc @test 1K + 100cc == (101//1)°C @test 100°C + 1Ra === (67267//180)K @test 100°C - 212°F === (0//1)K @test 100°C - 211°F === (5//9)K @test 100°C - 1°C === 99K @test 100°C - 32°F === (100//1)K @test 10cc + 2.0K/hr * 60minute + 3.0K/hr * 60minute === 15.0cc @test 10cc + 5kc === (15//1)cc @test 10°C + 5kc === (15//1)cc @test 10°C + (9//5)rac === (11//1)cc end end @testset "Promotion" begin @test_throws ErrorException Unitful.preferunits(°C) @test @inferred(eltype([1°C, 1K])) <: Quantity{Rational{Int}, 𝚯, typeof(K)} @test @inferred(eltype([1.0°C, 1K])) <: Quantity{Float64, 𝚯, typeof(K)} @test @inferred(eltype([1°C, 1°F])) <: Quantity{Rational{Int}, 𝚯, typeof(K)} @test @inferred(eltype([1.0°C, 1°F])) <: Quantity{Float64, 𝚯, typeof(K)} # context units should be identifiable as affine @test ContextUnits(°C, °F) isa AffineUnits let fc = ContextUnits(°F, °C), cc = ContextUnits(°C, °C) @test @inferred(promote(1fc, 1cc)) === ((-155//9)cc, (1//1)cc) @test @inferred(eltype([1cc, 1°C])) <: Quantity{Rational{Int}, 𝚯, typeof(cc)} end end end # preferred units work on AbstractQuantity struct QQQ <: Unitful.AbstractQuantity{Float64,𝐋,typeof(cm)} val::Float64 end Unitful.uconvert(U::Unitful.Units, q::QQQ) = uconvert(U, Quantity(q.val, cm)) @testset "Promotion" begin @testset "> Unit preferences" begin # Should warn on possible redundant units issue (ms and s) @test_logs (:warn, r"^Preferred units contain complex units") Unitful.preferunits(C/ms) # Test for wacky preferred units functionality Unitful.preferunits(C/s) @test @inferred(upreferred(V/m)) == kg*m*C^-1*s^-2 @test dimension(upreferred(V/m)) == dimension(V/m) # Reset preferred units to default, except for units of dimension 𝐋*𝐌*𝐈^-1*𝐓^-3, # because upreferred has already been called for that dimension Unitful.preferunits(A) # Only because we favor SI, we have the following: @test @inferred(upreferred(N)) === kg*m/s^2 @test @inferred(upreferred(dimension(N))) === kg*m/s^2 @test @inferred(upreferred(g)) === kg @test @inferred(upreferred(FreeUnits(g))) === FreeUnits(kg) # Test special units behaviors @test @inferred(upreferred(ContextUnits(g,mg))) === ContextUnits(mg,mg) @test @inferred(upreferred(FixedUnits(kg))) === FixedUnits(kg) @test @inferred(upreferred(upreferred(1.0ContextUnits(kg,g)))) === 1000.0ContextUnits(g,g) @test @inferred(upreferred(unit(1g |> ContextUnits(g,mg)))) === ContextUnits(mg,mg) @test @inferred(upreferred(1g |> ContextUnits(g,mg))) == 1000mg @test @inferred(upreferred(1N)) === 1*kg*m/s^2 @test ismissing(upreferred(missing)) # preferred units work on AbstractQuantity @test @inferred(upreferred(QQQ(10))) == 0.1m end @testset "> promote_unit" begin @test Unitful.promote_unit(FreeUnits(m)) === FreeUnits(m) @test Unitful.promote_unit(ContextUnits(m,mm)) === ContextUnits(m,mm) @test Unitful.promote_unit(FixedUnits(kg)) === FixedUnits(kg) @test Unitful.promote_unit(ContextUnits(m,mm), ContextUnits(km,mm)) === ContextUnits(mm,mm) @test Unitful.promote_unit(FreeUnits(m), ContextUnits(mm,km)) === ContextUnits(km,km) @test Unitful.promote_unit(FixedUnits(kg), ContextUnits(g,g)) === FixedUnits(kg) @test Unitful.promote_unit(ContextUnits(g,g), FixedUnits(kg)) === FixedUnits(kg) @test Unitful.promote_unit(FixedUnits(kg), FreeUnits(g)) === FixedUnits(kg) @test Unitful.promote_unit(FreeUnits(g), FixedUnits(kg)) === FixedUnits(kg) @test_throws DimensionError Unitful.promote_unit(m,kg) # FixedUnits throw a promotion error @test_throws ErrorException Unitful.promote_unit(FixedUnits(m), FixedUnits(mm)) # Only because we favor SI, we have the following: @test Unitful.promote_unit(m,km) === m @test Unitful.promote_unit(m,km,cm) === m @test Unitful.promote_unit(ContextUnits(m,mm), ContextUnits(km,cm)) === FreeUnits(m) end @testset "> Simple promotion" begin # promotion should do nothing to units alone # promote throws an error if no types are be changed @test_throws ErrorException promote(m, km) @test_throws ErrorException promote(ContextUnits(m, km), ContextUnits(mm, km)) @test_throws ErrorException promote(FixedUnits(m), FixedUnits(km)) # promote the numeric type @test @inferred(promote(1.0m, 1m)) === (1.0m, 1.0m) @test @inferred(promote(1m, 1.0m)) === (1.0m, 1.0m) @test @inferred(promote(1.0g, 1kg)) === (0.001kg, 1.0kg) @test @inferred(promote(1g, 1.0kg)) === (0.001kg, 1.0kg) @test @inferred(promote(1.0m, 1kg)) === (1.0m, 1.0kg) @test @inferred(promote(1kg, 1.0m)) === (1.0kg, 1.0m) @test_broken @inferred(promote(1.0m, 1)) === (1.0m, 1.0) # issue 52 @test @inferred(promote(π, 180°)) === (float(π), float(π)) # issue 168 @test @inferred(promote(180°, π)) === (float(π), float(π)) # issue 168 # prefer no units for dimensionless numbers @test @inferred(promote(1.0mm/m, 1.0km/m)) === (0.001,1000.0) @test @inferred(promote(1.0cm/m, 1.0mm/m, 1.0km/m)) === (0.01,0.001,1000.0) @test @inferred(promote(1.0rad,1.0°)) === (1.0,π/180.0) # Quantities with promotion context # Context overrides free units nm2μm = ContextUnits(nm,μm) μm2μm = ContextUnits(μm,μm) μm2mm = ContextUnits(μm,mm) @test @inferred(promote(1.0nm2μm, 1.0m)) === (0.001μm2μm, 1e6μm2μm) @test @inferred(promote(1.0m, 1.0μm2μm)) === (1e6μm2μm, 1.0μm2μm) @test ===(upreferred.(unit.(promote(1.0nm2μm, 2nm2μm)))[1], ContextUnits(μm,μm)) @test ===(upreferred.(unit.(promote(1.0nm2μm, 2nm2μm)))[2], ContextUnits(μm,μm)) # Context agreement @test @inferred(promote(1.0nm2μm, 1.0μm2μm)) === (0.001μm2μm, 1.0μm2μm) @test @inferred(promote(1μm2μm, 1.0nm2μm, 1.0m)) === (1.0μm2μm, 0.001μm2μm, 1e6μm2μm) @test @inferred(promote(1μm2μm, 1.0nm2μm, 1.0s)) === (1.0μm2μm, 1.0nm2μm, 1.0s) # Context disagreement: fall back to free units @test @inferred(promote(1.0nm2μm, 1.0μm2mm)) === (1e-9m, 1e-6m) end @testset "> Promotion during array creation" begin @test typeof([1.0m,1.0m]) == Array{typeof(1.0m),1} @test typeof([1.0m,1m]) == Array{typeof(1.0m),1} @test typeof([1.0m,1cm]) == Array{typeof(1.0m),1} @test typeof([1kg,1g]) == Array{typeof(1kg//1),1} @test typeof([1.0m,1]) == Array{Quantity{Float64},1} @test typeof([1.0m,1kg]) == Array{Quantity{Float64},1} @test typeof([1.0m/s 1; 1 0]) == Array{Quantity{Float64},2} end @testset "> Issue 52" begin x,y = 10m, 1 px,py = promote(x,y) # promoting the second time should not change the types @test_throws ErrorException promote(px, py) end @testset "> Some internal behaviors" begin # quantities @test Unitful.numtype(Quantity{Float64}) <: Float64 @test Unitful.numtype(Quantity{Float64, 𝐋}) <: Float64 @test Unitful.numtype(typeof(1.0kg)) <: Float64 @test Unitful.numtype(1.0kg) <: Float64 end end @testset "Hashing" begin @test hash(big(1.0)m) === hash(big(1.0)m) @test hash(1.0m) === hash(1m) @test hash(0.5m) === hash((1//2)m) @test hash(2.0m) === hash(2.0ContextUnits(m, cm)) @test hash(3.0m) === hash(3.0FixedUnits(m)) @test_broken hash(0.5m) === hash(500mm) @test_broken hash(1rad) === hash(1) end @testset "Unit string parsing" begin @test uparse("m") == m @test uparse("m,s") == (m,s) @test uparse("1.0") == 1.0 @test uparse("m/s") == m/s @test uparse("N*m") == N*m @test uparse("1.0m/s") == 1.0m/s @test uparse("m^-1") == m^-1 @test uparse("dB/Hz") == dB/Hz @test uparse("3.0dB/Hz") == 3.0dB/Hz # Invalid unit strings @test_throws Meta.ParseError uparse("N m") @test_throws ArgumentError uparse("abs(2)") @test_throws ArgumentError uparse("(1,2)") @test_throws ArgumentError uparse("begin end") # test ustrcheck_bool @test_throws ArgumentError uparse("basefactor") # non-Unit symbols # ustrcheck_bool(::Quantity) @test uparse("h") == Unitful.h @test uparse("π") == π # issue 112 end @testset "Unit and dimensional analysis" begin @test @inferred(unit(1m^2)) === m^2 @test @inferred(unit(typeof(1m^2))) === m^2 @test @inferred(unit(Float64)) === NoUnits @test @inferred(unit(Union{typeof(1m^2),Missing})) === m^2 @test @inferred(unit(Union{Float64,Missing})) === NoUnits @test @inferred(unit(missing)) === missing @test @inferred(unit(Missing)) === missing @test @inferred(dimension(1m^2)) === 𝐋^2 @test @inferred(dimension(1*ContextUnits(m,km)^2)) === 𝐋^2 @test @inferred(dimension(typeof(1m^2))) === 𝐋^2 @test @inferred(dimension(Float64)) === NoDims @test @inferred(dimension(m^2)) === 𝐋^2 @test @inferred(dimension(1m/s)) === 𝐋/𝐓 @test @inferred(dimension(m/s)) === 𝐋/𝐓 @test @inferred(dimension(1u"mol")) === 𝐍 @test @inferred(dimension(μm/m)) === NoDims @test @inferred(dimension(missing)) === missing @test @inferred(dimension(Missing)) === missing @test dimension.([1u"m", 1u"s"]) == [𝐋, 𝐓] @test dimension.([u"m", u"s"]) == [𝐋, 𝐓] @test (𝐋/𝐓)^2 === 𝐋^2 / 𝐓^2 @test isa(m, LengthUnits) @test isa(ContextUnits(m,km), LengthUnits) @test isa(FixedUnits(m), LengthUnits) @test !isa(m, AreaUnits) @test !isa(ContextUnits(m,km), AreaUnits) @test !isa(FixedUnits(m), AreaUnits) @test !isa(m, MassUnits) @test isa(m^2, AreaUnits) @test !isa(m^2, LengthUnits) @test isa(1m, Length) @test isa(1*ContextUnits(m,km), Length) @test isa(1*FixedUnits(m), Length) @test !isa(1m, LengthUnits) @test !isa(1m, Area) @test !isa(1m, Luminosity) @test isa(1ft, Length) @test isa(1m^2, Area) @test !isa(1m^2, Length) @test isa(1inch^3, Volume) @test isa(1/s, Frequency) @test isa(1kg, Mass) @test isa(1s, Time) @test isa(1A, Current) @test isa(1K, Temperature) @test isa(1u"cd", Luminosity) @test isa(2π*rad*1.0m, Length) @test isa(u"h", Action) @test isa(3u"dBm", Power) @test isa(3u"dBm*Hz*s", Power) @test isa(1kg/s, MassFlow) @test isa(1mol/s, MolarFlow) @test isa(1m^3/s, VolumeFlow) end # A number type with non-commutative multiplication struct MatNum <: Number mat::Matrix{Int} end Base.:(==)(x::MatNum, y::MatNum) = x.mat == y.mat Base.:*(x::MatNum, y::MatNum) = MatNum(x.mat*y.mat) # A number type that defines only `<=`, not `==` or `<` struct Issue399 <: Integer num::Int end Base.:<(::Issue399, ::Issue399) = error("< not defined") Base.:(==)(::Issue399, ::Issue399) = error("== not defined") Base.:(<=)(x::Issue399, y::Issue399) = x.num <= y.num @testset "Mathematics" begin @testset "> Comparisons" begin # make sure we are just picking one of the arguments, without surprising conversions # happening to the units... @test min(1FreeUnits(hr), 1FreeUnits(s)) === 1FreeUnits(s) @test min(1FreeUnits(hr), 1ContextUnits(s,ms)) === 1ContextUnits(s,ms) @test min(1ContextUnits(s,ms), 1FreeUnits(hr)) === 1ContextUnits(s,ms) @test min(1ContextUnits(s,minute), 1ContextUnits(hr,s)) === 1ContextUnits(s,minute) @test max(1ContextUnits(ft, mi), 1ContextUnits(m,mm)) === 1ContextUnits(m,mm) @test min(1FreeUnits(hr), 1FixedUnits(s)) === 1FixedUnits(s) @test min(1FixedUnits(s), 1FreeUnits(hr)) === 1FixedUnits(s) @test min(1FixedUnits(s), 1ContextUnits(ms,s)) === 1ContextUnits(ms,s) @test min(1ContextUnits(ms,s), 1FixedUnits(s)) === 1ContextUnits(ms,s) # automatic conversion prohibited @test_throws ErrorException min(1FixedUnits(s), 1FixedUnits(hr)) @test min(1FixedUnits(s), 1FixedUnits(s)) === 1FixedUnits(s) # now move on, presuming that's working well. @test max(1ft, 1m) == 1m @test max(10J, 1kg*m^2/s^2) === 10J @test max(1J//10, 1kg*m^2/s^2) === 1kg*m^2/s^2 @test @inferred(0m < 1.0m) @test @inferred(2.0m < 3.0m) @test @inferred(2.0m .< 3.0m) @test !(@inferred 3.0m .< 3.0m) @test @inferred(2.0m <= 3.0m) @test @inferred(3.0m <= 3.0m) @test @inferred(3.0m <= 3000.0mm) @test @inferred(2.0m .<= 3.0m) @test @inferred(3.0m .<= 3.0m) @test !@inferred(1.0m/mm <= 999) @test @inferred(1.0m/mm <= 1000) @test !@inferred(1.1 <= 1000mm/m) @test @inferred(1.0 <= 1000mm/m) @test @inferred(1μm/m < 1) @test @inferred(1 > 1μm/m) @test @inferred(1μm/m < 1mm/m) @test @inferred(1mm/m > 1μm/m) @test_throws DimensionError 1m < 1kg @test_throws DimensionError 1m < 1 @test_throws DimensionError 1 < 1m @test_throws DimensionError 1mm/m < 1m @test_throws DimensionError 1mm/m <= 1m @test Base.rtoldefault(typeof(1.0u"m")) === Base.rtoldefault(typeof(1.0)) @test Base.rtoldefault(typeof(1u"m")) === Base.rtoldefault(Int) @test_throws ErrorException Issue399(1)m < Issue399(2)m @test_throws ErrorException Issue399(1)m == Issue399(1)m @test @inferred(Issue399(1)m <= Issue399(2)m) # check NaN handling in min, max (consistent with isless) @test isequal(min(NaN * u"m", 1.0u"m"), 1.0u"m") @test isequal(min(1.0u"m", NaN * u"m"), 1.0u"m") @test isequal(max(NaN * u"m", 1.0u"m"), NaN * u"m") @test isequal(max(1.0u"m", NaN * u"m"), NaN * u"m") end @testset "> Addition and subtraction" begin @test @inferred(+(1A)) == 1A # Unary addition @test @inferred(3m + 3m) == 6m # Binary addition @test @inferred(-(1kg)) == (-1)*kg # Unary subtraction @test @inferred(3m - 2m) == 1m # Binary subtraction @test @inferred(zero(1m)) === 0m # Additive identity @test @inferred(zero(typeof(1m))) === 0m @test @inferred(zero(typeof(1.0m))) === 0.0m @test_throws ArgumentError zero(Quantity{Int}) @test zero(Quantity{Int, 𝐋}) == 0m @test zero(Quantity{Int, 𝐋}) isa Quantity{Int} @test @inferred(π/2*u"rad" + 90u"°") ≈ π # Dimless quantities @test @inferred(π/2*u"rad" - 90u"°") ≈ 0 # Dimless quantities @test @inferred(90u"deg" - 90u"°") == 0 @test_throws DimensionError 1+1m # Dim mismatched @test_throws DimensionError 1-1m end @testset "> Multiplication" begin @test @inferred(FreeUnits(g)*FreeUnits(kg)) === FreeUnits(g*kg) @test @inferred(ContextUnits(m,mm)*ContextUnits(kg,g)) === ContextUnits(m*kg,mm*g) @test @inferred(ContextUnits(m,mm)*FreeUnits(g)) === ContextUnits(m*g,mm*kg) @test @inferred(FreeUnits(g)*ContextUnits(m,mm)) === ContextUnits(m*g,mm*kg) @test @inferred(FixedUnits(g)*FixedUnits(kg)) === FixedUnits(g*kg) @test @inferred(FixedUnits(g)*FreeUnits(kg)) === FixedUnits(g*kg) @test @inferred(FixedUnits(g)*ContextUnits(kg,kg)) === FixedUnits(g*kg) @test @inferred(*(1s)) === 1s # Unary multiplication @test @inferred(3m * 2cm) === 3cm * 2m # Binary multiplication @test @inferred((3m)*m) === 3*(m*m) # Associative multiplication @test @inferred(true*1kg) === 1kg # Boolean multiplication (T) @test @inferred(false*1kg) === 0kg # Boolean multiplication (F) @test @inferred(true*(1+im)kg) === (1+im)kg # Boolean-complex multiplication (T) @test @inferred(false*(1+im)kg) === (0+0im)kg # Boolean-complex multiplication (F) @test @inferred((1+im)kg*true) === (1+im)kg # Complex-boolean multiplication (T) @test @inferred((1+im)kg*false) === (0+0im)kg # Complex-boolean multiplication (F) @test @inferred((NaN*kg)*false) === 0.0kg # `false` acts as "strong zero" @test @inferred(false*(-Inf*kg)) === -0.0kg # `false` acts as "strong zero" @test typeof(one(eltype([1.0s, 1kg]))) <: Float64 # issue 159, multiplicative identity # Multiplication can be non-commutative @test Quantity(MatNum([1 2; 3 4]), m) * MatNum([5 6; 7 8]) == Quantity(MatNum([19 22; 43 50]), m) @test MatNum([5 6; 7 8]) * Quantity(MatNum([1 2; 3 4]), m) == Quantity(MatNum([23 34; 31 46]), m) end @testset "> Division" begin @test 360° / 2 === 180.0° # Issue 110 @test 360° // 2 === 180°//1 @test 2m // 5s == (2//5)*(m/s) # Units propagate through rationals @test (2//3)*m // 5 == (2//15)*m # Quantity // Real @test 5.0m // s === 5.0m/s # Quantity // Unit. Just pass units through @test s//(5m) === (1//5)*s/m # Unit // Quantity. Will fail if denom is float @test (m//2) === 1//2 * m # Unit // Real @test (2//m) === (2//1) / m # Real // Unit @test (m//s) === m/s # Unit // Unit @test m / missing === missing # Unit / missing @test missing / m === missing # Missing / Unit (// is not defined for Missing) @test missing * u"dB" === missing # Missing * MixedUnits @test u"dB" * missing === missing # MixedUnits * Missing @test missing / u"dB" === missing # Missing / MixedUnits @test u"dB" / missing === missing # MixedUnits / Missing @test @inferred(div(10m, -3cm)) === -333 @test @inferred(div(10m, 3)) === 3m @test @inferred(div(10, 3m)) === 3/m @test @inferred(fld(10m, -3cm)) === -334 @test @inferred(fld(10m, 3)) === 3m @test @inferred(fld(10, 3m)) === 3/m @test @inferred(cld(10m, 3)) === 4m @test @inferred(cld(10, 3m)) === 4/m @test rem(10m, -3cm) == 1.0cm @test mod(10m, -3cm) == -2.0cm @test mod(1hr+3minute+5s, 24s) == 17s @test mod2pi(360°) === 0° # 2pi is 360° @test mod2pi(0.5pi*u"m/dm") ≈ pi # just testing the dimensionless fallback @test modf(2.5rad) === (0.5, 2.0) @test modf(-250cm/m) === (-1//2, -2//1) @test_throws MethodError modf(1m) @test @inferred(inv(s)) === s^-1 @test inv(ContextUnits(m,km)) === ContextUnits(m^-1,km^-1) @test inv(FixedUnits(m)) === FixedUnits(m^-1) end @testset "> Exponentiation" begin @test @inferred(m^3/m) === m^2 @test @inferred(𝐋^3/𝐋) === 𝐋^2 @test @inferred(sqrt(4m^2)) === 2.0m @test sqrt(4m^(2//3)) === 2.0m^(1//3) @test @inferred(sqrt(𝐋^2)) === 𝐋 @test @inferred(sqrt(m^2)) === m @test @inferred(cbrt(8m^3)) === 2.0m @test cbrt(8m) === 2.0m^(1//3) @test @inferred(cbrt(𝐋^3)) === 𝐋 @test @inferred(cbrt(m^3)) === m @test (2m)^3 === 8*m^3 @test (8m)^(1//3) === 2.0*m^(1//3) @test @inferred(cis(90°)) == im @test @inferred(cis((90 - rad2deg(1)*im)°)) ≈ ℯ*im # Test inferrability of literal powers _pow_m3(x) = x^-3 _pow_0(x) = x^0 _pow_3(x) = x^3 _pow_2_3(x) = x^(2//3) @static if VERSION ≥ v"1.8.0-DEV.501" @test @inferred(_pow_2_3(m)) == m^(2//3) @test @inferred(_pow_2_3(𝐋)) == 𝐋^(2//3) @test @inferred(_pow_2_3(1.0m)) == 1.0m^(2//3) end @test @inferred(_pow_m3(m)) == m^-3 @test @inferred(_pow_0(m)) == NoUnits @test @inferred(_pow_3(m)) == m^3 @test @inferred(_pow_m3(𝐋)) == 𝐋^-3 @test @inferred(_pow_0(𝐋)) == NoDims @test @inferred(_pow_3(𝐋)) == 𝐋^3 @test @inferred(_pow_m3(1.0m)) == 1.0m^-3 @test @inferred(_pow_0(1.0m)) == 1.0 @test @inferred(_pow_3(1.0m)) == 1.0m^3 end @testset "> Trigonometry" begin @test @inferred(sin(0.0rad)) == 0.0 @test @inferred(cos(π*rad)) == -1 @test @inferred(tan(π*rad/4)) ≈ 1 @test @inferred(csc(π*rad/2)) == 1 @test @inferred(sec(0.0*rad)) == 1 @test @inferred(cot(π*rad/4)) ≈ 1 @test @inferred(sin(90°)) == 1 @test @inferred(cos(0.0°)) == 1 @test @inferred(tan(45°)) == 1 @test @inferred(csc(90°)) == 1 @test @inferred(sec(0°)) == 1 @test @inferred(cot(45°)) == 1 @test @inferred(asin(1m/1000mm)) == 90° @test @inferred(acos(-1mm/1000μm)) == π*rad @test @inferred(atan(2000sqrt(3)ms/2.0s)) == 60° @test @inferred(acsc(5.0Hz*0.2s)) == π/2 @test @inferred(asec(1m/1nm)) ≈ π/2 @test @inferred(acot(2sqrt(3)s/2000ms)) ≈ 30° @test @inferred(sincos(250mrad)) === sincos(0.25) @test @inferred(sincos((1+2im)rad)) === sincos(1+2im) @test @inferred(sincos(30°)) === (sind(30), cosd(30)) @test @inferred(sinh(0.0rad)) == 0.0 @test @inferred(sinh(1J/N/m) + cosh(1rad)) ≈ MathConstants.e @test @inferred(tanh(1m/1μm)) == 1 @test @inferred(csch(0.0°)) == Inf @test @inferred(sech(0K/Ra)) == 1 @test @inferred(coth(1e3m*mm^-1)) == 1 @test @inferred(asinh(0.0mg/kg)) == 0 @test @inferred(acosh(1mm/1000μm)) == 0 @test @inferred(atanh(0W*s/J)) == 0 @test @inferred(acsch(hr/yr * 0)) == Inf @test @inferred(asech(1.0m/1000.0mm)) == 0 @test @inferred(acoth(1km/1000m)) == Inf @test @inferred(sinpi(rad/2)) == 1 @test @inferred(cospi(1rad)) == -1 @test @inferred(sinc(1rad)) === 0 @test @inferred(cosc(1ft/3inch)) === 0.25 @test @inferred(cispi(rad/2)) === complex(0.0, 1.0) @test @inferred(cispi(rad/2 + im*rad)) ≈ complex(0.0, exp(-π)) @test @inferred(sincospi(rad/2)) === (1.0, 0.0) if isdefined(Base, :tanpi) @test @inferred(tanpi(1f0rad)) === tanpi(1f0) @test @inferred(tanpi(250mrad)) === tanpi(0.25) @test @inferred(tanpi(-100mm/m)) === tanpi(-1//10) end @test @inferred(atan(m*sqrt(3),1m)) ≈ 60° @test @inferred(atan(m*sqrt(3),1.0m)) ≈ 60° @test @inferred(atan(m*sqrt(3),1000mm)) ≈ 60° @test @inferred(atan(m*sqrt(3),1e+3mm)) ≈ 60° @test_throws DimensionError atan(m*sqrt(3),1e+3s) @test @inferred(angle((3im)*V)) ≈ 90° @test @inferred(sincosd(5°)) == sincos(5°) == (sind(5°), cosd(5°)) end @testset "> Exponentials and logarithms" begin for f in (exp, exp10, exp2, expm1, log, log10, log1p, log2) @test f(1.0 * u"m/dm") ≈ f(10) end end @testset "> Matrix inversion" begin @test inv([1 1; -1 1]u"nm") ≈ [0.5 -0.5; 0.5 0.5]u"nm^-1" end @testset "> Is functions" begin @test_throws ErrorException isinteger(1.0m) @test isinteger(1.4m/mm) @test !isinteger(1.4mm/m) @test_throws ErrorException iseven(1.0m) @test iseven(2rad) @test !iseven(1rad) if hasmethod(iseven, (Float64,)) @test iseven(0.5m/mm) @test !iseven(0.125m/mm) end @test_throws ErrorException isodd(1.0m) @test isodd(1rad) @test !isodd(2rad) if hasmethod(isodd, (Float64,)) @test isodd(0.125m/mm) @test !isodd(0.5m/mm) end @test isfinite(1.0m) @test !isfinite(Inf*m) @test isnan(NaN*m) @test !isnan(1.0m) @static if VERSION ≥ v"1.7.0-DEV.119" @test isunordered(NaN*m) @test !isunordered(Inf*m) @test !isunordered(1.0*m) end end @testset "> Floating point tests" begin @test isapprox(1.0u"m",(1.0+eps(1.0))u"m") @test isapprox(1.0u"μm/m",1e-6) @test !isapprox(1.0u"μm/m",1e-7) @test !isapprox(1.0u"m",5) @test frexp(1.5m) == (0.75m, 1.0) @test unit(nextfloat(0.0m)) == m @test unit(nextfloat(0.0m, 4)) == m @test ustrip(nextfloat(0.0m, 4)) == nextfloat(0.0, 4) @test unit(prevfloat(0.0m)) == m @test unit(prevfloat(0.0m, 4)) == m @test ustrip(prevfloat(0.0m, 4)) == prevfloat(0.0, 4) # NaN behavior @test NaN*m != NaN*m @test isequal(NaN*m, NaN*m) @test isapprox(1.0u"m", 1.1u"m"; atol=0.2u"m") @test !isapprox(1.0u"m", 1.1u"m"; atol=0.05u"m") @test isapprox(1.0u"m", 1.1u"m"; atol=200u"mm") @test !isapprox(1.0u"m", 1.1u"m"; atol=50u"mm") @test isapprox(1.0u"m", 1.1u"m"; rtol=0.2) @test !isapprox(1.0u"m", 1.1u"m"; rtol=0.05) # isapprox with dimensionless Quantity vs Number and atol @test isapprox(1u"rad", 1.1; atol=0.5u"rad") @test !isapprox(1u"rad", 1.1; atol=0.01u"rad") @test isapprox(1.1, 1u"rad"; atol=0.5u"rad") @test isapprox(1.0u"rad", 1.1; atol=0.5) @test isapprox(1.0u"rad", 1.01; rtol=0.1) # Issue 465: z = fill((1+im)m, 2, 3) @test !isapprox(z, 2z) @test isapprox(z, z * (1 + 1e-15)) # Test eps @test eps(1.0u"s") == eps(1.0)u"s" @test eps(typeof(1.0u"s")) == eps(Float64) # Test promotion behavior @test !isapprox(1.0u"m", 1.0u"s") @test isapprox(1.0u"m", 1000.0u"mm") @test_throws ErrorException isapprox(1.0*FixedUnits(u"m"), 1000.0*FixedUnits(u"mm")) end end @testset "Fast mathematics" begin @testset "> fma and muladd" begin m2cm = ContextUnits(m,cm) m2mm = ContextUnits(m,mm) mm2cm = ContextUnits(mm,cm) cm2cm = ContextUnits(cm,cm) fm, fmm = FixedUnits(m), FixedUnits(mm) @test @inferred(fma(2.0, 3.0m, 1.0m)) === 7.0m # llvm good @test @inferred(fma(2.0, 3.0m2cm, 1.0mm2cm)) === 600.1cm2cm @test @inferred(fma(2.0, 3.0fm, 1.0fm)) === 7.0fm @test @inferred(fma(2.0, 3.0m, 35mm)) === 6.035m # llvm good @test @inferred(fma(2.0, 3.0m2cm, 35mm2cm)) === 603.5cm2cm @test_throws ErrorException fma(2.0, 3.0fm, 35fmm) #automatic conversion prohibited @test @inferred(fma(2.0m, 3.0, 35mm)) === 6.035m # llvm good @test @inferred(fma(2.0m2cm, 3.0, 35mm2cm)) === 603.5cm2cm @test @inferred(fma(2.0m, 1.0/m, 3.0)) === 5.0 # llvm good @test @inferred(fma(2.0m2cm, 1.0/m2mm, 3.0)) === 5.0 @test @inferred(fma(2.0cm, 1.0/s, 3.0mm/s)) === .023m/s # llvm good @test @inferred(fma(2.0cm2cm, 1.0/s, 3.0mm/s)) === 2.3cm2cm/s @test @inferred(fma(2m, 1/s, 3m/s)) === 5m/s # llvm good @test @inferred(fma(2, 1.0μm/m, 1)) === 1.000002 # llvm good @test @inferred(fma(1.0mm/m, 1.0mm/m, 1.0mm/m)) === 0.001001 # llvm good @test @inferred(fma(1.0mm/m, 1.0, 1.0)) ≈ 1.001 # llvm good @test @inferred(fma(1.0, 1.0μm/m, 1.0μm/m)) === 2.0μm/m # llvm good @test @inferred(fma(2, 1.0, 1μm/m)) === 2.000001 # llvm BAD @test @inferred(fma(2, 1μm/m, 1mm/m)) === 501//500000 # llvm BAD @test @inferred(muladd(2.0, 3.0m, 1.0m)) === 7.0m @test @inferred(muladd(2.0, 3.0m, 35mm)) === 6.035m @test @inferred(muladd(2.0m, 3.0, 35mm)) === 6.035m @test @inferred(muladd(2.0m, 1.0/m, 3.0)) === 5.0 @test @inferred(muladd(2.0cm, 1.0/s, 3.0mm/s)) === .023m/s @test @inferred(muladd(2m, 1/s, 3m/s)) === 5m/s @test @inferred(muladd(2, 1.0μm/m, 1)) === 1.000002 @test @inferred(muladd(1.0mm/m, 1.0mm/m, 1.0mm/m)) === 0.001001 @test @inferred(muladd(1.0mm/m, 1.0, 1.0)) ≈ 1.001 @test @inferred(muladd(1.0, 1.0μm/m, 1.0μm/m)) === 2.0μm/m @test @inferred(muladd(2, 1.0, 1μm/m)) === 2.000001 @test @inferred(muladd(2, 1μm/m, 1mm/m)) === 501//500000 @test_throws DimensionError fma(2m, 1/m, 1m) @test_throws DimensionError fma(2, 1m, 1V) @test muladd(1s, 1.0mol/s, 2.0mol) === 3.0mol # issue 138 end @testset "> @fastmath" begin one32 = one(Float32)*m eps32 = eps(Float32)*m eps32_2 = eps32/2 # Note: Cannot use local functions since these are not yet optimized fm_ieee_32(x) = x + eps32_2 + eps32_2 fm_fast_32(x) = @fastmath x + eps32_2 + eps32_2 @test fm_ieee_32(one32) == one32 @test (fm_fast_32(one32) == one32 || fm_fast_32(one32) == one32 + eps32 > one32) one64 = one(Float64)*m eps64 = eps(Float64)*m eps64_2 = eps64/2 # Note: Cannot use local functions since these are not yet optimized fm_ieee_64(x) = x + eps64_2 + eps64_2 fm_fast_64(x) = @fastmath x + eps64_2 + eps64_2 @test fm_ieee_64(one64) == one64 @test (fm_fast_64(one64) == one64 || fm_fast_64(one64) == one64 + eps64 > one64) # check updating operators fm_ieee_64_upd(x) = (r=x; r+=eps64_2; r+=eps64_2) fm_fast_64_upd(x) = @fastmath (r=x; r+=eps64_2; r+=eps64_2) @test fm_ieee_64_upd(one64) == one64 @test (fm_fast_64_upd(one64) == one64 || fm_fast_64_upd(one64) == one64 + eps64 > one64) for T in (Float32, Float64, BigFloat) _zero = convert(T, 0)*m _one = convert(T, 1)*m + eps(T)*m _two = convert(T, 2)*m + 1m//10 _three = convert(T, 3)*m + 1m//100 @test isapprox((@fastmath +_two), +_two) @test isapprox((@fastmath -_two), -_two) @test isapprox((@fastmath _zero+_one+_two), _zero+_one+_two) @test isapprox((@fastmath _zero-_one-_two), _zero-_one-_two) @test isapprox((@fastmath _one*_two*_three), _one*_two*_three) @test isapprox((@fastmath _one/_two/_three), _one/_two/_three) @test isapprox((@fastmath rem(_two, _three)), rem(_two, _three)) @test isapprox((@fastmath mod(_two, _three)), mod(_two, _three)) @test (@fastmath cmp(_two, _two)) == cmp(_two, _two) @test (@fastmath cmp(_two, _three)) == cmp(_two, _three) @test (@fastmath cmp(_three, _two)) == cmp(_three, _two) @test (@fastmath _one/_zero) == convert(T, Inf) @test (@fastmath -_one/_zero) == -convert(T, Inf) @test isnan(@fastmath _zero/_zero) # must not throw for x in (_zero, _two, convert(T, Inf)*m, convert(T, NaN)*m) @test (@fastmath isfinite(x)) @test !(@fastmath isinf(x)) @test !(@fastmath isnan(x)) @test !(@fastmath issubnormal(x)) end end for T in (Complex{Float32}, Complex{Float64}, Complex{BigFloat}) _zero = convert(T, 0)*m _one = convert(T, 1)*m + im*eps(real(convert(T,1)))*m _two = convert(T, 2)*m + im*m//10 _three = convert(T, 3)*m + im*m//100 @test isapprox((@fastmath +_two), +_two) @test isapprox((@fastmath -_two), -_two) @test isapprox((@fastmath _zero+_one+_two), _zero+_one+_two) @test isapprox((@fastmath _zero-_one-_two), _zero-_one-_two) @test isapprox((@fastmath _one*_two*_three), _one*_two*_three) @test isapprox((@fastmath _one/_two/_three), _one/_two/_three) @test (@fastmath _three == _two) == (_three == _two) @test (@fastmath _three != _two) == (_three != _two) @test isnan(@fastmath _one/_zero) # must not throw @test isnan(@fastmath -_one/_zero) # must not throw @test isnan(@fastmath _zero/_zero) # must not throw for x in (_zero, _two, convert(T, Inf)*m, convert(T, NaN)*m) @test (@fastmath isfinite(x)) @test !(@fastmath isinf(x)) @test !(@fastmath isnan(x)) @test !(@fastmath issubnormal(x)) end end # real arithmetic for T in (Float32, Float64, BigFloat) half = 1m/convert(T, 2) third = 1m/convert(T, 3) for f in (:+, :-, :abs, :abs2, :conj, :inv, :sign, :sqrt) @test isapprox((@eval @fastmath $f($half)), (@eval $f($half))) @test isapprox((@eval @fastmath $f($third)), (@eval $f($third))) end for f in (:+, :-, :*, :/, :%, :(==), :!=, :<, :<=, :>, :>=, :atan, :hypot, :max, :min) @test isapprox((@eval @fastmath $f($half, $third)), (@eval $f($half, $third))) @test isapprox((@eval @fastmath $f($third, $half)), (@eval $f($third, $half))) end for f in (:minmax,) @test isapprox((@eval @fastmath $f($half, $third))[1], (@eval $f($half, $third))[1]) @test isapprox((@eval @fastmath $f($half, $third))[2], (@eval $f($half, $third))[2]) @test isapprox((@eval @fastmath $f($third, $half))[1], (@eval $f($third, $half))[1]) @test isapprox((@eval @fastmath $f($third, $half))[2], (@eval $f($third, $half))[2]) end half = 1°/convert(T, 2) third = 1°/convert(T, 3) for f in (:cos, :sin, :tan, :cis) @test isapprox((@eval @fastmath $f($half)), (@eval $f($half))) @test isapprox((@eval @fastmath $f($third)), (@eval $f($third))) end @test all(x -> isapprox(x...), Iterators.zip((@eval @fastmath sincos($half)), (@eval sincos($half)))) @test all(x -> isapprox(x...), Iterators.zip((@eval @fastmath sincos($third)), (@eval sincos($third)))) end # complex arithmetic for T in (Complex{Float32}, Complex{Float64}, Complex{BigFloat}) half = (1+1im)V/T(2) third = (1-1im)V/T(3) # some of these functions promote their result to double # precision, but we want to check equality at precision T rtol = Base.rtoldefault(real(T)) for f in (:+, :-, :abs, :abs2, :conj, :inv, :sign, :sqrt) @test isapprox((@eval @fastmath $f($half)), (@eval $f($half)), rtol=rtol) @test isapprox((@eval @fastmath $f($third)), (@eval $f($third)), rtol=rtol) end for f in (:+, :-, :*, :/, :(==), :!=) @test isapprox((@eval @fastmath $f($half, $third)), (@eval $f($half, $third)), rtol=rtol) @test isapprox((@eval @fastmath $f($third, $half)), (@eval $f($third, $half)), rtol=rtol) end _d = 90°/T(2) @test isapprox((@fastmath cis(_d)), cis(_d)) end # mixed real/complex arithmetic for T in (Float32, Float64, BigFloat) CT = Complex{T} half = 1V/T(2) third = 1V/T(3) chalf = (1+1im)V/CT(2) cthird = (1-1im)V/CT(3) for f in (:+, :-, :*, :/, :(==), :!=) @test isapprox((@eval @fastmath $f($chalf, $third)), (@eval $f($chalf, $third))) @test isapprox((@eval @fastmath $f($half, $cthird)), (@eval $f($half, $cthird))) @test isapprox((@eval @fastmath $f($cthird, $half)), (@eval $f($cthird, $half))) @test isapprox((@eval @fastmath $f($third, $chalf)), (@eval $f($third, $chalf))) end @test isapprox((@fastmath third^3), third^3) @test isapprox((@fastmath chalf/third), chalf/third) @test isapprox((@fastmath chalf^3), chalf^3) end end end @testset "Rounding" begin @test_throws ErrorException floor(3.7m) @test_throws ErrorException ceil(3.7m) @test_throws ErrorException trunc(3.7m) @test_throws ErrorException round(3.7m) @test_throws ErrorException floor(Integer, 3.7m) @test_throws ErrorException ceil(Integer, 3.7m) @test_throws ErrorException trunc(Integer, 3.7m) @test_throws ErrorException round(Integer, 3.7m) @test_throws ErrorException floor(Int, 3.7m) @test_throws ErrorException ceil(Int, 3.7m) @test_throws ErrorException trunc(Int, 3.7m) @test_throws ErrorException round(Int, 3.7m) @test floor(1.0314m/mm) === 1031.0 @test floor(1.0314m/mm; digits=1) === 1031.4 @test ceil(1.0314m/mm) === 1032.0 @test ceil(1.0314m/mm; digits=1) === 1031.4 @test trunc(-1.0314m/mm) === -1031.0 @test trunc(-1.0314m/mm; digits=1) === -1031.4 @test round(1.0314m/mm) === 1031.0 @test round(1.0314m/mm; digits=1) === 1031.4 @test floor(Integer, 1.0314m/mm) === Integer(1031.0) @test ceil(Integer, 1.0314m/mm) === Integer(1032.0) @test trunc(Integer, -1.0314m/mm) === Integer(-1031.0) @test round(Integer, 1.0314m/mm) === Integer(1031.0) @test floor(Int16, 1.0314m/mm) === Int16(1031.0) @test ceil(Int16, 1.0314m/mm) === Int16(1032.0) @test trunc(Int16, -1.0314m/mm) === Int16(-1031.0) @test round(Int16, 1.0314m/mm) === Int16(1031.0) @test floor(typeof(1mm), 1.0314m) === 1031mm @test floor(typeof(1.0mm), 1.0314m) === 1031.0mm @test floor(typeof(1.0mm), 1.0314m; digits=1) === 1031.4mm @test ceil(typeof(1mm), 1.0314m) === 1032mm @test ceil(typeof(1.0mm), 1.0314m) === 1032.0mm @test ceil(typeof(1.0mm), 1.0314m; digits=1) === 1031.4mm @test trunc(typeof(1mm), -1.0314m) === -1031mm @test trunc(typeof(1.0mm), -1.0314m) === -1031.0mm @test trunc(typeof(1.0mm), -1.0314m; digits=1) === -1031.4mm @test round(typeof(1mm), 1.0314m) === 1031mm @test round(typeof(1.0mm), 1.0314m) === 1031.0mm @test round(typeof(1.0mm), 1.0314m; digits=1) === 1031.4mm @test round(typeof(1.0°), 1.125°) === 1.0° @test round(typeof(1.0°), 1.125°, RoundUp) === 2.0° @test round(typeof(1.0°), 1.125°, digits=1) === 1.1° @test round(typeof(1.0°), 1.125°, RoundUp, digits=1) === 1.2° @test round(typeof(1.0°), 1rad) === 57.0° @test round(typeof(1.0°), 1rad, RoundUp) === 58.0° @test floor(typeof(1.0°), 1.125°) === 1.0° @test floor(typeof(1.0°), 1.125°, digits=1) === 1.1° @test round(u"inch", 1.0314m) === 41.0u"inch" @test round(Int, u"inch", 1.0314m) === 41u"inch" @test round(typeof(1m), 137cm) === 1m @test round(137cm/m) === 1//1 @test round(u"m", -125u"cm", sigdigits=2) === -1.2u"m" @test round(u"m", (125//1)u"cm", sigdigits=2) === 1.2u"m" @test round(u"m", -125u"cm", RoundNearestTiesUp, sigdigits=2) === -1.2u"m" @test round(u"m", (125//1)u"cm", RoundNearestTiesUp, sigdigits=2) === 1.3u"m" @test floor(u"m", -125u"cm", sigdigits=2) === -1.3u"m" @test floor(u"m", (125//1)u"cm", sigdigits=2) === 1.2u"m" @test ceil(u"m", -125u"cm", sigdigits=2) === -1.2u"m" @test ceil(u"m", (125//1)u"cm", sigdigits=2) === 1.3u"m" @test trunc(u"m", -125u"cm", sigdigits=2) === -1.2u"m" @test trunc(u"m", (125//1)u"cm", sigdigits=2) === 1.2u"m" end @testset "Sgn, abs, &c." begin @test @inferred(abs(3V+4V*im)) == 5V @test @inferred(norm(3V+4V*im)) == 5V @test @inferred(abs2(3V+4V*im)) == 25V^2 @test @inferred(abs(-3m)) == 3m @test @inferred(abs2(-3m)) == 9m^2 @test @inferred(sign(-3.3m)) == -1.0 @test @inferred(signbit(0.0m)) == false @test @inferred(signbit(-0.0m)) == true @test @inferred(copysign(3.0m, -4.0s)) == -3.0m @test @inferred(copysign(3.0m, 4)) == 3.0m @test @inferred(copysign(3, -4.0m)) == -3 @test @inferred(flipsign(3.0m, -4)) == -3.0m @test @inferred(flipsign(-3.0m, -4)) == 3.0m @test @inferred(flipsign(-3.0, -4m)) == 3.0 @test @inferred(flipsign(-3, 4.0m)) == -3 @test @inferred(flipsign(3.0m, -4s)) == -3.0m @test @inferred(flipsign(-3m, 4.0s)) == -3m @test @inferred(flipsign((3.0+4.0im)m, -4)) == (-3.0-4.0im)m @test @inferred(flipsign((-3.0+4.0im)m, -4)) == (3.0-4.0im)m @test @inferred(flipsign(-3+4im, -4m)) == 3-4im @test @inferred(flipsign(-3.0+4.0im, 4m)) == -3.0+4.0im @test @inferred(flipsign((3.0+4.0im)m, -4s)) == (-3.0-4.0im)m @test @inferred(flipsign((-3+4im)m, 4.0s)) == (-3+4im)m @test @inferred(real(3m)) == 3.0m @test @inferred(real((3+4im)V)) == 3V @test @inferred(imag(3m)) == 0m @test @inferred(imag((3+4im)V)) == 4V @test @inferred(conj(3m)) == 3m @test @inferred(conj((3+4im)V)) == (3-4im)V @test @inferred(Base.hastypemax(typeof(1.0m))) === Base.hastypemax(typeof(1.0)) @test @inferred(Base.hastypemax(typeof(big(1)m))) === Base.hastypemax(typeof(big(1))) @test @inferred(typemin(1.0m)) == -Inf*m @test @inferred(typemax(typeof(1.0m))) == Inf*m @test @inferred(typemin(0x01*m)) == 0x00*m @test @inferred(typemax(typeof(0x01*m))) == 0xff*m @test @inferred(rand(typeof(1u"m"))) isa typeof(1u"m") @test @inferred(rand(MersenneTwister(0), typeof(1u"m"))) isa typeof(1u"m") end @testset "Collections" begin @testset "> Ranges" begin @testset ">> Some of test/ranges.jl, with units" begin @test @inferred(size(10m:1m:0m)) == (0,) @test length(1m:.2m:2m) == 6 @test length(1.0m:.2m:2.0m) == 6 @test length(2m:-.2m:1m) == 6 @test length(2.0m:-.2m:1.0m) == 6 @test @inferred(length(2m:.2m:1m)) == 0 @test length(2.0m:.2m:1.0m) == 0 @test length(1m:2m:0m) == 0 L32 = range(Int32(1)*m, stop=Int32(4)*m, length=4) L64 = range(Int64(1)*m, stop=Int64(4)*m, length=4) @test L32[1] == 1m && L64[1] == 1m @test L32[2] == 2m && L64[2] == 2m @test L32[3] == 3m && L64[3] == 3m @test L32[4] == 4m && L64[4] == 4m r = 5m:-1m:1m @test @inferred(r[1])==5m @test r[2]==4m @test r[3]==3m @test r[4]==2m @test r[5]==1m @test length(.1m:.1m:.3m) == 3 # @test length(1.1m:1.1m:3.3m) == 3 @test @inferred(length(1.1m:1.3m:3m)) == 2 @test length(1m:1m:1.8m) == 1 @test (1m:2m:13m)[2:6] == 3m:2m:11m @test typeof((1m:2m:13m)[2:6]) == typeof(3m:2m:11m) @test (1m:2m:13m)[2:3:7] == 3m:6m:13m @test typeof((1m:2m:13m)[2:3:7]) == typeof(3m:6m:13m) end @testset ">> StepRange" begin r = @inferred(colon(1m, 1m, 5m)) @test isa(r, StepRange{typeof(1m)}) @test @inferred(length(r)) === 5 @test @inferred(step(r)) === 1m @test @inferred(first(range(1mm, step=2m, length=4))) === 1mm @test @inferred(step(range(1mm, step=2m, length=4))) === 2000mm @test @inferred(last(range(1mm, step=2m, length=4))) === 6001mm @test @inferred(first(range(1m, step=2mm, length=4))) === (1//1)m @test @inferred(step(range(1m, step=2mm, length=4))) === (1//500)m @test @inferred(last(range(1m, step=2mm, length=4))) === (503//500)m @test_throws DimensionError(1m, 2V) range(1m, step=2V, length=5) @test_throws ArgumentError 1m:0m:5m end @testset ">> StepRangeLen" begin @test isa(@inferred(colon(1.0m, 1m, 5m)), StepRangeLen{typeof(1.0m)}) @test @inferred(length(1.0m:1m:5m)) === 5 @test @inferred(step(1.0m:1m:5m)) === 1.0m @test @inferred(length(0:10°:360°)) == 37 # issue 111 @test @inferred(length(0.0:10°:2pi)) == 37 # issue 111 fallout @test @inferred(last(0°:0.1:360°)) === 6.2 # issue 111 fallout @test @inferred(first(range(1mm, step=0.1mm, length=50))) === 1.0mm # issue 111 @test @inferred(step(range(1mm, step=0.1mm, length=50))) === 0.1mm # issue 111 @test @inferred(last(range(0, step=10°, length=37))) == 2pi @test @inferred(last(range(0°, step=2pi/36, length=37))) == 2pi @test step(range(1.0m, step=1m, length=5)) === 1.0m @test @inferred(first(range(1.0mm, step=2.0m, length=4))) === 1.0mm @test @inferred(step(range(1.0mm, step=2.0m, length=4))) === 2000.0mm @test @inferred(last(range(1.0mm, step=2.0m, length=4))) === 6001.0mm @test @inferred(first(range(1.0m, step=2.0mm, length=4))) === 1.0m @test @inferred(step(range(1.0m, step=2.0mm, length=4))) === 0.002m @test @inferred(last(range(1.0m, step=2.0mm, length=4))) === 1.006m @test_throws DimensionError range(1.0m, step=1.0V, length=5) @test_throws ArgumentError 1.0m:0.0m:5.0m @test (-2.0Hz:1.0Hz:2.0Hz)/1.0Hz == -2.0:1.0:2.0 # issue 160 @test (range(0, stop=2, length=5) * u"°")[2:end] == range(0.5, stop=2, length=4) * u"°" # issue 241 @test range(big(1.0)m, step=big(1.0)m, length=5) == (big(1.0):big(1.0):big(5.0))*m end @testset ">> LinSpace" begin # Not using Compat.range for these because kw args don't infer in julia 0.6.2 @test isa(@inferred(range(1.0m, stop=3.0m, length=5)), StepRangeLen{typeof(1.0m), Base.TwicePrecision{typeof(1.0m)}}) @test isa(@inferred(range(1.0m, stop=10m, length=5)), StepRangeLen{typeof(1.0m), Base.TwicePrecision{typeof(1.0m)}}) @test isa(@inferred(range(1m, stop=10.0m, length=5)), StepRangeLen{typeof(1.0m), Base.TwicePrecision{typeof(1.0m)}}) @test isa(@inferred(range(1m, stop=10m, length=5)), StepRangeLen{typeof(1.0m), Base.TwicePrecision{typeof(1.0m)}}) @test_throws Unitful.DimensionError range(1m, stop=10, length=5) @test_throws Unitful.DimensionError range(1, stop=10m, length=5) r = range(1m, stop=3m, length=3) @test r[1:2:end] == range(1m, stop=3m, length=2) end @testset ">> Range → Array" begin @test isa(collect(1m:1m:5m), Array{typeof(1m),1}) @test isa(collect(1m:2m:10m), Array{typeof(1m),1}) @test isa(collect(1.0m:2m:10m), Array{typeof(1.0m),1}) @test isa(collect(range(1.0m, stop=10.0m, length=5)), Array{typeof(1.0m),1}) end @testset ">> unit multiplication" begin @test @inferred((1:5)*mm) === 1mm:1mm:5mm @test @inferred(mm*(1:5)) === 1mm:1mm:5mm @test @inferred((1:2:5)*mm) === 1mm:2mm:5mm @test @inferred((1.0:2.0:5.01)*mm) === 1.0mm:2.0mm:5.0mm r = @inferred(range(0.1, step=0.1, length=3) * 1.0s) @test r[3] === 0.3s @test *(1:5, mm, s^-1) === 1mm*s^-1:1mm*s^-1:5mm*s^-1 @test *(1:5, mm, s^-1, mol^-1) === 1mm*s^-1*mol^-1:1mm*s^-1*mol^-1:5mm*s^-1*mol^-1 @test @inferred((0:2) * 3f0m) === StepRangeLen{typeof(0f0m)}(0.0m, 3.0m, 3) # issue #477 @test @inferred(3f0m * (0:2)) === StepRangeLen{typeof(0f0m)}(0.0m, 3.0m, 3) # issue #477 @test @inferred((0f0:2f0) * 3f0m) === 0f0m:3f0m:6f0m @test @inferred(3f0m * (0.0:2.0)) === 0.0m:3.0m:6.0m @test @inferred(LinRange(0f0, 1f0, 3) * 3f0m) === LinRange(0f0m, 3f0m, 3) @test @inferred(3f0m * LinRange(0.0, 1.0, 3)) === LinRange(0.0m, 3.0m, 3) @test @inferred(1.0s * range(0.1, step=0.1, length=3)) === @inferred(range(0.1, step=0.1, length=3) * 1.0s) end @testset ">> broadcasting" begin @test @inferred((1:5) .* mm) === 1mm:1mm:5mm @test @inferred(mm .* (1:5)) === 1mm:1mm:5mm @test @inferred((1:2:5) .* mm) === 1mm:2mm:5mm @test @inferred((1.0:2.0:5.01) .* mm) === 1.0mm:2.0mm:5.0mm r = @inferred(range(0.1, step=0.1, length=3) .* 1.0s) @test r[3] === 0.3s @test @inferred((0:2) .* 3f0m) === StepRangeLen{typeof(0f0m)}(0.0m, 3.0m, 3) # issue #477 @test @inferred(3f0m .* (0:2)) === StepRangeLen{typeof(0f0m)}(0.0m, 3.0m, 3) # issue #477 @test @inferred((0f0:2f0) .* 3f0m) === 0f0m:3f0m:6f0m @test @inferred(3f0m .* (0.0:2.0)) === 0.0m:3.0m:6.0m @test @inferred(LinRange(0f0, 1f0, 3) .* 3f0m) === LinRange(0f0m, 3f0m, 3) @test @inferred(3f0m .* LinRange(0.0, 1.0, 3)) === LinRange(0.0m, 3.0m, 3) @test @inferred(1.0s .* range(0.1, step=0.1, length=3)) === @inferred(range(0.1, step=0.1, length=3) * 1.0s) @test @inferred((1:2:5) .* cm .|> mm) === 10mm:20mm:50mm @test mm.((1:2:5) .* cm) === 10mm:20mm:50mm @test @inferred(StepRange(1cm,1mm,2cm) .|> km) === (1//100_000)km:(1//1_000_000)km:(2//100_000)km @test @inferred((1:2:5) .* km .|> upreferred) === 1000m:2000m:5000m @test @inferred((1:2:5)km .|> upreferred) === 1000m:2000m:5000m @test @inferred((1:2:5) .|> upreferred) === 1:2:5 @test @inferred((1.0:2.0:5.0) .* km .|> upreferred) === 1000.0m:2000.0m:5000.0m @test @inferred((1.0:2.0:5.0)km .|> upreferred) === 1000.0m:2000.0m:5000.0m @test @inferred((1.0:2.0:5.0) .|> upreferred) === 1.0:2.0:5.0 @test @inferred(StepRange(1cm,1mm,2cm) .|> upreferred) === (1//100)m:(1//1000)m:(2//100)m # float conversion, dimensionful for r = [1eV:1eV:5eV, 1eV:1eV:5_000_000eV, 5_000_000eV:-1eV:-1eV, -123_456_789eV:2eV:987_654_321eV, (-11//12)eV:(1//3)eV:(11//4)eV] for f = (mJ, upreferred) rf = @inferred(r .|> f) test_indices = length(r) ≤ 10_000 ? eachindex(r) : rand(eachindex(r), 10_000) @test eltype(rf) === typeof(f(zero(eltype(r)))) @test all(≈(rf[i], f(r[i]); rtol=eps()) for i = test_indices) end end # float conversion from unitless r = 1:1:360 rf = °.(r) @test all(≈(rf[i], °(r[i]); rtol=eps()) for i = eachindex(r)) # float conversion to unitless r = (1:1:360)° for f = (mrad, NoUnits, upreferred) rf = f.(r) @test eltype(rf) === typeof(f(zero(eltype(r)))) @test all(≈(rf[i], f(r[i]); rtol=eps()) for i = eachindex(r)) end # exact conversion from and to unitless @test rad.(1:1:360) === (1:1:360)rad @test mrad.(1:1:360) === (1_000:1_000:360_000)mrad @test upreferred.(1:1:360) === 1:1:360 @test NoUnits.((1:1:360)rad) === 1:1:360 @test upreferred.((1:1:360)rad) === 1:1:360 @test NoUnits.((1:2:5)mrad) === 1//1000:1//500:1//200 @test upreferred.((1:2:5)mrad) === 1//1000:1//500:1//200 @test @inferred((1:2:5) .* cm .|> mm .|> ustrip) === 10:20:50 @test @inferred((1f0:2f0:5f0) .* cm .|> mm .|> ustrip) === 10f0:20f0:50f0 @test @inferred(StepRange{typeof(1m),typeof(1cm)}(1m,1cm,2m) .|> ustrip) === 1:1//100:2 @test @inferred(StepRangeLen{typeof(1f0m)}(1.0m, 1.0cm, 101) .|> ustrip) === StepRangeLen{Float32}(1.0, 0.01, 101) @test @inferred(StepRangeLen{typeof(1.0m)}(Base.TwicePrecision(1.0m), Base.TwicePrecision(1.0cm), 101) .|> ustrip) === StepRangeLen{Float64}(Base.TwicePrecision(1.0), Base.TwicePrecision(0.01), 101) @test @inferred((1:0.1:1.0) .|> ustrip) == 1:0.1:1.0 @test @inferred((1m:0.1m:1.0m) .|> ustrip) == 1:0.1:1.0 @test @inferred(StepRange{typeof(0m),typeof(1cm)}(1m,1cm,2m) .|> ustrip) === 1:1//100:2 @test @inferred(StepRangeLen{typeof(1f0m)}(1.0m, 1.0cm, 101) .|> ustrip) === StepRangeLen{Float32}(1.0, 0.01, 101) @test @inferred(StepRangeLen{typeof(1.0m)}(Base.TwicePrecision(1.0m), Base.TwicePrecision(1.0cm), 101) .|> ustrip) === StepRangeLen{Float64}(Base.TwicePrecision(1.0), Base.TwicePrecision(0.01), 101) @test @inferred(StepRangeLen{typeof(1.0mm)}(Base.TwicePrecision(1.0m), Base.TwicePrecision(1.0cm), 101) .|> ustrip) === 1000.0:10.0:2000.0 @test ustrip.(1:0.1:1.0) == 1:0.1:1.0 @test ustrip.(1m:0.1m:1.0m) == 1:0.1:1.0 end @testset ">> quantities and non-quantities" begin @test range(1, step=1m/mm, length=5) == 1:1000:4001 @test range(1, step=1mm/m, length=5) == (1//1):(1//1000):(251//250) @test eltype(range(1, step=1m/mm, length=5)) == Int @test eltype(range(1, step=1mm/m, length=5)) == Rational{Int} @test range(1m/mm, step=1, length=5) == ((1//1):(1//1000):(251//250)) * m/mm @test range(1mm/m, step=1, length=5) == (1:1000:4001) * mm/m @test eltype(range(1m/mm, step=1, length=5)) == typeof((1//1)m/mm) @test eltype(range(1mm/m, step=1, length=5)) == typeof(1mm/m) end @testset ">> complex" begin @test range((1+2im)m, step=(1+2im)m, length=5) == range(1+2im, step=1+2im, length=5) * m @test range((1+2im)m, step=(1+2im)mm, length=5) == range(1//1+(2//1)im, step=1//1000+(1//500)im, length=5) * m @test range((1.0+2.0im)m, stop=(3.0+4.0im)m, length=5) == LinRange(1.0+2.0im, 3.0+4.0im, 5) * m @test range((1.0+2.0im)mm, stop=(3.0+4.0im)m, length=3) == LinRange(0.001+0.002im, 3.0+4.0im, 3) * m end @testset ">> step defaults to 1" begin @test range(1.0mm/m, length=5) == (1.0mm/m):(1000.0mm/m):(4001.0mm/m) @test range((1+2im)mm/m, length=5) == range(1+2im, step=1000, length=5)*mm/m @test_throws DimensionError range(1.0m, length=5) @test_throws DimensionError range((1+2im)m, length=5) @test (1mm/m):(5001mm/m) == (1:1000:5001) * mm/m @test (1m/mm):(5m/mm) == (1//1:1//1000:5//1) * m/mm @test (1mm/m):(1m/mm) == 1//1000:999001//1000 @test (1m/mm):(1mm/m) == 1000//1:999//1 @test (1.0mm/m):(5001mm/m) == (1.0:1000.0:5001.0) * mm/m @test (1m/mm):(5.0m/mm) == (1.0:0.001:5.0) * m/mm @test (1.0mm/m):(1m/mm) == 0.001:999.001 @test (1m/mm):(1.0mm/m) == 1000.0:1.0:999.0 @test_throws DimensionError (1m):(1m) @test_throws DimensionError (1m):(1000cm) @test_throws DimensionError (1m):(1s) @test (1m/cm):1 == 100:99 @test (1m/cm):1000 == 100:1000 @test (1m/cm):1.0 == 100.0:99.0 @test (1.0m/cm):1000 == 100.0:1000.0 @test_throws DimensionError (1m):1 @test 1:(1m/mm) == 1:1000 @test 1000:(1m/mm) == 1000:1000 @test 1.0:(1m/mm) == 1.0:1000.0 @test 1000:(1.0m/mm) == 1000.0:1000.0 @test_throws DimensionError 1:(1m) end @static if VERSION ≥ v"1.7" @testset ">> no start argument" begin @test range(stop=1.0m, step=2.0m, length=5) == -7.0m:2.0m:1.0m @test range(stop=1.0mm, step=1.0m, length=5) == -3999.0mm:1000.0mm:1.0mm @test range(stop=(1.0+2.0im)mm, step=(1.0+1.0im)m, length=5) == range(stop=1.0+2.0im, step=(1000+1000im), length=5)*mm @test range(stop=1.0mm/m, length=5) == (-3999.0mm/m):(1000.0mm/m):(1.0mm/m) @test range(stop=(1+2im)mm/m, length=5) == range(stop=1+2im, step=1000, length=5)*mm/m @test range(stop=1.0mm/m, step=1, length=5) == (-3999.0mm/m):(1000.0mm/m):(1.0mm/m) @test_throws DimensionError range(stop=1.0m, step=1V, length=5) @test_throws DimensionError range(stop=(1+2im)m, step=1V, length=5) @test_throws DimensionError range(stop=1.0m, length=5) @test_throws DimensionError range(stop=(1+2im)m, length=5) @test range(stop=1, step=1m/mm, length=5) == -3999:1000:1 @test range(stop=1, step=1mm/m, length=5) == (249//250):(1//1000):(1//1) @test eltype(range(stop=1, step=1m/mm, length=5)) == Int @test eltype(range(stop=1, step=1mm/m, length=5)) == Rational{Int} @test range(stop=1m/mm, step=1, length=5) == ((249//250):(1//1000):(1//1)) * m/mm @test range(stop=1mm/m, step=1, length=5) == (-3999:1000:1) * mm/m @test eltype(range(stop=1m/mm, step=1, length=5)) == typeof((1//1)m/mm) @test eltype(range(stop=1mm/m, step=1, length=5)) == typeof(1mm/m) @test_throws ArgumentError range(step=1m, length=5) end end end @testset "> Arrays" begin @testset ">> Array multiplication" begin # Quantity, quantity @test @inferred([1m, 2m]' * [3m, 4m]) == 11m^2 @test @inferred([1m, 2m]' * [3/m, 4/m]) == 11 @test typeof([1m, 2m]' * [3/m, 4/m]) == Int @test typeof([1m, 2V]' * [3/m, 4/V]) == Int @test @inferred([1V,2V]*[0.1/m, 0.4/m]') == [0.1V/m 0.4V/m; 0.2V/m 0.8V/m] # Probably broken as soon as we stopped using custom promote_op methods @test_broken @inferred([1m, 2V]' * [3/m, 4/V]) == [11] @test_broken @inferred([1m, 2V] * [3/m, 4/V]') == [3 4u"m*V^-1"; 6u"V*m^-1" 8] # Quantity, number or vice versa @test @inferred([1 2] * [3m,4m]) == [11m] @test typeof([1 2] * [3m,4m]) == Array{typeof(1u"m"),1} @test @inferred([3m 4m] * [1,2]) == [11m] @test typeof([3m 4m] * [1,2]) == Array{typeof(1u"m"),1} @test @inferred([1,2] * [3m,4m]') == [3m 4m; 6m 8m] @test typeof([1,2] * [3m,4m]') == Array{typeof(1u"m"),2} @test @inferred([3m,4m] * [1,2]') == [3m 6m; 4m 8m] @test typeof([3m,4m] * [1,2]') == Array{typeof(1u"m"),2} # re-allow vector*(1-row matrix), PR 20423 @test @inferred([1,2] * [3m 4m]) == [3m 4m; 6m 8m] @test typeof([1,2] * [3m 4m]) == Array{typeof(1u"m"),2} @test @inferred([3m,4m] * [1 2]) == [3m 6m; 4m 8m] @test typeof([3m,4m] * [1 2]) == Array{typeof(1u"m"),2} end @testset ">> Element-wise multiplication" begin @test @inferred([1m, 2m, 3m] * 5) == [5m, 10m, 15m] @test typeof([1m, 2m, 3m] * 5) == Array{typeof(1u"m"),1} @test @inferred([1m, 2m, 3m] .* 5m) == [5m^2, 10m^2, 15m^2] @test typeof([1m, 2m, 3m] * 5m) == Array{typeof(1u"m^2"),1} @test @inferred(5m .* [1m, 2m, 3m]) == [5m^2, 10m^2, 15m^2] @test typeof(5m .* [1m, 2m, 3m]) == Array{typeof(1u"m^2"),1} @test @inferred(Matrix{Float64}(I, 2, 2)*V) == [1.0V 0.0V; 0.0V 1.0V] @test @inferred(V*Matrix{Float64}(I, 2, 2)) == [1.0V 0.0V; 0.0V 1.0V] @test @inferred(Matrix{Float64}(I, 2, 2).*V) == [1.0V 0.0V; 0.0V 1.0V] @test @inferred(V.*Matrix{Float64}(I, 2, 2)) == [1.0V 0.0V; 0.0V 1.0V] @test @inferred([1V 2V; 0V 3V].*2) == [2V 4V; 0V 6V] @test @inferred([1V, 2V] .* [true, false]) == [1V, 0V] @test @inferred([1.0m, 2.0m] ./ 3) == [1m/3, 2m/3] @test @inferred([1V, 2.0V] ./ [3m, 4m]) == [1V/(3m), 0.5V/m] @test @inferred([1, 2]kg) == [1, 2] * kg @test @inferred([1, 2]kg .* [2, 3]kg^-1) == [2, 6] @test @inferred([1, 2]/kg) == [1/kg, 2/kg] end @testset ">> Array addition" begin @test @inferred([1m, 2m] + [3m, 4m]) == [4m, 6m] @test @inferred([1m, 2m] + [1m, 1cm]) == [2m, 201m//100] @test @inferred([1m] + [1cm]) == [(101//100)*m] # issue 127 b = [0.0, 0.0m] @test b + b == b @test b .+ b == b @test eltype(b+b) === Number # Dimensionless quantities @test @inferred([1mm/m] + [1.0cm/m]) == [0.011] @test typeof([1mm/m] + [1.0cm/m]) == Array{Float64,1} @test @inferred([1mm/m] + [1cm/m]) == [11//1000] @test typeof([1mm/m] + [1cm/m]) == Array{Rational{Int},1} @test @inferred([1mm/m] + [2]) == [2001//1000] @test typeof([1mm/m] + [2]) == Array{Rational{Int},1} @test_throws DimensionError [1m] + [2V] @test_throws DimensionError [1] + [1m] end @testset ">> Element-wise addition" begin @test @inferred(5m .+ [1m, 2m, 3m]) == [6m, 7m, 8m] @test Any[1.0m, 2.0m] .+ 3.0m == [4.0m, 5.0m] # issue 557 (actually a bug in Julia 1.8.1) end @testset ">> Element-wise comparison" begin @test @inferred([0.0m, 2.0m] .< [3.0m, 2.0μm]) == BitArray([true,false]) @test @inferred([0.0m, 2.0m] .> [3.0m, 2.0μm]) == BitArray([false,true]) @test @inferred([0.0m, 0.0μm] .<= [0.0mm, 0.0mm]) == BitArray([true, true]) @test @inferred([0.0m, 0.0μm] .>= [0.0mm, 0.0mm]) == BitArray([true, true]) @test @inferred([0.0m, 0.0μm] .== [0.0mm, 0.0mm]) == BitArray([true, true]) # Want to make sure we play nicely with StaticArrays for j in (<, <=, >, >=, ==) @test @inferred(Base.promote_op(j, typeof(1.0m), typeof(1.0μm))) == Bool end end @testset ">> isapprox on arrays" begin @test !isapprox([1.0m], [1.0V]) @test isapprox([1.0μm/m], [1e-6]) @test isapprox([1cm, 200cm], [0.01m, 2.0m]) @test !isapprox([1.0], [1.0m]) @test !isapprox([1.0m], [1.0]) @test isapprox([1.0m, NaN*m], [nextfloat(1.0)*m, NaN*m], nans=true) @test !isapprox([1.0m, NaN*m], [nextfloat(1.0)*m, NaN*m], nans=false) @test !isapprox([1.0m, 2.0m], [1.1m, 2.2m], rtol=0.05, atol=0.2m) @test !isapprox([1.0m], [nextfloat(1.0)*m], atol=eps(0.1)*m) @test isapprox([1.0u"rad"], [1.1]; atol=0.5u"rad") end @testset ">> Unit stripping" begin @test @inferred(ustrip([1u"m", 2u"m"])) == [1,2] @test_deprecated ustrip([1,2]) @test ustrip.([1,2]) == [1,2] @test typeof(ustrip([1u"m", 2u"m"])) <: Base.ReinterpretArray{Int,1} @test typeof(ustrip(Diagonal([1,2]u"m"))) <: Diagonal{Int} @test typeof(ustrip(Bidiagonal([1,2,3]u"m", [1,2]u"m", :U))) <: Bidiagonal{Int} @test typeof(ustrip(Tridiagonal([1,2]u"m", [3,4,5]u"m", [6,7]u"m"))) <: Tridiagonal{Int} @test typeof(ustrip(SymTridiagonal([1,2,3]u"m", [4,5]u"m"))) <: SymTridiagonal{Int} end @testset ">> Linear algebra" begin @test istril(1m) === true @test istril([1 1; 0 1]m) === false @test istril([1 0; 1 1]K) === true @test istril([1 0; 1 1]°C) === false @test istril([1//1 -5463//20; 1//1 1//1]°C) === true @test istriu(1m) === true @test istriu([1 1; 0 1]m) === true @test istriu([1 1; 0 1]K) === true @test istriu([1 1; 0 1]°C) === false @test istriu([1//1 1//1; -5463//20 1//1]°C) === true end @testset ">> Array initialization" begin Q = typeof(1u"m") @test @inferred(zeros(Q, 2)) == [0, 0]u"m" @test @inferred(zeros(Q, (2,))) == [0, 0]u"m" @test @inferred(zeros(Q)[]) == 0u"m" @test @inferred(fill!(similar([1.0, 2.0, 3.0], Q), zero(Q))) == [0, 0, 0]u"m" @test @inferred(ones(Q, 2)) == [1, 1]u"m" @test @inferred(ones(Q, (2,))) == [1, 1]u"m" @test @inferred(ones(Q)[]) == 1u"m" @test @inferred(fill!(similar([1.0, 2.0, 3.0], Q), oneunit(Q))) == [1, 1, 1]u"m" @test size(rand(Q, 2)) == (2,) @test size(rand(Q, 2, 3)) == (2,3) @test eltype(@inferred(rand(Q, 2))) == Q @test zero([1m, 2m]) == [0m, 0m] @test zero(Quantity{Int,𝐋}[1m, 1mm]) == [0m, 0mm] @test zero(Quantity{Int}[1m, 1s]) == [0m, 0s] @test zero(Quantity[1m, 1s]) == [0m, 0s] @test zero([1mm, missing]) == [0mm, 0mm] @test zero(Union{typeof(0.0s),Missing}[missing]) == [0.0s] if VERSION ≥ v"1.9.0-rc1" @test zero(Union{Quantity{Int,𝐋},Missing}[1mm, missing]) == [0m, 0m] @test zero(Union{Quantity{Float64,𝐋},Missing}[1.0mm, missing]) == [0.0m, 0.0m] @test zero(Union{Quantity{Int,𝚯},Missing}[1°C, 2°F, missing]) == [0K, 0K, 0K] @test zero(Vector{Union{Quantity{Float64,𝐋},Missing}}(undef, 1)) == [0.0m] else @test_broken zero(Union{Quantity{Int,𝐋},Missing}[1mm, missing]) == [0m, 0m] @test_broken zero(Union{Quantity{Float64,𝐋},Missing}[1.0mm, missing]) == [0.0m, 0.0m] @test_broken zero(Union{Quantity{Int,𝚯},Missing}[1°C, 2°F, missing]) == [0K, 0K, 0K] @test_broken zero(Vector{Union{Quantity{Float64,𝐋},Missing}}(undef, 1)) == [0.0m] end @test_broken zero(Union{Quantity,Missing}[1m, 1mm]) == [0m, 0mm] @test zero([1°C, 2°C]) == [0K, 0K] @test zero(Quantity[1°C, 2°F]) == [0K, 0K] @test zero(Union{typeof(0°C),Missing}[missing]) == [0K] @test zero(Vector{typeof(big(1)mm)}(undef, 1)) == [big(0)mm] @test zero(Vector{Union{typeof(big(1)mm),Missing}}(undef, 1)) == [big(0)mm] @test zero(Vector{Quantity{Float64,𝐋}}(undef, 1)) == [0.0m] @test_throws MethodError zero(Union{Quantity,Missing}[1m, 1s, missing]) @test_throws MethodError zero(Vector{Quantity}(undef, 1)) @test_throws MethodError zero(Vector{Union{Quantity,Missing}}(undef, 1)) end end end @testset "Display" begin withenv("UNITFUL_FANCY_EXPONENTS" => false) do @test string(typeof(1.0m/s)) == "Quantity{Float64, 𝐋 𝐓^-1, FreeUnits{(m, s^-1), 𝐋 𝐓^-1, nothing}}" @test string(typeof(m/s)) == "FreeUnits{(m, s^-1), 𝐋 𝐓^-1, nothing}" @test string(dimension(1u"m/s")) == "𝐋 𝐓^-1" @test string(NoDims) == "NoDims" end @testset ":fancy_exponent IOContext property" begin @test sprint(io -> show(IOContext(io, :fancy_exponent => true), u"m/s")) == "m s⁻¹" @test sprint(io -> show(IOContext(io, :fancy_exponent => false), u"m/s")) == "m s^-1" end @testset "no Core.Box in show(::IO, ::Unitlike)" begin # Regression: the old implementation used a closure that reassigned an outer local, forcing the variable to be wrapped in a `Core.Box`. That hurt inference (variable inferred as `Core.Box` instead of `String`) and triggered "local variable `first` is not defined" reports from static analyzers like JET. @test !any(==(Core.Box), only(code_typed(show, Tuple{IO, typeof(u"m")}; optimize=false)).first.slottypes) end end struct Foo <: Number end Base.show(io::IO, x::Foo) = print(io, "1") Base.show(io::IO, ::MIME"text/plain", ::Foo) = print(io, "42.0") @testset "Show quantities" begin withenv("UNITFUL_FANCY_EXPONENTS" => false) do @test repr(1.0 * u"m * s * kg^-1") == "1.0 m s kg^-1" @test repr("text/plain", 1.0 * u"m * s * kg^-1") == "1.0 m s kg^-1" @test repr(Foo() * u"m * s * kg^-1") == "1 m s kg^-1" @test repr("text/plain", Foo() * u"m * s * kg^-1") == "42.0 m s kg^-1" # Complex quantities @test repr((1+2im) * u"m/s") == "(1 + 2im) m s^-1" @test repr("text/plain", (1+2im) * u"m/s") == "(1 + 2im) m s^-1" # Angular degree printing #253 @test sprint(show, 1.0°) == "1.0°" @test repr("text/plain", 1.0°) == "1.0°" # Concise printing of ranges @test repr((1:10)*u"kg/m^3") == "(1:10) kg m^-3" @test repr((1.0:0.1:10.0)*u"kg/m^3") == "(1.0:0.1:10.0) kg m^-3" @test repr((1:10)*°) == "(1:10)°" @test repr(range(1.0+2.0im, length=5)*u"m") == "(1.0 + 2.0im:1.0 + 0.0im:5.0 + 2.0im) m" @test repr(range(1+2im, step=1+1im, length=5)*u"m") == "(1 + 2im:1 + 1im:5 + 6im) m" @test repr(StepRange((1//1)u"m", 1u"cm", (2//1)u"m")) == "(1//1:1//100:2//1) m" @test repr(StepRangeLen(1.0u"m", 1.0u"cm", 101)) == "(1.0:0.01:2.0) m" # Concise printing of affine ranges with mixed step unit @test repr(StepRange(1u"°C", 1u"K", 3u"°C")) == "(1:3) °C" @test repr(StepRange(1u"°C", 1.0u"K", 3u"°C")) == "(1:3) °C" @test repr(StepRange(1.0u"°C", 1u"K", 3.0u"°C")) == "(1.0:1.0:3.0) °C" @test repr(StepRange(1.0u"°C", 1.0u"K", 3.0u"°C")) == "(1.0:1.0:3.0) °C" @test repr(StepRange((0//1)u"°F", 1u"K", (9//1)u"°F")) == "(0//1:9//5:9//1) °F" @test repr(StepRangeLen{typeof(1.0u"°C"),typeof(1.0u"°C"),typeof(1u"K")}(1.0u"°C", 1u"K", 3, 1)) == "(1.0:1.0:3.0) °C" @test repr(StepRangeLen{typeof(1u"°C"),typeof(1u"°C"),typeof(1u"K")}(1u"°C", 1u"K", 3, 1)) == "(1:1:3) °C" @test repr(StepRangeLen{typeof(1.0u"°F"),typeof(1.0u"°F"),typeof(1u"K")}(0.0u"°F", 1u"K", 6)) == "(0.0:1.8:9.0) °F" end withenv("UNITFUL_FANCY_EXPONENTS" => true) do @test repr(1.0 * u"m * s * kg^(-1//2)") == "1.0 m s kg⁻¹ᐟ²" end withenv("UNITFUL_FANCY_EXPONENTS" => nothing) do @test repr(1.0 * u"m * s * kg^(-1//2)") == (Sys.isapple() ? "1.0 m s kg⁻¹ᐟ²" : "1.0 m s kg^-1/2") end @test Base.alignment(stdout, (1//3)m) == Base.alignment(stdout, 1//3) .+ (0, 2) @test Base.alignment(stdout, (2+3im)m) == Base.alignment(stdout, 2+3im) .+ (1, 3) @test Base.alignment(stdout, 3.0dB) == Base.alignment(stdout, 3.0) .+ (0, 3) @test Base.alignment(stdout, 3.0dBm) == Base.alignment(stdout, 3.0) .+ (0, 4) @test Base.alignment(stdout, 3.0dB*s) == Base.alignment(stdout, 3.0) .+ (1, 6) @test Base.alignment(stdout, 3.0dBm*s) == Base.alignment(stdout, 3.0) .+ (1, 7) end VERSION ≥ v"1.9.0" && @testset "printf" begin @test (@sprintf "%f %d %.2f %05d" 1.23u"m" 123.4u"°" 0.1234u"W" 12.34u"km") == "1.230000 m 123° 0.12 W 00012 km" end isdefined(Base, :get_extension) && @testset "Latexify extension" begin function unitfullatexifytest(val, mathrmexpected, siunitxexpected, siunitxsimpleexpected) @test latexify(val; fmt=FancyNumberFormatter()) == LaTeXString(replace(mathrmexpected, "\r\n" => "\n")) @test latexify(val; fmt=SiunitxNumberFormatter()) == LaTeXString(replace(siunitxexpected, "\r\n" => "\n")) @test latexify(val; fmt=SiunitxNumberFormatter(simple=true)) == LaTeXString(replace(siunitxsimpleexpected, "\r\n"=>"\n")) end @testset "Latexify units" begin unitfullatexifytest( u"H*J/kg", raw"$\mathrm{H}\,\mathrm{J}\,\mathrm{kg}^{-1}$", raw"\unit{\henry\joule\per\kilo\gram}", raw"\unit{H.J.kg^{-1}}", ) unitfullatexifytest( 24.7e9u"Gm/s^2", raw"$2.47 \cdot 10^{10}\,\mathrm{Gm}\,\mathrm{s}^{-2}$", raw"\qty{2.47e10}{\giga\meter\per\second\tothe{2}}", raw"\qty{2.47e10}{Gm.s^{-2}}", ) unitfullatexifytest( u"percent", raw"$\mathrm{\%}$", raw"\unit{\percent}", raw"\unit{\%}" ) unitfullatexifytest( 2u"°C", raw"$2\,\mathrm{^\circ C}$", raw"\qty{2}{\celsius}", raw"\qty{2}{\celsius}" ) unitfullatexifytest( 1u"°", raw"$1\mathrm{^{\circ}}$", raw"\qty{1}{\degree}", raw"\qty{1}{\degree}" ) unitfullatexifytest( [1, 2, 3]*m, raw""" \begin{equation} \left[ \begin{array}{c} 1 \\ 2 \\ 3 \\ \end{array} \right]\,\mathrm{m} \end{equation} """, raw""" \begin{equation} \left[ \begin{array}{c} \num{1} \\ \num{2} \\ \num{3} \\ \end{array} \right]\,\unit{\meter} \end{equation} """, raw""" \begin{equation} \left[ \begin{array}{c} \num{1} \\ \num{2} \\ \num{3} \\ \end{array} \right]\,\unit{m} \end{equation} """, ) unitfullatexifytest((1,2,3).*m, raw""" \begin{equation} \left[ \begin{array}{c} 1 \\ 2 \\ 3 \\ \end{array} \right]\,\mathrm{m} \end{equation} """, raw"\qtylist{1;2;3}{\meter}", raw"\qtylist{1;2;3}{m}", ) @test latexify(24.7e9u"Gm/s^2"; fmt="%.1e") == L"$2.5e+10\,\mathrm{Gm}\,\mathrm{s}^{-2}$" @test latexify(5.9722e24u"kg"; fmt=SiunitxNumberFormatter(version=2)) == raw"\SI{5.9722e24}{\kilo\gram}" @test latexify(u"eV"; fmt=SiunitxNumberFormatter(version=2)) == raw"\si{\electronvolt}" end @testset "permode" begin p = 5u"m^3*s^2/H/kg^4" @test latexify(p) == LaTeXString( raw"$5\,\mathrm{m}^{3}\,\mathrm{s}^{2}\,\mathrm{kg}^{-4}\,\mathrm{H}^{-1}$" ) @test latexify(p; permode=:power) == LaTeXString( raw"$5\,\mathrm{m}^{3}\,\mathrm{s}^{2}\,\mathrm{kg}^{-4}\,\mathrm{H}^{-1}$" ) @test latexify(p; permode=:slash) == LaTeXString( raw"$5\,\mathrm{m}^{3}\,\mathrm{s}^{2}\,/\,\mathrm{kg}^{4}\,\mathrm{H}$" ) @test latexify(p; permode=:frac) == LaTeXString( raw"$5\,\frac{\mathrm{m}^{3}\,\mathrm{s}^{2}}{\mathrm{kg}^{4}\,\mathrm{H}}$" ) @test latexify(p; permode=:frac, fmt=SiunitxNumberFormatter()) == latexify(p; fmt=SiunitxNumberFormatter()) @test latexify(m; permode=:frac) == latexify(m) @test latexify(u"m^-1"; permode=:frac) == LaTeXString(raw"$\frac{1}{\mathrm{m}}$") @test_throws ErrorException latexify(p; permode=:wrong) end @testset "Labels" begin @test latexify("x", m) == raw"$x\;\left/\;\mathrm{m}\right.$" @test latexify("x", m; labelformat=:slash) == raw"$x\;\left/\;\mathrm{m}\right.$" @test latexify("x", m; labelformat=:square) == raw"$x\;\left[\mathrm{m}\right]$" @test latexify("x", m; labelformat=:round) == raw"$x\;\left(\mathrm{m}\right)$" @test latexify("x", m; labelformat=:frac) == raw"$\frac{x}{\mathrm{m}}$" Latexify.set_default(labelformat=:square) @test latexify("x", m) == raw"$x\;\left[\mathrm{m}\right]$" @test_throws "Unknown labelformat" latexify("x", m; labelformat=:wrong) # Issue 816 @test latexify("T", u"°C"; labelformat=:slash) == raw"$T\;\left/\;\mathrm{^\circ C}\right.$" @test latexify("T", u"°F"; labelformat=:slash) == raw"$T\;\left/\;\mathrm{^\circ F}\right.$" end @testset "Parentheses" begin @test @latexify($(3u"mm")^2 - 4 * $(2u"mm^2")) == raw"$\left( 3\,\mathrm{mm} \right)^{2} - 4 \cdot 2\,\mathrm{mm}^{2}$" end end @testset "DimensionError message" begin function errorstr(e) b = IOBuffer() Base.showerror(b,e) String(take!(b)) end @test errorstr(DimensionError(1u"m",2)) == "DimensionError: 1 m and 2 are not dimensionally compatible." @test errorstr(DimensionError(1u"m",NoDims)) == "DimensionError: 1 m and NoDims are not dimensionally compatible." @test errorstr(DimensionError(u"m",2)) == "DimensionError: m and 2 are not dimensionally compatible." end @testset "Logarithmic quantities" begin @testset "> Explicit construction" begin @testset ">> Level" begin # Outer constructor @test Level{Decibel,1}(2) isa Level{Decibel,1,Int} @test_throws DimensionError Level{Decibel,1}(2V) @test_throws DimensionError Level{Decibel,1V}(2) @test_throws InexactError Level{Decibel,1}(1+1im) @test_throws DomainError Level{Decibel,1+0im}(2) @test_throws DomainError Level{Decibel,(1+0im)V}(2V) @test_throws DomainError Level{Decibel,(1+1im)V}(2V) # Inner constructor @test Level{Decibel,1,Int}(2) === Level{Decibel,1}(2) @test Level{Decibel,1,Int}(2) === Level{Decibel,1,Int}(2.0) @test Level{Decibel,1,Int}(2) === Level{Decibel,1,Int}(2+0im) @test_throws DimensionError Level{Decibel,1,typeof(2V)}(2V) @test_throws DimensionError Level{Decibel,1V,Int}(2) @test_throws TypeError Level{Decibel,1,Complex{Int}}(1+1im) @test_throws TypeError Level{Decibel,1V,typeof((1+1im)V)}((1+1im)V) @test_throws DomainError Level{Decibel,1+0im,Int}(2) @test_throws DomainError Level{Decibel,(1+0im)V,typeof(2V)}(2V) end @testset ">> Gain" begin @test Gain{Decibel}(1) isa Gain{Decibel,:?,Int} @test Gain{Decibel,:rp}(1) isa Gain{Decibel,:rp,Int} @test_throws MethodError Gain{Decibel}(1V) @test_throws MethodError Gain{Decibel,:?}(1V) @test_throws TypeError Gain{Decibel,:?,typeof(1V)}(1V) end end @testset "> Implicit construction" begin @testset ">> Level" begin @test 20*dBm == (@dB 100mW/mW) == (@dB 100mW/1mW) == dB(100mW,mW) == dB(100mW,1mW) @test 20*dBV == (@dB 10V/V) == (@dB 10V/1V) == dB(10V,V) == dB(10V,1V) @test_throws ArgumentError @dB 10V/V true end @testset ">> Gain" begin @test_throws LoadError @eval @dB 10 @test 20*dB === dB*20 end @testset ">> MixedUnits" begin @test dBm === MixedUnits{Level{Decibel, 1mW}}() @test dBm/Hz === MixedUnits{Level{Decibel, 1mW}}(Hz^-1) end end @testset "> Unit and dimensional analysis" begin @testset ">> Level" begin @test dimension(1dBm) === dimension(1mW) @test dimension(typeof(1dBm)) === dimension(1mW) @test dimension(1dBV) === dimension(1V) @test dimension(typeof(1dBV)) === dimension(1V) @test dimension(1dB) === NoDims @test dimension(typeof(1dB)) === NoDims @test dimension(@dB 3V/2.14V) === dimension(1V) @test dimension(typeof(@dB 3V/2.14V)) === dimension(1V) @test logunit(1dBm) === dBm @test logunit(typeof(1dBm)) === dBm end @testset ">> Gain" begin @test logunit(3dB) === dB @test logunit(3dB_rp) === dB_rp @test logunit(3dB_p) === dB_p @test logunit(typeof(3dB)) === dB @test logunit(typeof(3dB_rp)) === dB_rp @test logunit(typeof(3dB_p)) === dB_p end @testset ">> Quantity{<:Level}" begin @test dimension(1dBm/Hz) === dimension(1mW/Hz) @test dimension(typeof(1dBm/Hz)) === dimension(1mW/Hz) @test dimension(1dB/Hz) === dimension(Hz^-1) @test dimension(typeof(1dB/Hz)) === dimension(Hz^-1) @test dimension((@dB 3V/2.14V)/Hz) === dimension(1V/Hz) @test dimension(typeof((@dB 3V/2.14V)/Hz)) === dimension(1V/Hz) end end @testset "> Conversion" begin @test float(3dB) == 3.0dB @test float(@dB 3V/1V) === @dB 3.0V/1V for x = (20, 20.0, 20//1) for u = (dB, dB/m, dBV, dBV/m) @test @inferred(big(x*u)) == big(x)*u @test typeof(big(x*u)) == typeof(big(x)*u) @test big(typeof(x*u)) == typeof(big(x)*u) end end @test uconvert(V, (@dB 3V/2.14V)) === 3V @test uconvert(V, (@dB 3V/1V)) === 3V @test uconvert(mW/Hz, 0dBm/Hz) == 1mW/Hz @test uconvert(mW/Hz, (@dB 1mW/mW)/Hz) === 1mW/Hz @test uconvert(dB, 1Np) ≈ 8.685889638065037dB @test uconvert(dB, 10dB*m/mm) == 10000dB @test convert(typeof(1.0dB), 1Np) ≈ 8.685889638065037dB @test convert(typeof(1.0dBm), 1W) == 30.0dBm @test_throws DimensionError convert(typeof(1.0dBm), 1V) @test convert(typeof(3dB), 3dB) === 3dB @test convert(typeof(3.0dB), 3dB) === 3.0dB @test convert(Float64, 0u"dBFS") === 1.0 @test_throws ErrorException convert(Float64, u"10dB") @test convert(Float64, u"10dB_p") === 10.0 @test convert(Float64, u"20dB_rp") === 10.0 @test_throws ErrorException convert(typeof(1.0dB/s), 5.0Hz) @test convert(typeof(1.0dB_p/s), 100.0Hz) === 20.0dB_p/s @test convert(typeof(1.0dB_rp/s), 100.0Hz) === 40.0dB_rp/s @test_throws ErrorException convert(typeof(1.0dB/rad), 5.0) @test convert(typeof(1.0dB_p/rad), 100.0) === 20.0dB_p/rad @test convert(typeof(1.0dB_rp/rad), 100.0) === 40.0dB_rp/rad @test convert(typeof(1.0m/cm), 40.0dB_rp) === 1.0m/cm @test convert(typeof(1.0dB_p/s), 1.0dB_p/s) === 1.0dB_p/s # This currently (and unnecessarily) involves a conversion to linear and back to logarithmic. # This is lossy due to floating-point, therefore broken. @test_broken convert(Quantity{typeof(1.0dB_rp), NoDims, typeof(Unitful.NoUnits)}, 1dB_rp) === 1.0dB_rp @test_broken convert(typeof(1.0dB_p/s), 1dB_p/s) === 1.0dB_p/s # conversion to linear and back to logarithmic → lossy due to floating-point @test isapprox(convert(typeof(1.0dB_p/s), 1dB_p/s), 1.0dB_p/s, rtol = 1e-3, atol=0/s) # Wrongly throwing DimensionError @test_broken convert(typeof(1.0dBm/s), 5.0mW*Hz) === @dB(5.0mW/1mW)/s @test convert(typeof(1.0u"dBFS/rad"), 100.0) === @dB(100.0/1, true)/rad @test_broken convert(typeof(1.0dBm/rad), 20.0dBm) === @dB(100.0mW/1mW)/rad @test convert(typeof(1.0u"dBFS/rad"), 5.0u"dBFS") === 5.0u"dBFS/rad" @test_broken convert(Quantity{typeof(1.0dBm), dimension(Unitful.mW), typeof(Unitful.NoUnits)}, 5.0dBm) === Quantity{typeof(1.0dBm), dimension(Unitful.mW), typeof(Unitful.NoUnits)}(5.0dBm) @test convert(typeof(1.0m/cm), 40.0u"dBFS") === 1.0m/cm for L = (40u"dB_rp", 20u"dB_p", 40u"dBFS") for U = (NoUnits, FixedUnits(NoUnits), ContextUnits(NoUnits, m/mm)) @test convert(Quantity{Int,NoDims,typeof(U)}, L) === Quantity{Int,NoDims,typeof(U)}(100) @test convert(Quantity{Int,NoDims,typeof(U)}, L/rad) === Quantity{Int,NoDims,typeof(U)}(100) end @test convert(Quantity{Int}, L) === Quantity{Int,NoDims,typeof(NoUnits)}(100) @test convert(Quantity{Int}, L/rad) === 100/rad @test convert(Quantity{Int,NoDims}, L) === Quantity{Int,NoDims,typeof(NoUnits)}(100) @test convert(Quantity{Int,NoDims}, L/rad) === 100/rad end @test isapprox(uconvertrp(NoUnits, 6.02dB), 2.0, atol=0.001) @test uconvertrp(NoUnits, 1Np) ≈ MathConstants.e @test uconvertrp(Np, MathConstants.e) == 1Np @test uconvertrp(NoUnits, 1) == 1 @test uconvertrp(NoUnits, 20dB) == 10 @test uconvertrp(dB, 10) == 20dB @test isapprox(uconvertp(NoUnits, 3.01dB), 2.0, atol=0.001) @test uconvertp(NoUnits, 1Np) == (MathConstants.e)^2 @test uconvertp(Np, (MathConstants.e)^2) == 1Np @test uconvertp(NoUnits, 1) == 1 @test uconvertp(NoUnits, 20dB) == 100 @test uconvertp(dB, 100) == 20dB @test linear(@dB(1mW/mW)/Hz) === 1mW/Hz @test linear(@dB(1.4V/2.8V)/s) === 1.4V/s @test convert(Gain{Decibel}, 0dB_rp) === 0dB_rp @test convert(Gain{Neper}, 10dB) ≈ 1.151292546Np @test convert(Gain{Decibel,:?}, 0dB_rp) === 0dB @test convert(Gain{Neper,:?}, 10dB) ≈ 1.151292546Np @test convert(Gain{Decibel,:?,Float32}, 0dB_rp) === 0.0f0*dB @test convert(Gain{Neper,:rp,Float32}, 10dB) === 1.1512926f0*Np_rp end @testset "> Equality" begin @testset ">> Level" begin @test big(3.0)dBm == big(3.0)dBm @test isequal(big(3.0)dBm, big(3.0)dBm) @test hash(big(3.0)dBm) == hash(big(3.0)dBm) @test @dB(3.0V/2.0V) == @dB(3V/V) @test isequal(@dB(3.0V/2.0V), @dB(3V/V)) @test hash(@dB(3.0V/2.0V)) == hash(@dB(3V/V)) end @testset ">> Gain" begin @test 3dB == (3//1)dB @test isequal(3dB, (3//1)dB) @test_broken hash(3dB) == hash((3//1)dB) @test big(3)dB == big(3)dB @test isequal(big(3)dB, big(3)dB) @test_broken hash(big(3)dB) == hash(big(3)dB) @test 0.0dB == -0.0dB @test !isequal(0.0dB, -0.0dB) @test hash(0.0dB) != hash(-0.0dB) end @test !(20dBm == 20dB) @test !(20dB == 20dBm) @test !(20dBm == 20dBV) @test !(20dBV == 20dBm) end @testset "> Addition and subtraction" begin @testset ">> Level" begin @test isapprox(10dBm + 10dBm, 13dBm; atol=0.02dBm) @test !isapprox(10dBm + 10dBm, 13dBm; atol=0.00001dBm) @test isapprox(13dBm, 20mW; atol = 0.1mW) @test @dB(10mW/mW) + 1mW === 11mW @test 1mW + @dB(10mW/mW) === 11mW @test @dB(10mW/mW) + @dB(90mW/mW) === @dB(100mW/mW) @test (@dB 10mW/3mW) + (@dB 11mW/2mW) === 21mW @test (@dB 10mW/3mW) + 2mW === 12mW @test (@dB 10mW/3mW) + 1W === 101u"kg*m^2/s^3"//100 @test 20dB + 20dB == 40dB @test 20dB + 20.2dB == 40.2dB @test 1Np + 1.5Np == 2.5Np @test_throws DimensionError (1dBm + 1dBV) @test_throws DimensionError (1dBm + 1V) end @testset ">> Gain" begin for op in (:+, :*) @test @eval ($op)(20dB, 10dB) === 30dB @test @eval ($op)(20dB_rp, 10dB) === 30dB_rp @test @eval ($op)(20dB, 10dB_rp) === 30dB_rp @test @eval ($op)(20dB_p, 10dB) === 30dB_p @test @eval ($op)(20dB, 10dB_p) === 30dB_p @test @eval ($op)(20dB_rp, 10dB_p) === 30dB @test @eval ($op)(20dB_p, 10dB_rp) === 30dB @test_throws ErrorException @eval ($op)(1dB, 1Np) # no promotion @test_throws ErrorException @eval ($op)(1dB_rp, 1Np) end for op in (:-, :/) @test @eval ($op)(20dB, 10dB) === 10dB @test @eval ($op)(20dB_rp, 10dB) === 10dB_rp @test @eval ($op)(20dB, 10dB_rp) === 10dB_rp @test @eval ($op)(20dB_p, 10dB) === 10dB_p @test @eval ($op)(20dB, 10dB_p) === 10dB_p @test @eval ($op)(20dB_rp, 10dB_p) === 10dB @test @eval ($op)(20dB_p, 10dB_rp) === 10dB @test_throws ErrorException @eval ($op)(1dB, 1Np) # no promotion @test_throws ErrorException @eval ($op)(1dB_rp, 1Np) end end @testset ">> Level, meet Gain" begin for op in (:+, :*) @test @eval ($op)(10dBm, 30dB) == 40dBm @test @eval ($op)(30dB, 10dBm) == 40dBm @test @eval ($op)(10dBm, 30dB_rp) == 40dBm @test @eval ($op)(30dB_rp, 10dBm) == 40dBm @test @eval ($op)(10dBm, 30dB_p) == 40dBm @test @eval ($op)(30dB_p, 10dBm) == 40dBm @test @eval ($op)(0Np, 3dBm) == 3dBm end for op in (:-, :/) @test @eval ($op)(10dBm, 30dB) == -20dBm @test @eval ($op)(10dBm, 30dB_rp) == -20dBm @test @eval ($op)(10dBm, 30dB_p) == -20dBm @test @eval isapprox(($op)(10dBm, 1Np), 1.314dBm; atol=0.001dBm) @test @eval isapprox(($op)(10dBm, 1Np_rp), 1.314dBm; atol=0.001dBm) @test @eval isapprox(($op)(10dBm, 1Np_p), 1.314dBm; atol=0.001dBm) # cannot subtract Levels from Gains @test_throws ArgumentError @eval ($op)(10dB, 30dBm) @test_throws ArgumentError @eval ($op)(10dB_rp, 30dBm) @test_throws ArgumentError @eval ($op)(10dB_p, 30dBm) @test_throws ArgumentError @eval ($op)(1Np, 10dBm) @test_throws ArgumentError @eval ($op)(1Np_rp, 10dBm) @test_throws ArgumentError @eval ($op)(1Np_p, 10dBm) end end end @testset "> Multiplication and division" begin @testset ">> Level" begin @test (0dBm) * 10 == (10dBm) @test @dB(10V/V)*10 == 100V @test @dB(10V/V)/20 == 0.5V @test 10*@dB(10V/V) == 100V @test 10/@dB(10V/V) == 1V^-1 @test (0dBm) * (1W) == 1*mW*W @test (1W) * (0dBm) == 1*W*mW @test 100*((0dBm)/s) == (20dBm)/s @test isapprox((3.01dBm)*(3.01dBm), 4mW^2, atol=0.01mW^2) @test typeof((1dBm * big"2").val.val) == BigFloat @test 10dBm/10Hz == 1mW/Hz @test 10Hz/10dBm == 1Hz/mW @test true*3dBm == 3dBm @test false*3dBm == -Inf*dBm @test 3dBm*true == 3dBm @test 3dBm*false == -Inf*dBm @test (0dBV)*(1Np) ≈ 8.685889638dBV @test dBm/5 ≈ 0.2dBm @test linear((@dB 3W/W)//3) === 1W//1 @test (@dB 3W/W)//(@dB 3W/W) === 1//1 end @testset ">> Gain" begin @test 3dB * 2 === 6dB @test 2 * 3dB === 6dB @test 3dB_rp * 2 === 6dB_rp @test 2 * 3dB_rp === 6dB_rp @test 3dB_p * 2 === 6dB_p @test 2 * 3dB_p === 6dB_p @test 20dB * 1mW == 100mW @test 1mW * 20dB == 100mW @test 3dB * 2.1 ≈ 6.3dB @test 3dB * false == 0*dB @test false * 3dB == 0*dB @test 1V * 20dB == 10V @test 20dB * 1V == 10V @test 10J * 10dB == 100J @test 10W/m^3 * 10dB == 100W/m^3 end @testset ">> MixedUnits" begin @test_throws ArgumentError dB*dB @test_throws ArgumentError dB/Np @test dB/dB === NoUnits @test (dB*m)/(dB*s) === m/s @test m*dB === dB*m @test [1,2,3]u"dB" == u"dB"*[1,2,3] end end @testset "> Comparisons" begin @test 3dB < 5dB @test 3dBm < 5dBm @test_throws MethodError 3dB < 5dBm end @testset "> zero, one" begin @test zero(3dB) === 0dB @test zero(3dB_rp) === 0dB_rp @test zero(typeof(3dB)) === 0dB @test one(3dB) === 0dB @test one(3dB_rp) === 0dB_rp @test one(typeof(3dB)) === 0dB @test zero(3dBm) === (-Inf)*dBm @test zero(typeof(3dBm)) === (-Inf)*dBm @test one(3dBm) === 1.0 @test one(typeof(3dBm)) === 1.0 @test one(@dB 3mW/1mW) === 1 end @testset "> Unit stripping" begin @test ustrip(500.0Np) === 500.0 @test ustrip(20dB/Hz) === 20 @test ustrip(20dB) === 20 @test ustrip(20dB_rp) === 20 @test ustrip(13dBm) ≈ 13 @test ustrip(missing) === missing end @testset "> Display" begin withenv("UNITFUL_FANCY_EXPONENTS" => false) do @test repr(3u"dB/Hz") == "[3 dB] Hz^-1" @test repr("text/plain", 3u"dB/Hz") == "[3 dB] Hz^-1" end @test Unitful.abbr(3u"dBm") == "dBm" @test Unitful.abbr(@dB 3V/1.241V) == "dB (1.241 V)" @test string(360°) == "360°" end @testset "> Thanks for signing up for Log Facts!" begin @test_throws ErrorException 20dB == 100 @test 20dBm ≈ 100mW @test 20dBV ≈ 10V @test 40dBV ≈ 100V # Maximum sound pressure level is a full swing of atmospheric pressure @test isapprox(uconvert(dBSPL, 1u"atm"), 194dBSPL, atol=0.1dBSPL) end end @testset "Output ordered by unit exponent" begin ordered = Unitful.sortexp(u"J*mol^-1*K^-1") @test typeof(ordered[1]) <: Unitful.Unit{:Joule,<:Any} @test typeof(ordered[2]) <: Unitful.Unit{:Kelvin,<:Any} @test typeof(ordered[3]) <: Unitful.Unit{:Mole,<:Any} ordered = Unitful.sortexp(u"mol*J^-1*K^-1") @test typeof(ordered[1]) <: Unitful.Unit{:Mole,<:Any} @test typeof(ordered[2]) <: Unitful.Unit{:Joule,<:Any} @test typeof(ordered[3]) <: Unitful.Unit{:Kelvin,<:Any} end # Test that the @u_str macro will find units in other modules. module ShadowUnits using Unitful @unit m "m" MyMeter 1u"m" false Unitful.register(ShadowUnits) end @test (@test_logs (:warn, r"found in multiple") eval(:(typeof(u"m")))) == Unitful.FreeUnits{(Unitful.Unit{:MyMeter, 𝐋}(0, 1//1),), 𝐋, nothing} # Test that the @u_str macro will not find units in modules which are # not loaded before the u_str invocation. module FooUnits using Unitful @unit foo "foo" MyFoo 1u"m" false Unitful.register(FooUnits) end # NB: The following is LoadError in 1.0 but an ErrorException in 0.7. @test_throws Exception eval(:(module ShouldUseFooUnits using Unitful foo() = 1u"foo" end)) # Test that u_str works when FooUnits is correctly loaded. module DoesUseFooUnits using Unitful, ..FooUnits foo() = 1u"foo" end @test DoesUseFooUnits.foo() === 1u"foo" # Tests for unit extension modules in unit parsing @test_throws ArgumentError uparse("foo", unit_context=Unitful) @test uparse("foo", unit_context=FooUnits) === u"foo" @test uparse("foo", unit_context=[Unitful, FooUnits]) === u"foo" @test uparse("foo", unit_context=[FooUnits, Unitful]) === u"foo" # Test for #272 module OnlyUstrImported import Unitful: @u_str u = u"m" end @test OnlyUstrImported.u === m # Test to make sure user macros are working properly module TUM using Unitful using Test @dimension f "f" FakeDim12345 @derived_dimension FakeDim212345 f^2 @refunit fu "fu" FakeUnit12345 f false @unit fu2 "fu2" FakeUnit212345 1fu false end @testset "User macros" begin @test typeof(TUM.f) == Unitful.Dimensions{(Unitful.Dimension{:FakeDim12345}(1//1),)} @test 1(TUM.fu) == 1(TUM.fu2) @test isa(1(TUM.fu), TUM.FakeDim12345) @test isa(TUM.fu, TUM.FakeDim12345Units) @test isa(1(TUM.fu)^2, TUM.FakeDim212345) @test isa(TUM.fu^2, TUM.FakeDim212345Units) end if isdefined(Base, :get_extension) @testset "ForwardDiff extension, solving Issue 682" begin @test ForwardDiff.Dual(1.0)*u"cm/m" + ForwardDiff.Dual(1.0) == 1.01 @test ForwardDiff.Dual(1.0)*u"cm/m" == ForwardDiff.Dual(0.01) end @testset "NaNMath extension" begin @test isnan(NaNMath.sqrt(-1m)) @test unit(NaNMath.sqrt(-1m)) == m^(1//2) @test isnan(NaNMath.pow(-1m, 0.5)) @test unit(NaNMath.pow(-1m, 0.5)) == m^(1//2) @test NaNMath.sqrt(m) === Base.sqrt(m) @test NaNMath.pow(m, 2) === m^2 end end struct Num <: Real x::Float64 end Base.:+(a::Num, b::Num) = Num(a.x + b.x) Base.:-(a::Num, b::Num) = Num(a.x - b.x) Base.:*(a::Num, b::Num) = Num(a.x * b.x) Base.promote_rule(::Type{Num}, ::Type{<:Real}) = Num Base.ArithmeticStyle(::Type{Num}) = Base.ArithmeticRounds() Base.OrderStyle(::Type{Num}) = Base.Unordered() @testset "Custom types" begin # Test that @generated functions work with Quantities + custom types (#231) @test uconvert(u"°C", Num(373.15)u"K") == Num(100)u"°C" end @testset "Traits" begin @testset "> ArithmeticStyle" begin @test Base.ArithmeticStyle(1m) === Base.ArithmeticWraps() @test Base.ArithmeticStyle(1.0m) === Base.ArithmeticRounds() @test Base.ArithmeticStyle((1//1)m) === Base.ArithmeticUnknown() @test Base.ArithmeticStyle(Num(1)m) === Base.ArithmeticRounds() end @testset "> OrderStyle" begin @test Base.OrderStyle(1m) === Base.Ordered() @test Base.OrderStyle((1+1im)m) === Base.Unordered() @test Base.OrderStyle(Num(1)m) === Base.Unordered() end end @testset "Encoding" begin # Julia treats µ (U+00B5) and μ (U+03BC) as the same @test Unitful.µ0 === Unitful.μ0 @test Unitful.µm === Unitful.μm @test Unitful.dBµV === Unitful.dBμV @test u"µ0" === u"μ0" @test u"µm" === u"μm" @test u"dBµV" === u"dBμV" @test uparse("µ0") === uparse("μ0") @test uparse("µm") === uparse("μm") @test uparse("dBµV") === uparse("dBμV") @test @doc(Unitful.µm) == @doc(Unitful.μm) # Julia treats ɛ (U+025B) and ε (U+03B5) as the same @test Unitful.ɛ0 === Unitful.ε0 @test u"ɛ0" === u"ε0" @test uparse("ɛ0") === uparse("ε0") @test @doc(Unitful.ɛ0) == @doc(Unitful.ε0) # Julia treats Å (U+00C5) and Å (U+212B) as the same @test Unitful.Å === Unitful.Å @test u"Å" === u"Å" @test uparse("Å") === uparse("Å") @test @doc(Unitful.Å) == @doc(Unitful.Å) end @testset "Units aliases" begin @test Unitful.L === Unitful.l @test Unitful.mL === Unitful.ml @test 1Unitful.L === 1Unitful.l @test 2Unitful.mL === 2Unitful.ml @test Unitful.ϵ0 === Unitful.ε0 @test (1//2)Unitful.ϵ0 === (1//2)Unitful.ε0 @test Unitful.Å === Unitful.angstrom @test 1.0Unitful.Å === 1.0Unitful.angstrom @test Unitful.deg === Unitful.° @test 2Unitful.° === 2Unitful.deg @test u"deg" === u"°" @test uparse("deg") === uparse("°") @test Unitful.degC === Unitful.°C @test 2Unitful.°C === 2Unitful.degC @test u"degC" === u"°C" @test Unitful.degF === Unitful.°F @test 2Unitful.°F === 2Unitful.degF @test u"degF" === u"°F" end module DocUnits using Unitful using Unitful: 𝐋 "dimension docs" @dimension 𝐃 "𝐃" DocDimension true @derived_dimension DerivedDocDimension 𝐃*𝐋 true "refunit docs" @refunit dRefFoo "dRefFoo" DRefFoo 𝐃 true true "unit docs" @unit dFoo "dFoo" DFoo 1*dRefFoo*u"m" true true end using REPL # This is necessary to make `@doc` work correctly @testset "Docs" begin @test string(@doc(Unitful.L)) == string(@doc(Unitful.l)) @test string(@doc(Unitful.cL)) == string(@doc(Unitful.cl)) @test string(@doc(Unitful.ϵ0)) == string(@doc(Unitful.ε0)) @test string(@doc(Unitful.Å)) == string(@doc(Unitful.angstrom)) @test string(@doc DocUnits.𝐃) == "dimension docs\n" @test string(@doc DocUnits.dRefFoo) == "refunit docs\n" @test string(@doc DocUnits.dFoo) == "unit docs\n" CODEBLOCK_LANG = VERSION ≥ v"1.12.0-DEV" ? "julia" : "" @test string(@doc DocUnits.DocDimension) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.DocDimension{T, U} ``` A supertype for quantities and levels of dimension [`$(@__MODULE__).DocUnits.𝐃`](@ref) with a value of type `T` and units `U`. See also: [`$(@__MODULE__).DocUnits.𝐃`](@ref), `Unitful.Quantity`, `Unitful.Level`. """ @test string(@doc DocUnits.DocDimensionUnits) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.DocDimensionUnits{U} ``` A supertype for units of dimension [`$(@__MODULE__).DocUnits.𝐃`](@ref). Equivalent to `Unitful.Units{U, $(@__MODULE__).DocUnits.𝐃}`. See also: [`$(@__MODULE__).DocUnits.𝐃`](@ref), `Unitful.Units`. """ @test string(@doc DocUnits.DocDimensionFreeUnits) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.DocDimensionFreeUnits{U} ``` A supertype for `Unitful.FreeUnits` of dimension [`$(@__MODULE__).DocUnits.𝐃`](@ref). Equivalent to `Unitful.FreeUnits{U, $(@__MODULE__).DocUnits.𝐃}`. See also: [`$(@__MODULE__).DocUnits.𝐃`](@ref). """ @test string(@doc DocUnits.DerivedDocDimension) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.DerivedDocDimension{T, U} ``` A supertype for quantities and levels of dimension `𝐃 * 𝐋` with a value of type `T` and units `U`. See also: `Unitful.Quantity`, `Unitful.Level`. """ @test string(@doc DocUnits.DerivedDocDimensionUnits) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.DerivedDocDimensionUnits{U} ``` A supertype for units of dimension `𝐃 * 𝐋`. Equivalent to `Unitful.Units{U, 𝐃 * 𝐋}`. See also: `Unitful.Units`. """ @test string(@doc DocUnits.DerivedDocDimensionFreeUnits) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.DerivedDocDimensionFreeUnits{U} ``` A supertype for `Unitful.FreeUnits` of dimension `𝐃 * 𝐋`. Equivalent to `Unitful.FreeUnits{U, 𝐃 * 𝐋}`. """ @test string(@doc DocUnits.kdFoo) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.kdFoo ``` A prefixed unit, equal to 10^3 dFoo. Dimension: 𝐃 𝐋 See also: [`$(@__MODULE__).DocUnits.dFoo`](@ref). """ @test string(@doc DocUnits.kdRefFoo) == """ ```$CODEBLOCK_LANG $(@__MODULE__).DocUnits.kdRefFoo ``` A prefixed unit, equal to 10^3 dRefFoo. Dimension: 𝐃 See also: [`$(@__MODULE__).DocUnits.dRefFoo`](@ref). """ end # Test precompiled Unitful extension modules mktempdir() do load_path mktempdir() do load_cache_path write(joinpath(load_path, "ExampleExtension.jl"), """ module ExampleExtension using Unitful @unit year "year" JulianYear 365u"d" true function __init__() Unitful.register(ExampleExtension) end end """) pushfirst!(LOAD_PATH, load_path) pushfirst!(DEPOT_PATH, load_cache_path) @eval using ExampleExtension # Delay u"year" expansion until test time @eval @test uconvert(u"d", 1u"year") == 365u"d" end end using Aqua Aqua.test_all(Unitful, ambiguities=VERSION≥v"1.1", unbound_args=false, piracies=VERSION≥v"1.8")