diff --git a/.flake8 b/.flake8 index 4592939f..14040456 100644 --- a/.flake8 +++ b/.flake8 @@ -7,3 +7,6 @@ ignore = E712, # line break before binary operator W503 +per-file-ignores = + # flake8 is just plain wrong here, contradicting black + .github/generate-job-matrix.py:E225,E231 diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py new file mode 100644 index 00000000..bd14ee00 --- /dev/null +++ b/.github/generate-job-matrix.py @@ -0,0 +1,206 @@ +import argparse +import json +import os +import random +import typing +from types import SimpleNamespace + +from job_matrix import CombinationCollector, Compiler, Configuration + + +def make_gcc_config(version: int) -> Configuration: + return Configuration( + name=f"GCC-{version}", + os="ubuntu-24.04", + compiler=Compiler( + type="GCC", + version=version, + cc=f"gcc-{version}", + cxx=f"g++-{version}", + ), + cxx_modules=False, + std_format_support=version >= 13, + ) + + +def make_clang_config( + version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64" +) -> Configuration: + cfg = SimpleNamespace( + name=f"Clang-{version} ({platform})", + compiler=SimpleNamespace( + type="CLANG", + version=version, + ), + lib="libc++", + cxx_modules=version >= 17, + std_format_support=version >= 17, + ) + match platform: + case "x86-64": + cfg.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04" + cfg.compiler.cc = f"clang-{version}" + cfg.compiler.cxx = f"clang++-{version}" + case "arm64": + cfg.os = "macos-14" + pfx = f"/opt/homebrew/opt/llvm@{version}/bin" + cfg.compiler.cc = f"{pfx}/clang" + cfg.compiler.cxx = f"{pfx}/clang++" + case _: + raise KeyError(f"Unsupported platform {platform!r} for Clang") + ret = cfg + ret.compiler = Compiler(**vars(cfg.compiler)) + return Configuration(**vars(ret)) + + +def make_apple_clang_config(version: int) -> Configuration: + ret = Configuration( + name=f"Apple Clang {version}", + os="macos-13", + compiler=Compiler( + type="APPLE_CLANG", + version=f"{version}.0", + cc="clang", + cxx="clang++", + ), + cxx_modules=False, + std_format_support=False, + ) + return ret + + +def make_msvc_config(release: str, version: int) -> Configuration: + ret = Configuration( + name=f"MSVC {release}", + os="windows-2022", + compiler=Compiler( + type="MSVC", + version=version, + cc="", + cxx="", + ), + cxx_modules=False, + std_format_support=True, + ) + return ret + + +configs = { + c.name: c + for c in [make_gcc_config(ver) for ver in [12, 13, 14]] + + [ + make_clang_config(ver, platform) + for ver in [16, 17, 18] + for platform in ["x86-64", "arm64"] + # arm64 runners are expensive; only consider one version + if ver == 18 or platform != "arm64" + ] + + [make_apple_clang_config(ver) for ver in [15]] + + [make_msvc_config(release="14.4", version=194)] +} + +full_matrix = dict( + config=list(configs.values()), + std=[20, 23], + formatting=["std::format", "fmtlib"], + contracts=["none", "gsl-lite", "ms-gsl"], + build_type=["Release", "Debug"], +) + + +def main(): + parser = argparse.ArgumentParser() + # parser.add_argument("-I","--include",nargs="+",action="append") + # parser.add_argument("-X","--exclude",nargs="+",action="append") + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--preset", default=None) + parser.add_argument("--debug", nargs="+", default=["combinations"]) + parser.add_argument("--suppress-output", default=False, action="store_true") + + args = parser.parse_args() + + rgen = random.Random(args.seed) + + collector = CombinationCollector( + full_matrix, + hard_excludes=lambda e: ( + e.formatting == "std::format" and not e.config.std_format_support + ), + ) + match args.preset: + case None: + pass + case "all": + collector.all_combinations() + case "conan" | "cmake": + collector.all_combinations( + formatting="std::format", + contracts="gsl-lite", + build_type="Debug", + std=20, + ) + collector.all_combinations( + filter=lambda me: not me.config.std_format_support, + formatting="fmtlib", + contracts="gsl-lite", + build_type="Debug", + std=20, + ) + collector.sample_combinations(rgen=rgen, min_samples_per_value=2) + case "clang-tidy": + collector.all_combinations(config=configs["Clang-18 (x86-64)"]) + case "freestanding": + # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler + collector.all_combinations( + filter=lambda e: not ( + e.config.name.startswith("Clang-18") and e.build_type == "Debug" + ), + config=[configs[c] for c in ["GCC-14", "Clang-18 (x86-64)"]], + contracts="none", + std=23, + ) + case _: + raise KeyError(f"Unsupported preset {args.preset!r}") + + if not collector.combinations: + raise ValueError("No combination has been produced") + + data = sorted(collector.combinations) + + json_data = [e.as_json() for e in data] + + output_file = os.environ.get("GITHUB_OUTPUT") + if not args.suppress_output: + if output_file: + print(f"Writing outputs to {output_file}") + with open(output_file, "wt") as fh: + fh.write(f"matrix={json.dumps(json_data)}") + else: + print("No output file received!") + + for dbg in args.debug: + match dbg: + case "yaml": + import yaml + + json_data = json.loads(json.dumps(json_data)) + print(yaml.safe_dump(json_data)) + case "json": + print(json.dumps(json_data, indent=4)) + case "combinations": + for e in data: + print( + f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}" + ) + case "counts": + print(f"Total combinations {len(data)}") + for (k, v), n in sorted(collector.per_value_counts.items()): + print(f" {k}={v}: {n}") + case "none": + pass + case _: + raise KeyError(f"Unknown debug mode {dbg!r}") + + +if __name__ == "__main__": + main() diff --git a/.github/job_matrix.py b/.github/job_matrix.py new file mode 100644 index 00000000..0458a1e9 --- /dev/null +++ b/.github/job_matrix.py @@ -0,0 +1,139 @@ +import dataclasses +import itertools +import random +import typing +from dataclasses import dataclass + + +@dataclass(frozen=True, order=True) +class Compiler: + type: typing.Literal["GCC", "CLANG", "APPLE_CLANG", "MSVC"] + version: str | int + cc: str + cxx: str + + +@dataclass(frozen=True, order=True) +class Configuration: + name: str + os: str + compiler: Compiler + cxx_modules: bool + std_format_support: bool + conan_config: str = "" + lib: typing.Literal["libc++", "libstdc++"] | None = None + + def __str__(self): + return self.name + + +@dataclass(frozen=True, order=True) +class MatrixElement: + config: Configuration + std: typing.Literal[20, 23] + formatting: typing.Literal["std::format", "fmtlib"] + contracts: typing.Literal["none", "gsl-lite", "ms-gsl"] + build_type: typing.Literal["Release", "Debug"] + + def as_json(self): + def dataclass_to_json(obj): + """Convert dataclasses to something json-serialisable""" + if dataclasses.is_dataclass(obj): + return { + k: dataclass_to_json(v) for k, v in dataclasses.asdict(obj).items() + } + return obj + + ret = dataclass_to_json(self) + # patch boolean conan configuration options + config = ret["config"] + for k in ["cxx_modules"]: + config[k] = "True" if config[k] else "False" + return ret + + +class CombinationCollector: + """Incremental builder of MatrixElements, allowing successive selection of entries.""" + + def __init__( + self, + full_matrix: dict[str, list[typing.Any]], + *, + hard_excludes: typing.Callable[[MatrixElement], bool] | None = None, + ): + self.full_matrix = full_matrix + self.hard_excludes = hard_excludes + self.combinations: set[MatrixElement] = set() + self.per_value_counts: dict[tuple[str, typing.Any], int] = { + (k, v): 0 for k, options in full_matrix.items() for v in options + } + + def _make_submatrix(self, **overrides): + new_matrix = dict(self.full_matrix) + for k, v in overrides.items(): + if not isinstance(v, list): + v = [v] + new_matrix[k] = v + return new_matrix + + def _add_combination(self, e: MatrixElement): + if e in self.combinations or ( + self.hard_excludes is not None and self.hard_excludes(e) + ): + return + self.combinations.add(e) + # update per_value_counts + for k, v in vars(e).items(): + idx = (k, v) + self.per_value_counts[idx] = self.per_value_counts.get(idx, 0) + 1 + + def all_combinations( + self, + *, + filter: typing.Callable[[MatrixElement], bool] | None = None, + **overrides, + ): + """Adds all combinations in the submatrix defined by `overrides`.""" + matrix = self._make_submatrix(**overrides) + keys = tuple(matrix.keys()) + for combination in itertools.product(*matrix.values()): + cand = MatrixElement(**dict(zip(keys, combination))) + if filter and not filter(cand): + continue + self._add_combination(cand) + + def sample_combinations( + self, + *, + rgen: random.Random, + min_samples_per_value: int = 1, + filter: typing.Callable[[MatrixElement], bool] | None = None, + **overrides, + ): + """Adds samples from the submatrix defined by `overrides`, + ensuring each individual value appears at least n times. + """ + matrix = self._make_submatrix(**overrides) + missing: dict[tuple[str, typing.Any], int] = {} + for key, options in matrix.items(): + for value in options: + idx = (key, value) + missing[idx] = min_samples_per_value - self.per_value_counts.get(idx, 0) + while missing: + (force_key, force_option), remaining = next(iter(missing.items())) + if remaining <= 0: + missing.pop((force_key, force_option)) + continue + choice = {} + for key, options in matrix.items(): + choice[key] = force_option if key == force_key else rgen.choice(options) + cand = MatrixElement(**choice) + if filter and not filter(cand): + continue + self._add_combination(cand) + for idx in choice.items(): + if missing.pop(idx, 0) <= 0: + continue + remaining = min_samples_per_value - self.per_value_counts.get(idx, 0) + if remaining > 0: + missing[idx] = remaining diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 015d4229..41abb0eb 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -34,33 +34,33 @@ on: paths-ignore: - "docs/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset clang-tidy --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - build_type: ["Release", "Debug"] + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} @@ -127,8 +127,11 @@ jobs: sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default conan profile show -pr default - - run: echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - - run: echo "import_std=$([ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + - name: Set 'std_format' and 'import_std' environment variables + shell: bash + run: | + echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "import_std=$([ "${{ matrix.std }}" -ge "23" ] && [ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Run clang-tidy shell: bash run: | diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index 8d5fc75a..75abee7e 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -33,149 +33,35 @@ on: env: CHANNEL: ${{ fromJSON('["testing", "stable"]')[github.ref_type == 'tag' && startsWith(github.ref_name, 'v')] }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset conan --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "MSVC 14.4", - os: windows-2022, - compiler: { type: MSVC, version: 194, cc: "", cxx: "" }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "GCC-12", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 12, - cc: "gcc-12", - cxx: "g++-12", - }, - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - - { - name: "GCC-13", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 13, - cc: "gcc-13", - cxx: "g++-13", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-16", - os: ubuntu-22.04, - compiler: - { - type: CLANG, - version: 16, - cc: "clang-16", - cxx: "clang++-16", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - - { - name: "Clang-17", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 17, - cc: "clang-17", - cxx: "clang++-17", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18 on Apple M1 (arm64)", - os: macos-14, - compiler: - { - type: CLANG, - version: 18, - cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", - cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Apple Clang 15", - os: macos-13, - compiler: - { - type: APPLE_CLANG, - version: "15.0", - cc: "clang", - cxx: "clang++", - }, - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - build_type: ["Release", "Debug"] - exclude: - - formatting: "std::format" - config: { std_format_support: "False" } - + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} CXX: ${{ matrix.config.compiler.cxx }} - steps: - uses: actions/checkout@v4 - name: Generate unique cache id @@ -258,21 +144,21 @@ jobs: shell: bash run: | echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - echo "import_std=$([ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "import_std=$([ "${{ matrix.std }}" -ge "23" ] && [ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Create Conan package if: matrix.config.compiler.type != 'MSVC' shell: bash run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.mp-units.build:all=True \ - -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan-config }} + -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan_config }} - name: Create Conan package if: matrix.config.compiler.type == 'MSVC' shell: bash run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.mp-units.build:all=False \ - -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan-config }} + -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan_config }} - name: Obtain package reference id: get-package-ref shell: bash diff --git a/.github/workflows/ci-formatting.yml b/.github/workflows/ci-formatting.yml index 086fe625..e61d5b83 100644 --- a/.github/workflows/ci-formatting.yml +++ b/.github/workflows/ci-formatting.yml @@ -24,6 +24,10 @@ name: Formatting CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-24.04 diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index cde058db..7a234e02 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -34,51 +34,32 @@ on: paths-ignore: - "docs/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset freestanding --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none"] - std: [23] - config: - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - build_type: ["Release", "Debug"] - # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler - exclude: - - build_type: "Debug" - config: { name: "Clang-18" } + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index d5bf5df0..d8680291 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -38,136 +38,32 @@ on: - "example/**" - "test/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset conan --seed 42 --debug combinations counts test_package: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "MSVC 14.4", - os: windows-2022, - compiler: { type: MSVC, version: 194, cc: "", cxx: "" }, - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "GCC-12", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 12, - cc: "gcc-12", - cxx: "g++-12", - }, - cxx_modules: "False", - std_format_support: "False", - } - - { - name: "GCC-13", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 13, - cc: "gcc-13", - cxx: "g++-13", - }, - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Clang-16", - os: ubuntu-22.04, - compiler: - { - type: CLANG, - version: 16, - cc: "clang-16", - cxx: "clang++-16", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "False", - } - - { - name: "Clang-17", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 17, - cc: "clang-17", - cxx: "clang++-17", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Clang-18 on Apple M1 (arm64)", - os: macos-14, - compiler: - { - type: CLANG, - version: 18, - cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", - cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Apple Clang 15", - os: macos-14, - compiler: - { - type: APPLE_CLANG, - version: "15.0", - cc: "clang", - cxx: "clang++", - }, - cxx_modules: "False", - std_format_support: "False", - } - build_type: ["Release", "Debug"] - exclude: - - formatting: "std::format" - config: { std_format_support: "False" } + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} @@ -255,8 +151,8 @@ jobs: - name: Set 'std_format' and 'import_std' environment variables shell: bash run: | - echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - echo "import_std=$([ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + echo "import_std=$([ "${{ matrix.std }}" -ge "23" ] && [ "${{ matrix.config.cxx_modules }}" == "True" ] && [ "${{ matrix.config.contracts }}" == "none" ] && [ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Install Conan dependencies shell: bash run: | diff --git a/.github/workflows/citation.yml b/.github/workflows/citation.yml index e5f22acf..de8eb51c 100644 --- a/.github/workflows/citation.yml +++ b/.github/workflows/citation.yml @@ -5,6 +5,10 @@ on: paths: - CITATION.cff +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: Validate-CITATION-cff: runs-on: ubuntu-latest diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c7e0abb5..e742e8a1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,6 +22,10 @@ on: paths-ignore: - "docs/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3072cd79..09750e9d 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -36,6 +36,11 @@ on: - "mkdocs.yml" permissions: contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: deploy: runs-on: ubuntu-latest diff --git a/docs/blog/posts/isq-part-1-introduction.md b/docs/blog/posts/isq-part-1-introduction.md index e567e9e4..d49d399b 100644 --- a/docs/blog/posts/isq-part-1-introduction.md +++ b/docs/blog/posts/isq-part-1-introduction.md @@ -26,6 +26,7 @@ In this series, we will describe: - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Terms and Definitions diff --git a/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md b/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md index f64b0940..911d8a05 100644 --- a/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md +++ b/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md @@ -28,6 +28,7 @@ library on just units or dimensions. - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Limitations of units-only solutions diff --git a/docs/blog/posts/isq-part-3-modeling-isq.md b/docs/blog/posts/isq-part-3-modeling-isq.md index 58e21107..fab9661e 100644 --- a/docs/blog/posts/isq-part-3-modeling-isq.md +++ b/docs/blog/posts/isq-part-3-modeling-isq.md @@ -27,6 +27,7 @@ language. - Part 3 - Modeling ISQ - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Dimension is not enough to describe a quantity diff --git a/docs/blog/posts/isq-part-4-implemeting-isq.md b/docs/blog/posts/isq-part-4-implemeting-isq.md index 61dca347..0eea0078 100644 --- a/docs/blog/posts/isq-part-4-implemeting-isq.md +++ b/docs/blog/posts/isq-part-4-implemeting-isq.md @@ -28,6 +28,7 @@ Now, it is time to see how we can implement hierarchies of quantities of the sam - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - Part 4 - Implementing ISQ - [Part 5 - Benefits](isq-part-5-benefits.md) +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Modeling a hierarchy of kind _length_ diff --git a/docs/blog/posts/isq-part-5-benefits.md b/docs/blog/posts/isq-part-5-benefits.md index c9d63435..136e1e1f 100644 --- a/docs/blog/posts/isq-part-5-benefits.md +++ b/docs/blog/posts/isq-part-5-benefits.md @@ -26,6 +26,7 @@ how our ISQ model elegantly addresses the remaining problems. - [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) - [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) - Part 5 - Benefits +- [Part 6 - Challenges](isq-part-6-challenges.md) ## Generic but safe interfaces diff --git a/docs/blog/posts/isq-part-6-challenges.md b/docs/blog/posts/isq-part-6-challenges.md new file mode 100644 index 00000000..f658813e --- /dev/null +++ b/docs/blog/posts/isq-part-6-challenges.md @@ -0,0 +1,530 @@ +--- +date: 2024-11-11 +authors: + - mpusz +categories: + - Metrology +comments: true +--- + +# International System of Quantities (ISQ): Part 6 - Challenges + +This article might be the last one from our series. This time, we will discuss the challenges and +issues with modeling of the ISQ in software. + + + +## Articles from this series + +- [Part 1 - Introduction](isq-part-1-introduction.md) +- [Part 2 - Problems when ISQ is not used](isq-part-2-problems-when-isq-is-not-used.md) +- [Part 3 - Modeling ISQ](isq-part-3-modeling-isq.md) +- [Part 4 - Implementing ISQ](isq-part-4-implemeting-isq.md) +- [Part 5 - Benefits](isq-part-5-benefits.md) +- Part 6 - Challenges + + +## Ambiguity + +Some quantity names are ambiguous. It is not a problem of ISQ but of the English language and +the way we communicate things. When I say: "Every _width_ is a _length_, but not every _length_ +is a _width_" most people understand this right away. However, the same people trying to model +[our 3D box problem](isq-part-2-problems-when-isq-is-not-used.md#various-quantities-of-the-same-dimension-and-kinds) +try to do it as follows: + +```cpp +class Box { + quantity length_; + quantity width_; + quantity height_; +public: + // ... +}; +``` + +This looks correct at first sight. Only when we think about the sentence mentioned above will +we realize that this implementation has a problem. We intended to specify three orthogonal +dimensions of the box, each of which will be a strong quantity that is not convertible to others. +But we've failed. + +When we look at the +[tree of quantities of length](isq-part-4-implemeting-isq.md#modeling-a-hierarchy-of-kind-length) +we immediately see that both _width_ and _height_ are special _lengths_ so they are convertible to +it. + +To implement our task correctly, we had to define and use a new quantity of kind _length_: + +```cpp +inline constexpr struct horizontal_length final : quantity_spec {} horizontal_length; +``` + +We do not propose adding _horizontal length_ to ISO 80000-3. There are probably other similar +cases as well, but so far, this was the most common and obvious one we've encountered. + + +## No common quantities + +ISO 80000-1:2009 explicitly states: + +!!! quote + + Two or more quantities cannot be added or subtracted unless they belong to the same category + of mutually comparable quantities. + +This means that we should be able to add and subtract any quantities as long as they belong to +the same kind. However, ISO/IEC documents do not provide any rules or even hints about what should +be the result of such operations. + +If it is possible to add _radius_ and _distance_, then what quantity should be provided +in return? Undoubtedly, the resulting quantity type can't be the same as any of the arguments. +It is not a _radius_ or _distance_. It is some closer unspecified _length_, though. + +!!! info + + Finding the correct solution took us many months of experimentation and implementation. + Based on the hierarchy tree of quantities, we can define + [conversion rules](isq-part-3-modeling-isq.md#converting-between-quantities-of-the-same-kind) + and what a [common quantity should be](isq-part-3-modeling-isq.md#comparing-adding-and-subtracting-quantities-of-the-same-kind). + + +## Lack of consistency + +The documents of ISO/IEC 80000 are not 100% consistent, and programming languages do not like +inconsistencies. + +For example: + +- _time_ is mentioned as a base quantity of ISQ in ISO 80000-1 chapter 4.5. +- ISO 80000-3 "Space and time", does not define a quantity of _time_. It provides a _duration_ + quantity (item 3-9) with symbol _t_, and states in the Remarks section: + + !!! quote + + Duration is often just called time. + +- Other parts (e.g., IEC 80000-6 "Electromagnetism") often say: + + !!! quote + + ... _t_ is time (ISO 80000-3) + +To be consistent, ISO/IEC should either: + +- change ISO 80000-1 chapter 4.5 and all references in other parts to use _duration_ (unlikely), +- or add _time_ as an alias name to _duration_ in the definition 3-9 of ISO 80000-3. + + +## Lack of definitions + +ISQ defines derived quantities in terms of other quantities provided in the series. However, some +definitions mention quantities that are not defined in the ISQ at all. + +For example, _weight_ is defined as $F_\textsf{g} = m\;g$, where $m$ is the _mass_ of the body +(item 4-1 of ISO 80000-4 "Mechanics"), and $g$ is the _local acceleration of free fall_ (ISO 80000-3). + +The problem here is that ISO 80000-3 never defines a quantity with a symbol $g$ or named as a +_local acceleration of free fall_. The closest one we have is _acceleration_ (item 3-11) with +a symbol $a$. + +!!! info + + To have a proper definition of _weight_ in **mp-units** that is not defined in terms of just + any kind of _acceleration_, we have added `isq::acceleration_of_free_fall` in our definitions + as an extension to the original ISQ set of quantities. + + +## Not engineering-friendly + +Many quantities have proper physical definitions, but they are sometimes not engineering-friendly. + +For example, _velocity_ is defined as a rate of change of _position vector_ +$v = \frac{\textsf{d}r}{\textsf{d}t}$, where $r$ denotes the _position vector_ (item 3‑1.10) and +$t$ the _duration_ (item 3‑9). + +Next, a _speed_ quantity is defined as the magnitude of _velocity_. Despite being +physically correct, requiring every _speed_ to be derived from the vector quantity of _velocity_ +in software would be inconvenient. If this was the only case, people would always need to use +vector representations of _position vectors_ to talk about _speeds_, which differs from what we +do in practice. In practice, we divide any kind of _length_ by _time_ to get some kind of _speed_. + +ISO 80000-3 provides _length_, _height_, _distance_ and other quantities of kind _length_ that when +divided by _duration_ can serve really well to calculate _speed_. + +!!! info + + This is why in **mp-units**, we decided to divert from the official definition of _speed_ and + define it as: + + ```cpp + inline constexpr struct speed : quantity_spec {} speed; + ``` + + This allows us to create a quantity of kind _speed_ from any quantity of _length_ divided + by _time_. + + Additionally, it is essential to note that for the needs of our library, defining _velocity_ + as `position_vector / duration` would be wrong. We miss the delta part here. Even though it is + not mentioned in ISO 80000-3, the delta of _position vectors_ is actually a _displacement_. + This is why our _velocity_ is defined as: + + ```cpp + inline constexpr struct velocity : quantity_spec {} velocity; + ``` + + Please also note that _velocity_ is defined as a more specialized quantity of _speed_. + + +## Affine space agnostic + +[The affine space](../../users_guide/framework_basics/the_affine_space.md) is a powerful +abstraction, allowing us to model some problems safer or more accurately. It has two types of +entities: + +- point - a position specified with coordinate values (e.g., location, address, etc.), +- displacement vector - the difference between two points (e.g., shift, offset, displacement, + duration, etc.). + +Vectors support all the arithmetics operations, but points have some limitations. It is not +possible to: + +- add two _points_, +- subtract a _point_ from a _vector_, +- multiply nor divide _points_ with anything else. + +ISO/IEC series does not acknowledge this abstraction even though it would be really useful in +some cases. Let's discuss the following two examples. + +What does it mean to add two _altitudes_? It is not meaningful. On the other hand, subtracting +those should not result in an _altitude_, but in a quantity of _height_. Adding or +subtracting _height_ to/from _altitude_ results in _altitude_. Subtracting _altitude_ from +_height_ is meaningless again. Those quantities clearly model affine space. Maybe this is why +ISQ defines them as one quantity type _height_/_depth_/_altitude_? + +What does it mean to add two _position vectors_? It is not meaningful again. However, subtracting +those results in a _displacement_ as we noted in the previous chapter. Adding or subtracting +_displacement_ to/from _position vector_ results in another _position vector_, and subtracting +_position vector_ from _displacement_ does not have physical sense. Again, those quantities +perfectly model affine space. However, this time, those are defined as separate and independent +quantities (i.e., displacement is not modeled as delta _position vector_ or _position vector_ +is not modeled as a _displacement_ from the origin of a coordinate system). + +!!! info + + Currently, **mp-units** does not enforce the affine space behavior for such quantities. + Today, subtracting two _altitudes_ result in an _altitude_ and subtracting two + _position vectors_ result in a _position vector_. However, we plan to support automatic + conversion to a proper quantity type on subtraction and addition shortly. + + +## Non-negative quantities + +Some quantities in the ISQ are defined as non-negative. This is a really interesting property that +may be checked at runtime to increase safety. However, the number of such quantities is minimal. +From a few hundred quantities provided by the ISO/IEC series, only the following have this property +mentioned explicitly: + +- _width_/_breadth_, +- _thickness_, +- _diameter_, +- _radius_. + +If _height_ was defined separately from _altitude_, it could probably also join this group. + +Let's think a bit more about this. What does it mean that a quantity is non-negative? Indeed, +it is hard to imagine something of a negative _width_ or _radius_. However, if we subtract +two _widths_, the second one may be larger. This will result in a negative +quantity of _width_, violating our precondition. So, is it non-negative or not? + +Again, we have to talk about the affine space abstractions. Every empirical measurement can be +expressed as a point. Such points for some quantities may be non-negative indeed. + +Non-negative quantities do not end on the ones provided above. For example, _speed_ is a good +example here as well. In general, all magnitudes of vector quantities will also have this property. + +When subtracting two points, we end up with a delta/displacement type, which may be negative +even for quantities listed as non-negative in the ISQ. As stated in the previous chapter, +having affine space abstractions acknowledged in ISQ would greatly help here. + + +## Lack of quantity recipes + +Definition of many derived quantities provides their recipes in the form of +[quantity equations](../../appendix/glossary.md#quantity-equation) (e.g., _weight_ equation +in the previous chapter). However, some of them do not. Instead, they often provide a very +generic description. + +For example, _force_ is defined as: + +!!! quote + + vector (ISO 80000-2) quantity describing interaction between bodies or particles. + +This is not helpful for programming languages that like explicit definitions. Different +vendors may interpret the above differently, which will result in different implementations that +will not be compatible with each other. + +As the derived quantity of _force_ has to be a vector quantity, it has to be defined in terms of +at least one other vector quantity. We have a few to choose from: + +- _displacement_ ($\Delta{r}$), +- _velocity_ ($v$), +- _acceleration_ ($a$). + +It is not stated explicitly in ISQ which one of those should be used and how. + +!!! info + + In **mp-units** we decided to define _force_ as $F = m\;a$. + + +## Lack of generic quantities and name conflicts + +In the previous chapter, we complained about some definitions needing to be more complex or generic. +On the other hand, we also lack some generic quantities in ISQ that could serve as a root for +a quantity hierarchy tree. + +For example: + +- ISO 80000-4 "Mechanics" defines _power <mechanics>_ as $P = F\;v$ (scalar product of force $F$ + (item 4-9.1) acting to a body and its velocity $v$ (ISO 80000-3)), +- ISO 80000-6 "Electromagnetism" defines _power_ as $p = u\;i$ (scalar quantity given by the + product of _instantaneous voltage_ $u$ (item 6-11.3) and _instantaneous electric current_ $i$ + (item 6-1)). + +First, the above definitions have somehow conflicting names which makes it hard for the programming +languages to name them consistently by different vendors. + +!!! info + + In **mp-units**, we chose `mechanical_power` and `electromagnetism_power` for those. + +Second, we do not have any other more generic definition of _power_ to put above those in the tree. +Not having it makes it hard to answer what should be the result of: + +```cpp +quantity q = isq::mechanical_power(42 * W) + isq::electromagnetism_power(60 * W); +``` + +!!! info + + To solve the above problem, we have added `isq::power` in **mp-units**, that has a really + generic definition of: + + ```cpp + inline constexpr struct power : quantity_spec(length) / pow<3>(time)> {} power; + ``` + + +## Invalid definitions order + +_Energy_ is defined a bit better than _power_, but still not without issues. + +The first time ISQ mentions _energy_ is in the ISO 80000-4 "Mechanics". It defines +_potential energy_, _kinetic energy_, and a _mechanical energy_ as the sum of the first two. +Right after that a _mechanical work/work_ is defined. + +Then ISO 80000-5 "Thermodynamics" defines _energy <thermodynamics>_ as: + +!!! quote + + ability of a system to do work (ISO 80000-4). + +Next, _internal energy/thermodynamic energy_ is defined in terms of the change of heat. + +From the above, it seems that what is called _energy <thermodynamics>_ should actually be +the root of our tree and probably be provided in Part 4 before the _mechanical energy_ is defined. + + +## Hierarchies of derived quantities + +Derived quantities of the same kind are often independently defined in the ISQ. The ISO/IEC 80000 +series often does not suggest any hierarchy between those. Even more, it states: + +!!! quote "ISO/IEC Guide 99" + + The division of ‘quantity’ according to ‘kind of quantity’ is, to some extent, arbitrary. + +Because of this, it is unknown or ambiguous how to form a hierarchy tree for such quantities. + +To get some sense of the complexity here, let's look again at our tree of quantities of a kind +_energy_: + +```mermaid +flowchart TD + energy["energy
(mass * length2 / time2)
[J]"] + energy --- mechanical_energy["mechanical_energy"] + mechanical_energy --- potential_energy["potential_energy"] + mechanical_energy --- kinetic_energy["kinetic_energy"] + energy --- enthalpy["enthalpy"] + enthalpy --- internal_energy["internal_energy / thermodynamic_energy"] + internal_energy --- Helmholtz_energy["Helmholtz_energy / Helmholtz_function"] + enthalpy --- Gibbs_energy["Gibbs_energy / Gibbs_function"] + energy --- active_energy["active_energy"] +``` + +Not being exact means that every vendor may implement it differently. This will result in: + +- different convertibility rules among quantities: + + ```cpp + static_assert(implicitly_convertible(isq::potential_energy, isq::mechanical_energy)); + static_assert(explicitly_convertible(isq::mechanical_energy, isq::potential_energy)); + ``` + +- different common quantities resulting from the arithmetics on various quantities of the same + kind: + + ```cpp + static_assert((isq::potential_energy(1 * J) + isq::kinetic_energy(1 * J)).quantity_spec == isq::mechanical_energy); + ``` + +It would be great if ISQ could provide specific division of quantities into kinds and more +information about the position of each quantity within the hierarchy of quantities of +the same kind. + +!!! important + + We can try to do this by ourselves, but it is tough. Probably no one, for sure we are + not, is an expert in all the fields of ISO/IEC 80000 applicability. + + We need the help of subject matter experts who will help us build those trees for their domains + and then verify that everything works as expected. + + +## The same or a different kind? + +Some quantities are more complicated than others. For example, _power_ has: + +- scalar quantities expressed in: + - W (watts) (e.g., _mechanical power_, _active power_), + - VA (volt-ampere) (e.g., _apparent power_), + - var (e.g., _reactive power_), +- complex quantities expressed in VA (volt-ampere) (e.g., _complex power_). + +How should we model this? Maybe those should be two or three independent trees of quantities, each +having its own unit? + +```mermaid +flowchart TD + power["power
(mass * length2 / time3)
[W]"] + power --- mechanical_power["mechanical_power
(scalar_product(force, velocity))"] + power --- electromagnetism_power["electromagnetism_power | instantaneous_power
(instantaneous_voltage * instantaneous_electric_current)"] + power --- active_power["active_power
(1 / period * instantaneous_power * time)
(re(complex_power))
"] + + nonactive_power["nonactive_power
(mass * length2 / time3)
[VA]"] + nonactive_power --- reactive_power["reactive_power
(im(complex_power))
[var]"] + + complex_power["complex_power
{complex}
(voltage_phasor * electric_current_phasor)
(active_power + j * reactive_power)

