diff --git a/.flake8 b/.flake8 index 4592939f20..1404045659 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 0000000000..bd14ee0000 --- /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 0000000000..0458a1e9e6 --- /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 015d42296a..ac4d01701e 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 }}" + name: "${{ matrix.config.name }} C++${{ matrix.std }} ${{ matrix.formatting }} ${{ matrix.contracts }} ${{ 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 348ac4e1a1..186660aff9 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -24,162 +24,44 @@ name: Conan CI on: push: - branches: - - '**' paths-ignore: - "docs/**" pull_request: - branches: - - '**' paths-ignore: - "docs/**" 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 }}" + name: "${{ matrix.config.name }} C++${{ matrix.std }} ${{ matrix.formatting }} ${{ matrix.contracts }} ${{ 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 @@ -262,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 086fe625a2..e61d5b8344 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 cde058db6d..ffdf5af547 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 }}" + name: "${{ matrix.config.name }} C++${{ matrix.std }} ${{ matrix.formatting }} ${{ matrix.contracts }} ${{ 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 d5bf5df0af..d58db7083d 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 }}" + name: "${{ matrix.config.name }} C++${{ matrix.std }} ${{ matrix.formatting }} ${{ matrix.contracts }} ${{ 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 e5f22acf74..de8eb51c28 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 c7e0abb58e..e742e8a174 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 3072cd7967..09750e9ddd 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/conanfile.py b/conanfile.py index c340711379..7b9c1853b5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -58,7 +58,6 @@ class MPUnitsConan(ConanFile): "cxx_modules": [True, False], "import_std": [True, False], "std_format": [True, False], - "string_view_ret": [True, False], "no_crtp": [True, False], "contracts": ["none", "gsl-lite", "ms-gsl"], "freestanding": [True, False], @@ -67,7 +66,6 @@ class MPUnitsConan(ConanFile): # "cxx_modules" default set in config_options() # "import_std" default set in config_options() # "std_format" default set in config_options() - # "string_view_ret" default set in config_options() # "no_crtp" default set in config_options() "contracts": "gsl-lite", "freestanding": False, @@ -113,10 +111,6 @@ def _feature_compatibility(self): "min_cppstd": "23", "compiler": {"gcc": "", "clang": "18", "apple-clang": "", "msvc": ""}, }, - "static_constexpr_vars_in_constexpr_func": { - "min_cppstd": "23", - "compiler": {"gcc": "13", "clang": "17", "apple-clang": "", "msvc": ""}, - }, "explicit_this": { "min_cppstd": "23", "compiler": { @@ -134,7 +128,6 @@ def _option_feature_map(self): "std_format": "std_format", "cxx_modules": "cxx_modules", "import_std": "import_std", - "string_view_ret": "static_constexpr_vars_in_constexpr_func", "no_crtp": "explicit_this", } @@ -280,7 +273,6 @@ def generate(self): tc.cache_variables["MP_UNITS_API_FREESTANDING"] = True else: tc.cache_variables["MP_UNITS_API_STD_FORMAT"] = opt.std_format - tc.cache_variables["MP_UNITS_API_STRING_VIEW_RET"] = opt.string_view_ret tc.cache_variables["MP_UNITS_API_NO_CRTP"] = opt.no_crtp tc.cache_variables["MP_UNITS_API_CONTRACTS"] = str(opt.contracts).upper() @@ -340,10 +332,6 @@ def package_info(self): ) # handle API options - self.cpp_info.components["core"].defines.append( - "MP_UNITS_API_STRING_VIEW_RET=" - + str(int(self.options.string_view_ret == True)) - ) self.cpp_info.components["core"].defines.append( "MP_UNITS_API_NO_CRTP=" + str(int(self.options.no_crtp == True)) ) diff --git a/docs/blog/posts/2.4.0-released.md b/docs/blog/posts/2.4.0-released.md index 714379b48c..2e33210c58 100644 --- a/docs/blog/posts/2.4.0-released.md +++ b/docs/blog/posts/2.4.0-released.md @@ -1,5 +1,4 @@ --- -draft: true date: 2024-11-05 authors: - mpusz diff --git a/docs/blog/posts/isq-part-1-introduction.md b/docs/blog/posts/isq-part-1-introduction.md index e567e9e4ad..d49d399b3a 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 f64b094023..911d8a0510 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 a2ed08641b..fab9661ebb 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 @@ -150,8 +151,8 @@ flowchart TD path_length --- distance["distance"] distance --- radial_distance["radial_distance"] length --- wavelength["wavelength"] - length --- position_vector["position_vector
{vector}"] length --- displacement["displacement
{vector}"] + displacement --- position_vector["position_vector"] radius --- radius_of_curvature["radius_of_curvature"] ``` diff --git a/docs/blog/posts/isq-part-4-implemeting-isq.md b/docs/blog/posts/isq-part-4-implemeting-isq.md index aff8f2858c..0eea0078d9 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_ @@ -49,8 +50,8 @@ flowchart TD path_length --- distance["distance"] distance --- radial_distance["radial_distance"] length --- wavelength["wavelength"] - length --- position_vector["position_vector
{vector}"] length --- displacement["displacement
{vector}"] + displacement --- position_vector["position_vector"] radius --- radius_of_curvature["radius_of_curvature"] ``` @@ -74,8 +75,8 @@ inline constexpr auto arc_length = path_length; inline constexpr struct distance final : quantity_spec {} distance; inline constexpr struct radial_distance final : quantity_spec {} radial_distance; inline constexpr struct wavelength final : quantity_spec {} wavelength; -inline constexpr struct position_vector final : quantity_spec {} position_vector; inline constexpr struct displacement final : quantity_spec {} displacement; +inline constexpr struct position_vector final : quantity_spec {} position_vector; ``` Thanks to the expressivity and power of C++ templates, we can specify all quantity properties diff --git a/docs/blog/posts/isq-part-5-benefits.md b/docs/blog/posts/isq-part-5-benefits.md index c9d6343501..136e1e1f4d 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 0000000000..f658813e3d --- /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/getting_started/cpp_compiler_support.md b/docs/getting_started/cpp_compiler_support.md index d5eb5a3480..525585c302 100644 --- a/docs/getting_started/cpp_compiler_support.md +++ b/docs/getting_started/cpp_compiler_support.md @@ -19,7 +19,6 @@ C++ feature: | **`std::format`** | 20 | 13 | 17 | None | 194 | | **C++ modules** | 20 | None | 17 | None | None | | **`import std;`** | 23 | None | 18 | None | None | -| **Static `constexpr` variables in `constexpr` functions** | 23 | 13 | 17 | None | None | | **Explicit `this` parameter** | 23 | 14 | 18 | None | None | ??? note "MSVC bugs" @@ -87,20 +86,6 @@ C++ feature: - CMake: [CMAKE_CXX_MODULE_STD](https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_MODULE_STD.html) -## Static `constexpr` variables in `constexpr` functions - -- Allows returning `std::string_view` from the - [`unit_symbol()`](../users_guide/framework_basics/text_output.md#unit_symbol) - and [`dimension_symbol()`](../users_guide/framework_basics/text_output.md#dimension_symbol) - functions - - `std::string_view` type has a reference semantics so it has to point to a storage with - a longer lifetime. -- If this feature is not available, the API returns `mp_units::basic_fixed_string` instead. -- Tested as `__cpp_constexpr >= 202211L` [feature test macro](https://en.cppreference.com/w/cpp/feature_test). -- Related build options: - - Conan: [string_view_ret](installation_and_usage.md#string_view_ret) - - CMake: [MP_UNITS_API_STRING_VIEW_RET](installation_and_usage.md#MP_UNITS_API_STRING_VIEW_RET) - ## Explicit `this` parameter - This feature removes the need for the usage of the CRTP idiom in the diff --git a/docs/getting_started/installation_and_usage.md b/docs/getting_started/installation_and_usage.md index 79de4a58cc..8fb6115ba8 100644 --- a/docs/getting_started/installation_and_usage.md +++ b/docs/getting_started/installation_and_usage.md @@ -123,18 +123,6 @@ dependencies by other means, some modifications to the library's CMake files mig [conan std::format support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 -[`string_view_ret`](#string_view_ret){ #string_view_ret } - -: [:octicons-tag-24: 2.2.0][conan returning string_view] · :octicons-milestone-24: `True`/`False` (Default: automatically determined from settings) - - Enables returning `std::string_view` from the - [`unit_symbol()`](../users_guide/framework_basics/text_output.md#unit_symbol) - and [`dimension_symbol()`](../users_guide/framework_basics/text_output.md#dimension_symbol) - functions. If this feature is not available, those functions will return - `mp_units::basic_fixed_string` instead. - - [conan returning string_view]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 - [`no_crtp`](#no_crtp){ #no_crtp } : [:octicons-tag-24: 2.2.0][conan no crtp support] · :octicons-milestone-24: `True`/`False` (Default: automatically determined from settings) @@ -196,18 +184,6 @@ dependencies by other means, some modifications to the library's CMake files mig [cmake std::format support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 - [`MP_UNITS_API_STRING_VIEW_RET`](#MP_UNITS_API_STRING_VIEW_RET){ #MP_UNITS_API_STRING_VIEW_RET } - - : [:octicons-tag-24: 2.2.0][cmake returning string_view] · :octicons-milestone-24: `ON`/`OFF` (Default: automatically determined) - - Enables returning `std::string_view` from the - [`unit_symbol()`](../users_guide/framework_basics/text_output.md#unit_symbol) - and [`dimension_symbol()`](../users_guide/framework_basics/text_output.md#dimension_symbol) - functions. If this feature is not available, those functions will return - `mp_units::basic_fixed_string` instead. - - [cmake returning string_view]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 - [`MP_UNITS_API_NO_CRTP`](#MP_UNITS_API_NO_CRTP){ #MP_UNITS_API_NO_CRTP } : [:octicons-tag-24: 2.2.0][cmake no crtp support] · :octicons-milestone-24: `ON`/`OFF` (Default: automatically determined) diff --git a/docs/users_guide/examples/si_constants.md b/docs/users_guide/examples/si_constants.md index f5feb15353..847520f4e3 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/docs/users_guide/framework_basics/character_of_a_quantity.md b/docs/users_guide/framework_basics/character_of_a_quantity.md index 2ce5a16625..769fe55d81 100644 --- a/docs/users_guide/framework_basics/character_of_a_quantity.md +++ b/docs/users_guide/framework_basics/character_of_a_quantity.md @@ -97,22 +97,22 @@ enumeration can be appended to the `quantity_spec` describing such a quantity ty === "C++23" ```cpp - inline constexpr struct position_vector final : quantity_spec {} position_vector; inline constexpr struct displacement final : quantity_spec {} displacement; + inline constexpr struct position_vector final : quantity_spec {} position_vector; ``` === "C++20" ```cpp - inline constexpr struct position_vector final : quantity_spec {} position_vector; inline constexpr struct displacement final : quantity_spec {} displacement; + inline constexpr struct position_vector final : quantity_spec {} position_vector; ``` === "Portable" ```cpp - QUANTITY_SPEC(position_vector, length, quantity_character::vector); QUANTITY_SPEC(displacement, length, quantity_character::vector); + QUANTITY_SPEC(position_vector, displacement); ``` With the above, all the quantities derived from `position_vector` or `displacement` will have a correct @@ -126,19 +126,19 @@ character override is needed): === "C++23" ```cpp - inline constexpr struct velocity final : quantity_spec {} velocity; + inline constexpr struct velocity final : quantity_spec {} velocity; ``` === "C++20" ```cpp - inline constexpr struct velocity final : quantity_spec {} velocity; + inline constexpr struct velocity final : quantity_spec {} velocity; ``` === "Portable" ```cpp - QUANTITY_SPEC(velocity, speed, position_vector / duration); + QUANTITY_SPEC(velocity, speed, displacement / duration); ``` @@ -148,7 +148,7 @@ As we remember, the `quantity` class template is defined as follows: ```cpp template Rep = double> + RepresentationOf Rep = double> class quantity; ``` diff --git a/docs/users_guide/framework_basics/concepts.md b/docs/users_guide/framework_basics/concepts.md index 797e7201a1..c8177d39d1 100644 --- a/docs/users_guide/framework_basics/concepts.md +++ b/docs/users_guide/framework_basics/concepts.md @@ -159,15 +159,28 @@ A `Reference` can either be: [value of a quantity](../../appendix/glossary.md#quantity-value). -### `RepresentationOf` { #RepresentationOf } +### `RepresentationOf` { #RepresentationOf } -`RepresentationOf` concept is satisfied by all `Representation` types that are of a specified -[quantity character](../../appendix/glossary.md#character) `Ch`. +`RepresentationOf` concept is satisfied: + +- if the type of `V` satisfies [`QuantitySpec`](#QuantitySpec): + + - by all [`Representation`](#Representation) types when `V` describes + a [quantity kind](../../appendix/glossary.md#kind), + - otherwise, by [`Representation`](#Representation) types that are of + a [quantity character](../../appendix/glossary.md#character) associated with a provided + quantity specification `V`. + +- if `V` is of `quantity_character` type: + + - by [`Representation`](#Representation) types that are of a provided + [quantity character](../../appendix/glossary.md#character). A user can declare a custom representation type to be of a specific character by providing the specialization with `true` for one or more of the following variable templates: - `is_scalar` +- `is_complex` - `is_vector` - `is_tensor` diff --git a/docs/users_guide/framework_basics/design_overview.md b/docs/users_guide/framework_basics/design_overview.md index 7446bea7fd..3e5de7037e 100644 --- a/docs/users_guide/framework_basics/design_overview.md +++ b/docs/users_guide/framework_basics/design_overview.md @@ -316,7 +316,7 @@ This is why a `quantity` class template is defined in the library as: ```cpp template Rep = double> + RepresentationOf Rep = double> class quantity; ``` @@ -365,7 +365,7 @@ In the **mp-units** library, the quantity point is implemented as: ```cpp template auto PO, - RepresentationOf Rep = double> + RepresentationOf Rep = double> class quantity_point; ``` diff --git a/docs/users_guide/framework_basics/simple_and_typed_quantities.md b/docs/users_guide/framework_basics/simple_and_typed_quantities.md index 178c36e24f..dd7ee228a5 100644 --- a/docs/users_guide/framework_basics/simple_and_typed_quantities.md +++ b/docs/users_guide/framework_basics/simple_and_typed_quantities.md @@ -20,7 +20,7 @@ In the **mp-units** library, a quantity is represented with the following class ```cpp template Rep = double> + RepresentationOf Rep = double> class quantity; ``` diff --git a/docs/users_guide/framework_basics/systems_of_quantities.md b/docs/users_guide/framework_basics/systems_of_quantities.md index 3c24801a8b..f5df4d8204 100644 --- a/docs/users_guide/framework_basics/systems_of_quantities.md +++ b/docs/users_guide/framework_basics/systems_of_quantities.md @@ -109,8 +109,8 @@ flowchart TD path_length --- distance["distance"] distance --- radial_distance["radial_distance"] length --- wavelength["wavelength"] - length --- position_vector["position_vector
{vector}"] length --- displacement["displacement
{vector}"] + displacement --- position_vector["position_vector"] radius --- radius_of_curvature["radius_of_curvature"] ``` @@ -164,8 +164,8 @@ For example, here is how the above quantity kind tree can be modeled in the libr inline constexpr struct distance final : quantity_spec {} distance; inline constexpr struct radial_distance final : quantity_spec {} radial_distance; inline constexpr struct wavelength final : quantity_spec {} wavelength; - inline constexpr struct position_vector final : quantity_spec {} position_vector; inline constexpr struct displacement final : quantity_spec {} displacement; + inline constexpr struct position_vector final : quantity_spec {} position_vector; ``` === "C++20" @@ -186,8 +186,8 @@ For example, here is how the above quantity kind tree can be modeled in the libr inline constexpr struct distance final : quantity_spec {} distance; inline constexpr struct radial_distance final : quantity_spec {} radial_distance; inline constexpr struct wavelength final : quantity_spec {} wavelength; - inline constexpr struct position_vector final : quantity_spec {} position_vector; inline constexpr struct displacement final : quantity_spec {} displacement; + inline constexpr struct position_vector final : quantity_spec {} position_vector; ``` === "Portable" @@ -208,8 +208,8 @@ For example, here is how the above quantity kind tree can be modeled in the libr QUANTITY_SPEC(distance, path_length); QUANTITY_SPEC(radial_distance, distance); QUANTITY_SPEC(wavelength, length); - QUANTITY_SPEC(position_vector, length, quantity_character::vector); QUANTITY_SPEC(displacement, length, quantity_character::vector); + QUANTITY_SPEC(position_vector, displacement); ``` !!! note diff --git a/docs/users_guide/framework_basics/the_affine_space.md b/docs/users_guide/framework_basics/the_affine_space.md index 688c4379e3..50a297722e 100644 --- a/docs/users_guide/framework_basics/the_affine_space.md +++ b/docs/users_guide/framework_basics/the_affine_space.md @@ -96,7 +96,7 @@ origin: ```cpp template auto PO = default_point_origin(R), - RepresentationOf Rep = double> + RepresentationOf Rep = double> class quantity_point; ``` diff --git a/example/currency.cpp b/example/currency.cpp index 43aa28fd6d..bedf7c05ea 100644 --- a/example/currency.cpp +++ b/example/currency.cpp @@ -62,21 +62,6 @@ inline constexpr auto JPY = japanese_jen; static_assert(!std::equality_comparable_with, quantity>); - -#if 0 || !MP_UNITS_API_STRING_VIEW_RET // NOLINT(readability-avoid-unconditional-preprocessor-if) - -// if you have only a few currencies to handle -template -[[nodiscard]] double exchange_rate(std::chrono::sys_seconds timestamp) -{ - (void)timestamp; // get conversion ratios for this timestamp - if constexpr (From == us_dollar && To == euro) return 0.9215; - else if constexpr (From == euro && To == us_dollar) return 1.0848; - // ... -} - -#else - template [[nodiscard]] double exchange_rate(std::chrono::sys_seconds timestamp) { @@ -89,8 +74,6 @@ template return rates.at(std::make_pair(unit_symbol(From), unit_symbol(To))); } -#endif - template auto To, QuantityOf From> QuantityOf auto exchange_to(From q, std::chrono::sys_seconds timestamp) { diff --git a/example/include/validated_type.h b/example/include/validated_type.h index c8b5dc6a71..70be6dac3d 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/kalman_filter/kalman_filter-example_2.cpp b/example/kalman_filter/kalman_filter-example_2.cpp index b03f872483..5a07edebd5 100644 --- a/example/kalman_filter/kalman_filter-example_2.cpp +++ b/example/kalman_filter/kalman_filter-example_2.cpp @@ -61,7 +61,7 @@ void print(auto iteration, QuantityPoint auto measured, const kalman::SystemStat int main() { using namespace mp_units::si::unit_symbols; - using qp = quantity_point; + using qp = quantity_point; using state = kalman::system_state>; const quantity interval = isq::duration(5 * s); diff --git a/example/kalman_filter/kalman_filter-example_3.cpp b/example/kalman_filter/kalman_filter-example_3.cpp index 9c2ca7d4a9..65aa2b1f21 100644 --- a/example/kalman_filter/kalman_filter-example_3.cpp +++ b/example/kalman_filter/kalman_filter-example_3.cpp @@ -61,7 +61,7 @@ void print(auto iteration, QuantityPoint auto measured, const kalman::SystemStat int main() { using namespace mp_units::si::unit_symbols; - using qp = quantity_point; + using qp = quantity_point; using state = kalman::system_state>; const quantity interval = isq::duration(5 * s); diff --git a/example/kalman_filter/kalman_filter-example_4.cpp b/example/kalman_filter/kalman_filter-example_4.cpp index 20813ad829..64e240e1f5 100644 --- a/example/kalman_filter/kalman_filter-example_4.cpp +++ b/example/kalman_filter/kalman_filter-example_4.cpp @@ -62,7 +62,7 @@ void print(auto iteration, QuantityPoint auto measured, const kalman::SystemStat int main() { using namespace mp_units::si::unit_symbols; - using qp = quantity_point; + using qp = quantity_point; using state = kalman::system_state, quantity_point>; diff --git a/example/si_constants.cpp b/example/si_constants.cpp index e09588c4b1..a43779abae 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/CMakeLists.txt b/src/CMakeLists.txt index 8b19f595b4..ca5b7ed1f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,7 +48,6 @@ endif() # check for C++ features check_cxx_feature_supported(__cpp_lib_format ${projectPrefix}LIB_FORMAT_SUPPORTED) -check_cxx_feature_supported("__cpp_constexpr >= 202211L" ${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS) check_cxx_feature_supported(__cpp_explicit_this_parameter ${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED) # libc++ has a basic supports for std::format but does not set __cpp_lib_format @@ -73,9 +72,6 @@ endif() # project API settings option(${projectPrefix}API_STD_FORMAT "Enable `std::format` support" ${${projectPrefix}LIB_FORMAT_SUPPORTED}) -option(${projectPrefix}API_STRING_VIEW_RET "Enable returning `std::string_view` from `constexpr` functions" - ${${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS} -) option(${projectPrefix}API_NO_CRTP "Enable class definitions without CRTP idiom" ${${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED} ) @@ -83,7 +79,6 @@ option(${projectPrefix}API_FREESTANDING "Builds only freestanding part of the li set(${projectPrefix}API_CONTRACTS GSL-LITE CACHE STRING "Enable contract checking") message(STATUS "${projectPrefix}API_STD_FORMAT: ${${projectPrefix}API_STD_FORMAT}") -message(STATUS "${projectPrefix}API_STRING_VIEW_RET: ${${projectPrefix}API_STRING_VIEW_RET}") message(STATUS "${projectPrefix}API_NO_CRTP: ${${projectPrefix}API_NO_CRTP}") message(STATUS "${projectPrefix}API_FREESTANDING: ${${projectPrefix}API_FREESTANDING}") @@ -107,10 +102,6 @@ if(NOT ${projectPrefix}API_FREESTANDING AND ${projectPrefix}API_STD_FORMAT AND N message(FATAL_ERROR "`std::format` enabled but not supported") endif() -if(${projectPrefix}API_STRING_VIEW_RET AND NOT ${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS) - message(FATAL_ERROR "`std::string_view` returns enabled but not supported") -endif() - if(${projectPrefix}API_NO_CRTP AND NOT ${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED) message(FATAL_ERROR "`NO_CRTP` mode enabled but explicit `this` parameter is not supported") endif() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fa6c7d9545..1f3dc449d7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -75,6 +75,7 @@ add_mp_units_module( include/mp-units/framework/value_cast.h include/mp-units/compat_macros.h include/mp-units/concepts.h + include/mp-units/core.h include/mp-units/framework.h MODULE_INTERFACE_UNIT mp-units-core.cpp ) @@ -90,15 +91,16 @@ 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 include/mp-units/ostream.h - include/mp-units/format.h include/mp-units/random.h ) endif() set_feature_flag(API_STD_FORMAT) -set_feature_flag(API_STRING_VIEW_RET) set_feature_flag(API_NO_CRTP) # Text formatting diff --git a/src/core/include/mp-units/bits/core_gmf.h b/src/core/include/mp-units/bits/core_gmf.h index 5566bc923f..d3a44fcbe5 100644 --- a/src/core/include/mp-units/bits/core_gmf.h +++ b/src/core/include/mp-units/bits/core_gmf.h @@ -57,6 +57,7 @@ #ifndef MP_UNITS_IMPORT_STD #include #include +#include #include #include #include diff --git a/src/core/include/mp-units/bits/get_associated_quantity.h b/src/core/include/mp-units/bits/get_associated_quantity.h index 56385f8be6..6ec1601064 100644 --- a/src/core/include/mp-units/bits/get_associated_quantity.h +++ b/src/core/include/mp-units/bits/get_associated_quantity.h @@ -52,7 +52,7 @@ template [[nodiscard]] consteval auto all_are_kinds(U) { if constexpr (requires { U::_quantity_spec_; }) - return QuantityKindSpec>; + return QuantityKindSpec; else if constexpr (requires { U::_reference_unit_; }) return all_are_kinds(U::_reference_unit_); else if constexpr (requires { typename U::_num_; }) { diff --git a/src/core/include/mp-units/bits/hacks.h b/src/core/include/mp-units/bits/hacks.h index 87d7c47db4..70ee2c1417 100644 --- a/src/core/include/mp-units/bits/hacks.h +++ b/src/core/include/mp-units/bits/hacks.h @@ -81,6 +81,12 @@ // workarounds for https://cplusplus.github.io/CWG/issues/2387.html #define MP_UNITS_INLINE inline +#if __cpp_auto_cast >= 202110L && MP_UNITS_COMP_GCC != 12 +#define MP_UNITS_NONCONST_TYPE(expr) decltype(auto(expr)) +#else +#define MP_UNITS_NONCONST_TYPE(expr) std::remove_const_t +#endif + #if MP_UNITS_COMP_GCC #define MP_UNITS_REMOVE_CONST(expr) std::remove_const_t @@ -140,12 +146,6 @@ MP_UNITS_DIAGNOSTIC_POP #endif -#if !defined MP_UNITS_API_STRING_VIEW_RET && __cpp_constexpr >= 202211L - -#define MP_UNITS_API_STRING_VIEW_RET 1 - -#endif - #if !defined MP_UNITS_API_NO_CRTP && __cpp_explicit_this_parameter #define MP_UNITS_API_NO_CRTP 1 diff --git a/src/core/include/mp-units/bits/sudo_cast.h b/src/core/include/mp-units/bits/sudo_cast.h index e3e94f9255..1264cc0aef 100644 --- a/src/core/include/mp-units/bits/sudo_cast.h +++ b/src/core/include/mp-units/bits/sudo_cast.h @@ -154,8 +154,7 @@ template, - std::remove_const_t>) { + if constexpr (is_same_v) { return quantity_point{ sudo_cast(std::forward(qp).quantity_from(FromQP::point_origin)), FromQP::point_origin}; 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 0000000000..af5e7e3662 --- /dev/null +++ b/src/core/include/mp-units/cartesian_vector.h @@ -0,0 +1,282 @@ +// 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(const cartesian_vector&) = default; + cartesian_vector(cartesian_vector&&) = default; + cartesian_vector& operator=(const cartesian_vector&) = default; + cartesian_vector& operator=(cartesian_vector&&) = default; + + template + requires(... && std::constructible_from) + constexpr explicit(!(... && std::convertible_to)) cartesian_vector(Args&&... args) : + _coordinates_{static_cast(std::forward(args))...} + { + } + + template + requires std::constructible_from + constexpr explicit(!std::convertible_to) cartesian_vector(const cartesian_vector& other) : + _coordinates_{static_cast(other[0]), static_cast(other[1]), static_cast(other[2])} + { + } + + template + requires std::constructible_from + constexpr explicit(!std::convertible_to) cartesian_vector(cartesian_vector&& other) : + _coordinates_{static_cast(std::move(other[0])), static_cast(std::move(other[1])), + static_cast(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 + requires requires(T t, U u) { + { t += u } -> std::same_as; + } + constexpr cartesian_vector& operator+=(const cartesian_vector& other) + { + _coordinates_[0] += other[0]; + _coordinates_[1] += other[1]; + _coordinates_[2] += other[2]; + return *this; + } + + template + requires requires(T t, U u) { + { t -= u } -> std::same_as; + } + constexpr cartesian_vector& operator-=(const cartesian_vector& other) + { + _coordinates_[0] -= other[0]; + _coordinates_[1] -= other[1]; + _coordinates_[2] -= other[2]; + return *this; + } + + template + requires requires(T t, U u) { + { t *= u } -> std::same_as; + } + constexpr cartesian_vector& operator*=(const U& value) + { + _coordinates_[0] *= value; + _coordinates_[1] *= value; + _coordinates_[2] *= value; + return *this; + } + + template + requires requires(T t, U u) { + { t /= u } -> std::same_as; + } + constexpr cartesian_vector& operator/=(const U& value) + { + _coordinates_[0] /= value; + _coordinates_[1] /= value; + _coordinates_[2] /= value; + return *this; + } + + 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/complex.h b/src/core/include/mp-units/complex.h new file mode 100644 index 0000000000..9f09c05f75 --- /dev/null +++ b/src/core/include/mp-units/complex.h @@ -0,0 +1,43 @@ +// 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 + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif +#endif + +namespace mp_units { + +template +constexpr bool is_complex> = true; + +} diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index 09ba3ac379..702f3b6b23 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -28,6 +28,8 @@ #include #if MP_UNITS_HOSTED +#include +#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 1e93caf4d2..8074b09b5e 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 @@ class inplace_vector { 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/ext/prime.h b/src/core/include/mp-units/ext/prime.h index 5b372fe958..a5a8f10687 100644 --- a/src/core/include/mp-units/ext/prime.h +++ b/src/core/include/mp-units/ext/prime.h @@ -28,6 +28,7 @@ #include #ifndef MP_UNITS_IN_MODULE_INTERFACE +#include #ifdef MP_UNITS_IMPORT_STD import std; #else @@ -37,76 +38,382 @@ import std; #include #include #include +#include #endif #endif namespace mp_units::detail { -[[nodiscard]] consteval bool is_prime_by_trial_division(std::uintmax_t n) +// (a + b) % n. +// +// Precondition: (a < n). +// Precondition: (b < n). +// Precondition: (n > 0). +[[nodiscard]] consteval std::uint64_t add_mod(std::uint64_t a, std::uint64_t b, std::uint64_t n) { - for (std::uintmax_t f = 2; f * f <= n; f += 1 + (f % 2)) { - if (n % f == 0) { - return false; + MP_UNITS_EXPECTS_DEBUG(a < n); + MP_UNITS_EXPECTS_DEBUG(b < n); + MP_UNITS_EXPECTS_DEBUG(n > 0u); + + if (a >= n - b) { + return a - (n - b); + } else { + return a + b; + } +} + +// (a - b) % n. +// +// Precondition: (a < n). +// Precondition: (b < n). +// Precondition: (n > 0). +[[nodiscard]] consteval std::uint64_t sub_mod(std::uint64_t a, std::uint64_t b, std::uint64_t n) +{ + MP_UNITS_EXPECTS_DEBUG(a < n); + MP_UNITS_EXPECTS_DEBUG(b < n); + MP_UNITS_EXPECTS_DEBUG(n > 0u); + + if (a >= b) { + return a - b; + } else { + return n - (b - a); + } +} + +// (a * b) % n. +// +// Precondition: (a < n). +// Precondition: (b < n). +// Precondition: (n > 0). +[[nodiscard]] consteval std::uint64_t mul_mod(std::uint64_t a, std::uint64_t b, std::uint64_t n) +{ + MP_UNITS_EXPECTS_DEBUG(a < n); + MP_UNITS_EXPECTS_DEBUG(b < n); + MP_UNITS_EXPECTS_DEBUG(n > 0u); + + if (b == 0u || a < std::numeric_limits::max() / b) { + return (a * b) % n; + } + + const std::uint64_t batch_size = n / a; + const std::uint64_t num_batches = b / batch_size; + + return add_mod( + // Transform into "negative space" to make the first parameter as small as possible; + // then, transform back. + (n - mul_mod(n % a, num_batches, n)) % n, + + // Handle the leftover product (which is guaranteed to fit in the integer type). + (a * (b % batch_size)) % n, + + n); +} + +// (a / 2) % n. +// +// Precondition: (a < n). +// Precondition: (n % 2 == 1). +[[nodiscard]] consteval std::uint64_t half_mod_odd(std::uint64_t a, std::uint64_t n) +{ + MP_UNITS_EXPECTS_DEBUG(a < n); + MP_UNITS_EXPECTS_DEBUG(n % 2 == 1); + + return (a / 2u) + ((a % 2u == 0u) ? 0u : (n / 2u + 1u)); +} + +// (base ^ exp) % n. +[[nodiscard]] consteval std::uint64_t pow_mod(std::uint64_t base, std::uint64_t exp, std::uint64_t n) +{ + std::uint64_t result = 1u; + base %= n; + + while (exp > 0u) { + if (exp % 2u == 1u) { + result = mul_mod(result, base, n); } + + exp /= 2u; + base = mul_mod(base, base, n); } - return true; + + return result; } -// Return the first factor of n, as long as it is either k or n. +// Decompose a positive integer by factoring out all powers of 2. +struct NumberDecomposition { + std::uint64_t power_of_two; + std::uint64_t odd_remainder; +}; + +// Express any positive `n` as `(2^s * d)`, where `d` is odd. +[[nodiscard]] consteval NumberDecomposition decompose(std::uint64_t n) +{ + NumberDecomposition result{0u, n}; + while (result.odd_remainder % 2u == 0u) { + result.odd_remainder /= 2u; + ++result.power_of_two; + } + return result; +} + +// Perform a Miller-Rabin primality test on `n` using base `a`. // -// Precondition: no integer smaller than k evenly divides n. -[[nodiscard]] constexpr std::optional first_factor_maybe(std::uintmax_t n, std::uintmax_t k) +// Precondition: (a >= 2). +// Precondition: (n >= a + 2). +// Precondition: (n % 2 == 1). +[[nodiscard]] consteval bool miller_rabin_probable_prime(std::uint64_t a, std::uint64_t n) { - if (n % k == 0) { - return k; + MP_UNITS_EXPECTS_DEBUG(a >= 2u); + MP_UNITS_EXPECTS_DEBUG(n >= a + 2u); + MP_UNITS_EXPECTS_DEBUG(n % 2u == 1u); + + const auto [s, d] = decompose(n - 1u); + auto x = pow_mod(a, d, n); + if (x == 1u) { + return true; } - if (k * k > n) { - return n; + + const auto minus_one = n - 1u; + for (auto r = 0u; r < s; ++r) { + if (x == minus_one) { + return true; + } + x = mul_mod(x, x, n); } - return std::nullopt; + + return false; } -template -[[nodiscard]] consteval std::array first_n_primes() +// The Jacobi symbol, notated as `(a/n)`, is defined for odd positive `n` and any integer `a`, taking values +// in the set `{-1, 0, 1}`. Besides being a completely multiplicative function (so that, for example, both +// (a*b/n) = (a/n) * (b/n), and (a/n*m) = (a/n) * (a/m)), it obeys the following symmetry rules, which enable +// its calculation: +// +// 1. (a/1) = 1, and (1/n) = 1, for all a and n. +// +// 2. (a/n) = 0 whenever a and n have a nontrivial common factor. +// +// 3. (a/n) = (b/n) whenever (a % n) = (b % n). +// +// 4. (2a/n) = (a/n) if n % 8 = 1 or 7, and -(a/n) if n % 8 = 3 or 5. +// +// 5. (a/n) = (n/a) * x if a and n are both odd, positive, and coprime. Here, x is 1 if either (a % 4) = 1 +// or (n % 4) = 1, and -1 otherwise. +// +// 6. (-1/n) = 1 if n % 4 = 1, and -1 if n % 4 = 3. +[[nodiscard]] consteval int jacobi_symbol(std::int64_t raw_a, std::uint64_t n) { - std::array primes{}; - primes[0] = 2; - for (std::size_t i = 1; i < N; ++i) { - primes[i] = primes[i - 1] + 1; - while (!is_prime_by_trial_division(primes[i])) { - ++primes[i]; + // Rule 1: n=1 case. + if (n == 1u) { + return 1; + } + + // Starting conditions: transform `a` to strictly non-negative values, setting `result` to the sign that we + // pick up (if any) from following these rules (i.e., rules 3 and 6). + int result = ((raw_a >= 0) || (n % 4u == 1u)) ? 1 : -1; + auto a = static_cast(raw_a < 0 ? -raw_a : raw_a) % n; + + while (a != 0u) { + // Rule 4. + const int sign_for_even = (n % 8u == 1u || n % 8u == 7u) ? 1 : -1; + while (a % 2u == 0u) { + a /= 2u; + result *= sign_for_even; + } + + // Rule 1: a=1 case. + if (a == 1u) { + return result; + } + + // Rule 2. + if (std::gcd(a, n) != 1u) { + return 0; } + + // Note that at this point, we know that `a` and `n` are coprime, and are both odd and positive. + // Therefore, we meet the preconditions for rule 5 (the "flip-and-reduce" rule). + result *= (n % 4u == 1u || a % 4u == 1u) ? 1 : -1; + const std::uint64_t new_a = n % a; + n = a; + a = new_a; } - return primes; + + return 0; } -template -consteval void call_for_coprimes_up_to(std::uintmax_t n, const std::array& basis, Callable call) +[[nodiscard]] consteval bool is_perfect_square(std::uint64_t n) { - for (std::uintmax_t i = 0u; i < n; ++i) { - if (std::apply([&i](auto... primes) { return ((i % primes != 0) && ...); }, basis)) { - call(i); + if (n < 2u) { + return true; + } + + std::uint64_t prev = n / 2u; + while (true) { + const std::uint64_t curr = (prev + n / prev) / 2u; + if (curr * curr == n) { + return true; } + if (curr >= prev) { + return false; + } + prev = curr; } } -template -[[nodiscard]] consteval std::size_t num_coprimes_up_to(std::uintmax_t n, const std::array& basis) +// The "D" parameter in the Strong Lucas Probable Prime test. +// +// Default construction produces the first value to try, according to Selfridge's parameter selection. +// Calling `successor()` on this repeatedly will produce the sequence of values to try. +struct LucasDParameter { + std::uint64_t mag = 5u; + bool pos = true; + + friend constexpr int as_int(LucasDParameter p) + { + int D = static_cast(p.mag); + return p.pos ? D : -D; + } + friend constexpr LucasDParameter successor(LucasDParameter p) { return {.mag = p.mag + 2u, .pos = !p.pos}; } +}; + +// The first `D` in the infinite sequence {5, -7, 9, -11, ...} whose Jacobi symbol is -1. This is the D we +// want to use for the Strong Lucas Probable Prime test. +// +// Precondition: `n` is not a perfect square. +[[nodiscard]] consteval LucasDParameter find_first_D_with_jacobi_symbol_neg_one(std::uint64_t n) +{ + LucasDParameter D{}; + while (jacobi_symbol(as_int(D), n) != -1) { + D = successor(D); + } + return D; +} + +// An element of the Lucas sequence, with parameters tuned for the Strong Lucas test. +// +// The default values give the first element (i.e., k=1) of the sequence. +struct LucasSequenceElement { + std::uint64_t u = 1u; + std::uint64_t v = 1u; +}; + +// Produce the Lucas element whose index is twice the input element's index. +[[nodiscard]] consteval LucasSequenceElement double_strong_lucas_index(const LucasSequenceElement& element, + std::uint64_t n, LucasDParameter D) +{ + const auto& [u, v] = element; + + std::uint64_t v_squared = mul_mod(v, v, n); + std::uint64_t D_u_squared = mul_mod(D.mag % n, mul_mod(u, u, n), n); + std::uint64_t new_v = D.pos ? add_mod(v_squared, D_u_squared, n) : sub_mod(v_squared, D_u_squared, n); + new_v = half_mod_odd(new_v, n); + + return {.u = mul_mod(u, v, n), .v = new_v}; +} + +[[nodiscard]] consteval LucasSequenceElement increment_strong_lucas_index(const LucasSequenceElement& element, + std::uint64_t n, LucasDParameter D) +{ + const auto& [u, v] = element; + + const auto new_u = half_mod_odd(add_mod(u, v, n), n); + + const auto D_u = mul_mod(D.mag % n, u, n); + auto new_v = D.pos ? add_mod(v, D_u, n) : sub_mod(v, D_u, n); + new_v = half_mod_odd(new_v, n); + + return {.u = new_u, .v = new_v}; +} + +[[nodiscard]] consteval LucasSequenceElement find_strong_lucas_element(std::uint64_t i, std::uint64_t n, + LucasDParameter D) { - std::size_t count = 0u; - call_for_coprimes_up_to(n, basis, [&count](auto) { ++count; }); - return count; + LucasSequenceElement element{}; + + bool bits[64] = {}; + std::size_t n_bits = 0u; + while (i > 1u) { + bits[n_bits++] = (i & 1u); + i >>= 1u; + } + + for (auto j = n_bits; j > 0u; --j) { + element = double_strong_lucas_index(element, n, D); + if (bits[j - 1u]) { + element = increment_strong_lucas_index(element, n, D); + } + } + + return element; } -template -[[nodiscard]] consteval auto coprimes_up_to(std::size_t n, const std::array& basis) +// Perform a Strong Lucas Probable Prime test on `n`. +// +// Precondition: (n >= 2). +// Precondition: (n is odd). +[[nodiscard]] consteval bool strong_lucas_probable_prime(std::uint64_t n) { - std::array coprimes{}; - std::size_t i = 0u; + MP_UNITS_EXPECTS_DEBUG(n >= 2u); + MP_UNITS_EXPECTS_DEBUG(n % 2u == 1u); + + if (is_perfect_square(n)) { + return false; + } + + const auto D = find_first_D_with_jacobi_symbol_neg_one(n); - call_for_coprimes_up_to(n, basis, [&coprimes, &i](std::uintmax_t cp) { coprimes[i++] = cp; }); + const auto [s, d] = decompose(n + 1u); - return coprimes; + auto element = find_strong_lucas_element(d, n, D); + if (element.u == 0u) { + return true; + } + + for (auto i = 0u; i < s; ++i) { + if (element.v == 0u) { + return true; + } + element = double_strong_lucas_index(element, n, D); + } + + return false; +} + +// The Baillie-PSW test is technically a "probable prime" test. However, it is known to be correct for all +// 64-bit integers, and no counterexample of any size has ever been found. Thus, for this library's purposes, +// we can treat it as deterministic... probably. +[[nodiscard]] consteval bool baillie_psw_probable_prime(std::uint64_t n) +{ + if (n < 2u) { + return false; + } + if (n < 4u) { + return true; + } + if (n % 2u == 0u) { + return false; + } + + if (!miller_rabin_probable_prime(2u, n)) { + return false; + } + + return strong_lucas_probable_prime(n); +} + +template +[[nodiscard]] consteval std::array first_n_primes() +{ + std::array primes{}; + primes[0] = 2; + for (std::size_t i = 1; i < N; ++i) { + primes[i] = primes[i - 1] + 1; + while (!baillie_psw_probable_prime(primes[i])) { + ++primes[i]; + } + } + return primes; } template @@ -135,48 +442,45 @@ constexpr auto get_first_of(const Rng& rng, UnaryFunction f) return get_first_of(begin(rng), end(rng), f); } -// A configurable instantiation of the "wheel factorization" algorithm [1] for prime numbers. -// -// Instantiate with N to use a "basis" of the first N prime numbers. Higher values of N use fewer trial divisions, at -// the cost of additional space. The amount of space consumed is roughly the total number of numbers that are a) less -// than the _product_ of the basis elements (first N primes), and b) coprime with every element of the basis. This -// means it grows rapidly with N. Consider this approximate chart: -// -// N | Num coprimes | Trial divisions needed -// --+--------------+----------------------- -// 1 | 1 | 50.0 % -// 2 | 2 | 33.3 % -// 3 | 8 | 26.7 % -// 4 | 48 | 22.9 % -// 5 | 480 | 20.8 % -// -// Note the diminishing returns, and the rapidly escalating costs. Consider this behaviour when choosing the value of N -// most appropriate for your needs. -// -// [1] https://en.wikipedia.org/wiki/Wheel_factorization -template -struct wheel_factorizer { - static constexpr auto basis = first_n_primes(); - static constexpr auto wheel_size = product(basis); - static constexpr auto coprimes_in_first_wheel = - coprimes_up_to(wheel_size, basis); +#if __cpp_constexpr < 202211L +template +struct first_n_primes_impl { + static constexpr auto value = first_n_primes(); +}; +#endif - [[nodiscard]] static consteval std::uintmax_t find_first_factor(std::uintmax_t n) - { - if (const auto k = detail::get_first_of(basis, [&](auto p) { return first_factor_maybe(n, p); })) return *k; +[[nodiscard]] consteval std::uintmax_t find_first_factor(std::uintmax_t n) +{ +#if __cpp_constexpr >= 202211L + static constexpr auto first_100_primes = first_n_primes<100>(); +#else + constexpr auto first_100_primes = first_n_primes_impl<100>::value; +#endif - if (const auto k = detail::get_first_of(std::next(begin(coprimes_in_first_wheel)), end(coprimes_in_first_wheel), - [&](auto p) { return first_factor_maybe(n, p); })) - return *k; + for (const auto& p : first_100_primes) { + if (n % p == 0u) { + return p; + } + if (p * p > n) { + return n; + } + } - for (std::size_t wheel = wheel_size; wheel < n; wheel += wheel_size) - if (const auto k = - detail::get_first_of(coprimes_in_first_wheel, [&](auto p) { return first_factor_maybe(n, wheel + p); })) - return *k; + // If we got this far, and we haven't found a factor, and haven't terminated... maybe it's prime? Do a fast check. + if (baillie_psw_probable_prime(n)) { return n; } - [[nodiscard]] static consteval bool is_prime(std::size_t n) { return (n > 1) && find_first_factor(n) == n; } -}; + // If we're here, we know `n` is composite, so continue with trial division for all odd numbers. + std::uintmax_t factor = first_100_primes.back() + 2u; + while (factor * factor <= n) { + if (n % factor == 0u) { + return factor; + } + factor += 2u; + } + + return n; // Technically unreachable. +} } // namespace mp_units::detail diff --git a/src/core/include/mp-units/ext/type_traits.h b/src/core/include/mp-units/ext/type_traits.h index 70b9523c74..119a27c555 100644 --- a/src/core/include/mp-units/ext/type_traits.h +++ b/src/core/include/mp-units/ext/type_traits.h @@ -193,11 +193,4 @@ template typename T, typename T1, typename T2, typename... Ts> return get(); } -namespace detail { - -template -concept SymbolicConstant = std::is_empty_v && std::is_final_v; - -} - } // namespace mp_units diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index fe19a07e51..a99a310157 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -291,8 +291,8 @@ class MP_UNITS_STD_FMT::formatter, Char> { static constexpr auto dimension = get_quantity_spec(Reference).dimension; using quantity_t = mp_units::quantity; - using unit_t = std::remove_const_t; - using dimension_t = std::remove_const_t; + using unit_t = MP_UNITS_NONCONST_TYPE(unit); + using dimension_t = MP_UNITS_NONCONST_TYPE(dimension); using format_specs = mp_units::detail::fill_align_width_format_specs; format_specs specs_{}; diff --git a/src/core/include/mp-units/framework/construction_helpers.h b/src/core/include/mp-units/framework/construction_helpers.h index 43eade949a..36ffac6eb6 100644 --- a/src/core/include/mp-units/framework/construction_helpers.h +++ b/src/core/include/mp-units/framework/construction_helpers.h @@ -39,7 +39,7 @@ namespace mp_units { template struct delta_ { - template Rep = std::remove_cvref_t> + template Rep = std::remove_cvref_t> [[nodiscard]] constexpr quantity operator()(FwdRep&& lhs) const { return quantity{std::forward(lhs), R{}}; @@ -48,7 +48,7 @@ struct delta_ { template struct absolute_ { - template Rep = std::remove_cvref_t> + template Rep = std::remove_cvref_t> [[nodiscard]] constexpr quantity_point operator()(FwdRep&& lhs) const { diff --git a/src/core/include/mp-units/framework/dimension.h b/src/core/include/mp-units/framework/dimension.h index b39e38d9aa..73223ccbfd 100644 --- a/src/core/include/mp-units/framework/dimension.h +++ b/src/core/include/mp-units/framework/dimension.h @@ -52,7 +52,7 @@ import std; namespace mp_units { -template +template struct derived_dimension; MP_UNITS_EXPORT struct dimension_one; @@ -165,7 +165,7 @@ struct base_dimension : detail::dimension_interface { * @note User should not instantiate this type! It is not exported from the C++ module. The library will * instantiate this type automatically based on the dimensional arithmetic equation provided by the user. */ -template +template struct derived_dimension final : detail::dimension_interface, detail::derived_dimension_impl {}; /** @@ -286,14 +286,10 @@ constexpr Out dimension_symbol_to(Out out, D d, const dimension_symbol_formattin return detail::dimension_symbol_impl(out, d, fmt, false); } -// TODO Refactor to `dimension_symbol(D, fmt)` when P1045: constexpr Function Parameters is available -MP_UNITS_EXPORT template -#if defined MP_UNITS_COMP_CLANG && MP_UNITS_COMP_CLANG <= 18 -[[nodiscard]] constexpr auto dimension_symbol(D) -#else -[[nodiscard]] consteval auto dimension_symbol(D) -#endif +namespace detail { + +MP_UNITS_EXPORT template +[[nodiscard]] consteval auto dimension_symbol_impl(D) { constexpr auto oversized_symbol_text = []() consteval { // std::basic_string text; // TODO uncomment when https://wg21.link/P3032 is supported @@ -301,14 +297,22 @@ MP_UNITS_EXPORT template(std::back_inserter(text), D{}, fmt); return text; }(); - -#if MP_UNITS_API_STRING_VIEW_RET // Permitting static constexpr variables in constexpr functions - static constexpr basic_fixed_string storage(std::from_range, - oversized_symbol_text); - return storage.view(); -#else return basic_fixed_string(std::from_range, oversized_symbol_text); -#endif +} + +template +struct dimension_symbol_result { + static constexpr auto value = dimension_symbol_impl(D{}); +}; + +} // namespace detail + +// TODO Refactor to `dimension_symbol(D, fmt)` when P1045: constexpr Function Parameters is available +MP_UNITS_EXPORT template +[[nodiscard]] consteval std::string_view dimension_symbol(D) +{ + return detail::dimension_symbol_result::value.view(); } } // namespace mp_units diff --git a/src/core/include/mp-units/framework/expression_template.h b/src/core/include/mp-units/framework/expression_template.h index 17e40512d0..38dd14d3f0 100644 --- a/src/core/include/mp-units/framework/expression_template.h +++ b/src/core/include/mp-units/framework/expression_template.h @@ -31,28 +31,43 @@ #ifdef MP_UNITS_IMPORT_STD import std; #else +#include #include #include +#include #endif #endif namespace mp_units { +namespace detail { + +// `SymbolicArg` is provided because `SymbolicConstant` requires a complete type which is not the case +// for `OneType` below. +template +concept SymbolicArg = (!std::is_const_v) && (!std::is_reference_v); + +template +concept SymbolicConstant = SymbolicArg && std::is_empty_v && std::is_final_v && std::is_trivial_v && + std::is_trivially_copy_constructible_v && std::is_trivially_move_constructible_v; + /** * @brief Type list type used by the expression template framework * * @tparam Ts The list of types */ -template +template struct type_list {}; +} // namespace detail + /** * @brief Type list type storing the list of components with negative exponents * * @note Can't be empty */ -template -struct per {}; +template +struct per final {}; namespace detail { @@ -98,9 +113,9 @@ constexpr bool ratio_one = true; * * @note @p Den is an optional parameter to shorten the types presented to the user in the case when @p Den equals `1`. */ -template +template requires(detail::valid_ratio && detail::positive_ratio && !detail::ratio_one) -struct power { +struct power final { using factor = F; static constexpr detail::ratio exponent{Num, Den...}; }; @@ -126,7 +141,7 @@ constexpr bool is_specialization_of_power = false; template constexpr bool is_specialization_of_power> = true; -template +template consteval auto power_or_T_impl() { if constexpr (is_specialization_of_power) { @@ -143,7 +158,7 @@ consteval auto power_or_T_impl() } } -template +template // template // TODO ICE gcc 12 using power_or_T = decltype(power_or_T_impl()); @@ -294,7 +309,7 @@ struct expr_fractions_result { using _den_ = Den; // exposition only }; -template +template [[nodiscard]] consteval auto expr_fractions_impl() { constexpr std::size_t size = type_list_size; @@ -322,11 +337,11 @@ template /** * @brief Divides expression template spec to numerator and denominator parts */ -template +template struct expr_fractions : decltype(expr_fractions_impl>()) {}; // expr_make_spec -template typename To> +template typename To> [[nodiscard]] MP_UNITS_CONSTEVAL auto expr_make_spec_impl() { constexpr std::size_t num = type_list_size; @@ -350,7 +365,7 @@ template typename Pred, +template typename Pred, template typename To> [[nodiscard]] MP_UNITS_CONSTEVAL auto get_optimized_expression() { @@ -371,8 +386,8 @@ template typename To, typename OneType, template typename Pred, typename Lhs, - typename Rhs> +template typename To, SymbolicArg OneType, template typename Pred, + typename Lhs, typename Rhs> [[nodiscard]] MP_UNITS_CONSTEVAL auto expr_multiply(Lhs, Rhs) { if constexpr (is_same_v) { @@ -406,8 +421,8 @@ template typename To, typename OneType, template typename To, typename OneType, template typename Pred, typename Lhs, - typename Rhs> +template typename To, SymbolicArg OneType, template typename Pred, + typename Lhs, typename Rhs> [[nodiscard]] MP_UNITS_CONSTEVAL auto expr_divide(Lhs lhs, Rhs rhs) { if constexpr (is_same_v) { @@ -440,7 +455,7 @@ template typename To, typename OneType, template typename To, typename OneType, typename T> +template typename To, SymbolicArg OneType, typename T> [[nodiscard]] consteval auto expr_invert(T) { if constexpr (is_specialization_of) @@ -452,7 +467,7 @@ template typename To, typename OneType, typename T> } -template typename To, typename OneType, +template typename To, SymbolicArg OneType, template typename Pred, typename... Nums, typename... Dens> requires detail::non_zero [[nodiscard]] consteval auto expr_pow_impl(type_list, type_list) @@ -472,7 +487,7 @@ template typename To * @tparam Pred binary less then predicate * @tparam T Expression being the base of the operation */ -template typename To, typename OneType, +template typename To, SymbolicArg OneType, template typename Pred, typename T> requires detail::non_zero [[nodiscard]] consteval auto expr_pow(T v) @@ -536,7 +551,7 @@ template return pow(T{}); } -template typename Proj, template typename To, typename OneType, +template typename Proj, template typename To, SymbolicArg OneType, template typename Pred, expr_type_projectable... Nums, expr_type_projectable... Dens> [[nodiscard]] consteval auto expr_map_impl(type_list, type_list) @@ -554,7 +569,7 @@ template typename Proj, template typename To, ty * @tparam Pred binary less then predicate * @tparam T expression template to map from */ -template typename Proj, template typename To, typename OneType, +template typename Proj, template typename To, SymbolicArg OneType, template typename Pred, expr_projectable T> [[nodiscard]] consteval auto expr_map(T) { diff --git a/src/core/include/mp-units/framework/magnitude.h b/src/core/include/mp-units/framework/magnitude.h index 36592d59f0..cdb796f96e 100644 --- a/src/core/include/mp-units/framework/magnitude.h +++ b/src/core/include/mp-units/framework/magnitude.h @@ -51,13 +51,6 @@ import std; namespace mp_units { -namespace detail { - -// Higher numbers use fewer trial divisions, at the price of more storage space. -using factorizer = wheel_factorizer<4>; - -} // namespace detail - #if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18 MP_UNITS_EXPORT template @@ -629,14 +622,10 @@ using common_magnitude_type = decltype(_common_magnitude_type_impl(M)); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // `mag()` implementation. -// Sometimes we need to give the compiler a "shortcut" when factorizing large numbers (specifically, numbers whose -// _first factor_ is very large). If we don't, we can run into limits on the number of constexpr steps or iterations. -// -// To provide the first factor for a given number, specialize this variable template. -// -// WARNING: The program behaviour will be undefined if you provide a wrong answer, so check your math! MP_UNITS_EXPORT template -constexpr std::optional known_first_factor = std::nullopt; +[[deprecated("`known_first_factor` is no longer necessary and can simply be removed")]] +constexpr std::optional + known_first_factor = std::nullopt; namespace detail { @@ -646,12 +635,7 @@ template struct prime_factorization { [[nodiscard]] static consteval std::intmax_t get_or_compute_first_factor() { - constexpr auto opt = known_first_factor; - if constexpr (opt.has_value()) { - return opt.value(); // NOLINT(bugprone-unchecked-optional-access) - } else { - return static_cast(factorizer::find_first_factor(N)); - } + return static_cast(find_first_factor(N)); } static constexpr std::intmax_t first_base = get_or_compute_first_factor(); @@ -674,7 +658,7 @@ constexpr auto prime_factorization_v = prime_factorization::value; template [[nodiscard]] consteval Magnitude auto make_magnitude() { - if constexpr (MagConstant) + if constexpr (MagConstant) return magnitude{}; else return prime_factorization_v; diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 8c265516c5..544bfe21f8 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -78,14 +78,15 @@ concept QuantityConvertibleTo = // deduced thus the function is evaluated here and may emit truncating conversion or other warnings) requires(QFrom q) { sudo_cast(q); }; -template -concept InvokeResultOf = std::regular_invocable && RepresentationOf, Ch>; +template +concept InvokeResultOf = QuantitySpec && std::regular_invocable && + RepresentationOf, QS>; template, - std::remove_const_t>::character> -concept InvocableQuantities = - Quantity && Quantity && InvokeResultOf; + auto QS = std::invoke_result_t{}> +concept InvocableQuantities = QuantitySpec && Quantity && Quantity && + InvokeResultOf; // TODO remove the following when clang diagnostics improve // https://github.com/llvm/llvm-project/issues/96660 @@ -104,7 +105,7 @@ concept CommonlyInvocableQuantities = Quantity && Quantity && HaveCommonReference && std::convertible_to> && std::convertible_to> && - InvocableQuantities; + InvocableQuantities; template concept SameValueAs = (equivalent(get_unit(R1), get_unit(R2))) && std::convertible_to; @@ -128,7 +129,7 @@ MP_UNITS_EXPORT_BEGIN * @tparam R a reference of the quantity providing all information about quantity properties * @tparam Rep a type to be used to represent values of a quantity */ -template Rep = double> +template Rep = double> class quantity { public: Rep numerical_value_is_an_implementation_detail_; ///< needs to be public for a structural type @@ -173,21 +174,21 @@ class quantity { 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)) { } @@ -213,9 +214,9 @@ class quantity { 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; } @@ -227,14 +228,14 @@ class quantity { return quantity{*this}; } - template ToRep> + template ToRep> requires detail::QuantityConvertibleTo> [[nodiscard]] constexpr QuantityOf auto in() const { return quantity{*this}; } - template ToRep, detail::UnitCompatibleWith ToU> + template ToRep, detail::UnitCompatibleWith ToU> requires detail::QuantityConvertibleTo> [[nodiscard]] constexpr QuantityOf auto in(ToU) const { @@ -248,14 +249,14 @@ class quantity { return value_cast(*this); } - template ToRep> + template ToRep> requires requires(quantity q) { value_cast(q); } [[nodiscard]] constexpr QuantityOf auto force_in() const { return value_cast(*this); } - template ToRep, detail::UnitCompatibleWith ToU> + template ToRep, detail::UnitCompatibleWith ToU> requires requires(quantity q) { value_cast(q); } [[nodiscard]] constexpr QuantityOf auto force_in(ToU) const { @@ -421,11 +422,11 @@ class quantity { 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); } @@ -443,12 +444,12 @@ class quantity { 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); } @@ -474,17 +475,15 @@ class quantity { ret::reference}; } - template Q, RepresentationOf Value> - requires(Q::unit == ::mp_units::one) && - detail::InvokeResultOf, Rep, const Value&> + template Q, Representation Value> + requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator+(const Q& lhs, const Value& rhs) { return lhs + ::mp_units::quantity{rhs}; } - template Q, RepresentationOf Value> - requires(Q::unit == ::mp_units::one) && - detail::InvokeResultOf, Rep, const Value&> + template Q, Representation Value> + requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator+(const Value& lhs, const Q& rhs) { return ::mp_units::quantity{lhs} + rhs; @@ -501,17 +500,15 @@ class quantity { ret::reference}; } - template Q, RepresentationOf Value> - requires(Q::unit == ::mp_units::one) && - detail::InvokeResultOf, Rep, const Value&> + template Q, Representation Value> + requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator-(const Q& lhs, const Value& rhs) { return lhs - ::mp_units::quantity{rhs}; } - template Q, RepresentationOf Value> - requires(Q::unit == ::mp_units::one) && - detail::InvokeResultOf, Rep, const Value&> + template Q, Representation Value> + requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator-(const Value& lhs, const Q& rhs) { return ::mp_units::quantity{lhs} - rhs; @@ -530,17 +527,15 @@ class quantity { ret::reference}; } - template Q, RepresentationOf Value> - requires(Q::unit == ::mp_units::one) && - detail::InvokeResultOf, Rep, const Value&> + template Q, Representation Value> + requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator%(const Q& lhs, const Value& rhs) { return lhs % ::mp_units::quantity{rhs}; } - template Q, RepresentationOf Value> - requires(Q::unit == ::mp_units::one) && - detail::InvokeResultOf, Rep, const Value&> + template Q, Representation Value> + requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator%(const Value& lhs, const Q& rhs) { return ::mp_units::quantity{lhs} % rhs; @@ -555,18 +550,18 @@ class quantity { template Q, typename Value> requires(!Quantity) && - (!Reference) && detail::InvokeResultOf, Rep, const Value&> - [[nodiscard]] friend constexpr QuantityOf auto operator*(const Q& q, const Value& v) + (!Reference) && detail::InvokeResultOf, Rep, const Value&> + [[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) + (!Reference) && detail::InvokeResultOf, const Value&, Rep> + [[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> @@ -579,19 +574,19 @@ class quantity { template Q, typename Value> requires(!Quantity) && - (!Reference) && detail::InvokeResultOf, Rep, const Value&> - [[nodiscard]] friend constexpr QuantityOf auto operator/(const Q& q, const Value& v) + (!Reference) && detail::InvokeResultOf, Rep, const Value&> + [[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) + (!Reference) && detail::InvokeResultOf, const Value&, Rep> + [[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> @@ -605,7 +600,7 @@ class quantity { return ct_lhs.numerical_value_ref_in(ct::unit) == ct_rhs.numerical_value_ref_in(ct::unit); } - template Q, RepresentationOf Value> + template Q, Representation Value> requires(Q::unit == ::mp_units::one) && std::equality_comparable_with [[nodiscard]] friend constexpr bool operator==(const Q& lhs, const Value& rhs) { @@ -623,7 +618,7 @@ class quantity { return ct_lhs.numerical_value_ref_in(ct::unit) <=> ct_rhs.numerical_value_ref_in(ct::unit); } - template Q, RepresentationOf Value> + template Q, Representation Value> requires(Q::unit == ::mp_units::one) && std::three_way_comparable_with [[nodiscard]] friend constexpr auto operator<=>(const Q& lhs, const Value& rhs) { @@ -650,18 +645,21 @@ template requires requires { { mp_units::get_common_reference(Q1::reference, Q2::reference) } -> mp_units::Reference; typename std::common_type_t; + requires mp_units::RepresentationOf, + mp_units::get_common_quantity_spec(Q1::quantity_spec, Q2::quantity_spec)>; } struct std::common_type { using type = mp_units::quantity>; }; -template Value> - requires(Q::unit == mp_units::one) && requires { typename std::common_type_t; } +template + requires(Q::unit == mp_units::one) && + requires { typename mp_units::quantity>; } struct std::common_type { using type = mp_units::quantity>; }; -template Value> - requires(Q::unit == mp_units::one) && requires { typename std::common_type_t; } +template + requires requires { typename std::common_type; } struct std::common_type : std::common_type {}; diff --git a/src/core/include/mp-units/framework/quantity_concepts.h b/src/core/include/mp-units/framework/quantity_concepts.h index 15cdc260b4..275763ce74 100644 --- a/src/core/include/mp-units/framework/quantity_concepts.h +++ b/src/core/include/mp-units/framework/quantity_concepts.h @@ -31,7 +31,7 @@ namespace mp_units { -MP_UNITS_EXPORT template Rep> +MP_UNITS_EXPORT template Rep> class quantity; namespace detail { @@ -74,7 +74,7 @@ concept QuantityLikeImpl = requires(const T& qty, const Traits::rep& num) { * the provided quantity_spec type. */ MP_UNITS_EXPORT template -concept QuantityOf = Quantity && QuantitySpecOf, QS>; +concept QuantityOf = Quantity && QuantitySpecOf; /** * @brief A concept matching all external quantities like types diff --git a/src/core/include/mp-units/framework/quantity_point.h b/src/core/include/mp-units/framework/quantity_point.h index e96220d0d5..d297b78caa 100644 --- a/src/core/include/mp-units/framework/quantity_point.h +++ b/src/core/include/mp-units/framework/quantity_point.h @@ -67,7 +67,7 @@ struct point_origin_interface { } template - requires ReferenceOf, PO::_quantity_spec_> + requires ReferenceOf [[nodiscard]] friend constexpr QuantityPoint auto operator-(PO po, const Q& q) requires requires { -q; } { @@ -75,7 +75,7 @@ struct point_origin_interface { } template PO2> - requires QuantitySpecOf, PO2::_quantity_spec_> && + requires QuantitySpecOf && (is_derived_from_specialization_of_v || is_derived_from_specialization_of_v) [[nodiscard]] friend constexpr Quantity auto operator-(PO1 po1, PO2 po2) @@ -118,7 +118,7 @@ struct relative_point_origin : detail::point_origin_interface { static constexpr QuantityPoint auto _quantity_point_ = QP; static constexpr QuantitySpec auto _quantity_spec_ = []() { // select the strongest of specs - if constexpr (detail::QuantityKindSpec>) + if constexpr (detail::QuantityKindSpec) return QP.point_origin._quantity_spec_; else return QP.quantity_spec; @@ -172,7 +172,7 @@ template * @tparam Rep a type to be used to represent values of a quantity point */ MP_UNITS_EXPORT template auto PO = default_point_origin(R), - RepresentationOf Rep = double> + RepresentationOf Rep = double> class quantity_point { public: // member types and values @@ -329,14 +329,14 @@ class quantity_point { return ::mp_units::quantity_point{quantity_ref_from(point_origin).in(ToU{}), point_origin}; } - template ToRep> + template ToRep> requires detail::QuantityConvertibleTo> [[nodiscard]] constexpr QuantityPointOf auto in() const { return ::mp_units::quantity_point{quantity_ref_from(point_origin).template in(), point_origin}; } - template ToRep, detail::UnitCompatibleWith ToU> + template ToRep, detail::UnitCompatibleWith ToU> requires detail::QuantityConvertibleTo> [[nodiscard]] constexpr QuantityPointOf auto in(ToU) const { @@ -350,14 +350,14 @@ class quantity_point { return ::mp_units::quantity_point{quantity_ref_from(point_origin).force_in(ToU{}), point_origin}; } - template ToRep> + template ToRep> requires requires(quantity_type q) { value_cast(q); } [[nodiscard]] constexpr QuantityPointOf auto force_in() const { return ::mp_units::quantity_point{quantity_ref_from(point_origin).template force_in(), point_origin}; } - template ToRep, detail::UnitCompatibleWith ToU> + template ToRep, detail::UnitCompatibleWith ToU> requires requires(quantity_type q) { value_cast(q); } [[nodiscard]] constexpr QuantityPointOf auto force_in(ToU) const { @@ -497,7 +497,7 @@ class quantity_point { template QP, PointOrigin PO2> requires QuantityPointOf && - ReferenceOf, PO2::_quantity_spec_> + ReferenceOf [[nodiscard]] friend constexpr Quantity auto operator-(const QP& qp, PO2 po) { if constexpr (point_origin == po) @@ -520,7 +520,7 @@ class quantity_point { template QP> requires QuantityPointOf && - ReferenceOf, PO1::_quantity_spec_> + ReferenceOf [[nodiscard]] friend constexpr Quantity auto operator-(PO1 po, const QP& qp) { return -(qp - po); diff --git a/src/core/include/mp-units/framework/quantity_point_concepts.h b/src/core/include/mp-units/framework/quantity_point_concepts.h index 08834c9ae0..0158830854 100644 --- a/src/core/include/mp-units/framework/quantity_point_concepts.h +++ b/src/core/include/mp-units/framework/quantity_point_concepts.h @@ -76,7 +76,7 @@ MP_UNITS_EXPORT template concept PointOriginFor = PointOrigin && QuantitySpecOf; MP_UNITS_EXPORT template auto PO, - RepresentationOf Rep> + RepresentationOf Rep> class quantity_point; namespace detail { @@ -125,8 +125,8 @@ concept SameAbsolutePointOriginAs = */ MP_UNITS_EXPORT template concept QuantityPointOf = - QuantityPoint && (QuantitySpecOf, V> || - detail::SameAbsolutePointOriginAs, V>); + QuantityPoint && (QuantitySpecOf || + detail::SameAbsolutePointOriginAs); /** * @brief A concept matching all external quantity point like types diff --git a/src/core/include/mp-units/framework/quantity_spec.h b/src/core/include/mp-units/framework/quantity_spec.h index bfe9d0b2b8..835ae2c980 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{}; @@ -114,7 +114,7 @@ using type_list_of_quantity_spec_less = expr_less; template requires requires { Q::dimension; } -using to_dimension = std::remove_const_t; +using to_dimension = MP_UNITS_NONCONST_TYPE(Q::dimension); template [[nodiscard]] consteval auto get_associated_quantity(U); @@ -280,7 +280,7 @@ struct quantity_spec : detail::quantity_spec_interface * @code{.cpp} * inline constexpr struct area final : quantity_spec(length)> {} area; * inline constexpr struct volume final : quantity_spec(length)> {} volume; - * inline constexpr struct velocity final : quantity_spec {} velocity; + * inline constexpr struct velocity final : quantity_spec {} velocity; * inline constexpr struct speed final : quantity_spec {} speed; * inline constexpr struct force final : quantity_spec {} force; * inline constexpr struct power final : quantity_spec {} power; @@ -336,7 +336,7 @@ struct propagate_equation { * inline constexpr struct width final : quantity_spec {} width; * inline constexpr struct height final : quantity_spec {} height; * inline constexpr struct diameter final : quantity_spec {} diameter; - * inline constexpr struct position_vector final : quantity_spec {} position_vector; + * inline constexpr struct displacement final : quantity_spec {} displacement; * @endcode * * @note A common convention in this library is to assign the same name for a type and an object of this type. @@ -394,7 +394,7 @@ struct quantity_spec : detail::propagate_equation, detail * * @code{.cpp} * inline constexpr struct angular_measure final : quantity_spec {} angular_measure; - * inline constexpr struct velocity final : quantity_spec {} velocity; + * inline constexpr struct velocity final : quantity_spec {} velocity; * inline constexpr struct weight final : quantity_spec {} weight; * inline constexpr struct kinetic_energy final : quantity_spec(speed)> {} kinetic_energy; * @endcode @@ -470,7 +470,7 @@ struct derived_quantity_spec_impl : * auto frequency = inverse(period_duration); * auto area = pow<2>(length); * auto speed = distance / duration; - * auto velocity = position_vector / duration; + * auto velocity = displacement / duration; * auto acceleration = velocity / duration; * @endcode * @@ -480,7 +480,7 @@ struct derived_quantity_spec_impl : * - the dimension type of `area` is `derived_dimension>` * - the type of `speed` is `derived_quantity_spec>` * - the dimension type of `speed` is `derived_dimension>` - * - the type of `velocity` is `derived_quantity_spec>` + * - the type of `velocity` is `derived_quantity_spec>` * - the dimension type of `velocity` is `derived_dimension>` * - the type of `acceleration` is `derived_quantity_spec>` * - the dimension type of `acceleration` is `derived_dimension>>` @@ -491,7 +491,7 @@ struct derived_quantity_spec_impl : * @note User should not instantiate this type! It is not exported from the C++ module. The library will * instantiate this type automatically based on the dimensional arithmetic equation provided by the user. */ -template +template struct derived_quantity_spec final : detail::derived_quantity_spec_impl {}; /** @@ -925,10 +925,10 @@ template return extract_results{false}; else if constexpr (from_exp > to_exp) return extract_results{true, from_factor, to_factor, prepend_rest::first, - power_or_T{}}; + power_or_T{}}; else return extract_results{true, from_factor, to_factor, prepend_rest::second, - power_or_T{}}; + power_or_T{}}; } } diff --git a/src/core/include/mp-units/framework/quantity_spec_concepts.h b/src/core/include/mp-units/framework/quantity_spec_concepts.h index 6e7e0934f7..d59229bc25 100644 --- a/src/core/include/mp-units/framework/quantity_spec_concepts.h +++ b/src/core/include/mp-units/framework/quantity_spec_concepts.h @@ -78,7 +78,7 @@ concept NamedQuantitySpec = } // namespace detail -template +template struct derived_quantity_spec; namespace detail { @@ -93,9 +93,9 @@ namespace detail { */ template concept DerivedQuantitySpec = - QuantitySpec && (is_specialization_of || - (QuantityKindSpec && - is_specialization_of, derived_quantity_spec>)); + QuantitySpec && + (is_specialization_of || + (QuantityKindSpec && is_specialization_of)); } // namespace detail @@ -117,8 +117,8 @@ concept ChildQuantitySpecOf = (is_child_of(Child, Parent)); template concept NestedQuantityKindSpecOf = - QuantitySpec && QuantitySpec && (!SameQuantitySpec) && - ChildQuantitySpecOf; + QuantitySpec && QuantitySpec && + (!SameQuantitySpec) && ChildQuantitySpecOf; template concept QuantitySpecConvertibleTo = diff --git a/src/core/include/mp-units/framework/reference.h b/src/core/include/mp-units/framework/reference.h index 0e209eb8d9..10d0597d04 100644 --- a/src/core/include/mp-units/framework/reference.h +++ b/src/core/include/mp-units/framework/reference.h @@ -190,24 +190,21 @@ struct reference { }; -template Rep = std::remove_cvref_t> +template Rep = std::remove_cvref_t> requires(!detail::OffsetUnit) [[nodiscard]] constexpr quantity operator*(FwdRep&& lhs, R r) { return quantity{std::forward(lhs), r}; } -template Rep = std::remove_cvref_t> +template Rep = std::remove_cvref_t> requires(!detail::OffsetUnit) [[nodiscard]] constexpr quantity operator/(FwdRep&& lhs, R) { return quantity{std::forward(lhs), inverse(R{})}; } -template Rep = std::remove_cvref_t> +template Rep = std::remove_cvref_t> requires detail::OffsetUnit [[deprecated( "References using offset units (e.g., temperatures) should be constructed with the `delta` or `absolute` " @@ -217,8 +214,7 @@ operator*(FwdRep&& lhs, R r) return quantity{std::forward(lhs), r}; } -template Rep = std::remove_cvref_t> +template Rep = std::remove_cvref_t> requires detail::OffsetUnit [[deprecated( "References using offset units (e.g., temperatures) should be constructed with the `delta` or `absolute` " @@ -229,7 +225,7 @@ operator/(FwdRep&& lhs, R) } template - requires RepresentationOf, get_quantity_spec(R{}).character> + requires RepresentationOf, get_quantity_spec(R{})> // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) constexpr auto operator*(R, Rep&&) #if __cpp_deleted_function @@ -239,7 +235,7 @@ constexpr auto operator*(R, Rep&&) #endif template - requires RepresentationOf, get_quantity_spec(R{}).character> + requires RepresentationOf, get_quantity_spec(R{})> // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) constexpr auto operator/(R, Rep&&) #if __cpp_deleted_function diff --git a/src/core/include/mp-units/framework/representation_concepts.h b/src/core/include/mp-units/framework/representation_concepts.h index 6fce2ec22b..bb3a4f71e5 100644 --- a/src/core/include/mp-units/framework/representation_concepts.h +++ b/src/core/include/mp-units/framework/representation_concepts.h @@ -25,6 +25,7 @@ // IWYU pragma: private, include #include #include +#include #ifndef MP_UNITS_IN_MODULE_INTERFACE #ifdef MP_UNITS_IMPORT_STD @@ -97,6 +98,7 @@ concept ScalarRepresentation = Scalar && WeaklyRegular && requires(T a, T { a / f } -> Scalar; // scalar operations + { -a } -> Scalar; { a + b } -> Scalar; { a - b } -> Scalar; { a* b } -> Scalar; @@ -114,16 +116,16 @@ concept ComplexRepresentation = Complex && WeaklyRegular && requires(T a, { a / T(f) } -> Complex; // complex operations + { -a } -> Complex; { a + b } -> Complex; { 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? @@ -136,6 +138,7 @@ concept VectorRepresentation = Vector && WeaklyRegular && requires(T a, T { a / f } -> Vector; // vector operations + { -a } -> Vector; { a + b } -> Vector; { a - b } -> Vector; // TBD @@ -164,7 +167,11 @@ MP_UNITS_EXPORT template concept Representation = detail::ScalarRepresentation || detail::ComplexRepresentation || detail::VectorRepresentation || detail::TensorRepresentation; -MP_UNITS_EXPORT template -concept RepresentationOf = detail::IsOfCharacter && Representation; +MP_UNITS_EXPORT template +concept RepresentationOf = + Representation && + ((QuantitySpec && + (detail::QuantityKindSpec || detail::IsOfCharacter)) || + (std::same_as && detail::IsOfCharacter)); } // namespace mp_units diff --git a/src/core/include/mp-units/framework/unit.h b/src/core/include/mp-units/framework/unit.h index 5b9973e398..fa5edf14a4 100644 --- a/src/core/include/mp-units/framework/unit.h +++ b/src/core/include/mp-units/framework/unit.h @@ -146,7 +146,7 @@ template return false; } -template +template struct derived_unit; namespace detail { @@ -158,13 +158,13 @@ struct unit_interface { template [[nodiscard]] friend MP_UNITS_CONSTEVAL Unit auto operator*(M, U u) { - if constexpr (std::is_same_v)>>) + if constexpr (std::is_same_v)>) return u; else if constexpr (is_specialization_of_scaled_unit) { if constexpr (M{} * U::_mag_ == mag<1>) return U::_reference_unit_; else - return scaled_unit>{}; + return scaled_unit{}; } else return scaled_unit{}; } @@ -304,7 +304,7 @@ struct named_unit; * @tparam QuantitySpec a specification of a base quantity to be measured with this unit */ template - requires(!Symbol.empty()) && detail::BaseDimension> + requires(!Symbol.empty()) && detail::BaseDimension struct named_unit : detail::unit_interface { using _base_type_ = named_unit; // exposition only static constexpr auto _symbol_ = Symbol; ///< Unique base unit identifier @@ -312,7 +312,7 @@ struct named_unit : detail::unit_interface { }; template - requires(!Symbol.empty()) && detail::BaseDimension> + requires(!Symbol.empty()) && detail::BaseDimension struct named_unit : detail::unit_interface { using _base_type_ = named_unit; // exposition only static constexpr auto _symbol_ = Symbol; ///< Unique base unit identifier @@ -427,7 +427,7 @@ template if constexpr (common_magnitude == mag<1>) return canonical_lhs.reference_unit; else - return scaled_unit>{}; + return scaled_unit{}; } [[nodiscard]] consteval Unit auto get_common_scaled_unit(Unit auto u1, Unit auto u2, Unit auto u3, Unit auto... rest) @@ -513,7 +513,7 @@ struct derived_unit_impl : detail::unit_interface, detail::expr_fractions +template struct derived_unit final : detail::derived_unit_impl {}; /** @@ -889,13 +889,10 @@ constexpr Out unit_symbol_to(Out out, U u, const unit_symbol_formatting& fmt = u return detail::unit_symbol_impl(out, u, fmt, false); } -// TODO Refactor to `unit_symbol(U, fmt)` when P1045: constexpr Function Parameters is available -MP_UNITS_EXPORT template -#if defined MP_UNITS_COMP_CLANG && MP_UNITS_COMP_CLANG <= 18 -[[nodiscard]] constexpr auto unit_symbol(U) -#else -[[nodiscard]] consteval auto unit_symbol(U) -#endif +namespace detail { + +MP_UNITS_EXPORT template +[[nodiscard]] consteval auto unit_symbol_impl(U) { constexpr auto oversized_symbol_text = []() consteval { // std::basic_string text; // TODO uncomment when https://wg21.link/P3032 is supported @@ -903,14 +900,21 @@ MP_UNITS_EXPORT template(std::back_inserter(text), U{}, fmt); return text; }(); - -#if MP_UNITS_API_STRING_VIEW_RET // Permitting static constexpr variables in constexpr functions - static constexpr basic_fixed_string storage(std::from_range, - oversized_symbol_text); - return storage.view(); -#else return basic_fixed_string(std::from_range, oversized_symbol_text); -#endif +} + +template +struct unit_symbol_result { + static constexpr auto value = unit_symbol_impl(U{}); +}; + +} // namespace detail + +// TODO Refactor to `unit_symbol(U, fmt)` when P1045: constexpr Function Parameters is available +MP_UNITS_EXPORT template +[[nodiscard]] consteval std::string_view unit_symbol(U) +{ + return detail::unit_symbol_result::value.view(); } } // namespace mp_units diff --git a/src/core/include/mp-units/framework/value_cast.h b/src/core/include/mp-units/framework/value_cast.h index 020424dcd5..1d70616c67 100644 --- a/src/core/include/mp-units/framework/value_cast.h +++ b/src/core/include/mp-units/framework/value_cast.h @@ -63,7 +63,7 @@ template> * @tparam ToRep a representation type to use for a target quantity */ template> - requires RepresentationOf && std::constructible_from + requires RepresentationOf && std::constructible_from [[nodiscard]] constexpr quantity value_cast(FwdQ&& q) { return detail::sudo_cast>(std::forward(q)); @@ -81,7 +81,7 @@ template> - requires(convertible(Q::reference, ToU)) && RepresentationOf && + requires(convertible(Q::reference, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { @@ -89,7 +89,7 @@ template> - requires(convertible(Q::reference, ToU)) && RepresentationOf && + requires(convertible(Q::reference, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { @@ -148,7 +148,7 @@ template> - requires RepresentationOf && std::constructible_from + requires RepresentationOf && std::constructible_from [[nodiscard]] constexpr quantity_point value_cast(FwdQP&& qp) { return {value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), @@ -167,7 +167,7 @@ template> - requires(convertible(QP::reference, ToU)) && RepresentationOf && + requires(convertible(QP::reference, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { @@ -177,7 +177,7 @@ template> - requires(convertible(QP::reference, ToU)) && RepresentationOf && + requires(convertible(QP::reference, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { diff --git a/src/core/include/mp-units/ostream.h b/src/core/include/mp-units/ostream.h index 15b6127b02..1b3f63ac3a 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/core/mp-units-core.cpp b/src/core/mp-units-core.cpp index f56a846df6..ac86fab8a3 100644 --- a/src/core/mp-units-core.cpp +++ b/src/core/mp-units-core.cpp @@ -32,13 +32,4 @@ import std; #define MP_UNITS_IN_MODULE_INTERFACE -#include -#include -#include - -#if MP_UNITS_HOSTED -#include -#include -#include -#include -#endif +#include diff --git a/src/systems/include/mp-units/systems/hep.h b/src/systems/include/mp-units/systems/hep.h index c64844f891..b4bb997e75 100644 --- a/src/systems/include/mp-units/systems/hep.h +++ b/src/systems/include/mp-units/systems/hep.h @@ -35,9 +35,6 @@ MP_UNITS_EXPORT namespace mp_units { -template<> -constexpr std::optional known_first_factor<334'524'384'739> = 334'524'384'739; - namespace hep { // energy diff --git a/src/systems/include/mp-units/systems/isq/electromagnetism.h b/src/systems/include/mp-units/systems/isq/electromagnetism.h index 43988465b3..3f030b73ba 100644 --- a/src/systems/include/mp-units/systems/isq/electromagnetism.h +++ b/src/systems/include/mp-units/systems/isq/electromagnetism.h @@ -44,7 +44,7 @@ QUANTITY_SPEC(surface_density_of_electric_charge, electric_charge / area); inline constexpr auto areic_electric_charge = surface_density_of_electric_charge; QUANTITY_SPEC(linear_density_of_electric_charge, electric_charge / length); inline constexpr auto lineic_electric_charge = linear_density_of_electric_charge; -QUANTITY_SPEC(electric_dipole_moment, electric_charge* position_vector); // vector +QUANTITY_SPEC(electric_dipole_moment, electric_charge* displacement); // vector QUANTITY_SPEC(electric_polarization, electric_dipole_moment / volume); // vector QUANTITY_SPEC(electric_current_density, electric_charge_density* velocity); // vector inline constexpr auto areic_electric_current = electric_current_density; diff --git a/src/systems/include/mp-units/systems/isq/space_and_time.h b/src/systems/include/mp-units/systems/isq/space_and_time.h index a123f6ddd9..f0b71e908d 100644 --- a/src/systems/include/mp-units/systems/isq/space_and_time.h +++ b/src/systems/include/mp-units/systems/isq/space_and_time.h @@ -42,18 +42,18 @@ QUANTITY_SPEC(thickness, width); QUANTITY_SPEC(diameter, width); QUANTITY_SPEC(distance, path_length); QUANTITY_SPEC(radial_distance, distance); -QUANTITY_SPEC(position_vector, length, quantity_character::vector); QUANTITY_SPEC(displacement, length, quantity_character::vector); +QUANTITY_SPEC(position_vector, displacement); QUANTITY_SPEC(radius_of_curvature, radius); QUANTITY_SPEC(curvature, inverse(radius_of_curvature)); QUANTITY_SPEC(volume, pow<3>(length)); QUANTITY_SPEC(rotational_displacement, angular_measure, path_length / radius); inline constexpr auto angular_displacement = rotational_displacement; QUANTITY_SPEC(phase_angle, angular_measure); -QUANTITY_SPEC(speed, length / time); // differs from ISO 80000 -QUANTITY_SPEC(velocity, speed, position_vector / duration); // vector // differs from ISO 80000 -QUANTITY_SPEC(acceleration, velocity / duration); // vector -QUANTITY_SPEC(acceleration_of_free_fall, acceleration); // not in ISO 80000 +QUANTITY_SPEC(speed, length / time); // differs from ISO 80000 +QUANTITY_SPEC(velocity, speed, displacement / duration); // vector // differs from ISO 80000 +QUANTITY_SPEC(acceleration, velocity / duration); // vector +QUANTITY_SPEC(acceleration_of_free_fall, acceleration); // not in ISO 80000 QUANTITY_SPEC(angular_velocity, angular_displacement / duration, quantity_character::vector); QUANTITY_SPEC(angular_acceleration, angular_velocity / duration); QUANTITY_SPEC(time_constant, duration); diff --git a/src/systems/include/mp-units/systems/si/chrono.h b/src/systems/include/mp-units/systems/si/chrono.h index a856fc15b3..59bb4439fb 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)); } }; @@ -137,7 +137,7 @@ template Q> } template QP> - requires is_specialization_of, chrono_point_origin_> + requires is_specialization_of [[nodiscard]] constexpr auto to_chrono_time_point(const QP& qp) { using clock = decltype(QP::absolute_point_origin)::clock; diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index d79ed54b38..c1d990ba26 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 0000000000..6aac39f229 --- /dev/null +++ b/test/runtime/cartesian_vector_test.cpp @@ -0,0 +1,443 @@ +// 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("no arguments") + { + cartesian_vector v; + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + + SECTION("zero arguments") + { + cartesian_vector v{}; + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + + 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("convertible arguments") + { + cartesian_vector v{1, 2, 3}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 3.0); + } + } + + SECTION("convertibility from another vector") + { + 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 compound assignment addition") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + v1 += v2; + REQUIRE(v1[0] == 5.0); + REQUIRE(v1[1] == 7.0); + REQUIRE(v1[2] == 9.0); + } + + SECTION("cartesian_vector compound assignment subtraction") + { + cartesian_vector v1{4.0, 5.0, 6.0}; + cartesian_vector v2{1.0, 2.0, 3.0}; + v1 -= v2; + REQUIRE(v1[0] == 3.0); + REQUIRE(v1[1] == 3.0); + REQUIRE(v1[2] == 3.0); + } + + SECTION("cartesian_vector compound assignment scalar multiplication") + { + cartesian_vector v{1.0, 2.0, 3.0}; + v *= 2.0; + REQUIRE(v[0] == 2.0); + REQUIRE(v[1] == 4.0); + REQUIRE(v[2] == 6.0); + } + + SECTION("cartesian_vector compound assignment scalar division") + { + cartesian_vector v{2.0, 4.0, 6.0}; + v /= 2.0; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[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 9df1eb3bfb..1ea3da1d41 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(); - SECTION("parametrized") - { - constexpr rep a = 2; - constexpr rep b = 5; + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::max()); + } - auto stl_dist = std::uniform_int_distribution(a, b); - auto units_dist = mp_units::uniform_int_distribution(a * si::metre, b * si::metre); + SECTION("parametrized") + { + constexpr rep a = 2; + constexpr rep b = 5; - 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; + 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(dist.a() == q::zero()); - CHECK(dist.b() == q::one()); + 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.0; - constexpr rep b = 5.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::uniform_real_distribution(a, b); - auto units_dist = mp_units::uniform_real_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); - } -} + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::one()); + } -TEST_CASE("binomial_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 2.0; + constexpr rep b = 5.0; - SECTION("default") - { - auto dist = mp_units::binomial_distribution(); + auto stl_dist = std::uniform_real_distribution(a, b); + auto units_dist = mp_units::uniform_real_distribution(a * si::metre, b * si::metre); - CHECK(dist.p() == 0.5); - CHECK(dist.t() == q::one()); + 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("binomial_distribution") { - constexpr rep t = 5; - constexpr double p = 0.25; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::binomial_distribution(t, p); - auto units_dist = mp_units::binomial_distribution(t * si::metre, p); + SECTION("default") + { + auto dist = mp_units::binomial_distribution(); - 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); - } -} + CHECK(dist.p() == 0.5); + CHECK(dist.t() == q::one()); + } -TEST_CASE("negative_binomial_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr rep t = 5; + constexpr double p = 0.25; - SECTION("default") - { - auto dist = mp_units::negative_binomial_distribution(); + auto stl_dist = std::binomial_distribution(t, p); + auto units_dist = mp_units::binomial_distribution(t * si::metre, p); - CHECK(dist.p() == 0.5); - CHECK(dist.k() == q::one()); + 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("parametrized") + SECTION("negative_binomial_distribution") { - constexpr rep k = 5; - constexpr double p = 0.25; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::negative_binomial_distribution(k, p); - auto units_dist = mp_units::negative_binomial_distribution(k * si::metre, p); + SECTION("default") + { + auto dist = mp_units::negative_binomial_distribution(); - 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); - } -} + CHECK(dist.p() == 0.5); + CHECK(dist.k() == q::one()); + } -TEST_CASE("geometric_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr rep k = 5; + constexpr double p = 0.25; - SECTION("default") - { - auto dist = mp_units::geometric_distribution(); + auto stl_dist = std::negative_binomial_distribution(k, p); + auto units_dist = mp_units::negative_binomial_distribution(k * si::metre, p); - CHECK(dist.p() == 0.5); + 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("parametrized") + SECTION("geometric_distribution") { - constexpr double p = 0.25; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::geometric_distribution(p); - auto units_dist = mp_units::geometric_distribution(p); + SECTION("default") + { + auto dist = mp_units::geometric_distribution(); - 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); - } -} + CHECK(dist.p() == 0.5); + } -TEST_CASE("poisson_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr double p = 0.25; - SECTION("default") - { - auto dist = mp_units::poisson_distribution(); + auto stl_dist = std::geometric_distribution(p); + auto units_dist = mp_units::geometric_distribution(p); - CHECK(dist.mean() == 1.0); + 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("parametrized") + SECTION("poisson_distribution") { - constexpr double mean = 5.0; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::poisson_distribution(mean); - auto units_dist = mp_units::poisson_distribution(mean); + SECTION("default") + { + auto dist = mp_units::poisson_distribution(); - 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); - } -} + CHECK(dist.mean() == 1.0); + } -TEST_CASE("exponential_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr double mean = 5.0; - SECTION("default") - { - auto dist = mp_units::exponential_distribution(); + auto stl_dist = std::poisson_distribution(mean); + auto units_dist = mp_units::poisson_distribution(mean); - CHECK(dist.lambda() == 1.0); + 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("parametrized") + SECTION("exponential_distribution") { - constexpr double lambda = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::exponential_distribution(lambda); - auto units_dist = mp_units::exponential_distribution(lambda); + SECTION("default") + { + auto dist = mp_units::exponential_distribution(); - 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); - } -} + CHECK(dist.lambda() == 1.0); + } -TEST_CASE("gamma_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr double lambda = 2.0; - SECTION("default") - { - auto dist = mp_units::gamma_distribution(); + auto stl_dist = std::exponential_distribution(lambda); + auto units_dist = mp_units::exponential_distribution(lambda); - CHECK(dist.alpha() == 1.0); - CHECK(dist.beta() == 1.0); + 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("parametrized") + SECTION("gamma_distribution") { - constexpr double alpha = 5.0; - constexpr double beta = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::gamma_distribution(alpha, beta); - auto units_dist = mp_units::gamma_distribution(alpha, beta); + SECTION("default") + { + auto dist = mp_units::gamma_distribution(); - 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); - } -} + CHECK(dist.alpha() == 1.0); + CHECK(dist.beta() == 1.0); + } -TEST_CASE("weibull_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr double alpha = 5.0; + constexpr double beta = 2.0; - SECTION("default") - { - auto dist = mp_units::weibull_distribution(); + auto stl_dist = std::gamma_distribution(alpha, beta); + auto units_dist = mp_units::gamma_distribution(alpha, beta); - CHECK(dist.a() == 1.0); - CHECK(dist.b() == 1.0); + 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("parametrized") + SECTION("weibull_distribution") { - constexpr rep a = 5.0; - constexpr rep b = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::weibull_distribution(a, b); - auto units_dist = mp_units::weibull_distribution(a, b); + SECTION("default") + { + auto dist = mp_units::weibull_distribution(); - 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); - } -} + CHECK(dist.a() == 1.0); + CHECK(dist.b() == 1.0); + } -TEST_CASE("extreme_value_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; - SECTION("default") - { - auto dist = mp_units::extreme_value_distribution(); + auto stl_dist = std::weibull_distribution(a, b); + auto units_dist = mp_units::weibull_distribution(a, b); - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == 1.0); + 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("parametrized") + SECTION("extreme_value_distribution") { - constexpr rep a = 5.0; - constexpr rep b = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::extreme_value_distribution(a, b); - auto units_dist = mp_units::extreme_value_distribution(a * si::metre, b); + SECTION("default") + { + auto dist = mp_units::extreme_value_distribution(); - 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); - } -} + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == 1.0); + } -TEST_CASE("normal_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; - SECTION("default") - { - auto dist = mp_units::normal_distribution(); + auto stl_dist = std::extreme_value_distribution(a, b); + auto units_dist = mp_units::extreme_value_distribution(a * si::metre, b); - CHECK(dist.mean() == q::zero()); - CHECK(dist.stddev() == q::one()); + 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("parametrized") + SECTION("normal_distribution") { - constexpr rep mean = 5.0; - constexpr rep stddev = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::normal_distribution(mean, stddev); - auto units_dist = mp_units::normal_distribution(mean * si::metre, stddev * si::metre); + SECTION("default") + { + auto dist = mp_units::normal_distribution(); - 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); - } -} + CHECK(dist.mean() == q::zero()); + CHECK(dist.stddev() == q::one()); + } -TEST_CASE("lognormal_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep mean = 5.0; + constexpr rep stddev = 2.0; - SECTION("default") - { - auto dist = mp_units::lognormal_distribution(); + auto stl_dist = std::normal_distribution(mean, stddev); + auto units_dist = mp_units::normal_distribution(mean * si::metre, stddev * si::metre); - CHECK(dist.m() == q::zero()); - CHECK(dist.s() == q::one()); + 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("parametrized") + SECTION("lognormal_distribution") { - constexpr rep m = 5.0; - constexpr rep s = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::lognormal_distribution(m, s); - auto units_dist = mp_units::lognormal_distribution(m * si::metre, s * si::metre); + SECTION("default") + { + auto dist = mp_units::lognormal_distribution(); - 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); - } -} + CHECK(dist.m() == q::zero()); + CHECK(dist.s() == q::one()); + } -TEST_CASE("chi_squared_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep m = 5.0; + constexpr rep s = 2.0; - SECTION("default") - { - auto dist = mp_units::chi_squared_distribution(); + auto stl_dist = std::lognormal_distribution(m, s); + auto units_dist = mp_units::lognormal_distribution(m * si::metre, s * si::metre); - CHECK(dist.n() == 1.0); + 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("parametrized") + SECTION("chi_squared_distribution") { - constexpr rep n = 5.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::chi_squared_distribution(n); - auto units_dist = mp_units::chi_squared_distribution(n); + SECTION("default") + { + auto dist = mp_units::chi_squared_distribution(); - 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); - } -} + CHECK(dist.n() == 1.0); + } -TEST_CASE("cauchy_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep n = 5.0; - SECTION("default") - { - auto dist = mp_units::cauchy_distribution(); + auto stl_dist = std::chi_squared_distribution(n); + auto units_dist = mp_units::chi_squared_distribution(n); - CHECK(dist.a() == q::zero()); - CHECK(dist.b() == q::one()); + 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("parametrized") + SECTION("cauchy_distribution") { - constexpr rep a = 5.0; - constexpr rep b = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::cauchy_distribution(a, b); - auto units_dist = mp_units::cauchy_distribution(a * si::metre, b * si::metre); + SECTION("default") + { + auto dist = mp_units::cauchy_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); - } -} + CHECK(dist.a() == q::zero()); + CHECK(dist.b() == q::one()); + } -TEST_CASE("fisher_f_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep a = 5.0; + constexpr rep b = 2.0; - SECTION("default") - { - auto dist = mp_units::fisher_f_distribution(); + auto stl_dist = std::cauchy_distribution(a, b); + auto units_dist = mp_units::cauchy_distribution(a * si::metre, b * si::metre); - CHECK(dist.m() == 1.0); - CHECK(dist.n() == 1.0); + 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("fisher_f_distribution") { - constexpr rep m = 5.0; - constexpr rep n = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::fisher_f_distribution(m, n); - auto units_dist = mp_units::fisher_f_distribution(m, n); + SECTION("default") + { + auto dist = mp_units::fisher_f_distribution(); - 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); - } -} + CHECK(dist.m() == 1.0); + CHECK(dist.n() == 1.0); + } -TEST_CASE("student_t_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized") + { + constexpr rep m = 5.0; + constexpr rep n = 2.0; - SECTION("default") - { - auto dist = mp_units::student_t_distribution(); + auto stl_dist = std::fisher_f_distribution(m, n); + auto units_dist = mp_units::fisher_f_distribution(m, n); - CHECK(dist.n() == 1.0); + 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("parametrized") + SECTION("student_t_distribution") { - constexpr rep n = 2.0; + using rep = long double; + using q = quantity; - auto stl_dist = std::student_t_distribution(n); - auto units_dist = mp_units::student_t_distribution(n); + SECTION("default") + { + auto dist = mp_units::student_t_distribution(); - 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); - } -} + CHECK(dist.n() == 1.0); + } -TEST_CASE("discrete_distribution") -{ - using rep = std::int64_t; - using q = quantity; + SECTION("parametrized") + { + constexpr rep n = 2.0; - SECTION("default") - { - auto stl_dist = std::discrete_distribution(); - auto units_dist = mp_units::discrete_distribution(); + auto stl_dist = std::student_t_distribution(n); + auto units_dist = mp_units::student_t_distribution(n); - 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()); + 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("parametrized_input_it") + SECTION("discrete_distribution") { - constexpr std::array weights = {1.0, 2.0, 3.0}; + using rep = std::int64_t; + using q = quantity; - auto stl_dist = std::discrete_distribution(weights.cbegin(), weights.cend()); - auto units_dist = mp_units::discrete_distribution(weights.cbegin(), weights.cend()); + SECTION("default") + { + auto stl_dist = std::discrete_distribution(); + auto units_dist = mp_units::discrete_distribution(); - CHECK(units_dist.probabilities() == stl_dist.probabilities()); - } + 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_initializer_list") - { - const std::initializer_list weights = {1.0, 2.0, 3.0}; + SECTION("parametrized_input_it") + { + constexpr std::array weights = {1.0, 2.0, 3.0}; - auto stl_dist = std::discrete_distribution(weights); - auto units_dist = mp_units::discrete_distribution(weights); + 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()); - } + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } - SECTION("parametrized_range") - { - constexpr std::size_t count = 3; - constexpr double xmin = 1, xmax = 3; + SECTION("parametrized_initializer_list") + { + const std::initializer_list weights = {1.0, 2.0, 3.0}; - 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; }); + auto stl_dist = std::discrete_distribution(weights); + auto units_dist = mp_units::discrete_distribution(weights); - CHECK(units_dist.probabilities() == stl_dist.probabilities()); - } -} + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } -TEST_CASE("piecewise_constant_distribution") -{ - using rep = long double; - using q = quantity; + SECTION("parametrized_range") + { + constexpr std::size_t count = 3; + constexpr double xmin = 1, xmax = 3; - 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]}; + 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; }); - 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); + CHECK(units_dist.probabilities() == stl_dist.probabilities()); + } } - SECTION("parametrized_input_it") + SECTION("piecewise_constant_distribution") { - 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()); - } + using rep = long double; + using q = quantity; - 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]}; + 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]; + SECTION("default") + { + auto stl_dist = std::piecewise_constant_distribution(); + auto units_dist = mp_units::piecewise_constant_distribution(); - 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.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); + } - CHECK(units_dist.intervals() == intervals_qty_vec); - CHECK(units_dist.densities() == stl_dist.densities()); + 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 b1b01f214b..f3bc737aab 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 03eef67676..a163c18775 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("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("operator<< on a quantity", "[text][ostream][fmt]") +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("quantity format string with only %N should print quantity value only") + { + SECTION("integral representation") + { + SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%N}", 123 * isq::speed[km / h]) == "123"); } - SECTION("full format {:%N%?%U} on a quantity") + 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("quantity format string with only %U should print quantity unit symbol 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"); + 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("value only format {:%N} on a quantity") + SECTION("%U and %N can be put anywhere in a format string") { - 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("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") - { - 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"); - } + auto inf = std::numeric_limits::infinity() * si::metre; + auto nan = std::numeric_limits::quiet_NaN() * si::metre; - SECTION("explicit spec") + SECTION("full format {:%N%?%U} 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"); + 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("modified spec") + SECTION("value only format {:%N} on a quantity") { - 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"); + SECTION("full format on a quantity") + { + 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"); + } } -} -TEST_CASE("type specification", "[text][fmt]") -{ - SECTION("full format {:%N%?%U} on a quantity") + SECTION("type specification") { - SECTION("default spec") + SECTION("full format {:%N%?%U} 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[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"); + 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("different base types with the # specifier") { - SECTION("default spec") + SECTION("full format {:%N%?%U} on a quantity") { - 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("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("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("modified spec") + SECTION("value only format {:%N} on a quantity") { - 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[#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("value only format {:%N} on a quantity") + SECTION("localization with the 'L' 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"); - } -} + struct group2 : std::numpunct { + [[nodiscard]] char do_thousands_sep() const override { return '_'; } + [[nodiscard]] std::string do_grouping() const override { return "\2"; } + }; -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"; } + }; - 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}; - 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("default spec") + SECTION("full format on a quantity") { - 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("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("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"); + 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/linear_algebra_test.cpp b/test/runtime/linear_algebra_test.cpp index 4799decdf2..85a7afc72c 100644 --- a/test/runtime/linear_algebra_test.cpp +++ b/test/runtime/linear_algebra_test.cpp @@ -103,13 +103,13 @@ TEST_CASE("vector quantity", "[la]") { SECTION("non-truncating") { - const auto v = vector{3, 2, 1} * isq::position_vector[km]; + const auto v = vector{3, 2, 1} * isq::displacement[km]; CHECK(v.numerical_value_in(m) == vector{3000, 2000, 1000}); } SECTION("truncating") { - const auto v = vector{1001, 1002, 1003} * isq::position_vector[m]; + const auto v = vector{1001, 1002, 1003} * isq::displacement[m]; CHECK(v.force_numerical_value_in(km) == vector{1, 1, 1}); } } @@ -123,7 +123,7 @@ TEST_CASE("vector quantity", "[la]") SECTION("multiply by scalar value") { - const auto v = vector{1, 2, 3} * isq::position_vector[m]; + const auto v = vector{1, 2, 3} * isq::displacement[m]; SECTION("integral") { @@ -140,7 +140,7 @@ TEST_CASE("vector quantity", "[la]") SECTION("divide by scalar value") { - const auto v = vector{2, 4, 6} * isq::position_vector[m]; + const auto v = vector{2, 4, 6} * isq::displacement[m]; SECTION("integral") { CHECK((v / 2).numerical_value_in(m) == vector{1, 2, 3}); } SECTION("floating-point") { CHECK((v / 0.5).numerical_value_in(m) == vector{4., 8., 12.}); } @@ -148,32 +148,32 @@ TEST_CASE("vector quantity", "[la]") SECTION("add") { - const auto v = vector{1, 2, 3} * isq::position_vector[m]; + const auto v = vector{1, 2, 3} * isq::displacement[m]; SECTION("same unit") { - const auto u = vector{3, 2, 1} * isq::position_vector[m]; + const auto u = vector{3, 2, 1} * isq::displacement[m]; CHECK((v + u).numerical_value_in(m) == vector{4, 4, 4}); } SECTION("different units") { - const auto u = vector{3, 2, 1} * isq::position_vector[km]; + const auto u = vector{3, 2, 1} * isq::displacement[km]; CHECK((v + u).numerical_value_in(m) == vector{3001, 2002, 1003}); } } SECTION("subtract") { - const auto v = vector{1, 2, 3} * isq::position_vector[m]; + const auto v = vector{1, 2, 3} * isq::displacement[m]; SECTION("same unit") { - const auto u = vector{3, 2, 1} * isq::position_vector[m]; + const auto u = vector{3, 2, 1} * isq::displacement[m]; CHECK((v - u).numerical_value_in(m) == vector{-2, 0, 2}); } SECTION("different units") { - const auto u = vector{3, 2, 1} * isq::position_vector[km]; + const auto u = vector{3, 2, 1} * isq::displacement[km]; CHECK((v - u).numerical_value_in(m) == vector{-2999, -1998, -997}); } } @@ -255,7 +255,7 @@ TEST_CASE("vector quantity", "[la]") SECTION("divide by scalar quantity") { - const auto pos = vector{30, 20, 10} * isq::position_vector[km]; + const auto pos = vector{30, 20, 10} * isq::displacement[km]; SECTION("integral") { @@ -292,7 +292,7 @@ TEST_CASE("vector quantity", "[la]") SECTION("cross product with a vector quantity") { - const auto r = vector{3, 0, 0} * isq::position_vector[m]; + const auto r = vector{3, 0, 0} * isq::displacement[m]; const auto f = vector{0, 10, 0} * isq::force[N]; CHECK(cross_product(r, f) == vector{0, 0, 30} * isq::moment_of_force[N * m]); @@ -309,10 +309,10 @@ TEST_CASE("vector of quantities", "[la]") { SECTION("non-truncating") { - const vector> v = {3 * km, 2 * km, 1 * km}; + const vector> v = {3 * km, 2 * km, 1 * km}; - CHECK(vector>(v) == - vector>{3000 * m, 2000 * m, 1000 * m}); + CHECK(vector>(v) == + vector>{3000 * m, 2000 * m, 1000 * m}); } // truncating not possible (no way to apply quantity_cast to sub-components of a vector) @@ -327,11 +327,11 @@ TEST_CASE("vector of quantities", "[la]") SECTION("multiply by scalar value") { - const vector> v = {1 * m, 2 * m, 3 * m}; + const vector> v = {1 * m, 2 * m, 3 * m}; SECTION("integral") { - const vector> result = {2 * m, 4 * m, 6 * m}; + const vector> result = {2 * m, 4 * m, 6 * m}; SECTION("scalar on LHS") { CHECK(2 * v == result); } SECTION("scalar on RHS") { CHECK(v * 2 == result); } @@ -339,7 +339,7 @@ TEST_CASE("vector of quantities", "[la]") SECTION("floating-point") { - const vector> result = {0.5 * m, 1. * m, 1.5 * m}; + const vector> result = {0.5 * m, 1. * m, 1.5 * m}; SECTION("scalar on LHS") { CHECK(0.5 * v == result); } SECTION("scalar on RHS") { CHECK(v * 0.5 == result); } @@ -348,46 +348,46 @@ TEST_CASE("vector of quantities", "[la]") SECTION("divide by scalar value") { - const vector> v = {2 * m, 4 * m, 6 * m}; + const vector> v = {2 * m, 4 * m, 6 * m}; - SECTION("integral") { CHECK(v / 2 == vector>{1 * m, 2 * m, 3 * m}); } + SECTION("integral") { CHECK(v / 2 == vector>{1 * m, 2 * m, 3 * m}); } SECTION("floating-point") { - CHECK(v / 0.5 == vector>{4. * m, 8. * m, 12. * m}); + CHECK(v / 0.5 == vector>{4. * m, 8. * m, 12. * m}); } } SECTION("add") { - const vector> v = {1 * m, 2 * m, 3 * m}; + const vector> v = {1 * m, 2 * m, 3 * m}; SECTION("same unit") { - const vector> u = {3 * m, 2 * m, 1 * m}; + const vector> u = {3 * m, 2 * m, 1 * m}; - CHECK(v + u == vector>{4 * m, 4 * m, 4 * m}); + CHECK(v + u == vector>{4 * m, 4 * m, 4 * m}); } SECTION("different units") { - const vector> u = {3 * km, 2 * km, 1 * km}; + const vector> u = {3 * km, 2 * km, 1 * km}; - CHECK(v + u == vector>{3001 * m, 2002 * m, 1003 * m}); + CHECK(v + u == vector>{3001 * m, 2002 * m, 1003 * m}); } } SECTION("subtract") { - const vector> v = {1 * m, 2 * m, 3 * m}; + const vector> v = {1 * m, 2 * m, 3 * m}; SECTION("same unit") { - const vector> u = {3 * m, 2 * m, 1 * m}; - CHECK(v - u == vector>{-2 * m, 0 * m, 2 * m}); + const vector> u = {3 * m, 2 * m, 1 * m}; + CHECK(v - u == vector>{-2 * m, 0 * m, 2 * m}); } SECTION("different units") { - const vector> u = {3 * km, 2 * km, 1 * km}; - CHECK(v - u == vector>{-2999 * m, -1998 * m, -997 * m}); + const vector> u = {3 * km, 2 * km, 1 * km}; + CHECK(v - u == vector>{-2999 * m, -1998 * m, -997 * m}); } } @@ -454,7 +454,7 @@ TEST_CASE("vector of quantities", "[la]") SECTION("divide by scalar quantity") { - const vector> pos = {30 * km, 20 * km, 10 * km}; + const vector> pos = {30 * km, 20 * km, 10 * km}; SECTION("integral") { @@ -495,7 +495,7 @@ TEST_CASE("vector of quantities", "[la]") SECTION("cross product with a vector of quantities") { - const vector> r = {3 * m, 0 * m, 0 * m}; + const vector> r = {3 * m, 0 * m, 0 * m}; const vector> f = {0 * N, 10 * N, 0 * N}; CHECK(cross_product(r, f) == vector>{0 * N * m, 0 * N * m, 30 * N * m}); diff --git a/test/runtime/math_test.cpp b/test/runtime/math_test.cpp index 79018bff87..bee5f7e807 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<3>(q)' cubes both the value and a dimension") - { - CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]); - } -} + SECTION("'pow<1>(q)' returns 'q'") { CHECK(pow<1>(2 * isq::length[m]) == 2 * isq::length[m]); } -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]); -} + SECTION("'pow<2>(q)' squares both the value and a dimension") + { + CHECK(pow<2>(2 * isq::length[m]) == 4 * isq::area[m2]); + } -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("'pow<3>(q)' cubes both the value and a dimension") + { + CHECK(pow<3>(2 * isq::length[m]) == 8 * isq::volume[m3]); + } } - 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") + SECTION("'sqrt()' on quantity changes the value and the dimension accordingly") { - 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]); + REQUIRE(sqrt(4 * isq::area[m2]) == 2 * 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("'cbrt()' on quantity changes the value and the dimension accordingly") { - 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]); } + REQUIRE(cbrt(8 * isq::volume[m3]) == 2 * isq::length[m]); } - SECTION("'abs()' on a positive quantity returns the abs") + SECTION("'fma()' on quantity changes the value and the dimension accordingly") { - 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]); } + 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("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") + SECTION("fmod functions") { - REQUIRE(epsilon(isq::length[m]).numerical_value_in(m) == - std::numeric_limits::epsilon()); + 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("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") + SECTION("remainder functions") { - 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]); + 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]); + } } - // TODO Add tests for `N`, `kN` and `kg * m / s2` i `kg * km / s2` -} + SECTION("'isfinite()' accepts dimensioned arguments") { REQUIRE(isfinite(4.0 * isq::length[m])); } -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]); - } -} + SECTION("'isinf()' accepts dimensioned arguments") { REQUIRE(!isinf(4.0 * isq::length[m])); } -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]); - } -} + SECTION("'isnan()' accepts dimensioned arguments") { REQUIRE(!isnan(4.0 * isq::length[m])); } -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])); + + 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 14dec787cf..0984373ecc 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 c027b601af..9ea79b1fa9 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/concepts_test.cpp b/test/static/concepts_test.cpp index c79e8851ef..fbd20241a1 100644 --- a/test/static/concepts_test.cpp +++ b/test/static/concepts_test.cpp @@ -23,6 +23,10 @@ #include #include #include +#if MP_UNITS_HOSTED +#include +#include +#endif #ifdef MP_UNITS_IMPORT_STD import std; #else @@ -35,12 +39,6 @@ import std; #endif #endif -#if MP_UNITS_HOSTED -template -constexpr bool mp_units::is_complex> = true; -#endif - - namespace { using namespace mp_units; @@ -58,7 +56,7 @@ static_assert(!detail::BaseDimension) static_assert(!detail::BaseDimension); static_assert(!detail::BaseDimension(isq::dim_length))>); static_assert(!detail::BaseDimension>>); -static_assert(!detail::BaseDimension>); +static_assert(!detail::BaseDimension); static_assert(!detail::BaseDimension>); static_assert(!detail::BaseDimension); static_assert(!detail::BaseDimension); @@ -70,7 +68,7 @@ static_assert(Dimension); static_assert(Dimension(isq::dim_length))>); static_assert(Dimension>>); static_assert(Dimension); -static_assert(Dimension>); +static_assert(Dimension); static_assert(!Dimension>); static_assert(!Dimension); static_assert(!Dimension); @@ -84,11 +82,11 @@ inline constexpr auto speed = isq::length / isq::time; static_assert(QuantitySpec); static_assert(QuantitySpec); static_assert(QuantitySpec); -static_assert(QuantitySpec)>); +static_assert(QuantitySpec>); static_assert(QuantitySpec); static_assert(QuantitySpec(isq::length))>); static_assert(QuantitySpec); -static_assert(QuantitySpec>); +static_assert(QuantitySpec); static_assert(!QuantitySpec); static_assert(!QuantitySpec); @@ -96,35 +94,35 @@ static_assert(!QuantitySpec); static_assert(detail::NamedQuantitySpec); static_assert(detail::NamedQuantitySpec); static_assert(detail::NamedQuantitySpec); -static_assert(!detail::NamedQuantitySpec)>>); +static_assert(!detail::NamedQuantitySpec>); static_assert(!detail::NamedQuantitySpec); static_assert(!detail::NamedQuantitySpec(isq::length))>); static_assert(detail::NamedQuantitySpec); -static_assert(!detail::NamedQuantitySpec>); +static_assert(!detail::NamedQuantitySpec); static_assert(!detail::NamedQuantitySpec); static_assert(!detail::NamedQuantitySpec); // DerivedQuantitySpec static_assert(!detail::DerivedQuantitySpec); static_assert(!detail::DerivedQuantitySpec); -static_assert(!detail::DerivedQuantitySpec)>); +static_assert(!detail::DerivedQuantitySpec>); static_assert(!detail::DerivedQuantitySpec); static_assert(detail::DerivedQuantitySpec); static_assert(detail::DerivedQuantitySpec(isq::length))>); static_assert(!detail::DerivedQuantitySpec); -static_assert(detail::DerivedQuantitySpec>); +static_assert(detail::DerivedQuantitySpec); static_assert(!detail::DerivedQuantitySpec); static_assert(!detail::DerivedQuantitySpec); // QuantityKindSpec static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); -static_assert(detail::QuantityKindSpec)>>); +static_assert(detail::QuantityKindSpec>); static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec(isq::length))>); static_assert(!detail::QuantityKindSpec); -static_assert(!detail::QuantityKindSpec>); +static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); @@ -133,8 +131,8 @@ static_assert(!detail::QuantityKindSpec); // Unit static_assert(Unit); -static_assert(Unit); -static_assert(Unit)>); +static_assert(Unit); +static_assert(Unit>); static_assert(Unit); static_assert(Unit); static_assert(Unit); @@ -159,8 +157,8 @@ static_assert(!Unit); // PrefixableUnit static_assert(PrefixableUnit); static_assert(PrefixableUnit); -static_assert(!PrefixableUnit); -static_assert(!PrefixableUnit)>); +static_assert(!PrefixableUnit); +static_assert(!PrefixableUnit>); static_assert(!PrefixableUnit); static_assert(!PrefixableUnit); static_assert(!PrefixableUnit * si::second)>); @@ -184,8 +182,8 @@ static_assert(!PrefixableUnit); // AssociatedUnit static_assert(AssociatedUnit); static_assert(!AssociatedUnit); -static_assert(AssociatedUnit); -static_assert(AssociatedUnit)>); +static_assert(AssociatedUnit); +static_assert(AssociatedUnit>); static_assert(AssociatedUnit); static_assert(AssociatedUnit); static_assert(AssociatedUnit * si::second)>); @@ -209,7 +207,7 @@ static_assert(!AssociatedUnit); // UnitOf static_assert(UnitOf); static_assert(UnitOf); -static_assert(UnitOf); +static_assert(UnitOf); static_assert(UnitOf); static_assert(UnitOf); static_assert(UnitOf); @@ -230,7 +228,7 @@ static_assert(Reference); static_assert(Reference); static_assert(!Reference); static_assert(!Reference); -static_assert(!Reference)>); +static_assert(!Reference>); static_assert(!Reference); static_assert(!Reference); @@ -272,17 +270,31 @@ static_assert(!Representation>); static_assert(Representation>); static_assert(Representation>); static_assert(Representation>); +static_assert(Representation>); static_assert(!Representation); static_assert(!Representation); #endif // RepresentationOf static_assert(RepresentationOf); +static_assert(!RepresentationOf); +static_assert(!RepresentationOf); +static_assert(!RepresentationOf); static_assert(RepresentationOf); +static_assert(!RepresentationOf); +static_assert(!RepresentationOf); +static_assert(!RepresentationOf); static_assert(!RepresentationOf); static_assert(!RepresentationOf, quantity_character::scalar>); #if MP_UNITS_HOSTED static_assert(RepresentationOf, quantity_character::complex>); +static_assert(!RepresentationOf, quantity_character::scalar>); +static_assert(!RepresentationOf, quantity_character::vector>); +static_assert(!RepresentationOf, quantity_character::tensor>); +static_assert(RepresentationOf, quantity_character::vector>); +static_assert(!RepresentationOf, quantity_character::scalar>); +static_assert(!RepresentationOf, quantity_character::complex>); +static_assert(!RepresentationOf, quantity_character::tensor>); static_assert(!RepresentationOf); static_assert(!RepresentationOf); #endif diff --git a/test/static/custom_rep_test_min_impl.cpp b/test/static/custom_rep_test_min_impl.cpp index 4fb2537b76..e2810eaacc 100644 --- a/test/static/custom_rep_test_min_impl.cpp +++ b/test/static/custom_rep_test_min_impl.cpp @@ -86,7 +86,7 @@ static_assert(!std::convertible_to, quantity -concept creates_quantity = Unit && requires { T{} * U; }; +concept creates_quantity = Unit && requires { T{} * U; }; static_assert(creates_quantity, si::metre>); static_assert(creates_quantity, si::metre>); diff --git a/test/static/dimension_test.cpp b/test/static/dimension_test.cpp index c354e03c97..d022aacdba 100644 --- a/test/static/dimension_test.cpp +++ b/test/static/dimension_test.cpp @@ -65,9 +65,9 @@ inline constexpr auto energy = force * length; // concepts verification static_assert(detail::BaseDimension); -static_assert(!detail::BaseDimension>); +static_assert(!detail::BaseDimension); static_assert(Dimension); -static_assert(Dimension>); +static_assert(Dimension); static_assert(detail::BaseDimension); // length diff --git a/test/static/magnitude_test.cpp b/test/static/magnitude_test.cpp index 146e709aeb..72bc8af3ec 100644 --- a/test/static/magnitude_test.cpp +++ b/test/static/magnitude_test.cpp @@ -31,9 +31,6 @@ import mp_units; using namespace units; using namespace units::detail; -template<> -constexpr std::optional units::known_first_factor<9223372036854775783> = 9223372036854775783; - namespace { // A set of non-standard bases for testing purposes. @@ -182,20 +179,6 @@ static_assert(std::is_same_v{})), mag_2_> // mag_ratio<16'605'390'666'050, 10'000'000'000'000>(); // } -// SECTION("Can bypass computing primes by providing known_first_factor") -// { -// // Sometimes, even wheel factorization isn't enough to handle the compilers' limits on constexpr steps and/or -// // iterations. To work around these cases, we can explicitly provide the correct answer directly to the -// compiler. -// // -// // In this case, we test that we can represent the largest prime that fits in a signed 64-bit int. The reason -// this -// // test can pass is that we have provided the answer, by specializing the `known_first_factor` variable template -// // above in this file. -// mag<9'223'372'036'854'775'783>(); -// } -// } - // TEST_CASE("magnitude converts to numerical value") // { // SECTION("Positive integer powers of integer bases give integer values") diff --git a/test/static/math_test.cpp b/test/static/math_test.cpp index a573fd1f0e..48682ee820 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/prime_test.cpp b/test/static/prime_test.cpp index d3c50f09ea..d62e49f71b 100644 --- a/test/static/prime_test.cpp +++ b/test/static/prime_test.cpp @@ -33,49 +33,147 @@ using namespace mp_units::detail; namespace { -template -constexpr bool check_primes(std::index_sequence) -{ - return ((Is < 2 || wheel_factorizer::is_prime(Is) == is_prime_by_trial_division(Is)) && ...); -} +inline constexpr auto MAX_U64 = std::numeric_limits::max(); -static_assert(check_primes<2>(std::make_index_sequence<122>{})); +// Modular arithmetic. +static_assert(add_mod(1u, 2u, 5u) == 3u); +static_assert(add_mod(4u, 4u, 5u) == 3u); +static_assert(add_mod(MAX_U64 - 1u, MAX_U64 - 2u, MAX_U64) == MAX_U64 - 3u); -// This is the smallest number that can catch the bug where we use only _prime_ numbers in the first wheel, rather than -// numbers which are _coprime to the basis_. -// -// The basis for N = 4 is {2, 3, 5, 7}, so the wheel size is 210. 11 * 11 = 121 is within the first wheel. It is -// coprime with every element of the basis, but it is _not_ prime. If we keep only prime numbers, then we will neglect -// using numbers of the form (210 * n + 121) as trial divisors, which is a problem if any are prime. For n = 1, we have -// a divisor of (210 + 121 = 331), which happens to be prime but will not be used. Thus, (331 * 331 = 109561) is a -// composite number which could wrongly appear prime if we skip over 331. -static_assert(wheel_factorizer<4>::is_prime(109'561) == is_prime_by_trial_division(109'561)); - -static_assert(wheel_factorizer<1>::coprimes_in_first_wheel.size() == 1); -static_assert(wheel_factorizer<2>::coprimes_in_first_wheel.size() == 2); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel.size() == 8); -static_assert(wheel_factorizer<4>::coprimes_in_first_wheel.size() == 48); -static_assert(wheel_factorizer<5>::coprimes_in_first_wheel.size() == 480); - -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[0] == 1); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[1] == 7); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[2] == 11); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[3] == 13); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[4] == 17); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[5] == 19); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[6] == 23); -static_assert(wheel_factorizer<3>::coprimes_in_first_wheel[7] == 29); - -static_assert(!wheel_factorizer<1>::is_prime(0)); -static_assert(!wheel_factorizer<1>::is_prime(1)); -static_assert(wheel_factorizer<1>::is_prime(2)); - -static_assert(!wheel_factorizer<2>::is_prime(0)); -static_assert(!wheel_factorizer<2>::is_prime(1)); -static_assert(wheel_factorizer<2>::is_prime(2)); - -static_assert(!wheel_factorizer<3>::is_prime(0)); -static_assert(!wheel_factorizer<3>::is_prime(1)); -static_assert(wheel_factorizer<3>::is_prime(2)); +static_assert(sub_mod(2u, 1u, 5u) == 1u); +static_assert(sub_mod(1u, 2u, 5u) == 4u); +static_assert(sub_mod(MAX_U64 - 2u, MAX_U64 - 1u, MAX_U64) == MAX_U64 - 1u); +static_assert(sub_mod(1u, MAX_U64 - 1u, MAX_U64) == 2u); + +static_assert(mul_mod(6u, 7u, 10u) == 2u); +static_assert(mul_mod(13u, 11u, 50u) == 43u); +static_assert(mul_mod(MAX_U64 / 2u, 10u, MAX_U64) == MAX_U64 - 5u); + +static_assert(half_mod_odd(0u, 11u) == 0u); +static_assert(half_mod_odd(10u, 11u) == 5u); +static_assert(half_mod_odd(1u, 11u) == 6u); +static_assert(half_mod_odd(9u, 11u) == 10u); +static_assert(half_mod_odd(MAX_U64 - 1u, MAX_U64) == (MAX_U64 - 1u) / 2u); +static_assert(half_mod_odd(MAX_U64 - 2u, MAX_U64) == MAX_U64 - 1u); + +static_assert(pow_mod(5u, 8u, 9u) == ((5u * 5u * 5u * 5u) * (5u * 5u * 5u * 5u)) % 9u); +static_assert(pow_mod(2u, 64u, MAX_U64) == 1u); + +// Miller-Rabin primality testing. +static_assert(miller_rabin_probable_prime(2u, 5u)); +static_assert(miller_rabin_probable_prime(2u, 7u)); +static_assert(!miller_rabin_probable_prime(2u, 9u)); +static_assert(miller_rabin_probable_prime(2u, 11u)); + +static_assert(miller_rabin_probable_prime(2u, 2047u), "Known base 2 pseudoprime"); +static_assert(miller_rabin_probable_prime(2u, 3277u), "Known base 2 pseudoprime"); + +static_assert(miller_rabin_probable_prime(3u, 121u), "Known base 3 pseudoprime"); +static_assert(miller_rabin_probable_prime(3u, 703u), "Known base 3 pseudoprime"); + +static_assert(miller_rabin_probable_prime(2u, 225'653'407'801u), "Large known prime"); +static_assert(miller_rabin_probable_prime(2u, 334'524'384'739u), "Large known prime"); +static_assert(miller_rabin_probable_prime(2u, 9'007'199'254'740'881u), "Large known prime"); + +static_assert(miller_rabin_probable_prime(2u, 18'446'744'073'709'551'557u), "Largest 64-bit prime"); + +// Jacobi symbols --- a building block for the Strong Lucas probable prime test, needed for Baillie-PSW. +static_assert(jacobi_symbol(1, 1u) == 1, "Jacobi symbol always 1 when 'numerator' is 1"); +static_assert(jacobi_symbol(1, 3u) == 1, "Jacobi symbol always 1 when 'numerator' is 1"); +static_assert(jacobi_symbol(1, 5u) == 1, "Jacobi symbol always 1 when 'numerator' is 1"); +static_assert(jacobi_symbol(1, 987654321u) == 1, "Jacobi symbol always 1 when 'numerator' is 1"); + +static_assert(jacobi_symbol(3, 1u) == 1, "Jacobi symbol always 1 when 'denominator' is 1"); +static_assert(jacobi_symbol(5, 1u) == 1, "Jacobi symbol always 1 when 'denominator' is 1"); +static_assert(jacobi_symbol(-1234567890, 1u) == 1, "Jacobi symbol always 1 when 'denominator' is 1"); + +static_assert(jacobi_symbol(10, 5u) == 0, "Jacobi symbol always 0 when there's a common factor"); +static_assert(jacobi_symbol(25, 15u) == 0, "Jacobi symbol always 0 when there's a common factor"); +static_assert(jacobi_symbol(-24, 9u) == 0, "Jacobi symbol always 0 when there's a common factor"); + +static_assert(jacobi_symbol(14, 9u) == +jacobi_symbol(7, 9u), + "Divide numerator by 2: positive when (denom % 8) in {1, 7}"); +static_assert(jacobi_symbol(14, 15u) == +jacobi_symbol(7, 15u), + "Divide numerator by 2: positive when (denom % 8) in {1, 7}"); +static_assert(jacobi_symbol(14, 11u) == -jacobi_symbol(7, 11u), + "Divide numerator by 2: negative when (denom % 8) in {3, 5}"); +static_assert(jacobi_symbol(14, 13u) == -jacobi_symbol(7, 13u), + "Divide numerator by 2: negative when (denom % 8) in {3, 5}"); + +static_assert(jacobi_symbol(19, 9u) == +jacobi_symbol(9, 19u), "Flip is identity when (n % 4) = 1"); +static_assert(jacobi_symbol(17, 7u) == +jacobi_symbol(7, 17u), "Flip is identity when (a % 4) = 1"); +static_assert(jacobi_symbol(19, 7u) == -jacobi_symbol(9, 7u), "Flip changes sign when (n % 4) = 3 and (a % 4) = 3"); + +static_assert(jacobi_symbol(1001, 9907u) == -1, "Example from Wikipedia page"); +static_assert(jacobi_symbol(19, 45u) == 1, "Example from Wikipedia page"); +static_assert(jacobi_symbol(8, 21u) == -1, "Example from Wikipedia page"); +static_assert(jacobi_symbol(5, 21u) == 1, "Example from Wikipedia page"); + +// Tests for perfect square finder +static_assert(is_perfect_square(0u)); +static_assert(is_perfect_square(1u)); +static_assert(!is_perfect_square(2u)); +static_assert(is_perfect_square(4u)); + +constexpr std::uint64_t BIG_SQUARE = [](auto x) { return x * x; }((std::uint64_t{1u} << 32) - 1u); +static_assert(!is_perfect_square(BIG_SQUARE - 1u)); +static_assert(is_perfect_square(BIG_SQUARE)); +static_assert(!is_perfect_square(BIG_SQUARE + 1u)); + +// Tests for the Strong Lucas Probable Prime test. +static_assert(as_int(LucasDParameter{.mag = 5, .pos = true}) == 5); +static_assert(as_int(LucasDParameter{.mag = 7, .pos = false}) == -7); + +static_assert(as_int(LucasDParameter{}) == 5, "First D parameter in the sequence is 5"); +static_assert(as_int(successor(LucasDParameter{})) == -7, "Incrementing adds 2 to the mag, and flips the sign"); +static_assert(as_int(successor(successor(LucasDParameter{}))) == 9); +static_assert(as_int(successor(successor(successor(LucasDParameter{})))) == -11); + +static_assert(strong_lucas_probable_prime(3u), "Known small prime"); +static_assert(strong_lucas_probable_prime(5u), "Known small prime"); +static_assert(strong_lucas_probable_prime(7u), "Known small prime"); +static_assert(!strong_lucas_probable_prime(9u), "Known small composite"); + +// Test some Miller-Rabin pseudoprimes (https://oeis.org/A001262), which should NOT be marked prime. +static_assert(!strong_lucas_probable_prime(2047u), "Miller-Rabin pseudoprime"); +static_assert(!strong_lucas_probable_prime(3277u), "Miller-Rabin pseudoprime"); +static_assert(!strong_lucas_probable_prime(486737u), "Miller-Rabin pseudoprime"); + +// Test some Strong Lucas pseudoprimes (https://oeis.org/A217255). +static_assert(strong_lucas_probable_prime(5459u), "Strong Lucas pseudoprime"); +static_assert(strong_lucas_probable_prime(5777u), "Strong Lucas pseudoprime"); +static_assert(strong_lucas_probable_prime(10877u), "Strong Lucas pseudoprime"); +static_assert(strong_lucas_probable_prime(324899u), "Strong Lucas pseudoprime"); + +// Test some actual primes +static_assert(strong_lucas_probable_prime(225'653'407'801u), "Large known prime"); +static_assert(strong_lucas_probable_prime(334'524'384'739u), "Large known prime"); +static_assert(strong_lucas_probable_prime(9'007'199'254'740'881u), "Large known prime"); + +static_assert(strong_lucas_probable_prime(18'446'744'073'709'551'557u), "Largest 64-bit prime"); + +// Tests for Baillie-PSW, which is known to be correct for all 64-bit integers. +static_assert(baillie_psw_probable_prime(3u), "Known small prime"); +static_assert(baillie_psw_probable_prime(5u), "Known small prime"); +static_assert(baillie_psw_probable_prime(7u), "Known small prime"); +static_assert(!baillie_psw_probable_prime(9u), "Known small composite"); + +// Test some Miller-Rabin pseudoprimes (https://oeis.org/A001262), which should NOT be marked prime. +static_assert(!baillie_psw_probable_prime(2047u), "Miller-Rabin pseudoprime"); +static_assert(!baillie_psw_probable_prime(3277u), "Miller-Rabin pseudoprime"); +static_assert(!baillie_psw_probable_prime(486737u), "Miller-Rabin pseudoprime"); + +// Test some Strong Lucas pseudoprimes (https://oeis.org/A217255), which should NOT be marked prime. +static_assert(!baillie_psw_probable_prime(5459u), "Strong Lucas pseudoprime"); +static_assert(!baillie_psw_probable_prime(5777u), "Strong Lucas pseudoprime"); +static_assert(!baillie_psw_probable_prime(10877u), "Strong Lucas pseudoprime"); +static_assert(!baillie_psw_probable_prime(324899u), "Strong Lucas pseudoprime"); + +// Test some actual primes +static_assert(baillie_psw_probable_prime(225'653'407'801u), "Large known prime"); +static_assert(baillie_psw_probable_prime(334'524'384'739u), "Large known prime"); +static_assert(baillie_psw_probable_prime(9'007'199'254'740'881u), "Large known prime"); + +static_assert(baillie_psw_probable_prime(18'446'744'073'709'551'557u), "Largest 64-bit prime"); } // namespace diff --git a/test/static/quantity_point_test.cpp b/test/static/quantity_point_test.cpp index 990312c4a4..6b3fe08128 100644 --- a/test/static/quantity_point_test.cpp +++ b/test/static/quantity_point_test.cpp @@ -888,9 +888,9 @@ static_assert(invalid_unit_conversion); ///////// static_assert(std::is_same_v); -static_assert(std::is_same_v, +static_assert(std::is_same_v>>); -static_assert(std::is_same_v, +static_assert(std::is_same_v>>); static_assert(quantity_point{123 * m}.unit == si::metre); static_assert(quantity_point{123 * m}.quantity_spec == kind_of); @@ -914,9 +914,9 @@ static_assert(quantity_point{delta(20)}.quantity_spec == kind_of); -static_assert(std::is_same_v, +static_assert(std::is_same_v>); -static_assert(std::is_same_v, +static_assert(std::is_same_v>); static_assert(quantity_point{sys_seconds{24h}}.unit == si::second); static_assert(quantity_point{sys_seconds{24h}}.quantity_spec == kind_of); diff --git a/test/static/quantity_spec_test.cpp b/test/static/quantity_spec_test.cpp index 1c7bba626b..9db5c448ad 100644 --- a/test/static/quantity_spec_test.cpp +++ b/test/static/quantity_spec_test.cpp @@ -56,6 +56,7 @@ inline constexpr auto arc_length = path_length; QUANTITY_SPEC_(distance, path_length); QUANTITY_SPEC_(wavelength, length); QUANTITY_SPEC_(position_vector, length, quantity_character::vector); +QUANTITY_SPEC_(displacement, length, quantity_character::vector); QUANTITY_SPEC_(period_duration, time); QUANTITY_SPEC_(rotation, dimensionless); QUANTITY_SPEC_(repetency, inverse(wavelength)); @@ -68,7 +69,7 @@ QUANTITY_SPEC_(rotational_displacement, angular_measure, path_length / radius); QUANTITY_SPEC_(phase_angle, angular_measure); QUANTITY_SPEC_(solid_angular_measure, dimensionless, area / pow<2>(radius), is_kind); QUANTITY_SPEC_(speed, length / time); -QUANTITY_SPEC_(velocity, speed, position_vector / time); +QUANTITY_SPEC_(velocity, speed, displacement / time); QUANTITY_SPEC_(special_speed, speed); QUANTITY_SPEC_(rate_of_climb, speed, height / time); QUANTITY_SPEC_(special_rate_of_climb, rate_of_climb); @@ -377,9 +378,9 @@ static_assert(force * length != torque); static_assert(force * position_vector != energy); static_assert(force * position_vector != torque); static_assert(length / time != speed); -static_assert(position_vector / time != speed); +static_assert(displacement / time != speed); static_assert(length / time != velocity); -static_assert(position_vector / time != velocity); +static_assert(displacement / time != velocity); static_assert(length * time / period_duration != time); static_assert(length * height / width != length); @@ -390,12 +391,12 @@ static_assert(length / speed != time); static_assert(speed * time != length); static_assert(length / time / time != acceleration); -static_assert(position_vector / time / time != acceleration); -static_assert(position_vector / (time * time) != acceleration); +static_assert(displacement / time / time != acceleration); +static_assert(displacement / (time * time) != acceleration); static_assert(velocity / time != acceleration); static_assert(velocity / acceleration != time); static_assert(acceleration * time != velocity); -static_assert(acceleration * (time * time) != position_vector); +static_assert(acceleration * (time * time) != displacement); static_assert(acceleration / speed != frequency); // get_kind @@ -471,7 +472,7 @@ static_assert(get_complexity(speed * area / frequency) == 1); // explode static_assert(explode(frequency).quantity == inverse(period_duration)); static_assert(explode)>(speed).quantity == length / time); -static_assert(explode)>(velocity).quantity == position_vector / time); +static_assert(explode)>(velocity).quantity == displacement / time); static_assert(explode(angular_measure).quantity == arc_length / radius); static_assert(explode(acceleration * time).quantity == velocity); static_assert(explode(area).quantity == area); @@ -582,8 +583,8 @@ static_assert(convertible_impl(inverse(frequency), time) == yes); static_assert(convertible_impl(inverse(period_duration), frequency) == yes); static_assert(convertible_impl(length * length, area) == yes); static_assert(convertible_impl(length / time, speed) == yes); -static_assert(convertible_impl(position_vector / time, speed) == yes); -static_assert(convertible_impl(position_vector / time, velocity) == yes); +static_assert(convertible_impl(displacement / time, speed) == yes); +static_assert(convertible_impl(displacement / time, velocity) == yes); static_assert(convertible_impl(height / time, speed) == yes); static_assert(convertible_impl(height / time, rate_of_climb) == yes); static_assert(convertible_impl(area / length, length) == yes); @@ -597,12 +598,12 @@ static_assert(convertible_impl(area * (area / length), volume) == yes); static_assert(convertible_impl(volume / (length * length), length) == yes); static_assert(convertible_impl(length / speed, time) == yes); static_assert(convertible_impl(speed * time, length) == yes); -static_assert(convertible_impl(position_vector / time / time, acceleration) == yes); -static_assert(convertible_impl(position_vector / (time * time), acceleration) == yes); +static_assert(convertible_impl(displacement / time / time, acceleration) == yes); +static_assert(convertible_impl(displacement / (time * time), acceleration) == yes); static_assert(convertible_impl(velocity / time, acceleration) == yes); static_assert(convertible_impl(velocity / acceleration, time) == yes); static_assert(convertible_impl(acceleration * time, velocity) == yes); -static_assert(convertible_impl(acceleration * (time * time), position_vector) == yes); +static_assert(convertible_impl(acceleration * (time * time), displacement) == yes); static_assert(convertible_impl(mass * pow<2>(length) / pow<2>(time), energy) == yes); static_assert(convertible_impl(force * length, energy) == yes); static_assert(convertible_impl(force * position_vector, moment_of_force) == yes); @@ -648,18 +649,18 @@ static_assert(convertible_impl(distance / speed, time) == yes); // derived quantities to incompatible type static_assert(convertible_impl(height / time, velocity) == cast); -static_assert(convertible_impl(position_vector / time, rate_of_climb) == cast); +static_assert(convertible_impl(displacement / time, rate_of_climb) == cast); // type to compatible derived static_assert(convertible_impl(distance, speed* time) == yes); // type to more specialized derived quantity static_assert(convertible_impl(speed, height / time) == explicit_conversion); -static_assert(convertible_impl(speed, position_vector / time) == explicit_conversion); +static_assert(convertible_impl(speed, displacement / time) == explicit_conversion); // type to a derived quantity on a different branch static_assert(convertible_impl(velocity, height / time) == cast); -static_assert(convertible_impl(rate_of_climb, position_vector / time) == cast); +static_assert(convertible_impl(rate_of_climb, displacement / time) == cast); // derived quantities requiring explosion to a type static_assert(convertible_impl(acceleration * time, velocity) == yes); @@ -792,7 +793,7 @@ static_assert(convertible_impl(length / width, dimensionless) == yes); static_assert(convertible_impl(efficiency, strain) == cast); // quantity character checks -static_assert((position_vector / time).character == quantity_character::vector); +static_assert((displacement / time).character == quantity_character::vector); static_assert((position_vector / position_vector * time).character == quantity_character::scalar); static_assert((velocity / acceleration).character == quantity_character::scalar); diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 276a70e7c3..07c70fcb36 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -29,6 +29,8 @@ #include #include #if MP_UNITS_HOSTED +#include +#include #include #endif #ifdef MP_UNITS_IMPORT_STD @@ -45,11 +47,6 @@ import std; #endif #endif -#if MP_UNITS_HOSTED -template -constexpr bool mp_units::is_complex> = true; -#endif - template<> constexpr bool mp_units::is_vector = true; @@ -57,6 +54,10 @@ namespace { using namespace mp_units; using namespace mp_units::si::unit_symbols; +#if MP_UNITS_HOSTED +using namespace std::complex_literals; +using v = cartesian_vector; +#endif ////////////////////////////// // quantity class invariants @@ -74,6 +75,13 @@ concept invalid_types = requires { requires !requires { typename Q; }; // bool is not a valid representation type requires !requires { typename Q>; }; // quantity used as Rep requires !requires { typename Q; }; // vector representation expected +#if MP_UNITS_HOSTED + requires !requires { + typename Q>; + }; // scalar representation expected + requires !requires { typename Q>; }; // incompatible character + requires !requires { typename Q; }; // incompatible character +#endif }; static_assert(invalid_types); @@ -180,6 +188,26 @@ static_assert(std::convertible_to, quantity, quantity>); static_assert(std::convertible_to, quantity>); +#if MP_UNITS_HOSTED +static_assert(std::constructible_from>, + quantity>>); +static_assert(std::convertible_to>, + quantity>>); +static_assert(std::constructible_from>, + quantity>>); +static_assert(std::convertible_to>, + quantity>>); + +static_assert(std::constructible_from>, + quantity>>); +static_assert(std::convertible_to>, + quantity>>); +static_assert(std::constructible_from>, + quantity>>); +static_assert(std::convertible_to>, + quantity>>); +#endif + // conversion between different quantities not allowed static_assert(!std::constructible_from, quantity>); static_assert(!std::convertible_to, quantity>); @@ -239,6 +267,16 @@ static_assert(!std::convertible_to, double>); static_assert(std::constructible_from>); static_assert(!std::convertible_to, double>); static_assert(std::constructible_from>); +#if MP_UNITS_HOSTED +static_assert(!std::convertible_to>, std::complex>); +static_assert(std::constructible_from, quantity>>); +static_assert(!std::convertible_to, std::complex>); +static_assert(std::constructible_from, quantity>); +static_assert(!std::convertible_to>, cartesian_vector>); +static_assert(std::constructible_from, quantity>>); +static_assert(!std::convertible_to, cartesian_vector>); +static_assert(std::constructible_from, quantity>); +#endif /////////////////////////////////// @@ -282,13 +320,15 @@ static_assert((1. * rad + 1. * deg).in(rad) != 0 * rad); static_assert((1. * rad + 1. * deg).in(deg) != 0 * deg); #if MP_UNITS_HOSTED -using namespace std::complex_literals; static_assert(((2.f + 1if) * isq::voltage_phasor[V]).in(mV).numerical_value_in(mV) == 2000.f + 1000if); static_assert(((2.f + 1if) * isq::voltage_phasor[V]).in(mV).numerical_value_in(V) == 2.f + 1if); static_assert(((2. + 1i) * isq::voltage_phasor[V]).in(mV).numerical_value_in(mV) == 2000. + 1000i); static_assert(((2. + 1i) * isq::voltage_phasor[V]).in(mV).numerical_value_in(V) == 2. + 1i); static_assert(((2.L + 1il) * isq::voltage_phasor[V]).in(mV).numerical_value_in(mV) == 2000.L + 1000il); static_assert(((2.L + 1il) * isq::voltage_phasor[V]).in(mV).numerical_value_in(V) == 2.L + 1il); + +static_assert((v{1., 2., 3.} * isq::position_vector[m]).in(mm).numerical_value_in(mm) == v{1000., 2000., 3000.}); +static_assert((v{1., 2., 3.} * isq::position_vector[m]).in(mm).numerical_value_in(m) == v{1., 2., 3.}); #endif template typename Q> @@ -323,7 +363,7 @@ static_assert(invalid_getter_with_unit_conversion); /////////////////////////////////////// template Rep = double> + RepresentationOf Rep = double> struct child_quantity : quantity { using quantity_type = quantity; static constexpr auto reference = R; @@ -385,6 +425,12 @@ static_assert(quantity{123}.unit == one); static_assert(quantity{123}.quantity_spec == kind_of); #if MP_UNITS_HOSTED +static_assert(quantity{123. + 1i}.unit == one); +static_assert(quantity{123. + 1i}.quantity_spec == kind_of); + +static_assert(quantity{v{1., 2., 3}}.unit == one); +static_assert(quantity{v{1., 2., 3}}.quantity_spec == kind_of); + using namespace std::chrono_literals; static_assert(std::is_same_v); static_assert(std::is_same_v); @@ -428,21 +474,21 @@ static_assert((-123 * m).numerical_value_in(m) == -123); static_assert((+(-123 * m)).numerical_value_in(m) == -123); static_assert((-(-123 * m)).numerical_value_in(m) == 123); -static_assert([](auto v) { - const auto vv = v++; // NOLINT(bugprone-inc-dec-in-conditions) - return std::pair(v, vv); +static_assert([](auto val) { + const auto vv = val++; // NOLINT(bugprone-inc-dec-in-conditions) + return std::pair(val, vv); }(123 * m) == std::pair(124 * m, 123 * m)); -static_assert([](auto v) { - const auto vv = ++v; // NOLINT(bugprone-inc-dec-in-conditions) - return std::pair(v, vv); +static_assert([](auto val) { + const auto vv = ++val; // NOLINT(bugprone-inc-dec-in-conditions) + return std::pair(val, vv); }(123 * m) == std::pair(124 * m, 124 * m)); -static_assert([](auto v) { - const auto vv = v--; // NOLINT(bugprone-inc-dec-in-conditions) - return std::pair(v, vv); +static_assert([](auto val) { + const auto vv = val--; // NOLINT(bugprone-inc-dec-in-conditions) + return std::pair(val, vv); }(123 * m) == std::pair(122 * m, 123 * m)); -static_assert([](auto v) { - const auto vv = --v; // NOLINT(bugprone-inc-dec-in-conditions) - return std::pair(v, vv); +static_assert([](auto val) { + const auto vv = --val; // NOLINT(bugprone-inc-dec-in-conditions) + return std::pair(val, vv); }(123 * m) == std::pair(122 * m, 122 * m)); static_assert(is_same_v); @@ -461,6 +507,28 @@ static_assert((1 * m *= 2 * one).numerical_value_in(m) == 2); static_assert((2 * m /= 2 * one).numerical_value_in(m) == 1); static_assert((7 * m %= 2 * m).numerical_value_in(m) == 1); +#if MP_UNITS_HOSTED +static_assert(((1. + 1i) * V += (1. + 1i) * V).numerical_value_in(V) == 2. + 2i); +static_assert(((2. + 2i) * V -= (1. + 1i) * V).numerical_value_in(V) == 1. + 1i); +static_assert(((1. + 1i) * V += 1. * V).numerical_value_in(V) == 2. + 1i); +static_assert(((2. + 2i) * V -= 1. * V).numerical_value_in(V) == 1. + 2i); +static_assert(((1. + 1i) * V *= 2.).numerical_value_in(V) == 2. + 2i); +static_assert(((2. + 2i) * V /= 2.).numerical_value_in(V) == 1. + 1i); +static_assert(((1. + 1i) * V *= 2. * one).numerical_value_in(V) == 2. + 2i); +static_assert(((2. + 2i) * V /= 2. * one).numerical_value_in(V) == 1. + 1i); +static_assert(((1. + 1i) * V *= 2. + 1i).numerical_value_in(V) == (1. + 1i) * (2. + 1i)); +static_assert(((2. + 2i) * V /= 2. + 1i).numerical_value_in(V) == (2. + 2i) / (2. + 1i)); +static_assert(((1. + 1i) * V *= (2. + 1i) * one).numerical_value_in(V) == (1. + 1i) * (2. + 1i)); +static_assert(((2. + 2i) * V /= (2. + 1i) * one).numerical_value_in(V) == (2. + 2i) / (2. + 1i)); + +static_assert((v{1., 2., 3.}* m += v{1., 2., 3.} * m).numerical_value_in(m) == v{2., 4., 6.}); +static_assert((v{2., 4., 6.}* m -= v{1., 2., 3.} * m).numerical_value_in(m) == v{1., 2., 3.}); +static_assert((v{1., 2., 3.}* m *= 2.).numerical_value_in(m) == v{2., 4., 6.}); +static_assert((v{2., 4., 6.}* m /= 2.).numerical_value_in(m) == v{1., 2., 3.}); +static_assert((v{1., 2., 3.}* m *= 2. * one).numerical_value_in(m) == v{2., 4., 6.}); +static_assert((v{2., 4., 6.}* m /= 2. * one).numerical_value_in(m) == v{1., 2., 3.}); +#endif + // different representation types static_assert((2.5 * m += 3 * m).numerical_value_in(m) == 5.5); static_assert((5.5 * m -= 3 * m).numerical_value_in(m) == 2.5); @@ -473,6 +541,12 @@ static_assert((7.5 * m /= 3 * one).numerical_value_in(m) == 2.5); static_assert((1 * m += 1 * km).numerical_value_in(m) == 1001); static_assert((2000 * m -= 1 * km).numerical_value_in(m) == 1000); static_assert((3500 * m %= 1 * km).numerical_value_in(m) == 500); +#if MP_UNITS_HOSTED +static_assert(((1000. + 1000i) * V += (1. + 1i) * kV).numerical_value_in(V) == 2000. + 2000i); +static_assert(((2000. + 2000i) * V -= (1. + 1i) * kV).numerical_value_in(V) == 1000. + 1000i); +static_assert((v{1000., 2000., 3000.}* m += v{1., 2., 3.} * km).numerical_value_in(m) == v{2000., 4000., 6000.}); +static_assert((v{2000., 4000., 6000.}* m -= v{1., 2., 3.} * km).numerical_value_in(m) == v{1000., 2000., 3000.}); +#endif // convertible quantity types static_assert((isq::length(1 * m) += isq::height(1 * m)).numerical_value_in(m) == 2); @@ -534,6 +608,26 @@ concept invalid_compound_assignments = requires() { requires !requires(Q l) { l %= 2 * m; }; requires !requires(Q l) { l %= 2. * m; }; +#if MP_UNITS_HOSTED + // modulo operations on a complex representation type not allowed + requires !requires(Q> l) { l %= (2. + 2i) * V; }; + + // modulo operations on a vector representation type not allowed + requires !requires(Q> l) { l %= v{2.} * m; }; + + // arithmetics of vector and scalar quantities not allowed + requires !requires(Q> l) { l += 2. * m; }; + requires !requires(Q> l) { l -= 2. * m; }; + + // multiplication and division of vector quantities not allowed + requires !requires(Q> l) { l *= v{1., 2., 3.}; }; + requires !requires(Q> l) { l /= v{1., 2., 3.}; }; + requires !requires(Q> l) { l *= v{1., 2., 3.} * one; }; + requires !requires(Q> l) { l /= v{1., 2., 3.} * one; }; + requires !requires(Q> l) { l *= v{1., 2., 3.} * m; }; + requires !requires(Q> l) { l /= v{1., 2., 3.} * m; }; +#endif + // no unit constants requires !requires(Q l) { l += m; }; requires !requires(Q l) { l -= m; }; @@ -562,6 +656,13 @@ concept invalid_binary_operations = requires { requires !requires(Q a, Q b) { a % b; }; requires !requires(Q a, Q b) { b % a; }; +#if MP_UNITS_HOSTED + // no complex modulo + requires !requires(Q> a) { a % ((2. + 2i) * V); }; + // no vector modulo + requires !requires(Q> a) { a % (v{2.} * m); }; +#endif + // unit constants requires !requires { Q(1) + m; }; requires !requires { Q(1) - m; }; @@ -866,6 +967,22 @@ static_assert(4 / (2 * one) == 2 * one); static_assert(4 * one / 2 == 2 * one); static_assert(4 * one % (2 * one) == 0 * one); +#if MP_UNITS_HOSTED +static_assert(((3. + 3i) * one *= (2. + 2i) * one) == (3. + 3i) * (2. + 2i) * one); +static_assert(((6. + 6i) * one /= (2. + 2i) * one) == (6. + 6i) / (2. + 2i) * one); +static_assert((1. + 1i) * one + (1. + 1i) * one == (2. + 2i) * one); +static_assert((2. + 2i) * one - (1. + 1i) * one == (1. + 1i) * one); +static_assert((2. + 2i) * one * (2. * one) == (4. + 4i) * one); +static_assert((2. + 2i) * ((2. + 2i) * one) == (2. + 2i) * (2. + 2i) * one); +static_assert((2. + 2i) * one * (2. + 2i) == (2. + 2i) * (2. + 2i) * one); +static_assert((4. + 4i) * one / ((2. + 2i) * one) == (4. + 4i) / (2. + 2i) * one); +static_assert((4. + 4i) / ((2. + 2i) * one) == (4. + 4i) / (2. + 2i) * one); +static_assert((4. + 4i) * one / (2. + 2i) == (4. + 4i) / (2. + 2i) * one); + +static_assert(v{1., 2., 3.} * one + v{1., 2., 3.} * one == v{2., 4., 6.} * one); +static_assert(v{2., 4., 6.} * one - v{1., 2., 3.} * one == v{1., 2., 3.} * one); +#endif + static_assert(1 * one + 1 == 2); static_assert(1 + 1 * one == 2); static_assert(2 * one - 1 == 1); @@ -877,6 +994,22 @@ static_assert(2.23 - 1 * one == 1.23); static_assert(4 * one % (2) == 0); static_assert(4 % (2 * one) == 0); +#if MP_UNITS_HOSTED +static_assert((1. + 1i) * one + (1. + 1i) == 2. + 2i); +static_assert((1. + 1i) + (1. + 1i) * one == 2. + 2i); +static_assert((2. + 2i) * one - (1. + 1i) == 1. + 1i); +static_assert((2. + 2i) - (1. + 1i) * one == 1. + 1i); +static_assert(1. * one + (1. + 1i) == (2. + 1i)); +static_assert(1. + (1. + 1i) * one == (2. + 1i)); +static_assert(2. * one - (1. + 1i) == (1. - 1i)); +static_assert(2. - (1. + 1i) * one == (1. - 1i)); + +static_assert(v{1., 2., 3.} * one + v{1., 2., 3.} == v{2., 4., 6.}); +static_assert(v{1., 2., 3.} + v{1., 2., 3.} * one == v{2., 4., 6.}); +static_assert(v{2., 4., 6.} * one - v{1., 2., 3.} == v{1., 2., 3.}); +static_assert(v{2., 4., 6.} - v{1., 2., 3.} * one == v{1., 2., 3.}); +#endif + static_assert(2 * rad * (2 * rad) == 4 * pow<2>(rad)); // modulo arithmetics @@ -916,6 +1049,55 @@ static_assert(is_same_v constexpr bool invalid_comparison = !requires { 2 * R1 == 2 * R2; } && !requires { 2 * R2 == 2 * R1; }; static_assert(invalid_comparison); +#if MP_UNITS_HOSTED +static_assert((1. + 1i) * one == 1. + 1i); +static_assert(1. + 1i != (2. + 2i) * one); +static_assert(v{1., 2., 3.} * one == v{1., 2., 3.}); +static_assert(v{1., 2., 3.} != v{3., 2., 1.} * one); +#endif /////////////////////// // ordering operators @@ -1127,10 +1315,10 @@ static_assert(!QuantityOf, isq::distance / isq::dura static_assert(!QuantityOf, isq::width / isq::duration>); static_assert(QuantityOf, isq::width / isq::duration>); static_assert(QuantityOf[m / s]>, isq::width / isq::duration>); -static_assert(!QuantityOf, isq::position_vector / isq::duration>); -static_assert(QuantityOf, isq::position_vector / isq::duration>); -static_assert(QuantityOf[m / s]>, isq::position_vector / isq::duration>); -static_assert(QuantityOf, isq::position_vector / isq::duration>); +static_assert(!QuantityOf, isq::displacement / isq::duration>); +static_assert(QuantityOf, isq::displacement / isq::duration>); +static_assert(QuantityOf[m / s]>, isq::displacement / isq::duration>); +static_assert(QuantityOf, isq::displacement / isq::duration>); static_assert(QuantityOf); // kind of static_assert(QuantityOf[m]), isq::height>); // kind of diff --git a/test/static/reference_test.cpp b/test/static/reference_test.cpp index 09758aacf7..4d6e1cfd76 100644 --- a/test/static/reference_test.cpp +++ b/test/static/reference_test.cpp @@ -195,7 +195,7 @@ static_assert(is_of_type<2 * m_per_s, quantity>, - derived_unit)>, per>>{}, + derived_unit), per>>{}, int>>); static_assert(120 * length[kilometre] / (2 * time[hour]) == 60 * speed[kilometre / hour]); static_assert(is_of_type<[] { @@ -204,15 +204,15 @@ static_assert(is_of_type<[] { return distance * length[kilometre] / (duration * time[hour]); }(), quantity>, - derived_unit)>, per>>{}, + derived_unit), per>>{}, int>>); static_assert(is_of_type>, - derived_unit)>, per>>{}, + derived_unit), per>>{}, std::int64_t>>); static_assert(is_of_type<120.L * length[kilometre] / (2 * time[hour]), quantity>, - derived_unit)>, per>>{}, + derived_unit), per>>{}, long double>>); static_assert(is_of_type<1. / 4 * area[square(metre)], decltype(1. * area[square(metre)] / 4)>); @@ -229,10 +229,11 @@ static_assert(is_of_type<42 * nu::length[nu::second] / (42 * nu::time[nu::second quantity>, one_>{}, int>>); static_assert(is_of_type<42 * nu::speed[nu::second / nu::second], quantity{}, int>>); static_assert(is_of_type<42 * nu::speed[one], quantity{}, int>>); -static_assert(is_of_type<42 * mass[kilogram] * (1 * nu::length[nu::second]) / (1 * nu::time[nu::second]), - quantity>, - std::remove_const_t)>>{}, - int>>); +static_assert( + is_of_type< + 42 * mass[kilogram] * (1 * nu::length[nu::second]) / (1 * nu::time[nu::second]), + quantity>, MP_UNITS_NONCONST_TYPE(si::kilo)>{}, + int>>); template concept invalid_nu_unit = !requires { dim[unit]; }; @@ -378,4 +379,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 diff --git a/test/static/si_test.cpp b/test/static/si_test.cpp index 4f306fd25d..40a9e41125 100644 --- a/test/static/si_test.cpp +++ b/test/static/si_test.cpp @@ -61,7 +61,8 @@ static_assert(1 * Qm == 1'000'000'000'000'000'000 * Tm); // check for invalid prefixes template typename prefix, auto V1> -concept can_not_be_prefixed = Unit && !requires { typename prefix; }; +concept can_not_be_prefixed = + Unit && !requires { typename prefix; }; static_assert(can_not_be_prefixed); static_assert(can_not_be_prefixed); diff --git a/test/static/unit_test.cpp b/test/static/unit_test.cpp index dccd3a313e..ee375f2edb 100644 --- a/test/static/unit_test.cpp +++ b/test/static/unit_test.cpp @@ -89,11 +89,11 @@ inline constexpr struct speed_of_light_in_vacuum_ final : named_unit<"c", mag<29 static_assert(Unit); static_assert(Unit); static_assert(Unit); -static_assert(Unit); +static_assert(Unit); static_assert(Unit); static_assert(Unit); static_assert(Unit); -static_assert(Unit)>); +static_assert(Unit>); static_assert(Unit); static_assert(Unit); static_assert(Unit * second)>); @@ -101,20 +101,20 @@ static_assert(Unit); static_assert(Unit); static_assert(Unit); static_assert(Unit); -static_assert(Unit); +static_assert(Unit); static_assert(PrefixableUnit); static_assert(PrefixableUnit); static_assert(PrefixableUnit); static_assert(PrefixableUnit); static_assert(PrefixableUnit); -static_assert(!PrefixableUnit); +static_assert(!PrefixableUnit); static_assert(!PrefixableUnit); -static_assert(!PrefixableUnit)>); +static_assert(!PrefixableUnit>); static_assert(!PrefixableUnit); static_assert(!PrefixableUnit); static_assert(!PrefixableUnit * second)>); -static_assert(!PrefixableUnit); +static_assert(!PrefixableUnit); // named unit static_assert(is_of_type); @@ -194,14 +194,14 @@ static_assert(standard_gravity != metre / square(second)); // magnitude is diff static_assert(standard_gravity._symbol_ == symbol_text{u8"g₀", "g_0"}); // prefixed_unit -static_assert(is_of_type)>>); +static_assert(is_of_type)>); static_assert(is_of_type); static_assert(get_canonical_unit(kilometre).mag == mag<1000>); static_assert(convertible(kilometre, metre)); static_assert(kilometre != metre); static_assert(kilometre._symbol_ == "km"); -static_assert(is_of_type)>>); +static_assert(is_of_type)>); static_assert(is_of_type, per>>>); static_assert(get_canonical_unit(kilojoule).mag == mag<1'000'000>); @@ -212,8 +212,7 @@ static_assert(kilojoule._symbol_ == "kJ"); static_assert(is_of_type, kilo_>); static_assert(is_of_type, kilo_>); -static_assert( - is_of_type)>, per>>); // !!! +static_assert(is_of_type), per>>); // !!! // prefixes