[VA]"] + complex_power --- apparent_power["apparent_power
(voltage * electric_current)
(mod(complex_power))
"] +``` + +This will mean that we will not be able to add or compare _active power_, _reactive power_, and +_apparent power_, which probably makes a lot of sense. However, it also means that the following +will fail to compile: + +```cpp +quantity apparent = isq::apparent_power(100 * VA); +quantity active = isq::active_power(60 * W); +quantity q = sqrt(pow<2>(apparent) - pow<2>(active)); // Compile-time error +``` + +Also the following will not work: + +```cpp +quantity active = isq::active_power(60 * W); +quantity reactive = isq::reactive_power(40 * var); +quantity q = sqrt(pow<2>(active) + pow<2>(reactive)); // Compile-time error +``` + +If we want the above to work maybe we need to implement the tree as follows? + +```mermaid +flowchart TD + power["power
(mass * length2 / time3)
[W]"] + power --- mechanical_power["mechanical_power
(scalar_product(force, velocity))"] + power --- electromagnetism_power["electromagnetism_power | instantaneous_power
(instantaneous_voltage * instantaneous_electric_current)"] + power --- apparent_power["apparent_power
(voltage * electric_current)
(mod(complex_power))

[VA]"] + apparent_power --- active_power["active_power
(1 / period * instantaneous_power * time)
(re(complex_power))
"] + apparent_power --- nonactive_power["nonactive_power
(sqrt(apparent_power2 - active_power2))
"] + nonactive_power --- reactive_power["reactive_power
(im(complex_power))
[var]"] + apparent_power --- complex_power["complex_power
{complex}
(voltage_phasor * electric_current_phasor)
(active_power + j * reactive_power)
"] +``` + +However, the above allows direct addition and comparison of _active power_ and _nonactive power_, +and also will not complain if someone will try to use watt (W) as a unit of _apparent power_ or +_reactive power_. + +Again, ISQ does not provide a direct answer here. + + +## More base quantities? + +Is ISQ really based on only seven base quantities? Let's look at the definition of +_traffic intensity_ in IEC 80000-13 "Information science and technology": + +!!! quote + + number of simultaneously busy resources in a particular pool of resources. + +It looks like a definition of a specialized dimensionless quantity or, more correctly, a quantity +of dimension one. This would not be the only such case. Even in the same Part 13, we can find +quantities like _storage capacity_ with a similar property. + +Only when we look closer do we start to see differences. All dimensionless quantities, even if they +have their own dedicated units, can also be measured in a unit of one (1). This is true for +_storage capacity_ (also measured in bits), _angular measure_ (also measured in radians), +_solid angular measure (also measured in steradians), and more. + +However, _traffic intensity_ can only be measured in erlangs (E), not in a unit one (1). +Does it mean that it is a "hidden" 8-th base quantity in ISQ? If so, should it have its own +dimension as well? + +Angular quantities are another interesting case here. Scientists have written petitions and papers +for years to make them an additional dimension in ISQ and SI. More about this can be found in +our documentation's [Strong Angular System](../../users_guide/systems/strong_angular_system.md) +chapter. + + +## Summary + +ISQ is tremendous and solves many problems we had in modeling various subjects for years in +software. As a result, we have more powerful tools in our hands that allow us to deliver safer +products. + +Unfortunately, ISQ, contrarily to SI, is not widely recognized, and no libraries besides +**mp-units** model it in any programming language. Keeping it behind a paywall does not help +either. We hope that posts from this series will spread in the community, raise awareness +of ISQ and its benefits, and encourage authors of other libraries to implement it in their +products. + +Despite all the benefits, it is essential to realize that ISQ has many problems. International +standards should be specified in such a way that there is no room for ambiguity in their +interpretation by different parties trying to use them. As described above, this is not the case +here. + +ISQ is not ready to be unambiguously modeled in software by various vendors. Here are the most +important problems to solve to allow this: + +1. ISQ needs to define basic operations on quantities: + +    - what the result of addition and subtraction should be when arguments differ, +    - convertibility rules. + +2. The exact quantity equation recipe needs to be included for many derived quantities. +3. Many ISQ quantities do not provide their exact relation versus other quantities of the same + kind (no strict hierarchy). +4. Some missing quantities need to be included. Others would benefit from corrected names. + +Additionally: + +- extending ISQ with affine space abstractions, +- specifying more quantities as non-negative, +- adding more base quantities (i.e., _angle_) + +could improve the safety of our programs and products that people depend on with their lives on +a daily basis. + +I hope you enjoyed following this series and learned more about the International System +of Quantities. Please try it out in your domain and share feedback with us. We always love to +hear about the projects in which our library is being used and about use cases it helps +address. diff --git a/docs/users_guide/examples/si_constants.md b/docs/users_guide/examples/si_constants.md index f5feb153..847520f4 100644 --- a/docs/users_guide/examples/si_constants.md +++ b/docs/users_guide/examples/si_constants.md @@ -16,19 +16,11 @@ work in practice. --8<-- "example/si_constants.cpp:28:40" ``` -As always, we start with the inclusion of all the needed header files. After that, for -the simplicity of this example, we -[hack the character of quantities](../framework_basics/character_of_a_quantity.md#hacking-the-character) -to be able to express vector quantities with simple scalar types. - -```cpp title="si_constants.cpp" linenums="14" ---8<-- "example/si_constants.cpp:42:44" -``` - +As always, we start with the inclusion of all the needed header files. The main part of the example prints all of the SI-defining constants: -```cpp title="si_constants.cpp" linenums="17" ---8<-- "example/si_constants.cpp:45:" +```cpp title="si_constants.cpp" linenums="14" +--8<-- "example/si_constants.cpp:42:" ``` While analyzing the output of this program (provided below), we can easily notice that a direct diff --git a/example/include/validated_type.h b/example/include/validated_type.h index c8b5dc6a..70be6dac 100644 --- a/example/include/validated_type.h +++ b/example/include/validated_type.h @@ -129,8 +129,8 @@ std::basic_ostream& operator<<(std::basic_ostream& template struct MP_UNITS_STD_FMT::formatter, Char> : formatter { template - auto format(const validated_type& v, FormatContext& ctx) const -> decltype(ctx.out()) + auto format(const validated_type& val, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format(v.value(), ctx); + return formatter::format(val.value(), ctx); } }; diff --git a/example/si_constants.cpp b/example/si_constants.cpp index e09588c4..a43779ab 100644 --- a/example/si_constants.cpp +++ b/example/si_constants.cpp @@ -39,10 +39,6 @@ import mp_units; #include #endif -template - requires mp_units::is_scalar -constexpr bool mp_units::is_vector = true; - int main() { using namespace mp_units; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2132d71a..e6251cf2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -91,6 +91,7 @@ if(NOT ${projectPrefix}API_FREESTANDING) include/mp-units/bits/fmt.h include/mp-units/bits/requires_hosted.h include/mp-units/ext/format.h + include/mp-units/cartesian_vector.h include/mp-units/complex.h include/mp-units/format.h include/mp-units/math.h diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h new file mode 100644 index 00000000..0749fae0 --- /dev/null +++ b/src/core/include/mp-units/cartesian_vector.h @@ -0,0 +1,230 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// 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. + +#pragma once + +#include +// +#include +#include + +#if MP_UNITS_HOSTED +#include +#endif + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#include +#if MP_UNITS_HOSTED +#include +#endif +#endif +#endif + +namespace mp_units { + +MP_UNITS_EXPORT template +class cartesian_vector { +public: + // public members required to satisfy structural type requirements :-( + T _coordinates_[3]; + using value_type = T; + + cartesian_vector() = default; + cartesian_vector(const cartesian_vector&) = default; + cartesian_vector(cartesian_vector&&) = default; + cartesian_vector& operator=(const cartesian_vector&) = default; + cartesian_vector& operator=(cartesian_vector&&) = default; + + template Arg1, std::convertible_to... Args> + constexpr cartesian_vector(Arg1&& arg1, Args&&... args) : + _coordinates_(std::forward(arg1), std::forward(args)...) + { + } + + template U> + constexpr cartesian_vector(const cartesian_vector& other) : _coordinates_{other[0], other[1], other[2]} + { + } + + template U> + constexpr cartesian_vector(cartesian_vector&& other) : + _coordinates_{std::move(other[0]), std::move(other[1]), std::move(other[2])} + { + } + + template U> + constexpr cartesian_vector& operator=(const cartesian_vector& other) + { + _coordinates_[0] = other[0]; + _coordinates_[1] = other[1]; + _coordinates_[2] = other[2]; + return *this; + } + + template U> + constexpr cartesian_vector& operator=(cartesian_vector&& other) + { + _coordinates_[0] = std::move(other[0]); + _coordinates_[1] = std::move(other[1]); + _coordinates_[2] = std::move(other[2]); + return *this; + } + + [[nodiscard]] constexpr T magnitude() const + requires treat_as_floating_point + { + return std::hypot(_coordinates_[0], _coordinates_[1], _coordinates_[2]); + } + + [[nodiscard]] constexpr cartesian_vector unit() const + requires treat_as_floating_point + { + return *this / magnitude(); + } + + [[nodiscard]] constexpr T& operator[](std::size_t i) { return _coordinates_[i]; } + [[nodiscard]] constexpr const T& operator[](std::size_t i) const { return _coordinates_[i]; } + + [[nodiscard]] constexpr cartesian_vector operator+() const { return *this; } + [[nodiscard]] constexpr cartesian_vector operator-() const + { + return {-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]}; + } + + template U, typename V> + requires requires(U u, V v) { u + v; } + [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], + lhs._coordinates_[1] + rhs._coordinates_[1], + lhs._coordinates_[2] + rhs._coordinates_[2]}; + } + + template U, typename V> + requires requires(U u, V v) { u - v; } + [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], + lhs._coordinates_[1] - rhs._coordinates_[1], + lhs._coordinates_[2] - rhs._coordinates_[2]}; + } + + template U> + requires requires(U u, T t) { u* t; } + [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& lhs, const T& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] * rhs, lhs._coordinates_[1] * rhs, + lhs._coordinates_[2] * rhs}; + } + + template U> + requires requires(T t, U u) { t* u; } + [[nodiscard]] friend constexpr auto operator*(const T& lhs, const cartesian_vector& rhs) + { + return rhs * lhs; + } + + template U> + requires requires(U u, T t) { u / t; } + [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& lhs, const T& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] / rhs, lhs._coordinates_[1] / rhs, + lhs._coordinates_[2] / rhs}; + } + + template U, std::equality_comparable_with V> + [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && + lhs._coordinates_[2] == rhs._coordinates_[2]; + } + + [[nodiscard]] friend constexpr T norm(const cartesian_vector& vec) + requires treat_as_floating_point + { + return vec.magnitude(); + } + + [[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& vec) + requires treat_as_floating_point + { + return vec.unit(); + } + + template U, typename V> + requires requires(U u, V v, decltype(u * v) t) { + u* v; + t + t; + } + [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + + lhs._coordinates_[2] * rhs._coordinates_[2]; + } + + template U, typename V> + requires requires(U u, V v, decltype(u * v) t) { + u* v; + t - t; + } + [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return ::mp_units::cartesian_vector{ + lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], + lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], + lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; + } + +#if MP_UNITS_HOSTED + friend constexpr std::ostream& operator<<(std::ostream& os, const cartesian_vector& vec) + { + return os << '[' << vec[0] << ", " << vec[1] << ", " << vec[2] << ']'; + } +#endif +}; + +template + requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } +cartesian_vector(Arg, Args...) -> cartesian_vector>; + +template +constexpr bool is_vector> = true; + +} // namespace mp_units + +#if MP_UNITS_HOSTED +// TODO use parse and use formatter for the underlying type +template +struct MP_UNITS_STD_FMT::formatter, Char> : + formatter, Char> { + template + auto format(const mp_units::cartesian_vector& vec, FormatContext& ctx) const + { + return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]); + } +}; +#endif diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index a99978f8..702f3b6b 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -28,6 +28,7 @@ #include #if MP_UNITS_HOSTED +#include #include #include #include diff --git a/src/core/include/mp-units/ext/inplace_vector.h b/src/core/include/mp-units/ext/inplace_vector.h index 1e93caf4..8074b09b 100644 --- a/src/core/include/mp-units/ext/inplace_vector.h +++ b/src/core/include/mp-units/ext/inplace_vector.h @@ -90,16 +90,16 @@ public: constexpr T* data() noexcept { return data_; } constexpr const T* data() const noexcept { return data_; } - constexpr reference push_back(const T& v) + constexpr reference push_back(const T& val) requires std::constructible_from { - return emplace_back(v); + return emplace_back(val); } - constexpr reference push_back(T&& v) + constexpr reference push_back(T&& val) requires std::constructible_from { - return emplace_back(std::forward(v)); + return emplace_back(std::forward(val)); } template diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index bfd55b92..71bbc699 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -174,21 +174,21 @@ public: template requires detail::SameValueAs, Rep> - constexpr quantity(FwdValue&& v, R2) : numerical_value_is_an_implementation_detail_(std::forward(v)) + constexpr quantity(FwdValue&& val, R2) : numerical_value_is_an_implementation_detail_(std::forward(val)) { } template> requires(!detail::SameValueAs) && detail::QuantityConvertibleTo, quantity> - constexpr quantity(FwdValue&& v, R2) : quantity(quantity{std::forward(v), R2{}}) + constexpr quantity(FwdValue&& val, R2) : quantity(quantity{std::forward(val), R2{}}) { } template FwdValue> requires(unit == ::mp_units::one) - constexpr explicit(false) quantity(FwdValue&& v) : - numerical_value_is_an_implementation_detail_(std::forward(v)) + constexpr explicit(false) quantity(FwdValue&& val) : + numerical_value_is_an_implementation_detail_(std::forward(val)) { } @@ -214,9 +214,9 @@ public: template FwdValue> requires(unit == ::mp_units::one) - constexpr quantity& operator=(FwdValue&& v) + constexpr quantity& operator=(FwdValue&& val) { - numerical_value_is_an_implementation_detail_ = std::forward(v); + numerical_value_is_an_implementation_detail_ = std::forward(val); return *this; } @@ -422,11 +422,11 @@ public: requires(!Quantity) && requires(rep a, Value b) { { a *= b } -> std::same_as; } - friend constexpr decltype(auto) operator*=(Q&& lhs, const Value& v) + friend constexpr decltype(auto) operator*=(Q&& lhs, const Value& val) { // TODO use *= when compiler bug is resolved: // https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445 - lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ * v; + lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ * val; return std::forward(lhs); } @@ -444,12 +444,12 @@ public: requires(!Quantity) && requires(rep a, Value b) { { a /= b } -> std::same_as; } - friend constexpr decltype(auto) operator/=(Q&& lhs, const Value& v) + friend constexpr decltype(auto) operator/=(Q&& lhs, const Value& val) { - MP_UNITS_EXPECTS_DEBUG(v != quantity_values::zero()); + MP_UNITS_EXPECTS_DEBUG(val != quantity_values::zero()); // TODO use /= when compiler bug is resolved: // https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445 - lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ / v; + lhs.numerical_value_is_an_implementation_detail_ = lhs.numerical_value_is_an_implementation_detail_ / val; return std::forward(lhs); } @@ -551,17 +551,17 @@ public: template Q, typename Value> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, Rep, const Value&> - [[nodiscard]] friend constexpr QuantityOf auto operator*(const Q& q, const Value& v) + [[nodiscard]] friend constexpr QuantityOf auto operator*(const Q& q, const Value& val) { - return ::mp_units::quantity{q.numerical_value_ref_in(unit) * v, R}; + return ::mp_units::quantity{q.numerical_value_ref_in(unit) * val, R}; } template Q> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, const Value&, Rep> - [[nodiscard]] friend constexpr QuantityOf auto operator*(const Value& v, const Q& q) + [[nodiscard]] friend constexpr QuantityOf auto operator*(const Value& val, const Q& q) { - return ::mp_units::quantity{v * q.numerical_value_ref_in(unit), R}; + return ::mp_units::quantity{val * q.numerical_value_ref_in(unit), R}; } template Q, auto R2, typename Rep2> @@ -575,18 +575,18 @@ public: template Q, typename Value> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, Rep, const Value&> - [[nodiscard]] friend constexpr QuantityOf auto operator/(const Q& q, const Value& v) + [[nodiscard]] friend constexpr QuantityOf auto operator/(const Q& q, const Value& val) { - MP_UNITS_EXPECTS_DEBUG(v != quantity_values::zero()); - return ::mp_units::quantity{q.numerical_value_ref_in(unit) / v, R}; + MP_UNITS_EXPECTS_DEBUG(val != quantity_values::zero()); + return ::mp_units::quantity{q.numerical_value_ref_in(unit) / val, R}; } template Q> requires(!Quantity) && (!Reference) && detail::InvokeResultOf, const Value&, Rep> - [[nodiscard]] friend constexpr QuantityOf auto operator/(const Value& v, const Q& q) + [[nodiscard]] friend constexpr QuantityOf auto operator/(const Value& val, const Q& q) { - return ::mp_units::quantity{v / q.numerical_value_ref_in(unit), ::mp_units::one / R}; + return ::mp_units::quantity{val / q.numerical_value_ref_in(unit), ::mp_units::one / R}; } template Q, auto R2, typename Rep2> diff --git a/src/core/include/mp-units/framework/quantity_spec.h b/src/core/include/mp-units/framework/quantity_spec.h index 900cf190..8d37e151 100644 --- a/src/core/include/mp-units/framework/quantity_spec.h +++ b/src/core/include/mp-units/framework/quantity_spec.h @@ -59,7 +59,7 @@ template requires(!AssociatedUnit) || UnitOf [[nodiscard]] consteval Reference auto make_reference(QS, U u) { - if constexpr (QuantityKindSpec) + if constexpr (requires { requires(get_quantity_spec(U{}) == QS{}); }) return u; else return reference{}; diff --git a/src/core/include/mp-units/framework/representation_concepts.h b/src/core/include/mp-units/framework/representation_concepts.h index 4ddbcb88..8be28d05 100644 --- a/src/core/include/mp-units/framework/representation_concepts.h +++ b/src/core/include/mp-units/framework/representation_concepts.h @@ -118,12 +118,11 @@ concept ComplexRepresentation = Complex && WeaklyRegular && requires(T a, { a - b } -> Complex; { a* b } -> Complex; { a / b } -> Complex; - // TBD - // { re(a) } -> Scalar; - // { im(a) } -> Scalar; - // { mod(a) } -> Scalar; - // { arg(a) } -> Scalar; - // { conj(a) } -> Complex; + { real(a) } -> Scalar; + { imag(a) } -> Scalar; + { abs(a) } -> Scalar; + { arg(a) } -> Scalar; + { conj(a) } -> Complex; }; // TODO how to check for a complex(Scalar, Scalar) -> Complex? diff --git a/src/core/include/mp-units/ostream.h b/src/core/include/mp-units/ostream.h index 15b6127b..1b3f63ac 100644 --- a/src/core/include/mp-units/ostream.h +++ b/src/core/include/mp-units/ostream.h @@ -68,8 +68,8 @@ void to_stream_impl(std::basic_ostream& os, const quantity -std::basic_ostream& to_stream(std::basic_ostream& os, const T& v) - requires requires { detail::to_stream_impl(os, v); } +std::basic_ostream& to_stream(std::basic_ostream& os, const T& val) + requires requires { detail::to_stream_impl(os, val); } { if (os.width()) { // std::setw() applies to the whole output so it has to be first put into std::string @@ -77,11 +77,11 @@ std::basic_ostream& to_stream(std::basic_ostream& oss.flags(os.flags()); oss.imbue(os.getloc()); oss.precision(os.precision()); - detail::to_stream_impl(oss, v); + detail::to_stream_impl(oss, val); return os << std::move(oss).str(); } - detail::to_stream_impl(os, v); + detail::to_stream_impl(os, val); return os; } @@ -93,10 +93,10 @@ constexpr bool is_mp_units_stream = requires(OStream os, T v) { detail::to_strea MP_UNITS_EXPORT_BEGIN template -std::basic_ostream& operator<<(std::basic_ostream& os, const T& v) +std::basic_ostream& operator<<(std::basic_ostream& os, const T& val) requires detail::is_mp_units_stream, T> { - return detail::to_stream(os, v); + return detail::to_stream(os, val); } MP_UNITS_EXPORT_END diff --git a/src/systems/include/mp-units/systems/si/chrono.h b/src/systems/include/mp-units/systems/si/chrono.h index a856fc15..51434421 100644 --- a/src/systems/include/mp-units/systems/si/chrono.h +++ b/src/systems/include/mp-units/systems/si/chrono.h @@ -83,10 +83,10 @@ struct quantity_like_traits> { return q.count(); } - [[nodiscard]] static constexpr T from_numerical_value(const rep& v) noexcept( + [[nodiscard]] static constexpr T from_numerical_value(const rep& val) noexcept( std::is_nothrow_copy_constructible_v) { - return T(v); + return T(val); } }; @@ -113,10 +113,10 @@ struct quantity_point_like_traits) { - return T(std::chrono::duration(v)); + return T(std::chrono::duration(val)); } }; diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index d79ed54b..c1d990ba 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -24,13 +24,14 @@ find_package(Catch2 3 REQUIRED) add_executable( unit_tests_runtime + atomic_test.cpp + cartesian_vector_test.cpp distribution_test.cpp fixed_string_test.cpp fmt_test.cpp math_test.cpp - atomic_test.cpp - truncation_test.cpp quantity_test.cpp + truncation_test.cpp ) if(${projectPrefix}BUILD_CXX_MODULES) target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES) diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp new file mode 100644 index 00000000..252134d0 --- /dev/null +++ b/test/runtime/cartesian_vector_test.cpp @@ -0,0 +1,381 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// 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. + +#include "almost_equals.h" +#include +#include +#include +#include +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#endif + +using namespace mp_units; +using namespace Catch::Matchers; + +TEST_CASE("cartesian_vector operations", "[vector]") +{ + SECTION("cartesian_vector initialization and access") + { + SECTION("one argument") + { + cartesian_vector v{1.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + + SECTION("two arguments") + { + cartesian_vector v{1.0, 2.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 0); + } + + SECTION("all arguments") + { + cartesian_vector v{1.0, 2.0, 3.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 3.0); + } + } + + SECTION("convertibility") + { + cartesian_vector v1{1, 2, 3}; + + SECTION("construction") + { + cartesian_vector v2 = v1; + REQUIRE(v2[0] == 1.0); + REQUIRE(v2[1] == 2.0); + REQUIRE(v2[2] == 3.0); + } + + SECTION("assignment") + { + cartesian_vector v2{3.0, 2.0, 1.0}; + v2 = v1; + REQUIRE(v2[0] == 1.0); + REQUIRE(v2[1] == 2.0); + REQUIRE(v2[2] == 3.0); + } + } + + SECTION("cartesian_vector addition") + { + SECTION("double + double") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5.0); + REQUIRE(result[1] == 7.0); + REQUIRE(result[2] == 9.0); + } + + SECTION("double + int") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5.0); + REQUIRE(result[1] == 7.0); + REQUIRE(result[2] == 9.0); + } + + SECTION("int + double") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5.0); + REQUIRE(result[1] == 7.0); + REQUIRE(result[2] == 9.0); + } + + SECTION("int + int") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5); + REQUIRE(result[1] == 7); + REQUIRE(result[2] == 9); + } + } + + SECTION("cartesian_vector subtraction") + { + SECTION("double - double") + { + cartesian_vector v1{4.0, 5.0, 6.0}; + cartesian_vector v2{1.0, 2.0, 3.0}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3.0); + REQUIRE(result[1] == 3.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("double - int") + { + cartesian_vector v1{4.0, 5.0, 6.0}; + cartesian_vector v2{1, 2, 3}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3.0); + REQUIRE(result[1] == 3.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("int - double") + { + cartesian_vector v1{4, 5, 6}; + cartesian_vector v2{1.0, 2.0, 3.0}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3.0); + REQUIRE(result[1] == 3.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("int - int") + { + cartesian_vector v1{4, 5, 6}; + cartesian_vector v2{1, 2, 3}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3); + REQUIRE(result[1] == 3); + REQUIRE(result[2] == 3); + } + } + + SECTION("cartesian_vector scalar multiplication") + { + SECTION("double * double") + { + cartesian_vector v{1.0, 2.0, 3.0}; + cartesian_vector result = v * 2.0; + REQUIRE(result[0] == 2.0); + REQUIRE(result[1] == 4.0); + REQUIRE(result[2] == 6.0); + } + + SECTION("double * int") + { + cartesian_vector v{1.0, 2.0, 3.0}; + cartesian_vector result = v * 2; + REQUIRE(result[0] == 2.0); + REQUIRE(result[1] == 4.0); + REQUIRE(result[2] == 6.0); + } + + SECTION("int * double") + { + cartesian_vector v{1, 2, 3}; + cartesian_vector result = v * 2.0; + REQUIRE(result[0] == 2.0); + REQUIRE(result[1] == 4.0); + REQUIRE(result[2] == 6.0); + } + + SECTION("int * int") + { + cartesian_vector v{1, 2, 3}; + cartesian_vector result = v * 2; + REQUIRE(result[0] == 2); + REQUIRE(result[1] == 4); + REQUIRE(result[2] == 6); + } + } + + SECTION("cartesian_vector scalar division") + { + SECTION("double / double") + { + cartesian_vector v{2.0, 4.0, 6.0}; + cartesian_vector result = v / 2.0; + REQUIRE(result[0] == 1.0); + REQUIRE(result[1] == 2.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("double / int") + { + cartesian_vector v{2.0, 4.0, 6.0}; + cartesian_vector result = v / 2; + REQUIRE(result[0] == 1.0); + REQUIRE(result[1] == 2.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("int / double") + { + cartesian_vector v{2, 4, 6}; + cartesian_vector result = v / 2.0; + REQUIRE(result[0] == 1.0); + REQUIRE(result[1] == 2.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("int / int") + { + cartesian_vector v{2, 4, 6}; + cartesian_vector result = v / 2; + REQUIRE(result[0] == 1); + REQUIRE(result[1] == 2); + REQUIRE(result[2] == 3); + } + } + + SECTION("cartesian_vector magnitude") + { + cartesian_vector v1{3.0, 4.0, 0.0}; + cartesian_vector v2{2.0, 3.0, 6.0}; + REQUIRE(v1.magnitude() == 5.0); + REQUIRE(v2.magnitude() == 7.0); + } + + SECTION("cartesian_vector unit vector") + { + cartesian_vector v{3.0, 4.0, 0.0}; + cartesian_vector unit_v = v.unit(); + REQUIRE_THAT(unit_v.magnitude(), WithinULP(1.0, 1)); + } + + SECTION("cartesian_vector equality") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{1, 2, 3}; + cartesian_vector v3{1.1, 2.0, 3.0}; + cartesian_vector v4{1.0, 2.1, 3.0}; + cartesian_vector v5{1.0, 2.0, 3.1}; + REQUIRE(v1 == v2); + REQUIRE(v1 != v3); + REQUIRE(v1 != v4); + REQUIRE(v1 != v5); + } + + SECTION("cartesian_vector scalar product") + { + SECTION("double * double") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + REQUIRE(scalar_product(v1, v2) == 32.0); + } + + SECTION("double * int") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4, 5, 6}; + REQUIRE(scalar_product(v1, v2) == 32.0); + } + + SECTION("int * double") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4.0, 5.0, 6.0}; + REQUIRE(scalar_product(v1, v2) == 32.0); + } + + SECTION("int * int") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4, 5, 6}; + REQUIRE(scalar_product(v1, v2) == 32); + } + } + + SECTION("cartesian_vector vector product") + { + SECTION("double * double") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3.0); + REQUIRE(result[1] == 6.0); + REQUIRE(result[2] == -3.0); + } + + SECTION("double * int") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3.0); + REQUIRE(result[1] == 6.0); + REQUIRE(result[2] == -3.0); + } + + SECTION("int * double") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3.0); + REQUIRE(result[1] == 6.0); + REQUIRE(result[2] == -3.0); + } + + SECTION("int * int") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3); + REQUIRE(result[1] == 6); + REQUIRE(result[2] == -3); + } + } +} + +TEST_CASE("cartesian_vector text output", "[vector][fmt][ostream]") +{ + std::ostringstream os; + + SECTION("integral representation") + { + cartesian_vector v{1, 2, 3}; + os << v; + + SECTION("iostream") { CHECK(os.str() == "[1, 2, 3]"); } + SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); } + } + + SECTION("floating-point representation") + { + cartesian_vector v{1.2, 2.3, 3.4}; + os << v; + + SECTION("iostream") { CHECK(os.str() == "[1.2, 2.3, 3.4]"); } + SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); } + } +} diff --git a/test/runtime/distribution_test.cpp b/test/runtime/distribution_test.cpp index 9df1eb3b..1ea3da1d 100644 --- a/test/runtime/distribution_test.cpp +++ b/test/runtime/distribution_test.cpp @@ -43,618 +43,621 @@ import mp_units; using namespace mp_units; -TEST_CASE("uniform_int_distribution") +TEST_CASE("distributions", "[random][distribution]") { - using rep = std::int64_t; - using q = quantity; - - SECTION("default") + SECTION("uniform_int_distribution") { - auto dist = mp_units::uniform_int_distribution(); + using rep = std::int64_t; + using q = quantity; - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == q::max()); + SECTION("default") + { + auto dist = mp_units::uniform_int_distribution(); + + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::max()); + } + + SECTION("parametrized") + { + constexpr rep a = 2; + constexpr rep b = 5; + + auto stl_dist = std::uniform_int_distribution(a, b); + auto units_dist = mp_units::uniform_int_distribution(a * si::metre, b * si::metre); + + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } } - SECTION("parametrized") + SECTION("uniform_real_distribution") { - constexpr rep a = 2; - constexpr rep b = 5; + using rep = long double; + using q = quantity; - auto stl_dist = std::uniform_int_distribution(a, b); - auto units_dist = mp_units::uniform_int_distribution(a * si::metre, b * si::metre); + SECTION("default") + { + auto dist = mp_units::uniform_real_distribution(); - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("uniform_real_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::uniform_real_distribution(); - - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == q::one()); - } - - SECTION("parametrized") - { - constexpr rep a = 2.0; - constexpr rep b = 5.0; - - auto stl_dist = std::uniform_real_distribution(a, b); - auto units_dist = mp_units::uniform_real_distribution(a * si::metre, b * si::metre); - - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("binomial_distribution") -{ - using rep = std::int64_t; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::binomial_distribution(); - - CHECK(dist.p() == 0.5); - CHECK(dist.t() == q::one()); - } - - SECTION("parametrized") - { - constexpr rep t = 5; - constexpr double p = 0.25; - - auto stl_dist = std::binomial_distribution(t, p); - auto units_dist = mp_units::binomial_distribution(t * si::metre, p); - - CHECK(units_dist.p() == stl_dist.p()); - CHECK(units_dist.t() == stl_dist.t() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("negative_binomial_distribution") -{ - using rep = std::int64_t; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::negative_binomial_distribution(); - - CHECK(dist.p() == 0.5); - CHECK(dist.k() == q::one()); - } - - SECTION("parametrized") - { - constexpr rep k = 5; - constexpr double p = 0.25; - - auto stl_dist = std::negative_binomial_distribution(k, p); - auto units_dist = mp_units::negative_binomial_distribution(k * si::metre, p); - - CHECK(units_dist.p() == stl_dist.p()); - CHECK(units_dist.k() == stl_dist.k() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("geometric_distribution") -{ - using rep = std::int64_t; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::geometric_distribution(); - - CHECK(dist.p() == 0.5); - } - - SECTION("parametrized") - { - constexpr double p = 0.25; - - auto stl_dist = std::geometric_distribution(p); - auto units_dist = mp_units::geometric_distribution(p); - - CHECK(units_dist.p() == stl_dist.p()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("poisson_distribution") -{ - using rep = std::int64_t; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::poisson_distribution(); - - CHECK(dist.mean() == 1.0); - } - - SECTION("parametrized") - { - constexpr double mean = 5.0; - - auto stl_dist = std::poisson_distribution(mean); - auto units_dist = mp_units::poisson_distribution(mean); - - CHECK(units_dist.mean() == stl_dist.mean()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("exponential_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::exponential_distribution(); - - CHECK(dist.lambda() == 1.0); - } - - SECTION("parametrized") - { - constexpr double lambda = 2.0; - - auto stl_dist = std::exponential_distribution(lambda); - auto units_dist = mp_units::exponential_distribution(lambda); - - CHECK(units_dist.lambda() == stl_dist.lambda()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("gamma_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::gamma_distribution(); - - CHECK(dist.alpha() == 1.0); - CHECK(dist.beta() == 1.0); - } - - SECTION("parametrized") - { - constexpr double alpha = 5.0; - constexpr double beta = 2.0; - - auto stl_dist = std::gamma_distribution(alpha, beta); - auto units_dist = mp_units::gamma_distribution(alpha, beta); - - CHECK(units_dist.alpha() == stl_dist.alpha()); - CHECK(units_dist.beta() == stl_dist.beta()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("weibull_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::weibull_distribution(); - - CHECK(dist.a() == 1.0); - CHECK(dist.b() == 1.0); - } - - SECTION("parametrized") - { - constexpr rep a = 5.0; - constexpr rep b = 2.0; - - auto stl_dist = std::weibull_distribution(a, b); - auto units_dist = mp_units::weibull_distribution(a, b); - - CHECK(units_dist.a() == stl_dist.a()); - CHECK(units_dist.b() == stl_dist.b()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("extreme_value_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::extreme_value_distribution(); - - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == 1.0); - } - - SECTION("parametrized") - { - constexpr rep a = 5.0; - constexpr rep b = 2.0; - - auto stl_dist = std::extreme_value_distribution(a, b); - auto units_dist = mp_units::extreme_value_distribution(a * si::metre, b); - - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("normal_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::normal_distribution(); - - CHECK(dist.mean() == q::zero()); - CHECK(dist.stddev() == q::one()); - } - - SECTION("parametrized") - { - constexpr rep mean = 5.0; - constexpr rep stddev = 2.0; - - auto stl_dist = std::normal_distribution(mean, stddev); - auto units_dist = mp_units::normal_distribution(mean * si::metre, stddev * si::metre); - - CHECK(units_dist.mean() == stl_dist.mean() * si::metre); - CHECK(units_dist.stddev() == stl_dist.stddev() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("lognormal_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::lognormal_distribution(); - - CHECK(dist.m() == q::zero()); - CHECK(dist.s() == q::one()); - } - - SECTION("parametrized") - { - constexpr rep m = 5.0; - constexpr rep s = 2.0; - - auto stl_dist = std::lognormal_distribution(m, s); - auto units_dist = mp_units::lognormal_distribution(m * si::metre, s * si::metre); - - CHECK(units_dist.m() == stl_dist.m() * si::metre); - CHECK(units_dist.s() == stl_dist.s() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("chi_squared_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::chi_squared_distribution(); - - CHECK(dist.n() == 1.0); - } - - SECTION("parametrized") - { - constexpr rep n = 5.0; - - auto stl_dist = std::chi_squared_distribution(n); - auto units_dist = mp_units::chi_squared_distribution(n); - - CHECK(units_dist.n() == stl_dist.n()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("cauchy_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::cauchy_distribution(); - - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == q::one()); - } - - SECTION("parametrized") - { - constexpr rep a = 5.0; - constexpr rep b = 2.0; - - auto stl_dist = std::cauchy_distribution(a, b); - auto units_dist = mp_units::cauchy_distribution(a * si::metre, b * si::metre); - - CHECK(units_dist.a() == stl_dist.a() * si::metre); - CHECK(units_dist.b() == stl_dist.b() * si::metre); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("fisher_f_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::fisher_f_distribution(); - - CHECK(dist.m() == 1.0); - CHECK(dist.n() == 1.0); - } - - SECTION("parametrized") - { - constexpr rep m = 5.0; - constexpr rep n = 2.0; - - auto stl_dist = std::fisher_f_distribution(m, n); - auto units_dist = mp_units::fisher_f_distribution(m, n); - - CHECK(units_dist.m() == stl_dist.m()); - CHECK(units_dist.n() == stl_dist.n()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("student_t_distribution") -{ - using rep = long double; - using q = quantity; - - SECTION("default") - { - auto dist = mp_units::student_t_distribution(); - - CHECK(dist.n() == 1.0); - } - - SECTION("parametrized") - { - constexpr rep n = 2.0; - - auto stl_dist = std::student_t_distribution(n); - auto units_dist = mp_units::student_t_distribution(n); - - CHECK(units_dist.n() == stl_dist.n()); - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - } -} - -TEST_CASE("discrete_distribution") -{ - using rep = std::int64_t; - using q = quantity; - - SECTION("default") - { - auto stl_dist = std::discrete_distribution(); - auto units_dist = mp_units::discrete_distribution(); - - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - CHECK(units_dist.probabilities() == stl_dist.probabilities()); - } - - SECTION("parametrized_input_it") - { - constexpr std::array weights = {1.0, 2.0, 3.0}; - - auto stl_dist = std::discrete_distribution(weights.cbegin(), weights.cend()); - auto units_dist = mp_units::discrete_distribution(weights.cbegin(), weights.cend()); - - CHECK(units_dist.probabilities() == stl_dist.probabilities()); - } - - SECTION("parametrized_initializer_list") - { - const std::initializer_list weights = {1.0, 2.0, 3.0}; - - auto stl_dist = std::discrete_distribution(weights); - auto units_dist = mp_units::discrete_distribution(weights); - - CHECK(units_dist.probabilities() == stl_dist.probabilities()); - } - - SECTION("parametrized_range") - { - constexpr std::size_t count = 3; - constexpr double xmin = 1, xmax = 3; - - auto stl_dist = std::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); - auto units_dist = mp_units::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); - - CHECK(units_dist.probabilities() == stl_dist.probabilities()); - } -} - -TEST_CASE("piecewise_constant_distribution") -{ - using rep = long double; - using q = quantity; - - std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; - std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - SECTION("default") - { - auto stl_dist = std::piecewise_constant_distribution(); - auto units_dist = mp_units::piecewise_constant_distribution(); - - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - CHECK(stl_dist.intervals().size() == 2); - CHECK(units_dist.intervals().size() == 2); - CHECK(stl_dist.densities().size() == 1); - CHECK(units_dist.densities().size() == 1); - } - - SECTION("parametrized_input_it") - { - constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; - constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - constexpr std::array weights = {1.0, 2.0, 3.0}; - - auto stl_dist = - std::piecewise_constant_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); - auto units_dist = - mp_units::piecewise_constant_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); - - CHECK(stl_dist.intervals() == intervals_rep_vec); - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_initializer_list") - { - const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; - const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - auto stl_dist = std::piecewise_constant_distribution(intervals_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_constant_distribution( - intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_range") - { - constexpr std::size_t nw = 2; - constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; - constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; - - auto stl_dist = std::piecewise_constant_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_constant_distribution( - nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } -} - -TEST_CASE("piecewise_linear_distribution") -{ - using rep = long double; - using q = quantity; - - std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; - std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - SECTION("default") - { - auto stl_dist = std::piecewise_linear_distribution(); - auto units_dist = mp_units::piecewise_linear_distribution(); - - CHECK(units_dist.min() == stl_dist.min() * si::metre); - CHECK(units_dist.max() == stl_dist.max() * si::metre); - CHECK(stl_dist.intervals().size() == 2); - CHECK(units_dist.intervals().size() == 2); - CHECK(stl_dist.densities().size() == 2); - CHECK(units_dist.densities().size() == 2); - } - - SECTION("parametrized_input_it") - { - constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; - constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - constexpr std::array weights = {1.0, 2.0, 3.0}; - - auto stl_dist = - std::piecewise_linear_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); - auto units_dist = - mp_units::piecewise_linear_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); - - CHECK(stl_dist.intervals() == intervals_rep_vec); - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_initializer_list") - { - const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; - const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], - 3.0 * isq::length[si::metre]}; - - auto stl_dist = std::piecewise_linear_distribution(intervals_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_linear_distribution( - intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); - } - - SECTION("parametrized_range") - { - constexpr std::size_t nw = 2; - constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; - constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; - - auto stl_dist = std::piecewise_linear_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); - auto units_dist = mp_units::piecewise_linear_distribution( - nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); - - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::one()); + } + + SECTION("parametrized") + { + constexpr rep a = 2.0; + constexpr rep b = 5.0; + + auto stl_dist = std::uniform_real_distribution(a, b); + auto units_dist = mp_units::uniform_real_distribution(a * si::metre, b * si::metre); + + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("binomial_distribution") + { + using rep = std::int64_t; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::binomial_distribution(); + + CHECK(dist.p() == 0.5); + CHECK(dist.t() == q::one()); + } + + SECTION("parametrized") + { + constexpr rep t = 5; + constexpr double p = 0.25; + + auto stl_dist = std::binomial_distribution(t, p); + auto units_dist = mp_units::binomial_distribution(t * si::metre, p); + + CHECK(units_dist.p() == stl_dist.p()); + CHECK(units_dist.t() == stl_dist.t() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("negative_binomial_distribution") + { + using rep = std::int64_t; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::negative_binomial_distribution(); + + CHECK(dist.p() == 0.5); + CHECK(dist.k() == q::one()); + } + + SECTION("parametrized") + { + constexpr rep k = 5; + constexpr double p = 0.25; + + auto stl_dist = std::negative_binomial_distribution(k, p); + auto units_dist = mp_units::negative_binomial_distribution(k * si::metre, p); + + CHECK(units_dist.p() == stl_dist.p()); + CHECK(units_dist.k() == stl_dist.k() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("geometric_distribution") + { + using rep = std::int64_t; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::geometric_distribution(); + + CHECK(dist.p() == 0.5); + } + + SECTION("parametrized") + { + constexpr double p = 0.25; + + auto stl_dist = std::geometric_distribution(p); + auto units_dist = mp_units::geometric_distribution(p); + + CHECK(units_dist.p() == stl_dist.p()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("poisson_distribution") + { + using rep = std::int64_t; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::poisson_distribution(); + + CHECK(dist.mean() == 1.0); + } + + SECTION("parametrized") + { + constexpr double mean = 5.0; + + auto stl_dist = std::poisson_distribution(mean); + auto units_dist = mp_units::poisson_distribution(mean); + + CHECK(units_dist.mean() == stl_dist.mean()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("exponential_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::exponential_distribution(); + + CHECK(dist.lambda() == 1.0); + } + + SECTION("parametrized") + { + constexpr double lambda = 2.0; + + auto stl_dist = std::exponential_distribution(lambda); + auto units_dist = mp_units::exponential_distribution(lambda); + + CHECK(units_dist.lambda() == stl_dist.lambda()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("gamma_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::gamma_distribution(); + + CHECK(dist.alpha() == 1.0); + CHECK(dist.beta() == 1.0); + } + + SECTION("parametrized") + { + constexpr double alpha = 5.0; + constexpr double beta = 2.0; + + auto stl_dist = std::gamma_distribution(alpha, beta); + auto units_dist = mp_units::gamma_distribution(alpha, beta); + + CHECK(units_dist.alpha() == stl_dist.alpha()); + CHECK(units_dist.beta() == stl_dist.beta()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("weibull_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::weibull_distribution(); + + CHECK(dist.a() == 1.0); + CHECK(dist.b() == 1.0); + } + + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; + + auto stl_dist = std::weibull_distribution(a, b); + auto units_dist = mp_units::weibull_distribution(a, b); + + CHECK(units_dist.a() == stl_dist.a()); + CHECK(units_dist.b() == stl_dist.b()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("extreme_value_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::extreme_value_distribution(); + + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == 1.0); + } + + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; + + auto stl_dist = std::extreme_value_distribution(a, b); + auto units_dist = mp_units::extreme_value_distribution(a * si::metre, b); + + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("normal_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::normal_distribution(); + + CHECK(dist.mean() == q::zero()); + CHECK(dist.stddev() == q::one()); + } + + SECTION("parametrized") + { + constexpr rep mean = 5.0; + constexpr rep stddev = 2.0; + + auto stl_dist = std::normal_distribution(mean, stddev); + auto units_dist = mp_units::normal_distribution(mean * si::metre, stddev * si::metre); + + CHECK(units_dist.mean() == stl_dist.mean() * si::metre); + CHECK(units_dist.stddev() == stl_dist.stddev() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("lognormal_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::lognormal_distribution(); + + CHECK(dist.m() == q::zero()); + CHECK(dist.s() == q::one()); + } + + SECTION("parametrized") + { + constexpr rep m = 5.0; + constexpr rep s = 2.0; + + auto stl_dist = std::lognormal_distribution(m, s); + auto units_dist = mp_units::lognormal_distribution(m * si::metre, s * si::metre); + + CHECK(units_dist.m() == stl_dist.m() * si::metre); + CHECK(units_dist.s() == stl_dist.s() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("chi_squared_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::chi_squared_distribution(); + + CHECK(dist.n() == 1.0); + } + + SECTION("parametrized") + { + constexpr rep n = 5.0; + + auto stl_dist = std::chi_squared_distribution(n); + auto units_dist = mp_units::chi_squared_distribution(n); + + CHECK(units_dist.n() == stl_dist.n()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("cauchy_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::cauchy_distribution(); + + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::one()); + } + + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; + + auto stl_dist = std::cauchy_distribution(a, b); + auto units_dist = mp_units::cauchy_distribution(a * si::metre, b * si::metre); + + CHECK(units_dist.a() == stl_dist.a() * si::metre); + CHECK(units_dist.b() == stl_dist.b() * si::metre); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("fisher_f_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::fisher_f_distribution(); + + CHECK(dist.m() == 1.0); + CHECK(dist.n() == 1.0); + } + + SECTION("parametrized") + { + constexpr rep m = 5.0; + constexpr rep n = 2.0; + + auto stl_dist = std::fisher_f_distribution(m, n); + auto units_dist = mp_units::fisher_f_distribution(m, n); + + CHECK(units_dist.m() == stl_dist.m()); + CHECK(units_dist.n() == stl_dist.n()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("student_t_distribution") + { + using rep = long double; + using q = quantity; + + SECTION("default") + { + auto dist = mp_units::student_t_distribution(); + + CHECK(dist.n() == 1.0); + } + + SECTION("parametrized") + { + constexpr rep n = 2.0; + + auto stl_dist = std::student_t_distribution(n); + auto units_dist = mp_units::student_t_distribution(n); + + CHECK(units_dist.n() == stl_dist.n()); + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + } + } + + SECTION("discrete_distribution") + { + using rep = std::int64_t; + using q = quantity; + + SECTION("default") + { + auto stl_dist = std::discrete_distribution(); + auto units_dist = mp_units::discrete_distribution(); + + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } + + SECTION("parametrized_input_it") + { + constexpr std::array weights = {1.0, 2.0, 3.0}; + + auto stl_dist = std::discrete_distribution(weights.cbegin(), weights.cend()); + auto units_dist = mp_units::discrete_distribution(weights.cbegin(), weights.cend()); + + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } + + SECTION("parametrized_initializer_list") + { + const std::initializer_list weights = {1.0, 2.0, 3.0}; + + auto stl_dist = std::discrete_distribution(weights); + auto units_dist = mp_units::discrete_distribution(weights); + + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } + + SECTION("parametrized_range") + { + constexpr std::size_t count = 3; + constexpr double xmin = 1, xmax = 3; + + auto stl_dist = std::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); + auto units_dist = mp_units::discrete_distribution(count, xmin, xmax, [](double val) { return val; }); + + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } + } + + SECTION("piecewise_constant_distribution") + { + using rep = long double; + using q = quantity; + + std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; + std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + + SECTION("default") + { + auto stl_dist = std::piecewise_constant_distribution(); + auto units_dist = mp_units::piecewise_constant_distribution(); + + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + CHECK(stl_dist.intervals().size() == 2); + CHECK(units_dist.intervals().size() == 2); + CHECK(stl_dist.densities().size() == 1); + CHECK(units_dist.densities().size() == 1); + } + + SECTION("parametrized_input_it") + { + constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; + constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + constexpr std::array weights = {1.0, 2.0, 3.0}; + + auto stl_dist = + std::piecewise_constant_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); + auto units_dist = + mp_units::piecewise_constant_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); + + CHECK(stl_dist.intervals() == intervals_rep_vec); + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_initializer_list") + { + const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; + const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + + auto stl_dist = std::piecewise_constant_distribution(intervals_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_constant_distribution( + intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_range") + { + constexpr std::size_t nw = 2; + constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; + constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; + + auto stl_dist = std::piecewise_constant_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_constant_distribution( + nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + } + + SECTION("piecewise_linear_distribution") + { + using rep = long double; + using q = quantity; + + std::vector intervals_rep_vec = {1.0, 2.0, 3.0}; + std::vector intervals_qty_vec = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + + SECTION("default") + { + auto stl_dist = std::piecewise_linear_distribution(); + auto units_dist = mp_units::piecewise_linear_distribution(); + + CHECK(units_dist.min() == stl_dist.min() * si::metre); + CHECK(units_dist.max() == stl_dist.max() * si::metre); + CHECK(stl_dist.intervals().size() == 2); + CHECK(units_dist.intervals().size() == 2); + CHECK(stl_dist.densities().size() == 2); + CHECK(units_dist.densities().size() == 2); + } + + SECTION("parametrized_input_it") + { + constexpr std::array intervals_rep = {1.0, 2.0, 3.0}; + constexpr std::array intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + constexpr std::array weights = {1.0, 2.0, 3.0}; + + auto stl_dist = + std::piecewise_linear_distribution(intervals_rep.cbegin(), intervals_rep.cend(), weights.cbegin()); + auto units_dist = + mp_units::piecewise_linear_distribution(intervals_qty.cbegin(), intervals_qty.cend(), weights.cbegin()); + + CHECK(stl_dist.intervals() == intervals_rep_vec); + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_initializer_list") + { + const std::initializer_list intervals_rep = {1.0, 2.0, 3.0}; + const std::initializer_list intervals_qty = {1.0 * isq::length[si::metre], 2.0 * isq::length[si::metre], + 3.0 * isq::length[si::metre]}; + + auto stl_dist = std::piecewise_linear_distribution(intervals_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_linear_distribution( + intervals_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } + + SECTION("parametrized_range") + { + constexpr std::size_t nw = 2; + constexpr rep xmin_rep = 1.0, xmax_rep = 3.0; + constexpr q xmin_qty = 1.0 * isq::length[si::metre], xmax_qty = 3.0 * isq::length[si::metre]; + + auto stl_dist = std::piecewise_linear_distribution(nw, xmin_rep, xmax_rep, [](rep val) { return val; }); + auto units_dist = mp_units::piecewise_linear_distribution( + nw, xmin_qty, xmax_qty, [](q qty) { return qty.numerical_value_ref_in(qty.unit); }); + + CHECK(units_dist.intervals() == intervals_qty_vec); + CHECK(units_dist.densities() == stl_dist.densities()); + } } } diff --git a/test/runtime/fixed_string_test.cpp b/test/runtime/fixed_string_test.cpp index b1b01f21..f3bc737a 100644 --- a/test/runtime/fixed_string_test.cpp +++ b/test/runtime/fixed_string_test.cpp @@ -38,19 +38,22 @@ import mp_units; using namespace mp_units; -TEST_CASE("fixed_string::at", "[fixed_string]") +TEST_CASE("fixed_string operations", "[fixed_string]") { - basic_fixed_string txt = "abc"; - SECTION("in range") + SECTION("fixed_string::at") { - CHECK(txt.at(0) == 'a'); - CHECK(txt.at(1) == 'b'); - CHECK(txt.at(2) == 'c'); - } - SECTION("out of range") - { - REQUIRE_THROWS_MATCHES(txt.at(3), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); - REQUIRE_THROWS_MATCHES(txt.at(1024), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); + basic_fixed_string txt = "abc"; + SECTION("in range") + { + CHECK(txt.at(0) == 'a'); + CHECK(txt.at(1) == 'b'); + CHECK(txt.at(2) == 'c'); + } + SECTION("out of range") + { + REQUIRE_THROWS_MATCHES(txt.at(3), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); + REQUIRE_THROWS_MATCHES(txt.at(1024), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); + } } } diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index 03eef676..a163c187 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -39,6 +39,7 @@ import std; #ifdef MP_UNITS_MODULES import mp_units; #else +#include #include #include // IWYU pragma: keep #include @@ -48,14 +49,275 @@ import mp_units; #include #endif -template - requires mp_units::is_scalar -constexpr bool mp_units::is_vector = true; - using namespace mp_units; using namespace mp_units::si::unit_symbols; +using v = cartesian_vector; -TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") +TEST_CASE("dimension_symbol", "[dimension][symbol]") +{ + using enum text_encoding; + + std::ostringstream os; + + SECTION("default formatting") + { + os << dimension_symbol(isq::power.dimension); + CHECK(os.str() == "L²MT⁻³"); + } + + SECTION("Portable mode") + { + os << dimension_symbol(isq::power.dimension); + CHECK(os.str() == "L^2MT^-3"); + } +} + +TEST_CASE("unit_symbol", "[unit][symbol]") +{ + using enum text_encoding; + using enum unit_symbol_solidus; + using enum unit_symbol_separator; + + std::ostringstream os; + + SECTION("default formatting") + { + os << unit_symbol(m / s2); + CHECK(os.str() == "m/s²"); + } + + SECTION("Portable mode") + { + os << unit_symbol(m / s2); + CHECK(os.str() == "m/s^2"); + } + + SECTION("solidus") + { + os << unit_symbol(m / s2); + CHECK(os.str() == "m s⁻²"); + } + + SECTION("separator") + { + os << unit_symbol(m / s2); + CHECK(os.str() == "m⋅s⁻²"); + } +} + +// TODO add dimension formatting tests + +TEST_CASE("unit formatting", "[unit][fmt]") +{ + SECTION("Unit formatting should use proper text encoding") + { + SECTION("Unicode text output") + { + CHECK(MP_UNITS_STD_FMT::format("{:U}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:U}", si::kilo) == "kΩ"); + CHECK(MP_UNITS_STD_FMT::format("{:U}", us) == "µs"); + CHECK(MP_UNITS_STD_FMT::format("{:U}", m / s2) == "m/s²"); + } + + SECTION("Unicode text output is used by default") + { + CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{}", si::kilo) == "kΩ"); + CHECK(MP_UNITS_STD_FMT::format("{}", us) == "µs"); + CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²"); + } + + SECTION("Portable text output") + { + CHECK(MP_UNITS_STD_FMT::format("{:P}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:P}", si::kilo) == "kohm"); + CHECK(MP_UNITS_STD_FMT::format("{:P}", us) == "us"); + CHECK(MP_UNITS_STD_FMT::format("{:P}", m / s2) == "m/s^2"); + } + } + + SECTION("unit formatting should print solidus according to specs") + { + SECTION("Solidus for only one element in denominator") + { + CHECK(MP_UNITS_STD_FMT::format("{:1}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:1}", m / s2) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:1}", kg / m / s2) == "kg m⁻¹ s⁻²"); + } + + SECTION("Solidus for only one element in denominator is used by default") + { + CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²"); + } + + SECTION("Always use solidus") + { + CHECK(MP_UNITS_STD_FMT::format("{:a}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:a}", m / s2) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)"); + } + + SECTION("Never use solidus") + { + CHECK(MP_UNITS_STD_FMT::format("{:n}", km / h) == "km h⁻¹"); + CHECK(MP_UNITS_STD_FMT::format("{:n}", m / s2) == "m s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:n}", kg / m / s2) == "kg m⁻¹ s⁻²"); + } + } + + SECTION("Unit formatting should user proper separator") + { + SECTION("Space") + { + CHECK(MP_UNITS_STD_FMT::format("{:s}", kg * m / s2) == "kg m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:s}", kg / m / s2) == "kg m⁻¹ s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:sa}", kg / m / s2) == "kg/(m s²)"); + } + + SECTION("Space is used by default") + { + CHECK(MP_UNITS_STD_FMT::format("{}", kg * m / s2) == "kg m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)"); + } + + SECTION("Dot") + { + CHECK(MP_UNITS_STD_FMT::format("{:d}", kg * m / s2) == "kg⋅m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:d}", kg / m / s2) == "kg⋅m⁻¹⋅s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:ad}", kg / m / s2) == "kg/(m⋅s²)"); + } + } +} + +TEST_CASE("unit formatting error handling", "[unit][fmt][exception]") +{ + SECTION("unknown unit modifiers should throw") + { + SECTION("only the invalid modifier") + { + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:x}", MP_UNITS_STD_FMT::make_format_args(m)), + MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("invalid unit modifier specified")); + } + + SECTION("invalid modifier in the front") + { + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:xUda}", MP_UNITS_STD_FMT::make_format_args(m)), + MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("invalid unit modifier specified")); + } + + SECTION("invalid modifier in the end") + { + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udax}", MP_UNITS_STD_FMT::make_format_args(m)), + MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("invalid unit modifier specified")); + } + + SECTION("invalid modifier in the middle") + { + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udxa}", MP_UNITS_STD_FMT::make_format_args(m)), + MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("invalid unit modifier specified")); + } + } + + SECTION("repeated unit modifiers should throw") + { + SECTION("text encoding") + { + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:UdaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dUaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dUUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); + } + + SECTION("solidus") + { + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:aUda}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:daUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:daaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + } + + SECTION("separator") + { + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dUad}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dadU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:addU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); + } + } + + SECTION("more then one modifier of the same kind should throw") + { + SECTION("text encoding") + { + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:UdaP}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dPaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dPUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); + } + + SECTION("solidus") + { + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:aUdn}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dnUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:da1U}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + } + + SECTION("separator") + { + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:dUas}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:sadU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES( + MP_UNITS_STD_FMT::vformat("{:adsU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); + } + } + + SECTION("half_high_dot separator requested for portable encoding should throw") + { + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPa}", MP_UNITS_STD_FMT::make_format_args(m)), + MP_UNITS_STD_FMT::format_error, + Catch::Matchers::Message("half_high_dot unit separator allowed only for UTF-8 encoding")); + } +} + +TEST_CASE("default quantity formatting", "[quantity][ostream][fmt]") { std::ostringstream os; @@ -138,12 +400,12 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") } } - SECTION("surface tension") + SECTION("entropy") { - const auto q = 20 * isq::force[N] / (2 * isq::length[m]); + const auto q = 20 * isq::kinetic_energy[J] / (delta(2)); os << q; - SECTION("iostream") { CHECK(os.str() == "10 N/m"); } + SECTION("iostream") { CHECK(os.str() == "10 J/K"); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } @@ -173,10 +435,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("angular impulse") { - const auto q = 123 * isq::angular_impulse[N * m * s]; + const auto q = v{1, 2, 3} * isq::angular_impulse[N * m * s]; os << q; - SECTION("iostream") { CHECK(os.str() == "123 m N s"); } + SECTION("iostream") { CHECK(os.str() == "[1, 2, 3] m N s"); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } @@ -203,10 +465,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("angular acceleration") { - const auto q = 123 * isq::angular_acceleration[rad / s2]; + const auto q = v{1, 2, 3} * isq::angular_acceleration[rad / s2]; os << q; - SECTION("iostream") { CHECK(os.str() == "123 rad/s²"); } + SECTION("iostream") { CHECK(os.str() == "[1, 2, 3] rad/s²"); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } @@ -375,266 +637,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") } } -TEST_CASE("quantity format string with only %N should print quantity value only", "[text][fmt]") -{ - SECTION("integral representation") - { - SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%N}", 123 * isq::speed[km / h]) == "123"); } - - SECTION("negative value") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5"); - } - } - - SECTION("floating-point representation") - { - SECTION("positive value") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5"); - } - - SECTION("negative value") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.859999999999999"); - } - - SECTION("nan") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::quiet_NaN() * isq::length[m]) == "nan"); - } - - SECTION("inf") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::infinity() * isq::length[m]) == "inf"); - } - - SECTION("-inf") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits::infinity() * isq::length[m]) == "-inf"); - } - } -} - -TEST_CASE("quantity format string with only %U should print quantity unit symbol only", "[text][fmt]") -{ - CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::speed[km / h]) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::resistance[si::kilo]) == "kΩ"); - CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::time[us]) == "µs"); - CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::acceleration[m / s2]) == "m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * percent) == "%"); -} - -TEST_CASE("Unit formatting should use proper text encoding") -{ - SECTION("Unicode text output") - { - CHECK(MP_UNITS_STD_FMT::format("{:U}", km / h) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:U}", si::kilo) == "kΩ"); - CHECK(MP_UNITS_STD_FMT::format("{:U}", us) == "µs"); - CHECK(MP_UNITS_STD_FMT::format("{:U}", m / s2) == "m/s²"); - } - - SECTION("Unicode text output is used by default") - { - CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{}", si::kilo) == "kΩ"); - CHECK(MP_UNITS_STD_FMT::format("{}", us) == "µs"); - CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²"); - } - - SECTION("Portable text output") - { - CHECK(MP_UNITS_STD_FMT::format("{:P}", km / h) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:P}", si::kilo) == "kohm"); - CHECK(MP_UNITS_STD_FMT::format("{:P}", us) == "us"); - CHECK(MP_UNITS_STD_FMT::format("{:P}", m / s2) == "m/s^2"); - } -} - -TEST_CASE("Unit formatting should print solidus according to specs") -{ - SECTION("Solidus for only one element in denominator") - { - CHECK(MP_UNITS_STD_FMT::format("{:1}", km / h) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:1}", m / s2) == "m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:1}", kg / m / s2) == "kg m⁻¹ s⁻²"); - } - - SECTION("Solidus for only one element in denominator is used by default") - { - CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²"); - } - - SECTION("Always use solidus") - { - CHECK(MP_UNITS_STD_FMT::format("{:a}", km / h) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:a}", m / s2) == "m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)"); - } - - SECTION("Never use solidus") - { - CHECK(MP_UNITS_STD_FMT::format("{:n}", km / h) == "km h⁻¹"); - CHECK(MP_UNITS_STD_FMT::format("{:n}", m / s2) == "m s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:n}", kg / m / s2) == "kg m⁻¹ s⁻²"); - } -} - -TEST_CASE("Unit formatting should user proper separator") -{ - SECTION("Space") - { - CHECK(MP_UNITS_STD_FMT::format("{:s}", kg * m / s2) == "kg m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:s}", kg / m / s2) == "kg m⁻¹ s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:sa}", kg / m / s2) == "kg/(m s²)"); - } - - SECTION("Space is used by default") - { - CHECK(MP_UNITS_STD_FMT::format("{}", kg * m / s2) == "kg m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)"); - } - - SECTION("Dot") - { - CHECK(MP_UNITS_STD_FMT::format("{:d}", kg * m / s2) == "kg⋅m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:d}", kg / m / s2) == "kg⋅m⁻¹⋅s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:ad}", kg / m / s2) == "kg/(m⋅s²)"); - } -} - -TEST_CASE("unknown unit modifiers should throw", "[text][fmt][exception]") -{ - SECTION("only the invalid modifier") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:x}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); - } - - SECTION("invalid modifier in the front") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:xUda}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); - } - - SECTION("invalid modifier in the end") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udax}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); - } - - SECTION("invalid modifier in the middle") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udxa}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); - } -} - -TEST_CASE("repeated unit modifiers should throw", "[text][fmt][exception]") -{ - SECTION("text encoding") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUaU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUUa}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); - } - - SECTION("solidus") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUda}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daUa}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daaU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); - } - - SECTION("separator") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUad}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dadU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:addU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - } -} - -TEST_CASE("more then one modifier of the same kind should throw", "[text][fmt][exception]") -{ - SECTION("text encoding") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaP}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPaU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPUa}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'UAP' unit modifiers may be used in the format spec")); - } - - SECTION("solidus") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUdn}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dnUa}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:da1U}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); - } - - SECTION("separator") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUas}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:sadU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:adsU}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - } -} - -TEST_CASE("half_high_dot separator requested for portable encoding should throw", "[text][fmt][exception]") -{ - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dPa}", MP_UNITS_STD_FMT::make_format_args(m)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("half_high_dot unit separator allowed only for UTF-8 encoding")); -} - -TEST_CASE("%U and %N can be put anywhere in a format string", "[text][fmt]") -{ - SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%N%U}", 123 * isq::speed[km / h]) == "123km/h"); } - - SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%N###%U}", 123 * isq::speed[km / h]) == "123###km/h"); } - - SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%U %N}", 123 * isq::speed[km / h]) == "km/h 123"); } -} - -TEST_CASE("quantity fill and align specification", "[text][fmt][ostream]") +TEST_CASE("quantity fill and align specification", "[quantity][ostream][fmt]") { SECTION("ostream") { @@ -726,356 +729,373 @@ TEST_CASE("quantity fill and align specification", "[text][fmt][ostream]") } } -TEST_CASE("sign specification", "[text][fmt]") +TEST_CASE("quantity subentities selection", "[quantity][fmt]") { - auto inf = std::numeric_limits::infinity() * si::metre; - auto nan = std::numeric_limits::quiet_NaN() * si::metre; - - SECTION("full format {:%N%?%U} on a quantity") + SECTION("quantity format string with only %N should print quantity value only") { - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", 1 * isq::length[m]) == - "1m,+1m,1m, 1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", -1 * isq::length[m]) == - "-1m,-1m,-1m,-1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", inf) == - "infm,+infm,infm, infm"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", nan) == - "nanm,+nanm,nanm, nanm"); + SECTION("integral representation") + { + SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%N}", 123 * isq::speed[km / h]) == "123"); } + + SECTION("negative value") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5"); + } + } + + SECTION("floating-point representation") + { + SECTION("positive value") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5"); + } + + SECTION("negative value") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.859999999999999"); + } + + SECTION("nan") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::quiet_NaN() * isq::length[m]) == "nan"); + } + + SECTION("inf") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::infinity() * isq::length[m]) == "inf"); + } + + SECTION("-inf") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits::infinity() * isq::length[m]) == "-inf"); + } + } } - SECTION("value only format {:%N} on a quantity") + SECTION("quantity format string with only %U should print quantity unit symbol only") { - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", 1 * isq::length[m]) == "1,+1,1, 1"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", -1 * isq::length[m]) == "-1,-1,-1,-1"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", inf) == "inf,+inf,inf, inf"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", nan) == "nan,+nan,nan, nan"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::speed[km / h]) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::resistance[si::kilo]) == "kΩ"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::time[us]) == "µs"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", v{1, 2, 3} * isq::acceleration[m / s2]) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * percent) == "%"); + } + + SECTION("%U and %N can be put anywhere in a format string") + { + SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%N%U}", 123 * isq::speed[km / h]) == "123km/h"); } + + SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%N###%U}", 123 * isq::speed[km / h]) == "123###km/h"); } + + SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%U %N}", 123 * isq::speed[km / h]) == "km/h 123"); } } } -TEST_CASE("precision specification", "[text][fmt]") +// TODO provide basic tests if format string when provided in a quantity formatter are passed to respective dimensions +// and units formatters (detail formatting tests for dimensions and units are done separately) + +TEST_CASE("quantity numerical value formatting for `std` arithmetic types", "[quantity][fmt]") { - SECTION("full format on a quantity") + SECTION("sign specification") { - SECTION("default spec") + auto inf = std::numeric_limits::infinity() * si::metre; + auto nan = std::numeric_limits::quiet_NaN() * si::metre; + + SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{::N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", 1 * isq::length[m]) == + "1m,+1m,1m, 1m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", -1 * isq::length[m]) == + "-1m,-1m,-1m,-1m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", inf) == + "infm,+infm,infm, infm"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", nan) == + "nanm,+nanm,nanm, nanm"); } - SECTION("explicit spec") + SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); - } - - SECTION("modified spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.0f]}", 1.2345 * isq::length[m]) == "1m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", 1 * isq::length[m]) == "1,+1,1, 1"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", -1 * isq::length[m]) == + "-1,-1,-1,-1"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", inf) == "inf,+inf,inf, inf"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", nan) == "nan,+nan,nan, nan"); } } - SECTION("value only format {:%N} on a quantity") + SECTION("precision specification") { - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.0f]}", 1.2345 * isq::length[m]) == "1"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.1f]}", 1.2345 * isq::length[m]) == "1.2"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.2f]}", 1.2345 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3f]}", 1.2345 * isq::length[m]) == "1.234"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000"); - } -} - -TEST_CASE("type specification", "[text][fmt]") -{ - SECTION("full format {:%N%?%U} on a quantity") - { - SECTION("default spec") + SECTION("full format on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{::N[b]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[B]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[d]}", 42 * isq::length[m]) == "42 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[o]}", 42 * isq::length[m]) == "52 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[x]}", 42 * isq::length[m]) == "2a m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[X]}", 42 * isq::length[m]) == "2A m"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.0f]}", 1.2345 * isq::length[m]) == "1m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000m"); + } + } + + SECTION("value only format {:%N} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.0f]}", 1.2345 * isq::length[m]) == "1"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.1f]}", 1.2345 * isq::length[m]) == "1.2"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.2f]}", 1.2345 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3f]}", 1.2345 * isq::length[m]) == "1.234"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000"); + } + } + + SECTION("type specification") + { + SECTION("full format {:%N%?%U} on a quantity") + { + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[b]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[B]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[d]}", 42 * isq::length[m]) == "42 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[o]}", 42 * isq::length[m]) == "52 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[x]}", 42 * isq::length[m]) == "2a m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[X]}", 42 * isq::length[m]) == "2A m"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); #else - CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); #endif - CHECK(MP_UNITS_STD_FMT::format("{::N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); - } + CHECK(MP_UNITS_STD_FMT::format("{::N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); + } - SECTION("explicit spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[b]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[B]}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[d]}", 42 * isq::length[m]) == "42 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[o]}", 42 * isq::length[m]) == "52 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[x]}", 42 * isq::length[m]) == "2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[X]}", 42 * isq::length[m]) == "2A m"); + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[b]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[B]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[d]}", 42 * isq::length[m]) == "42 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[o]}", 42 * isq::length[m]) == "52 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[x]}", 42 * isq::length[m]) == "2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[X]}", 42 * isq::length[m]) == "2A m"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); #else - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); - } + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); + } - SECTION("modified spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[b]}", 42 * isq::length[m]) == "101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[B]}", 42 * isq::length[m]) == "101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[d]}", 42 * isq::length[m]) == "42m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[o]}", 42 * isq::length[m]) == "52m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[x]}", 42 * isq::length[m]) == "2am"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[X]}", 42 * isq::length[m]) == "2Am"); + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[b]}", 42 * isq::length[m]) == "101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[B]}", 42 * isq::length[m]) == "101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[d]}", 42 * isq::length[m]) == "42m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[o]}", 42 * isq::length[m]) == "52m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[x]}", 42 * isq::length[m]) == "2am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[X]}", 42 * isq::length[m]) == "2Am"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0m"); #else - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0m"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08m"); + } } - } - SECTION("value only format {:%N} on a quantity") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[b]}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[B]}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[d]}", 42 * isq::length[m]) == "42"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[o]}", 42 * isq::length[m]) == "52"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[x]}", 42 * isq::length[m]) == "2a"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[X]}", 42 * isq::length[m]) == "2A"); + SECTION("value only format {:%N} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[b]}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[B]}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[d]}", 42 * isq::length[m]) == "42"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[o]}", 42 * isq::length[m]) == "52"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[x]}", 42 * isq::length[m]) == "2a"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[X]}", 42 * isq::length[m]) == "2A"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); #else - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); - } -} - -TEST_CASE("different base types with the # specifier", "[text][fmt]") -{ - SECTION("full format {:%N%?%U} on a quantity") - { - SECTION("default spec") - { - CHECK(MP_UNITS_STD_FMT::format("{::N[#b]}", 42 * isq::length[m]) == "0b101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#B]}", 42 * isq::length[m]) == "0B101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#o]}", 42 * isq::length[m]) == "052 m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#x]}", 42 * isq::length[m]) == "0x2a m"); - CHECK(MP_UNITS_STD_FMT::format("{::N[#X]}", 42 * isq::length[m]) == "0X2A m"); - } - - SECTION("explicit spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#b]}", 42 * isq::length[m]) == "0b101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#B]}", 42 * isq::length[m]) == "0B101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#o]}", 42 * isq::length[m]) == "052 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#x]}", 42 * isq::length[m]) == "0x2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#X]}", 42 * isq::length[m]) == "0X2A m"); - } - - SECTION("modified spec") - { - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#b]}", 42 * isq::length[m]) == "0b101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#B]}", 42 * isq::length[m]) == "0B101010m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#o]}", 42 * isq::length[m]) == "052m"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#x]}", 42 * isq::length[m]) == "0x2am"); - CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#X]}", 42 * isq::length[m]) == "0X2Am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); } } - SECTION("value only format {:%N} on a quantity") + SECTION("different base types with the # specifier") { - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#b]}", 42 * isq::length[m]) == "0b101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#B]}", 42 * isq::length[m]) == "0B101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#o]}", 42 * isq::length[m]) == "052"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#x]}", 42 * isq::length[m]) == "0x2a"); - CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#X]}", 42 * isq::length[m]) == "0X2A"); + SECTION("full format {:%N%?%U} on a quantity") + { + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[#b]}", 42 * isq::length[m]) == "0b101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#B]}", 42 * isq::length[m]) == "0B101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#o]}", 42 * isq::length[m]) == "052 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#x]}", 42 * isq::length[m]) == "0x2a m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#X]}", 42 * isq::length[m]) == "0X2A m"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#b]}", 42 * isq::length[m]) == "0b101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#B]}", 42 * isq::length[m]) == "0B101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#o]}", 42 * isq::length[m]) == "052 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#x]}", 42 * isq::length[m]) == "0x2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#X]}", 42 * isq::length[m]) == "0X2A m"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#b]}", 42 * isq::length[m]) == "0b101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#B]}", 42 * isq::length[m]) == "0B101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#o]}", 42 * isq::length[m]) == "052m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#x]}", 42 * isq::length[m]) == "0x2am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#X]}", 42 * isq::length[m]) == "0X2Am"); + } + } + + SECTION("value only format {:%N} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#b]}", 42 * isq::length[m]) == "0b101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#B]}", 42 * isq::length[m]) == "0B101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#o]}", 42 * isq::length[m]) == "052"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#x]}", 42 * isq::length[m]) == "0x2a"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#X]}", 42 * isq::length[m]) == "0X2A"); + } } -} -TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]") -{ - struct group2 : std::numpunct { - [[nodiscard]] char do_thousands_sep() const override { return '_'; } - [[nodiscard]] std::string do_grouping() const override { return "\2"; } - }; - - struct group3 : std::numpunct { - [[nodiscard]] char do_thousands_sep() const override { return '\''; } - [[nodiscard]] std::string do_grouping() const override { return "\3"; } - }; - - const std::locale grp2{std::locale::classic(), new group2}; - const std::locale grp3{std::locale::classic(), new group3}; - - SECTION("full format on a quantity") + SECTION("localization with the 'L' specifier") { - SECTION("default spec") - { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); - } + struct group2 : std::numpunct { + [[nodiscard]] char do_thousands_sep() const override { return '_'; } + [[nodiscard]] std::string do_grouping() const override { return "\2"; } + }; - SECTION("explicit spec") - { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); - } + struct group3 : std::numpunct { + [[nodiscard]] char do_thousands_sep() const override { return '\''; } + [[nodiscard]] std::string do_grouping() const override { return "\3"; } + }; - SECTION("modified spec") + const std::locale grp2{std::locale::classic(), new group2}; + const std::locale grp3{std::locale::classic(), new group3}; + + SECTION("full format on a quantity") { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458m/s"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458m/s"); + } } } } -TEST_CASE("unit_symbol", "[text]") -{ - using enum text_encoding; - using enum unit_symbol_solidus; - using enum unit_symbol_separator; - - std::ostringstream os; - - SECTION("default formatting") - { - os << unit_symbol(m / s2); - CHECK(os.str() == "m/s²"); - } - - SECTION("Portable mode") - { - os << unit_symbol(m / s2); - CHECK(os.str() == "m/s^2"); - } - - SECTION("solidus") - { - os << unit_symbol(m / s2); - CHECK(os.str() == "m s⁻²"); - } - - SECTION("separator") - { - os << unit_symbol(m / s2); - CHECK(os.str() == "m⋅s⁻²"); - } -} - -TEST_CASE("dimension_symbol", "[text]") -{ - using enum text_encoding; - - std::ostringstream os; - - SECTION("default formatting") - { - os << dimension_symbol(isq::power.dimension); - CHECK(os.str() == "L²MT⁻³"); - } - - SECTION("Portable mode") - { - os << dimension_symbol(isq::power.dimension); - CHECK(os.str() == "L^2MT^-3"); - } -} - -TEST_CASE("value_cast", "[text][ostream]") +TEST_CASE("check if `value_cast` properly changes the numerical value of a quantity", "[value_cast][ostream]") { std::ostringstream os; diff --git a/test/runtime/math_test.cpp b/test/runtime/math_test.cpp index 79018bff..bee5f7e8 100644 --- a/test/runtime/math_test.cpp +++ b/test/runtime/math_test.cpp @@ -43,486 +43,488 @@ using namespace mp_units::si::unit_symbols; // classical -TEST_CASE("'pow()' on quantity changes the value and the dimension accordingly", "[math][pow]") +TEST_CASE("math operations", "[math]") { - SECTION("'pow<0>(q)' returns '1'") { CHECK(pow<0>(2 * isq::length[m]) == 1 * one); } - - SECTION("'pow<1>(q)' returns 'q'") { CHECK(pow<1>(2 * isq::length[m]) == 2 * isq::length[m]); } - - SECTION("'pow<2>(q)' squares both the value and a dimension") + SECTION("'pow()' on quantity changes the value and the dimension accordingly") { - CHECK(pow<2>(2 * isq::length[m]) == 4 * isq::area[m2]); + SECTION("'pow<0>(q)' returns '1'") { CHECK(pow<0>(2 * isq::length[m]) == 1 * one); } + + SECTION("'pow<1>(q)' returns 'q'") { CHECK(pow<1>(2 * isq::length[m]) == 2 * isq::length[m]); } + + SECTION("'pow<2>(q)' squares both the value and a dimension") + { + CHECK(pow<2>(2 * isq::length[m]) == 4 * isq::area[m2]); + } + + SECTION("'pow<3>(q)' cubes both the value and a dimension") + { + CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]); + } } - SECTION("'pow<3>(q)' cubes both the value and a dimension") + SECTION("'sqrt()' on quantity changes the value and the dimension accordingly") { - CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]); - } -} - -TEST_CASE("'sqrt()' on quantity changes the value and the dimension accordingly", "[math][sqrt]") -{ - REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]); -} - -TEST_CASE("'cbrt()' on quantity changes the value and the dimension accordingly", "[math][cbrt]") -{ - REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]); -} - -TEST_CASE("'fma()' on quantity changes the value and the dimension accordingly", "[math][fma]") -{ - REQUIRE(fma(1.0 * isq::length[m], 2.0 * one, 2.0 * isq::length[m]) == 4.0 * isq::length[m]); - REQUIRE(fma(isq::speed(10.0 * m / s), isq::time(2.0 * s), isq::height(42.0 * m)) == isq::length(62.0 * m)); -} - -TEST_CASE("fmod functions", "[math][fmod]") -{ - SECTION("fmod should work on the same quantities") - { - REQUIRE(fmod(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); - REQUIRE(fmod(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]); - REQUIRE(fmod(3 * isq::length[km], 2 * isq::length[km]) == 1 * isq::length[km]); - REQUIRE(fmod(4 * isq::length[km], 2.5f * isq::length[km]) == 1.5 * isq::length[km]); - } - SECTION("fmod should work with different units of the same dimension") - { - REQUIRE(fmod(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]); - REQUIRE(fmod(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]); - REQUIRE(fmod(3. * isq::length[km], 2000. * isq::length[m]) == 1000 * isq::length[m]); - REQUIRE(fmod(4 * isq::length[km], 2500 * isq::length[m]) == 1500 * isq::length[m]); - } -} - -TEST_CASE("remainder functions", "[math][remainder]") -{ - SECTION("remainder should work on the same quantities") - { - REQUIRE(remainder(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); - REQUIRE(remainder(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]); - REQUIRE(remainder(3 * isq::length[km], 2 * isq::length[km]) == -1 * isq::length[km]); - REQUIRE(remainder(4 * isq::length[km], 2.75f * isq::length[km]) == 1.25 * isq::length[km]); - } - SECTION("remainder should work with different units of the same dimension") - { - REQUIRE(remainder(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]); - REQUIRE(remainder(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]); - REQUIRE(remainder(3. * isq::length[km], 2000. * isq::length[m]) == -1000 * isq::length[m]); - REQUIRE(remainder(4 * isq::length[km], 2750 * isq::length[m]) == 1250 * isq::length[m]); - } -} - -TEST_CASE("'isfinite()' accepts dimensioned arguments", "[math][isfinite]") { REQUIRE(isfinite(4.0 * isq::length[m])); } - -TEST_CASE("'isinf()' accepts dimensioned arguments", "[math][isinf]") { REQUIRE(!isinf(4.0 * isq::length[m])); } - -TEST_CASE("'isnan()' accepts dimensioned arguments", "[math][isnan]") { REQUIRE(!isnan(4.0 * isq::length[m])); } - - -TEST_CASE("'pow()' on quantity changes the value and the dimension accordingly", "[math][pow]") -{ - REQUIRE(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m])); -} - -// TODO add tests for exp() - -TEST_CASE("absolute functions on quantity returns the absolute value", "[math][abs][fabs]") -{ - SECTION("'abs()' on a negative quantity returns the abs") - { - SECTION("integral representation") { REQUIRE(abs(-1 * isq::length[m]) == 1 * isq::length[m]); } - - SECTION("floating-point representation") { REQUIRE(abs(-1. * isq::length[m]) == 1 * isq::length[m]); } - } - - SECTION("'abs()' on a positive quantity returns the abs") - { - SECTION("integral representation") { REQUIRE(abs(1 * isq::length[m]) == 1 * isq::length[m]); } - - SECTION("floating-point representation") { REQUIRE(abs(1. * isq::length[m]) == 1 * isq::length[m]); } - } -} - -TEST_CASE("numeric_limits functions", "[limits]") -{ - SECTION("'epsilon' works as expected using default floating type") - { - REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == - std::numeric_limits::epsilon()); - } - SECTION("'epsilon' works as expected using integers") - { - REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == - std::numeric_limits::epsilon()); - } -} - -TEST_CASE("floor functions", "[floor]") -{ - SECTION("floor 1 second with target unit second should be 1 second") - { - REQUIRE(floor(1 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("floor 1000 milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1000 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor 1001 milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1001 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor 1999 milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1999 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor -1000 milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-1000 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("floor -999 milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-999 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("floor 1.3 seconds with target unit second should be 1 second") - { - REQUIRE(floor(1.3 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("floor -1.3 seconds with target unit second should be -2 seconds") - { - REQUIRE(floor(-1.3 * isq::time[s]) == -2 * isq::time[s]); - } - SECTION("floor 1001. milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1001. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor 1999. milliseconds with target unit second should be 1 second") - { - REQUIRE(floor(1999. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("floor -1000. milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-1000. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("floor -999. milliseconds with target unit second should be -1 second") - { - REQUIRE(floor(-999. * isq::time[ms]) == -1 * isq::time[s]); - } - - // TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2` -} - -TEST_CASE("ceil functions", "[ceil]") -{ - SECTION("ceil 1 second with target unit second should be 1 second") - { - REQUIRE(ceil(1 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("ceil 1000 milliseconds with target unit second should be 1 second") - { - REQUIRE(ceil(1000 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("ceil 1001 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1001 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil 1999 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1999 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil -1000 milliseconds with target unit second should be -1 second") - { - REQUIRE(ceil(-1000 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("ceil -999 milliseconds with target unit second should be 0 seconds") - { - REQUIRE(ceil(-999 * isq::time[ms]) == 0 * isq::time[s]); - } - SECTION("ceil 1.3 seconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1.3 * isq::time[s]) == 2 * isq::time[s]); - } - SECTION("ceil -1.3 seconds with target unit second should be -1 second") - { - REQUIRE(ceil(-1.3 * isq::time[s]) == -1 * isq::time[s]); - } - SECTION("ceil 1001. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1001. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil 1999. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(ceil(1999. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("ceil -1000. milliseconds with target unit second should be -1 second") - { - REQUIRE(ceil(-1000. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("ceil -999. milliseconds with target unit second should be 0 seconds") - { - REQUIRE(ceil(-999. * isq::time[ms]) == 0 * isq::time[s]); - } -} - -TEST_CASE("round functions", "[round]") -{ - SECTION("round 1 second with target unit second should be 1 second") - { - REQUIRE(round(1 * isq::time[s]) == 1 * isq::time[s]); - } - SECTION("round 1000 milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1000 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1001 milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1001 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1499 milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1499 * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1500 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1500 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round 1999 milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1999 * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round -1000 milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1000 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1001 milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1001 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1499 milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1499 * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1500 milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1500 * isq::time[ms]) == -2 * isq::time[s]); - } - SECTION("round -1999 milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1999 * isq::time[ms]) == -2 * isq::time[s]); - } - SECTION("round 1000. milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1000. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1001. milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1001. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1499. milliseconds with target unit second should be 1 second") - { - REQUIRE(round(1499. * isq::time[ms]) == 1 * isq::time[s]); - } - SECTION("round 1500. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1500. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round 1999. milliseconds with target unit second should be 2 seconds") - { - REQUIRE(round(1999. * isq::time[ms]) == 2 * isq::time[s]); - } - SECTION("round -1000. milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1000. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1001. milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1001. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1499. milliseconds with target unit second should be -1 second") - { - REQUIRE(round(-1499. * isq::time[ms]) == -1 * isq::time[s]); - } - SECTION("round -1500. milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1500. * isq::time[ms]) == -2 * isq::time[s]); - } - SECTION("round -1999. milliseconds with target unit second should be -2 seconds") - { - REQUIRE(round(-1999. * isq::time[ms]) == -2 * isq::time[s]); - } -} - -TEST_CASE("hypot functions", "[hypot]") -{ - SECTION("hypot should work on the same quantities") - { - REQUIRE(hypot(3. * isq::length[km], 4. * isq::length[km]) == 5. * isq::length[km]); - REQUIRE(hypot(2. * isq::length[km], 3. * isq::length[km], 6. * isq::length[km]) == 7. * isq::length[km]); - } - SECTION("hypot should work with different units of the same dimension") - { - REQUIRE(hypot(3. * isq::length[km], 4000. * isq::length[m]) == 5. * isq::length[km]); - REQUIRE(hypot(2. * isq::length[km], 3000. * isq::length[m], 6. * isq::length[km]) == 7. * isq::length[km]); - } -} - -TEST_CASE("SI trigonometric functions", "[trig][si]") -{ - SECTION("sin") - { - REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one)); - REQUIRE_THAT(si::sin(90 * deg), AlmostEquals(1. * one)); - REQUIRE_THAT(si::sin(180 * deg), AlmostEquals(0. * one)); - REQUIRE_THAT(si::sin(270 * deg), AlmostEquals(-1. * one)); - } - - SECTION("cos") - { - REQUIRE_THAT(si::cos(0 * deg), AlmostEquals(1. * one)); - REQUIRE_THAT(si::cos(90 * deg), AlmostEquals(0. * one)); - REQUIRE_THAT(si::cos(180 * deg), AlmostEquals(-1. * one)); - REQUIRE_THAT(si::cos(270 * deg), AlmostEquals(0. * one)); - } - - SECTION("tan") - { - REQUIRE_THAT(si::tan(0 * deg), AlmostEquals(0. * one)); - REQUIRE_THAT(si::tan(45. * deg), AlmostEquals(1. * one)); - REQUIRE_THAT(si::tan(135. * deg), AlmostEquals(-1. * one)); - REQUIRE_THAT(si::tan(180. * deg), AlmostEquals(0. * one)); - } -} - -TEST_CASE("SI inverse trigonometric functions", "[inv trig][si]") -{ - SECTION("asin") - { - REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg)); - REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg)); - REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg)); - } - - SECTION("acos") - { - REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg)); - REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg)); - REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg)); - } - - SECTION("atan") - { - REQUIRE_THAT(si::atan(-1 * one), AlmostEquals(-45. * deg)); - REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg)); - REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg)); - } -} - -TEST_CASE("SI atan2 functions", "[atan2][si]") -{ - SECTION("atan2 should work on the same quantities") - { - REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * deg)); - REQUIRE_THAT(si::atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * deg)); - REQUIRE_THAT(si::atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * deg)); - } - SECTION("atan2 should work with different units of the same dimension") - { - REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * deg)); - REQUIRE_THAT(si::atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * deg)); - REQUIRE_THAT(si::atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * deg)); - } -} - - -TEST_CASE("Angle trigonometric functions", "[trig][angle]") -{ - using namespace mp_units::angular; - using namespace mp_units::angular::unit_symbols; - using mp_units::angular::unit_symbols::deg; - - SECTION("sin") - { - REQUIRE_THAT(sin(0 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(90 * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(180 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(270 * angle[deg]), AlmostEquals(-1. * one)); - - REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2)); - REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); - } - - SECTION("cos") - { - REQUIRE_THAT(cos(0 * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(cos(90 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(cos(180 * angle[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(cos(270 * angle[deg]), AlmostEquals(0. * one)); - - REQUIRE_THAT(cos(0 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(cos(100 * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(cos(200 * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(cos(300 * angle[grad]), AlmostEquals(0. * one)); - } - - SECTION("tan") - { - REQUIRE_THAT(tan(0 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(tan(45 * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(tan(135 * angle[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(180 * angle[deg]), AlmostEquals(0. * one)); - - REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); - } -} - -TEST_CASE("Angle inverse trigonometric functions", "[inv trig][angle]") -{ - using namespace mp_units::angular; - using namespace mp_units::angular::unit_symbols; - using mp_units::angular::unit_symbols::deg; - - SECTION("asin") - { - REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); - REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); - } - - SECTION("acos") - { - REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); - REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); - } - - SECTION("atan") - { - REQUIRE_THAT(atan(-1 * one), AlmostEquals(-45. * angle[deg])); - REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg])); - } -} - -TEST_CASE("Angle atan2 functions", "[atan2][angle]") -{ - using namespace mp_units::angular; - using namespace mp_units::angular::unit_symbols; - using mp_units::angular::unit_symbols::deg; - - SECTION("atan2 should work on the same quantities") - { - REQUIRE_THAT(atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * angle[deg])); - REQUIRE_THAT(atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * angle[deg])); - } - SECTION("atan2 should work with different units of the same dimension") - { - REQUIRE_THAT(atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * angle[deg])); - REQUIRE_THAT(atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * angle[deg])); + REQUIRE(sqrt(4 * isq::area[m2]) == 2 * isq::length[m]); + } + + SECTION("'cbrt()' on quantity changes the value and the dimension accordingly") + { + REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]); + } + + SECTION("'fma()' on quantity changes the value and the dimension accordingly") + { + REQUIRE(fma(1.0 * isq::length[m], 2.0 * one, 2.0 * isq::length[m]) == 4.0 * isq::length[m]); + REQUIRE(fma(isq::speed(10.0 * m / s), isq::time(2.0 * s), isq::height(42.0 * m)) == isq::length(62.0 * m)); + } + + SECTION("fmod functions") + { + SECTION("fmod should work on the same quantities") + { + REQUIRE(fmod(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); + REQUIRE(fmod(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]); + REQUIRE(fmod(3 * isq::length[km], 2 * isq::length[km]) == 1 * isq::length[km]); + REQUIRE(fmod(4 * isq::length[km], 2.5f * isq::length[km]) == 1.5 * isq::length[km]); + } + SECTION("fmod should work with different units of the same dimension") + { + REQUIRE(fmod(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]); + REQUIRE(fmod(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]); + REQUIRE(fmod(3. * isq::length[km], 2000. * isq::length[m]) == 1000 * isq::length[m]); + REQUIRE(fmod(4 * isq::length[km], 2500 * isq::length[m]) == 1500 * isq::length[m]); + } + } + + SECTION("remainder functions") + { + SECTION("remainder should work on the same quantities") + { + REQUIRE(remainder(4. * isq::length[km], 3. * isq::length[km]) == 1. * isq::length[km]); + REQUIRE(remainder(-9. * isq::length[km], 3. * isq::length[km]) == -0. * isq::length[km]); + REQUIRE(remainder(3 * isq::length[km], 2 * isq::length[km]) == -1 * isq::length[km]); + REQUIRE(remainder(4 * isq::length[km], 2.75f * isq::length[km]) == 1.25 * isq::length[km]); + } + SECTION("remainder should work with different units of the same dimension") + { + REQUIRE(remainder(4. * isq::length[km], 3000. * isq::length[m]) == 1000. * isq::length[m]); + REQUIRE(remainder(-9. * isq::length[km], 3000. * isq::length[m]) == -0. * isq::length[m]); + REQUIRE(remainder(3. * isq::length[km], 2000. * isq::length[m]) == -1000 * isq::length[m]); + REQUIRE(remainder(4 * isq::length[km], 2750 * isq::length[m]) == 1250 * isq::length[m]); + } + } + + SECTION("'isfinite()' accepts dimensioned arguments") { REQUIRE(isfinite(4.0 * isq::length[m])); } + + SECTION("'isinf()' accepts dimensioned arguments") { REQUIRE(!isinf(4.0 * isq::length[m])); } + + SECTION("'isnan()' accepts dimensioned arguments") { REQUIRE(!isnan(4.0 * isq::length[m])); } + + + SECTION("'pow()' on quantity changes the value and the dimension accordingly") + { + REQUIRE(pow<1, 4>(16 * isq::area[m2]) == sqrt(4 * isq::length[m])); + } + + // TODO add tests for exp() + + SECTION("absolute functions on quantity returns the absolute value") + { + SECTION("'abs()' on a negative quantity returns the abs") + { + SECTION("integral representation") { REQUIRE(abs(-1 * isq::length[m]) == 1 * isq::length[m]); } + + SECTION("floating-point representation") { REQUIRE(abs(-1. * isq::length[m]) == 1 * isq::length[m]); } + } + + SECTION("'abs()' on a positive quantity returns the abs") + { + SECTION("integral representation") { REQUIRE(abs(1 * isq::length[m]) == 1 * isq::length[m]); } + + SECTION("floating-point representation") { REQUIRE(abs(1. * isq::length[m]) == 1 * isq::length[m]); } + } + } + + SECTION("numeric_limits functions") + { + SECTION("'epsilon' works as expected using default floating type") + { + REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == + std::numeric_limits::epsilon()); + } + SECTION("'epsilon' works as expected using integers") + { + REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == + std::numeric_limits::epsilon()); + } + } + + SECTION("floor functions") + { + SECTION("floor 1 second with target unit second should be 1 second") + { + REQUIRE(floor(1 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("floor 1000 milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1000 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor 1001 milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1001 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor 1999 milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1999 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor -1000 milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-1000 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("floor -999 milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-999 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("floor 1.3 seconds with target unit second should be 1 second") + { + REQUIRE(floor(1.3 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("floor -1.3 seconds with target unit second should be -2 seconds") + { + REQUIRE(floor(-1.3 * isq::time[s]) == -2 * isq::time[s]); + } + SECTION("floor 1001. milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1001. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor 1999. milliseconds with target unit second should be 1 second") + { + REQUIRE(floor(1999. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("floor -1000. milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-1000. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("floor -999. milliseconds with target unit second should be -1 second") + { + REQUIRE(floor(-999. * isq::time[ms]) == -1 * isq::time[s]); + } + + // TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2` + } + + SECTION("ceil functions") + { + SECTION("ceil 1 second with target unit second should be 1 second") + { + REQUIRE(ceil(1 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("ceil 1000 milliseconds with target unit second should be 1 second") + { + REQUIRE(ceil(1000 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("ceil 1001 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1001 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil 1999 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1999 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil -1000 milliseconds with target unit second should be -1 second") + { + REQUIRE(ceil(-1000 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("ceil -999 milliseconds with target unit second should be 0 seconds") + { + REQUIRE(ceil(-999 * isq::time[ms]) == 0 * isq::time[s]); + } + SECTION("ceil 1.3 seconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1.3 * isq::time[s]) == 2 * isq::time[s]); + } + SECTION("ceil -1.3 seconds with target unit second should be -1 second") + { + REQUIRE(ceil(-1.3 * isq::time[s]) == -1 * isq::time[s]); + } + SECTION("ceil 1001. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1001. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil 1999. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(ceil(1999. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("ceil -1000. milliseconds with target unit second should be -1 second") + { + REQUIRE(ceil(-1000. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("ceil -999. milliseconds with target unit second should be 0 seconds") + { + REQUIRE(ceil(-999. * isq::time[ms]) == 0 * isq::time[s]); + } + } + + SECTION("round functions") + { + SECTION("round 1 second with target unit second should be 1 second") + { + REQUIRE(round(1 * isq::time[s]) == 1 * isq::time[s]); + } + SECTION("round 1000 milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1000 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1001 milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1001 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1499 milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1499 * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1500 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1500 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round 1999 milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1999 * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round -1000 milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1000 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1001 milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1001 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1499 milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1499 * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1500 milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1500 * isq::time[ms]) == -2 * isq::time[s]); + } + SECTION("round -1999 milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1999 * isq::time[ms]) == -2 * isq::time[s]); + } + SECTION("round 1000. milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1000. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1001. milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1001. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1499. milliseconds with target unit second should be 1 second") + { + REQUIRE(round(1499. * isq::time[ms]) == 1 * isq::time[s]); + } + SECTION("round 1500. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1500. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round 1999. milliseconds with target unit second should be 2 seconds") + { + REQUIRE(round(1999. * isq::time[ms]) == 2 * isq::time[s]); + } + SECTION("round -1000. milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1000. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1001. milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1001. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1499. milliseconds with target unit second should be -1 second") + { + REQUIRE(round(-1499. * isq::time[ms]) == -1 * isq::time[s]); + } + SECTION("round -1500. milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1500. * isq::time[ms]) == -2 * isq::time[s]); + } + SECTION("round -1999. milliseconds with target unit second should be -2 seconds") + { + REQUIRE(round(-1999. * isq::time[ms]) == -2 * isq::time[s]); + } + } + + SECTION("hypot functions") + { + SECTION("hypot should work on the same quantities") + { + REQUIRE(hypot(3. * isq::length[km], 4. * isq::length[km]) == 5. * isq::length[km]); + REQUIRE(hypot(2. * isq::length[km], 3. * isq::length[km], 6. * isq::length[km]) == 7. * isq::length[km]); + } + SECTION("hypot should work with different units of the same dimension") + { + REQUIRE(hypot(3. * isq::length[km], 4000. * isq::length[m]) == 5. * isq::length[km]); + REQUIRE(hypot(2. * isq::length[km], 3000. * isq::length[m], 6. * isq::length[km]) == 7. * isq::length[km]); + } + } + + SECTION("SI trigonometric functions") + { + SECTION("sin") + { + REQUIRE_THAT(si::sin(0 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(si::sin(90 * deg), AlmostEquals(1. * one)); + REQUIRE_THAT(si::sin(180 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(si::sin(270 * deg), AlmostEquals(-1. * one)); + } + + SECTION("cos") + { + REQUIRE_THAT(si::cos(0 * deg), AlmostEquals(1. * one)); + REQUIRE_THAT(si::cos(90 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(si::cos(180 * deg), AlmostEquals(-1. * one)); + REQUIRE_THAT(si::cos(270 * deg), AlmostEquals(0. * one)); + } + + SECTION("tan") + { + REQUIRE_THAT(si::tan(0 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(si::tan(45. * deg), AlmostEquals(1. * one)); + REQUIRE_THAT(si::tan(135. * deg), AlmostEquals(-1. * one)); + REQUIRE_THAT(si::tan(180. * deg), AlmostEquals(0. * one)); + } + } + + SECTION("SI inverse trigonometric functions") + { + SECTION("asin") + { + REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg)); + REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg)); + REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg)); + } + + SECTION("acos") + { + REQUIRE_THAT(si::asin(-1 * one), AlmostEquals(-90. * deg)); + REQUIRE_THAT(si::asin(0 * one), AlmostEquals(0. * deg)); + REQUIRE_THAT(si::asin(1 * one), AlmostEquals(90. * deg)); + } + + SECTION("atan") + { + REQUIRE_THAT(si::atan(-1 * one), AlmostEquals(-45. * deg)); + REQUIRE_THAT(si::atan(0 * one), AlmostEquals(0. * deg)); + REQUIRE_THAT(si::atan(1 * one), AlmostEquals(45. * deg)); + } + } + + SECTION("SI atan2 functions") + { + SECTION("atan2 should work on the same quantities") + { + REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * deg)); + REQUIRE_THAT(si::atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * deg)); + REQUIRE_THAT(si::atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * deg)); + } + SECTION("atan2 should work with different units of the same dimension") + { + REQUIRE_THAT(si::atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * deg)); + REQUIRE_THAT(si::atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * deg)); + REQUIRE_THAT(si::atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * deg)); + } + } + + SECTION("Angle trigonometric functions") + { + using namespace mp_units::angular; + using namespace mp_units::angular::unit_symbols; + using mp_units::angular::unit_symbols::deg; + + SECTION("sin") + { + REQUIRE_THAT(sin(0 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(90 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(sin(180 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(270 * angle[deg]), AlmostEquals(-1. * one)); + + REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2)); + REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); + } + + SECTION("cos") + { + REQUIRE_THAT(cos(0 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(cos(90 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(180 * angle[deg]), AlmostEquals(-1. * one)); + REQUIRE_THAT(cos(270 * angle[deg]), AlmostEquals(0. * one)); + + REQUIRE_THAT(cos(0 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(cos(100 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(200 * angle[grad]), AlmostEquals(-1. * one)); + REQUIRE_THAT(cos(300 * angle[grad]), AlmostEquals(0. * one)); + } + + SECTION("tan") + { + REQUIRE_THAT(tan(0 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(45 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(tan(135 * angle[deg]), AlmostEquals(-1. * one)); + REQUIRE_THAT(tan(180 * angle[deg]), AlmostEquals(0. * one)); + + REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); + REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); + } + } + + SECTION("Angle inverse trigonometric functions") + { + using namespace mp_units::angular; + using namespace mp_units::angular::unit_symbols; + using mp_units::angular::unit_symbols::deg; + + SECTION("asin") + { + REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); + REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); + } + + SECTION("acos") + { + REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); + REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); + } + + SECTION("atan") + { + REQUIRE_THAT(atan(-1 * one), AlmostEquals(-45. * angle[deg])); + REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg])); + } + } + + SECTION("Angle atan2 functions") + { + using namespace mp_units::angular; + using namespace mp_units::angular::unit_symbols; + using mp_units::angular::unit_symbols::deg; + + SECTION("atan2 should work on the same quantities") + { + REQUIRE_THAT(atan2(-1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(-45. * angle[deg])); + REQUIRE_THAT(atan2(0. * isq::length[km], 1. * isq::length[km]), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(atan2(1. * isq::length[km], 1. * isq::length[km]), AlmostEquals(45. * angle[deg])); + } + SECTION("atan2 should work with different units of the same dimension") + { + REQUIRE_THAT(atan2(-1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(-45. * angle[deg])); + REQUIRE_THAT(atan2(0. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(atan2(1. * isq::length[km], 1000. * isq::length[m]), AlmostEquals(45. * angle[deg])); + } } } diff --git a/test/runtime/quantity_test.cpp b/test/runtime/quantity_test.cpp index 14dec787..0984373e 100644 --- a/test/runtime/quantity_test.cpp +++ b/test/runtime/quantity_test.cpp @@ -60,22 +60,25 @@ constexpr bool within_4_ulps(T a, T b) } // namespace -// conversion requiring radical magnitudes -TEST_CASE("unit conversions support radical magnitudes", "[conversion][radical]") +TEST_CASE("quantity operations", "[quantity]") { - REQUIRE(within_4_ulps(sqrt((1.0 * m) * (1.0 * km)).numerical_value_in(m), sqrt(1000.0))); -} + // conversion requiring radical magnitudes + SECTION("unit conversions support radical magnitudes") + { + REQUIRE(within_4_ulps(sqrt((1.0 * m) * (1.0 * km)).numerical_value_in(m), sqrt(1000.0))); + } -// Reproducing issue #474 exactly: -TEST_CASE("Issue 474 is fixed", "[conversion][radical]") -{ - constexpr auto val_issue_474 = 8.0 * si::si2019::boltzmann_constant * 1000.0 * K / (std::numbers::pi * 10 * Da); - REQUIRE(within_4_ulps(sqrt(val_issue_474).numerical_value_in(m / s), - sqrt(val_issue_474.numerical_value_in(m * m / s / s)))); -} + // Reproducing issue #474 exactly: + SECTION("Issue 474 is fixed") + { + constexpr auto val_issue_474 = 8.0 * si::si2019::boltzmann_constant * 1000.0 * K / (std::numbers::pi * 10 * Da); + REQUIRE(within_4_ulps(sqrt(val_issue_474).numerical_value_in(m / s), + sqrt(val_issue_474.numerical_value_in(m * m / s / s)))); + } -TEST_CASE("Volatile representation type", "[volatile]") -{ - volatile std::int16_t vint = 123; - REQUIRE(quantity(vint * m).numerical_value_in(m) == 123); + SECTION("Volatile representation type") + { + volatile std::int16_t vint = 123; + REQUIRE(quantity(vint * m).numerical_value_in(m) == 123); + } } diff --git a/test/static/cgs_test.cpp b/test/static/cgs_test.cpp index c027b601..9ea79b1f 100644 --- a/test/static/cgs_test.cpp +++ b/test/static/cgs_test.cpp @@ -25,24 +25,28 @@ #include #include #include - -template - requires mp_units::is_scalar -constexpr bool mp_units::is_vector = true; +#if MP_UNITS_HOSTED +#include +#endif namespace { using namespace mp_units; using namespace mp_units::cgs; using namespace mp_units::cgs::unit_symbols; +#if MP_UNITS_HOSTED +using v = cartesian_vector; +#endif // https://en.wikipedia.org/wiki/Centimetre%E2%80%93gram%E2%80%93second_system_of_units#Definitions_and_conversion_factors_of_CGS_units_in_mechanics static_assert(isq::length(100 * cm) == isq::length(1 * si::metre)); static_assert(isq::mass(1000 * g) == isq::mass(1 * si::kilogram)); static_assert(isq::time(1 * s) == isq::time(1 * si::second)); static_assert(isq::speed(100 * cm / s) == isq::speed(1 * si::metre / si::second)); -static_assert(isq::acceleration(100 * Gal) == isq::acceleration(1 * si::metre / square(si::second))); -static_assert(isq::force(100'000 * dyn) == isq::force(1 * si::newton)); +#if MP_UNITS_HOSTED +static_assert(isq::acceleration(v{100} * Gal) == isq::acceleration(v{1} * si::metre / square(si::second))); +static_assert(isq::force(v{100'000} * dyn) == isq::force(v{1} * si::newton)); +#endif static_assert(isq::energy(10'000'000 * erg) == isq::energy(1 * si::joule)); static_assert(isq::power(10'000'000 * erg / s) == isq::power(1 * si::watt)); static_assert(isq::pressure(10 * Ba) == isq::pressure(1 * si::pascal)); diff --git a/test/static/math_test.cpp b/test/static/math_test.cpp index a573fd1f..48682ee8 100644 --- a/test/static/math_test.cpp +++ b/test/static/math_test.cpp @@ -237,17 +237,17 @@ static_assert(compare(round(-1999. * isq::time[ms]), -2. * isq::time #endif // non-truncating -static_assert(compare(inverse(250 * Hz), 4000 * us)); -static_assert(compare(inverse(250 * kHz), 4 * us)); -static_assert(compare(inverse(250 * uHz), 4 * ks)); +static_assert(compare(kind_of(inverse(250 * Hz)), 4000 * us)); +static_assert(compare(kind_of(inverse(250 * kHz)), 4 * us)); +static_assert(compare(kind_of(inverse(250 * uHz)), 4 * ks)); // truncating -static_assert(compare(inverse(1 * kHz), 0 * s)); +static_assert(compare(kind_of(inverse(1 * kHz)), 0 * s)); // floating-point representation does not truncate -static_assert(compare(inverse(1. * kHz), 0.001 * s)); +static_assert(compare(kind_of(inverse(1. * kHz)), 0.001 * s)); // check if constraints work properly for a derived unit of a narrowed kind -static_assert(compare(inverse(1 * s), 1 * Hz)); +static_assert(compare(kind_of(inverse(1 * s)), 1 * Hz)); } // namespace diff --git a/test/static/reference_test.cpp b/test/static/reference_test.cpp index 09758aac..fce21a0b 100644 --- a/test/static/reference_test.cpp +++ b/test/static/reference_test.cpp @@ -378,4 +378,14 @@ static_assert(invalid_comparison) static_assert(invalid_comparison); static_assert(invalid_comparison); +// make_reference +static_assert(is_of_type>); +static_assert(is_of_type>); +static_assert(is_of_type, metre), metre_>); +static_assert(is_of_type); +static_assert(is_of_type); +static_assert(is_of_type, hertz), hertz_>); +static_assert(is_of_type); +static_assert(is_of_type, watt), reference, watt_>>); + } // namespace