mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Compare commits
20 Commits
ximinez/nu
...
legleux/li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb5354e4fe | ||
|
|
19da83abf3 | ||
|
|
8a02c904a6 | ||
|
|
e7ca96362f | ||
|
|
4593754347 | ||
|
|
2caa18ddd5 | ||
|
|
8dbf9b8ab4 | ||
|
|
94ef68090c | ||
|
|
8fa81e24ed | ||
|
|
4e1c3b0922 | ||
|
|
f148d9a4e7 | ||
|
|
453e3efcc9 | ||
|
|
8b4ff1c171 | ||
|
|
c33630f859 | ||
|
|
94103e97d7 | ||
|
|
cfcb09933a | ||
|
|
e36ba02762 | ||
|
|
fe47e5adb2 | ||
|
|
96a6922bce | ||
|
|
7721bda445 |
89
.github/scripts/strategy-matrix/generate.py
vendored
89
.github/scripts/strategy-matrix/generate.py
vendored
@@ -4,14 +4,15 @@ import itertools
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
THIS_DIR = Path(__file__).parent.resolve()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
architecture: list[dict]
|
||||
os: list[dict]
|
||||
architecture: list[dict[str, Any]]
|
||||
os: list[dict[str, Any]]
|
||||
build_type: list[str]
|
||||
cmake_args: list[str]
|
||||
|
||||
@@ -32,7 +33,52 @@ We will further set additional CMake arguments as follows:
|
||||
"""
|
||||
|
||||
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
def build_config_name(
|
||||
os_entry: dict[str, Any], architecture: dict[str, Any], build_type: str
|
||||
) -> str:
|
||||
parts = [os_entry["distro_name"]]
|
||||
for key in ("distro_version", "compiler_name", "compiler_version"):
|
||||
if value := os_entry[key]:
|
||||
parts.append(value)
|
||||
platform = architecture["platform"]
|
||||
parts.append(platform[platform.find("/") + 1 :])
|
||||
parts.append(build_type.lower())
|
||||
return "-".join(parts)
|
||||
|
||||
|
||||
def build_container_image(os_entry: dict[str, Any]) -> str:
|
||||
image = f"ghcr.io/xrplf/ci/{os_entry['distro_name']}-{os_entry['distro_version']}"
|
||||
tag = f"{os_entry['compiler_name']}-{os_entry['compiler_version']}-sha-{os_entry['image_sha']}"
|
||||
return f"{image}:{tag}"
|
||||
|
||||
|
||||
def generate_packaging_matrix(config: Config) -> list[dict[str, str]]:
|
||||
"""Emit packaging entries for each os entry with `package: true`.
|
||||
Packaging always uses Release build on linux/amd64. The package format
|
||||
(deb or rpm) is inferred at runtime from the container's package manager.
|
||||
"""
|
||||
architecture = next(
|
||||
(a for a in config.architecture if a["platform"] == "linux/amd64"),
|
||||
None,
|
||||
)
|
||||
if architecture is None:
|
||||
raise Exception("linux/amd64 architecture required for packaging")
|
||||
|
||||
entries = []
|
||||
for os_entry in config.os:
|
||||
if not os_entry.get("package", False):
|
||||
continue
|
||||
config_name = build_config_name(os_entry, architecture, "Release")
|
||||
entries.append(
|
||||
{
|
||||
"artifact_name": f"xrpld-{config_name}",
|
||||
"container_image": build_container_image(os_entry),
|
||||
}
|
||||
)
|
||||
return entries
|
||||
|
||||
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list[dict[str, Any]]:
|
||||
configurations = []
|
||||
for architecture, os, build_type, cmake_args in itertools.product(
|
||||
config.architecture, config.os, config.build_type, config.cmake_args
|
||||
@@ -100,14 +146,15 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
continue
|
||||
|
||||
# RHEL:
|
||||
# - 9 using GCC 12: Debug on linux/amd64.
|
||||
# - 9 using GCC 12: Debug and Release on linux/amd64
|
||||
# (Release is required for RPM packaging).
|
||||
# - 10 using Clang: Release on linux/amd64.
|
||||
if os["distro_name"] == "rhel":
|
||||
skip = True
|
||||
if os["distro_version"] == "9":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Debug"
|
||||
and build_type in ["Debug", "Release"]
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
@@ -122,7 +169,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
continue
|
||||
|
||||
# Ubuntu:
|
||||
# - Jammy using GCC 12: Debug on linux/arm64.
|
||||
# - Jammy using GCC 12: Debug on linux/arm64, Release on
|
||||
# linux/amd64 (Release is required for DEB packaging).
|
||||
# - Noble using GCC 14: Release on linux/amd64.
|
||||
# - Noble using Clang 18: Debug on linux/amd64.
|
||||
# - Noble using Clang 19: Release on linux/arm64.
|
||||
@@ -135,6 +183,12 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
elif os["distro_version"] == "noble":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
|
||||
@@ -216,17 +270,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
|
||||
# Generate a unique name for the configuration, e.g. macos-arm64-debug
|
||||
# or debian-bookworm-gcc-12-amd64-release.
|
||||
config_name = os["distro_name"]
|
||||
if (n := os["distro_version"]) != "":
|
||||
config_name += f"-{n}"
|
||||
if (n := os["compiler_name"]) != "":
|
||||
config_name += f"-{n}"
|
||||
if (n := os["compiler_version"]) != "":
|
||||
config_name += f"-{n}"
|
||||
config_name += (
|
||||
f"-{architecture['platform'][architecture['platform'].find('/')+1:]}"
|
||||
)
|
||||
config_name += f"-{build_type.lower()}"
|
||||
config_name = build_config_name(os, architecture, build_type)
|
||||
if "-Dcoverage=ON" in cmake_args:
|
||||
config_name += "-coverage"
|
||||
if "-Dunity=ON" in cmake_args:
|
||||
@@ -330,10 +374,19 @@ if __name__ == "__main__":
|
||||
required=False,
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--packaging",
|
||||
help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
matrix = []
|
||||
if args.config is None or args.config == "":
|
||||
if args.packaging:
|
||||
config_path = args.config if args.config else THIS_DIR / "linux.json"
|
||||
matrix += generate_packaging_matrix(read_config(config_path))
|
||||
elif args.config is None or args.config == "":
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "linux.json")
|
||||
)
|
||||
|
||||
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -120,7 +120,8 @@
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "ab4d1f0",
|
||||
"package": true
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
@@ -162,7 +163,8 @@
|
||||
"distro_version": "jammy",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "ab4d1f0"
|
||||
"image_sha": "ab4d1f0",
|
||||
"package": true
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
|
||||
27
.github/workflows/on-pr.yml
vendored
27
.github/workflows/on-pr.yml
vendored
@@ -67,6 +67,7 @@ jobs:
|
||||
.github/workflows/reusable-build-test.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-clang-tidy-files.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
@@ -81,6 +82,8 @@ jobs:
|
||||
CMakeLists.txt
|
||||
conanfile.py
|
||||
conan.lock
|
||||
package/**
|
||||
|
||||
- name: Check whether to run
|
||||
# This step determines whether the rest of the workflow should
|
||||
# run. The rest of the workflow will run if this job runs AND at
|
||||
@@ -137,6 +140,30 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
generate-version:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
src/libxrpl/protocol/BuildInfo.cpp
|
||||
- name: Generate version
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
|
||||
package:
|
||||
needs: [should-run, build-test, generate-version]
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
with:
|
||||
version: ${{ needs.generate-version.outputs.version }}
|
||||
|
||||
upload-recipe:
|
||||
needs:
|
||||
- should-run
|
||||
|
||||
40
.github/workflows/on-tag.yml
vendored
40
.github/workflows/on-tag.yml
vendored
@@ -1,5 +1,5 @@
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote when a versioned
|
||||
# tag is pushed.
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote and builds
|
||||
# release packages when a versioned tag is pushed.
|
||||
name: Tag
|
||||
|
||||
on:
|
||||
@@ -22,3 +22,39 @@ jobs:
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
|
||||
build-test:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [linux]
|
||||
with:
|
||||
ccache_enabled: false
|
||||
os: ${{ matrix.os }}
|
||||
strategy_matrix: minimal
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
generate-version:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
src/libxrpl/protocol/BuildInfo.cpp
|
||||
- name: Generate version
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
|
||||
package:
|
||||
needs: [build-test, generate-version]
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
with:
|
||||
version: ${{ needs.generate-version.outputs.version }}
|
||||
|
||||
23
.github/workflows/on-trigger.yml
vendored
23
.github/workflows/on-trigger.yml
vendored
@@ -24,6 +24,7 @@ on:
|
||||
- ".github/workflows/reusable-build-test.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-clang-tidy-files.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
@@ -38,6 +39,7 @@ on:
|
||||
- "CMakeLists.txt"
|
||||
- "conanfile.py"
|
||||
- "conan.lock"
|
||||
- "package/**"
|
||||
|
||||
# Run at 06:32 UTC on every day of the week from Monday through Friday. This
|
||||
# will force all dependencies to be rebuilt, which is useful to verify that
|
||||
@@ -98,3 +100,24 @@ jobs:
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
|
||||
generate-version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
src/libxrpl/protocol/BuildInfo.cpp
|
||||
- name: Generate version
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
|
||||
package:
|
||||
needs: [build-test, generate-version]
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
with:
|
||||
version: ${{ needs.generate-version.outputs.version }}
|
||||
|
||||
91
.github/workflows/reusable-package.yml
vendored
Normal file
91
.github/workflows/reusable-package.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
|
||||
# Discovers which configurations to package from linux.json (entries with
|
||||
# "package": true) and fans out one job per entry.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pkg_release:
|
||||
description: "Package release number. Increment when repackaging the same executable."
|
||||
required: false
|
||||
type: string
|
||||
default: "1"
|
||||
version:
|
||||
description: "Version string used for naming the output artifact."
|
||||
required: true
|
||||
type: string
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
|
||||
- name: Generate packaging matrix
|
||||
id: generate
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
run: |
|
||||
./generate.py --packaging --config=linux.json >> "${GITHUB_OUTPUT}"
|
||||
|
||||
package:
|
||||
needs: generate-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "${{ matrix.artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.container_image }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set source date epoch
|
||||
run: |
|
||||
echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct "$GITHUB_SHA")" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Download pre-built binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binary executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PKG_VERSION: ${{ inputs.version }}
|
||||
PKG_RELEASE: ${{ inputs.pkg_release }}
|
||||
SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }}
|
||||
run: |
|
||||
./package/build_pkg.sh . "$BUILD_DIR" "$PKG_VERSION" "$PKG_RELEASE"
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg-${{ inputs.version }}
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
|
||||
if-no-files-found: error
|
||||
@@ -39,7 +39,8 @@ jobs:
|
||||
- name: Generate strategy matrix
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
id: generate
|
||||
env:
|
||||
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }}
|
||||
GENERATE_OPTION: ${{ inputs.strategy_matrix == 'all' && '--all' || '' }}
|
||||
run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >> "${GITHUB_OUTPUT}"
|
||||
run: |
|
||||
./generate.py \
|
||||
${{ inputs.strategy_matrix == 'all' && '--all' || '' }} \
|
||||
${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }} \
|
||||
>> "${GITHUB_OUTPUT}"
|
||||
|
||||
@@ -134,6 +134,7 @@ endif()
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
if(tests)
|
||||
|
||||
@@ -12,14 +12,14 @@ if(is_root_project AND TARGET xrpld)
|
||||
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg"
|
||||
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld"
|
||||
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}"
|
||||
RENAME xrpld.cfg
|
||||
COMPONENT runtime
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt"
|
||||
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld"
|
||||
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}"
|
||||
RENAME validators.txt
|
||||
COMPONENT runtime
|
||||
)
|
||||
|
||||
136
cmake/XrplPackaging.cmake
Normal file
136
cmake/XrplPackaging.cmake
Normal file
@@ -0,0 +1,136 @@
|
||||
#[===================================================================[
|
||||
Linux packaging support: RPM and Debian targets + install tests
|
||||
#]===================================================================]
|
||||
|
||||
if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/opt/xrpld")
|
||||
message(
|
||||
STATUS
|
||||
"Packaging targets require -DCMAKE_INSTALL_PREFIX=/opt/xrpld "
|
||||
"(current: '${CMAKE_INSTALL_PREFIX}'); skipping."
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED pkg_release)
|
||||
set(pkg_release 1)
|
||||
endif()
|
||||
|
||||
find_program(RPMBUILD_EXECUTABLE rpmbuild)
|
||||
find_program(DPKG_BUILDPACKAGE_EXECUTABLE dpkg-buildpackage)
|
||||
if(RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE)
|
||||
add_custom_target(
|
||||
package
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/package/build_pkg.sh ${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_BINARY_DIR} "${xrpld_version}" ${pkg_release}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS xrpld
|
||||
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(
|
||||
STATUS
|
||||
"Neither rpmbuild nor dpkg-buildpackage found; 'package' target not available"
|
||||
)
|
||||
endif()
|
||||
|
||||
#[===================================================================[
|
||||
CTest fixtures for package install verification (requires docker)
|
||||
#]===================================================================]
|
||||
|
||||
find_program(DOCKER_EXECUTABLE docker)
|
||||
if(NOT DOCKER_EXECUTABLE)
|
||||
message(STATUS "docker not found; package install tests not available")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(DEB_TEST_IMAGE
|
||||
"geerlingguy/docker-ubuntu2204-ansible@sha256:bbe4c56c16c57c902554b9a47833590926b7a7d4440aef3d9851473b9f7be9d4"
|
||||
)
|
||||
set(RPM_TEST_IMAGE
|
||||
"geerlingguy/docker-rockylinux9-ansible@sha256:790c2db9add93c0daa903ace816f352c9c04abb046ecfa12c581e8d4c59f41d6"
|
||||
)
|
||||
|
||||
# Only register install-test fixtures for package formats the host can build,
|
||||
# since the smoketest needs a corresponding .deb/.rpm artifact in build/.
|
||||
set(PKG_TYPES "")
|
||||
if(DPKG_BUILDPACKAGE_EXECUTABLE)
|
||||
list(APPEND PKG_TYPES deb)
|
||||
endif()
|
||||
if(RPMBUILD_EXECUTABLE)
|
||||
list(APPEND PKG_TYPES rpm)
|
||||
endif()
|
||||
|
||||
foreach(PKG IN LISTS PKG_TYPES)
|
||||
if(PKG STREQUAL "deb")
|
||||
set(IMAGE ${DEB_TEST_IMAGE})
|
||||
else()
|
||||
set(IMAGE ${RPM_TEST_IMAGE})
|
||||
endif()
|
||||
|
||||
# This image runs systemd for full testing xrpld.service
|
||||
add_test(
|
||||
NAME ${PKG}_container_start
|
||||
COMMAND
|
||||
sh -c
|
||||
"docker rm -f xrpld_${PKG}_install_test 2>/dev/null || true && \
|
||||
docker run -d \
|
||||
--name xrpld_${PKG}_install_test \
|
||||
--cgroupns host \
|
||||
--volume '${CMAKE_SOURCE_DIR}:/root:ro' \
|
||||
--volume /sys/fs/cgroup:/sys/fs/cgroup:rw \
|
||||
--tmpfs /run/lock \
|
||||
${IMAGE}"
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_container_start
|
||||
PROPERTIES FIXTURES_SETUP ${PKG}_container LABELS packaging
|
||||
)
|
||||
|
||||
# On CI: always stop. Locally: leave running on failure for diagnosis.
|
||||
add_test(
|
||||
NAME ${PKG}_container_stop
|
||||
COMMAND
|
||||
sh -c
|
||||
"if [ -n \"$CI\" ] || ! docker exec xrpld_${PKG}_install_test test -f /tmp/test_failed 2>/dev/null; then \
|
||||
docker rm -f xrpld_${PKG}_install_test; \
|
||||
else \
|
||||
echo 'Tests failed — leaving xrpld_${PKG}_install_test running for diagnosis'; \
|
||||
echo 'Clean up with: docker rm -f xrpld_${PKG}_install_test'; \
|
||||
fi"
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_container_stop
|
||||
PROPERTIES FIXTURES_CLEANUP ${PKG}_container LABELS packaging
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME ${PKG}_install
|
||||
COMMAND
|
||||
docker exec -w /root xrpld_${PKG}_install_test bash
|
||||
/root/package/test/smoketest.sh local
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_install
|
||||
PROPERTIES
|
||||
FIXTURES_REQUIRED ${PKG}_container
|
||||
FIXTURES_SETUP ${PKG}_installed
|
||||
LABELS packaging
|
||||
TIMEOUT 600
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME ${PKG}_install_paths
|
||||
COMMAND
|
||||
docker exec -w /root xrpld_${PKG}_install_test sh
|
||||
/root/package/test/check_install_paths.sh
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_install_paths
|
||||
PROPERTIES
|
||||
FIXTURES_REQUIRED "${PKG}_container;${PKG}_installed"
|
||||
LABELS packaging
|
||||
TIMEOUT 60
|
||||
)
|
||||
endforeach()
|
||||
@@ -97,12 +97,15 @@ words:
|
||||
- desync
|
||||
- desynced
|
||||
- determ
|
||||
- disablerepo
|
||||
- distro
|
||||
- doxyfile
|
||||
- dxrpl
|
||||
- enabled
|
||||
- enablerepo
|
||||
- endmacro
|
||||
- exceptioned
|
||||
- EXPECT_STREQ
|
||||
- Falco
|
||||
- fcontext
|
||||
- finalizers
|
||||
@@ -158,6 +161,7 @@ words:
|
||||
- Merkle
|
||||
- Metafuncton
|
||||
- misprediction
|
||||
- missingok
|
||||
- mptbalance
|
||||
- MPTDEX
|
||||
- mptflags
|
||||
@@ -189,7 +193,9 @@ words:
|
||||
- NOLINT
|
||||
- NOLINTNEXTLINE
|
||||
- nonxrp
|
||||
- noreplace
|
||||
- noripple
|
||||
- notifempty
|
||||
- nudb
|
||||
- nullptr
|
||||
- nunl
|
||||
@@ -209,6 +215,7 @@ words:
|
||||
- preauthorize
|
||||
- preauthorizes
|
||||
- preclaim
|
||||
- preun
|
||||
- protobuf
|
||||
- protos
|
||||
- ptrs
|
||||
@@ -243,12 +250,14 @@ words:
|
||||
- sfields
|
||||
- shamap
|
||||
- shamapitem
|
||||
- shlibs
|
||||
- sidechain
|
||||
- SIGGOOD
|
||||
- sle
|
||||
- sles
|
||||
- soci
|
||||
- socidb
|
||||
- SRPMS
|
||||
- sslws
|
||||
- statsd
|
||||
- STATSDCOLLECTOR
|
||||
@@ -276,8 +285,8 @@ words:
|
||||
- txn
|
||||
- txns
|
||||
- txs
|
||||
- UBSAN
|
||||
- ubsan
|
||||
- UBSAN
|
||||
- umant
|
||||
- unacquired
|
||||
- unambiguity
|
||||
@@ -314,7 +323,6 @@ words:
|
||||
- xbridge
|
||||
- xchain
|
||||
- ximinez
|
||||
- EXPECT_STREQ
|
||||
- XMACRO
|
||||
- xrpkuwait
|
||||
- xrpl
|
||||
|
||||
@@ -2,19 +2,13 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
#endif // !defined(_MSC_VER)
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class Number;
|
||||
@@ -22,39 +16,18 @@ class Number;
|
||||
std::string
|
||||
to_string(Number const& amount);
|
||||
|
||||
/** Returns a rough estimate of log10(value).
|
||||
*
|
||||
* The return value is a pair (log, rem), where log is the estimated
|
||||
* base-10 logarithm (roughly floor(log10(value))), and rem is value with
|
||||
* all trailing 0s removed (i.e., divided by the largest power of 10 that
|
||||
* evenly divides value). If rem is 1, then value is an exact power of ten, and
|
||||
* log is the exact log10(value).
|
||||
*
|
||||
* This function only works for positive values.
|
||||
*/
|
||||
template <std::unsigned_integral T>
|
||||
constexpr std::pair<int, T>
|
||||
logTenEstimate(T value)
|
||||
{
|
||||
int log = 0;
|
||||
T remainder = value;
|
||||
while (value >= 10)
|
||||
{
|
||||
if (value % 10 == 0)
|
||||
remainder = remainder / 10;
|
||||
value /= 10;
|
||||
++log;
|
||||
}
|
||||
return {log, remainder};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr std::optional<int>
|
||||
logTen(T value)
|
||||
{
|
||||
auto const est = logTenEstimate(value);
|
||||
if (est.second == 1)
|
||||
return est.first;
|
||||
int log = 0;
|
||||
while (value >= 10 && value % 10 == 0)
|
||||
{
|
||||
value /= 10;
|
||||
++log;
|
||||
}
|
||||
if (value == 1)
|
||||
return log;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -68,10 +41,12 @@ isPowerOfTen(T value)
|
||||
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
||||
*
|
||||
* The mantissa is in the range [min, max], where
|
||||
* * min is a power of 10, and
|
||||
* * max = min * 10 - 1.
|
||||
*
|
||||
* The mantissa_scale enum indicates whether the range is "small" or
|
||||
* "large". This intentionally prevents the creation of any
|
||||
* MantissaRanges representing other values.
|
||||
* The mantissa_scale enum indicates whether the range is "small" or "large".
|
||||
* This intentionally restricts the number of MantissaRanges that can be
|
||||
* instantiated to two: one for each scale.
|
||||
*
|
||||
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
|
||||
* value of 10^15, and a max value of 10^16-1. This was sufficient for
|
||||
@@ -85,8 +60,8 @@ isPowerOfTen(T value)
|
||||
* "large" scale.
|
||||
*
|
||||
* The "large" scale is intended to represent all values that can be represented
|
||||
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1
|
||||
* (truncated), and a max value of 2^63-1.
|
||||
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
|
||||
* value of 10^19-1.
|
||||
*
|
||||
* Note that if the mentioned amendments are eventually retired, this class
|
||||
* should be left in place, but the "small" scale option should be removed. This
|
||||
@@ -98,52 +73,25 @@ struct MantissaRange
|
||||
enum mantissa_scale { small, large };
|
||||
|
||||
explicit constexpr MantissaRange(mantissa_scale scale_)
|
||||
: max(getMax(scale_)), internalMin(getInternalMin(scale_, min)), scale(scale_)
|
||||
: min(getMin(scale_)), log(logTen(min).value_or(-1)), scale(scale_)
|
||||
{
|
||||
// Keep the error messages terse. Since this is constexpr, if any of these throw, it won't
|
||||
// compile, so there's no real need to worry about runtime exceptions here.
|
||||
if (min * 10 <= max)
|
||||
throw std::out_of_range("Invalid mantissa range: min * 10 <= max");
|
||||
if (max / 10 >= min)
|
||||
throw std::out_of_range("Invalid mantissa range: max / 10 >= min");
|
||||
if ((min - 1) * 10 > max)
|
||||
throw std::out_of_range("Invalid mantissa range: (min - 1) * 10 > max");
|
||||
// This is a little hacky
|
||||
if ((max + 10) / 10 < min)
|
||||
throw std::out_of_range("Invalid mantissa range: (max + 10) / 10 < min");
|
||||
if (computeLog(internalMin) != log)
|
||||
throw std::out_of_range("Invalid mantissa range: computeLog(internalMin) != log");
|
||||
}
|
||||
|
||||
// Explicitly delete copy and move operations
|
||||
MantissaRange(MantissaRange const&) = delete;
|
||||
MantissaRange(MantissaRange&&) = delete;
|
||||
MantissaRange&
|
||||
operator=(MantissaRange const&) = delete;
|
||||
MantissaRange&
|
||||
operator=(MantissaRange&&) = delete;
|
||||
|
||||
rep max;
|
||||
rep min{computeMin(max)};
|
||||
/* Used to determine if mantissas are in range, but have fewer digits than max.
|
||||
*
|
||||
* Unlike min, internalMin is always an exact power of 10, so a mantissa in the internal
|
||||
* representation will always have a consistent number of digits.
|
||||
*/
|
||||
rep internalMin;
|
||||
rep log{computeLog(min)};
|
||||
rep min;
|
||||
rep max{(min * 10) - 1};
|
||||
int log;
|
||||
mantissa_scale scale;
|
||||
|
||||
private:
|
||||
static constexpr rep
|
||||
getMax(mantissa_scale scale)
|
||||
getMin(mantissa_scale scale_)
|
||||
{
|
||||
switch (scale)
|
||||
switch (scale_)
|
||||
{
|
||||
case small:
|
||||
return 9'999'999'999'999'999ULL;
|
||||
return 1'000'000'000'000'000ULL;
|
||||
case large:
|
||||
return std::numeric_limits<std::int64_t>::max();
|
||||
return 1'000'000'000'000'000'000ULL;
|
||||
default:
|
||||
// Since this can never be called outside a non-constexpr
|
||||
// context, this throw assures that the build fails if an
|
||||
@@ -151,59 +99,19 @@ private:
|
||||
throw std::runtime_error("Unknown mantissa scale");
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr rep
|
||||
computeMin(rep max)
|
||||
{
|
||||
return (max / 10) + 1;
|
||||
}
|
||||
|
||||
static constexpr rep
|
||||
getInternalMin(mantissa_scale scale, rep min)
|
||||
{
|
||||
switch (scale)
|
||||
{
|
||||
case large:
|
||||
return 1'000'000'000'000'000'000ULL;
|
||||
default:
|
||||
if (isPowerOfTen(min))
|
||||
return min;
|
||||
throw std::runtime_error("Unknown/bad mantissa scale");
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr rep
|
||||
computeLog(rep min)
|
||||
{
|
||||
auto const estimate = logTenEstimate(min);
|
||||
return estimate.first + (estimate.second == 1 ? 0 : 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Like std::integral, but only 64-bit integral types.
|
||||
template <class T>
|
||||
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
|
||||
|
||||
namespace detail {
|
||||
#ifdef _MSC_VER
|
||||
using uint128_t = boost::multiprecision::uint128_t;
|
||||
using int128_t = boost::multiprecision::int128_t;
|
||||
#else // !defined(_MSC_VER)
|
||||
using uint128_t = __uint128_t;
|
||||
using int128_t = __int128_t;
|
||||
#endif // !defined(_MSC_VER)
|
||||
|
||||
template <class T>
|
||||
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
|
||||
} // namespace detail
|
||||
|
||||
/** Number is a floating point type that can represent a wide range of values.
|
||||
*
|
||||
* It can represent all values that can be represented by an STAmount -
|
||||
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
|
||||
* as much precision as those types require.
|
||||
*
|
||||
* ---- Internal Operational Representation ----
|
||||
* ---- Internal Representation ----
|
||||
*
|
||||
* Internally, Number is represented with three values:
|
||||
* 1. a bool sign flag,
|
||||
@@ -218,21 +126,15 @@ concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>
|
||||
*
|
||||
* A non-zero mantissa is (almost) always normalized, meaning it and the
|
||||
* exponent are grown or shrunk until the mantissa is in the range
|
||||
* [MantissaRange.internalMin, MantissaRange.internalMin * 10 - 1].
|
||||
*
|
||||
* This internal representation is only used during some operations to ensure
|
||||
* that the mantissa is a known, predictable size. The class itself stores the
|
||||
* values using the external representation described below.
|
||||
* [MantissaRange.min, MantissaRange.max].
|
||||
*
|
||||
* Note:
|
||||
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
|
||||
* should only be used at specific conversion points, some constexpr
|
||||
* values, and in unit tests.
|
||||
* 2. Unlike MantissaRange.min, internalMin is always an exact power of 10,
|
||||
* so a mantissa in the internal representation will always have a
|
||||
* consistent number of digits.
|
||||
* 3. The functions toInternal() and fromInternal() are used to convert
|
||||
* between the two representations.
|
||||
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
|
||||
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
|
||||
* 10^20-1 > 2^64-1). This avoids under- and overflows.
|
||||
*
|
||||
* ---- External Interface ----
|
||||
*
|
||||
@@ -245,12 +147,13 @@ concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>
|
||||
* represent the full range of valid XRP and MPT integer values accurately.
|
||||
*
|
||||
* Note:
|
||||
* 1. The "large" mantissa range is (2^63/10+1) to 2^63-1. 2^63-1 is between
|
||||
* 10^18 and 10^19-1, and (2^63/10+1) is between 10^17 and 10^18-1. Thus,
|
||||
* the mantissa may have 18 or 19 digits. This value will be modified to
|
||||
* always have 19 digits before some operations to ensure consistency.
|
||||
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
|
||||
* mantissa range.
|
||||
* 2. The functions mantissa() and exponent() return the external view of the
|
||||
* Number value, specifically using a signed 63-bit mantissa.
|
||||
* Number value, specifically using a signed 63-bit mantissa. This may
|
||||
* require altering the internal representation to fit into that range
|
||||
* before the value is returned. The interface guarantees consistency of
|
||||
* the two values.
|
||||
* 3. Number cannot represent -2^63 (std::numeric_limits<std::int64_t>::min())
|
||||
* as an exact integer, but it doesn't need to, because all asset values
|
||||
* on-ledger are non-negative. This is due to implementation details of
|
||||
@@ -305,7 +208,8 @@ class Number
|
||||
using rep = std::int64_t;
|
||||
using internalrep = MantissaRange::rep;
|
||||
|
||||
rep mantissa_{0};
|
||||
bool negative_{false};
|
||||
internalrep mantissa_{0};
|
||||
int exponent_{std::numeric_limits<int>::lowest()};
|
||||
|
||||
public:
|
||||
@@ -313,6 +217,10 @@ public:
|
||||
constexpr static int minExponent = -32768;
|
||||
constexpr static int maxExponent = 32768;
|
||||
|
||||
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
|
||||
static_assert(maxRep == 9'223'372'036'854'775'807);
|
||||
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
|
||||
|
||||
// May need to make unchecked private
|
||||
struct unchecked
|
||||
{
|
||||
@@ -390,7 +298,8 @@ public:
|
||||
friend constexpr bool
|
||||
operator==(Number const& x, Number const& y) noexcept
|
||||
{
|
||||
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
|
||||
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ &&
|
||||
x.exponent_ == y.exponent_;
|
||||
}
|
||||
|
||||
friend constexpr bool
|
||||
@@ -404,8 +313,8 @@ public:
|
||||
{
|
||||
// If the two amounts have different signs (zero is treated as positive)
|
||||
// then the comparison is true iff the left is negative.
|
||||
bool const lneg = x.mantissa_ < 0;
|
||||
bool const rneg = y.mantissa_ < 0;
|
||||
bool const lneg = x.negative_;
|
||||
bool const rneg = y.negative_;
|
||||
|
||||
if (lneg != rneg)
|
||||
return lneg;
|
||||
@@ -433,11 +342,9 @@ public:
|
||||
[[nodiscard]] constexpr int
|
||||
signum() const noexcept
|
||||
{
|
||||
if (mantissa_ < 0)
|
||||
{
|
||||
if (negative_)
|
||||
return -1;
|
||||
}
|
||||
return (mantissa_ != 0 ? 1 : 0);
|
||||
return (mantissa_ != 0u) ? 1 : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] Number
|
||||
@@ -476,9 +383,6 @@ public:
|
||||
friend Number
|
||||
root2(Number f);
|
||||
|
||||
friend Number
|
||||
power(Number const& f, unsigned n, unsigned d);
|
||||
|
||||
// Thread local rounding control. Default is to_nearest
|
||||
enum rounding_mode { to_nearest, towards_zero, downward, upward };
|
||||
static rounding_mode
|
||||
@@ -543,39 +447,22 @@ private:
|
||||
static_assert(isPowerOfTen(smallRange.min));
|
||||
static_assert(smallRange.min == 1'000'000'000'000'000LL);
|
||||
static_assert(smallRange.max == 9'999'999'999'999'999LL);
|
||||
static_assert(smallRange.internalMin == smallRange.min);
|
||||
static_assert(smallRange.log == 15);
|
||||
static_assert(smallRange.min < maxRep);
|
||||
static_assert(smallRange.max < maxRep);
|
||||
constexpr static MantissaRange largeRange{MantissaRange::large};
|
||||
static_assert(!isPowerOfTen(largeRange.min));
|
||||
static_assert(largeRange.min == 922'337'203'685'477'581ULL);
|
||||
static_assert(largeRange.max == internalrep(9'223'372'036'854'775'807ULL));
|
||||
static_assert(largeRange.max == std::numeric_limits<rep>::max());
|
||||
static_assert(largeRange.internalMin == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(isPowerOfTen(largeRange.min));
|
||||
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
||||
static_assert(largeRange.log == 18);
|
||||
// There are 2 values that will not fit in largeRange without some extra
|
||||
// work
|
||||
// * 9223372036854775808
|
||||
// * 9223372036854775809
|
||||
// They both end up < min, but with a leftover. If they round up, everything
|
||||
// will be fine. If they don't, we'll need to bring them up into range.
|
||||
// Guard::bringIntoRange handles this situation.
|
||||
static_assert(largeRange.min < maxRep);
|
||||
static_assert(largeRange.max > maxRep);
|
||||
|
||||
// The range for the mantissa when normalized.
|
||||
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
||||
// changing the values inside the range.
|
||||
static thread_local std::reference_wrapper<MantissaRange const> range_;
|
||||
|
||||
// And one is needed because it needs to choose between oneSmall and
|
||||
// oneLarge based on the current range
|
||||
static Number
|
||||
one(MantissaRange const& range);
|
||||
|
||||
static Number
|
||||
root(MantissaRange const& range, Number f, unsigned d);
|
||||
|
||||
void
|
||||
normalize(MantissaRange const& range);
|
||||
|
||||
void
|
||||
normalize();
|
||||
|
||||
@@ -598,15 +485,11 @@ private:
|
||||
friend void
|
||||
doNormalize(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
T& mantissa_,
|
||||
int& exponent_,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa);
|
||||
|
||||
[[nodiscard]]
|
||||
bool
|
||||
isnormal(MantissaRange const& range) const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isnormal() const noexcept;
|
||||
|
||||
@@ -616,69 +499,18 @@ private:
|
||||
[[nodiscard]] Number
|
||||
shiftExponent(int exponentDelta) const;
|
||||
|
||||
// Safely return the absolute value of a rep (int64) mantissa as an internalrep (uint64).
|
||||
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
|
||||
// is negative, returns the positive value. This takes a little extra work
|
||||
// because converting std::numeric_limits<std::int64_t>::min() flirts with
|
||||
// UB, and can vary across compilers.
|
||||
static internalrep
|
||||
externalToInternal(rep mantissa);
|
||||
|
||||
/** Breaks down the number into components, potentially de-normalizing it.
|
||||
*
|
||||
* Ensures that the mantissa always has range_.log + 1 digits.
|
||||
*
|
||||
*/
|
||||
template <detail::UnsignedMantissa Rep = internalrep>
|
||||
std::tuple<bool, Rep, int>
|
||||
toInternal(MantissaRange const& range) const;
|
||||
|
||||
/** Breaks down the number into components, potentially de-normalizing it.
|
||||
*
|
||||
* Ensures that the mantissa always has range_.log + 1 digits.
|
||||
*
|
||||
*/
|
||||
template <detail::UnsignedMantissa Rep = internalrep>
|
||||
std::tuple<bool, Rep, int>
|
||||
toInternal() const;
|
||||
|
||||
/** Rebuilds the number from components.
|
||||
*
|
||||
* If "expectNormal" is true, the values are expected to be normalized - all
|
||||
* in their valid ranges.
|
||||
*
|
||||
* If "expectNormal" is false, the values are expected to be "near
|
||||
* normalized", meaning that the mantissa has to be modified at most once to
|
||||
* bring it back into range.
|
||||
*
|
||||
*/
|
||||
template <bool expectNormal = true, detail::UnsignedMantissa Rep = internalrep>
|
||||
void
|
||||
fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange);
|
||||
|
||||
/** Rebuilds the number from components.
|
||||
*
|
||||
* If "expectNormal" is true, the values are expected to be normalized - all
|
||||
* in their valid ranges.
|
||||
*
|
||||
* If "expectNormal" is false, the values are expected to be "near
|
||||
* normalized", meaning that the mantissa has to be modified at most once to
|
||||
* bring it back into range.
|
||||
*
|
||||
*/
|
||||
template <bool expectNormal = true, detail::UnsignedMantissa Rep = internalrep>
|
||||
void
|
||||
fromInternal(bool negative, Rep mantissa, int exponent);
|
||||
|
||||
class Guard;
|
||||
|
||||
public:
|
||||
constexpr static internalrep largestMantissa = largeRange.max;
|
||||
};
|
||||
|
||||
inline constexpr Number::Number(
|
||||
bool negative,
|
||||
internalrep mantissa,
|
||||
int exponent,
|
||||
unchecked) noexcept
|
||||
: mantissa_{negative ? -static_cast<rep>(mantissa) : static_cast<rep>(mantissa)}
|
||||
, exponent_{exponent}
|
||||
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, unchecked) noexcept
|
||||
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -689,6 +521,12 @@ constexpr Number::Number(internalrep mantissa, int exponent, unchecked) noexcept
|
||||
|
||||
constexpr static Number numZero{};
|
||||
|
||||
inline Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
|
||||
: Number(negative, mantissa, exponent, unchecked{})
|
||||
{
|
||||
normalize();
|
||||
}
|
||||
|
||||
inline Number::Number(internalrep mantissa, int exponent, normalized)
|
||||
: Number(false, mantissa, exponent, normalized{})
|
||||
{
|
||||
@@ -711,7 +549,17 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
||||
constexpr Number::rep
|
||||
Number::mantissa() const noexcept
|
||||
{
|
||||
return mantissa_;
|
||||
auto m = mantissa_;
|
||||
if (m > maxRep)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
|
||||
"xrpl::Number::mantissa",
|
||||
"large normalized mantissa has no remainder");
|
||||
m /= 10;
|
||||
}
|
||||
auto const sign = negative_ ? -1 : 1;
|
||||
return sign * static_cast<Number::rep>(m);
|
||||
}
|
||||
|
||||
/** Returns the exponent of the external view of the Number.
|
||||
@@ -722,7 +570,16 @@ Number::mantissa() const noexcept
|
||||
constexpr int
|
||||
Number::exponent() const noexcept
|
||||
{
|
||||
return exponent_;
|
||||
auto e = exponent_;
|
||||
if (mantissa_ > maxRep)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
|
||||
"xrpl::Number::exponent",
|
||||
"large normalized mantissa has no remainder");
|
||||
++e;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
constexpr Number
|
||||
@@ -737,7 +594,7 @@ Number::operator-() const noexcept
|
||||
if (mantissa_ == 0)
|
||||
return Number{};
|
||||
auto x = *this;
|
||||
x.mantissa_ = -x.mantissa_;
|
||||
x.negative_ = !x.negative_;
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -818,37 +675,31 @@ Number::min() noexcept
|
||||
inline Number
|
||||
Number::max() noexcept
|
||||
{
|
||||
return Number{false, range_.get().max, maxExponent, unchecked{}};
|
||||
return Number{false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
||||
}
|
||||
|
||||
inline Number
|
||||
Number::lowest() noexcept
|
||||
{
|
||||
return Number{true, range_.get().max, maxExponent, unchecked{}};
|
||||
}
|
||||
|
||||
inline bool
|
||||
Number::isnormal(MantissaRange const& range) const noexcept
|
||||
{
|
||||
auto const abs_m = externalToInternal(mantissa_);
|
||||
|
||||
return *this == Number{} ||
|
||||
(range.min <= abs_m && abs_m <= range.max && //
|
||||
minExponent <= exponent_ && exponent_ <= maxExponent);
|
||||
return Number{true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
||||
}
|
||||
|
||||
inline bool
|
||||
Number::isnormal() const noexcept
|
||||
{
|
||||
return isnormal(range_);
|
||||
MantissaRange const& range = range_;
|
||||
auto const abs_m = mantissa_;
|
||||
return *this == Number{} ||
|
||||
(range.min <= abs_m && abs_m <= range.max && (abs_m <= maxRep || abs_m % 10 == 0) &&
|
||||
minExponent <= exponent_ && exponent_ <= maxExponent);
|
||||
}
|
||||
|
||||
template <Integral64 T>
|
||||
std::pair<T, int>
|
||||
Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
||||
{
|
||||
bool negative = mantissa_ < 0;
|
||||
internalrep mantissa = externalToInternal(mantissa_);
|
||||
bool negative = negative_;
|
||||
internalrep mantissa = mantissa_;
|
||||
int exponent = exponent_;
|
||||
|
||||
if constexpr (std::is_unsigned_v<T>)
|
||||
@@ -857,19 +708,11 @@ Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
||||
!negative,
|
||||
"xrpl::Number::normalizeToRange",
|
||||
"Number is non-negative for unsigned range.");
|
||||
// To avoid logical errors in release builds, throw if the Number is
|
||||
// negative for an unsigned range.
|
||||
if (negative)
|
||||
throw std::runtime_error(
|
||||
"Number::normalizeToRange: Number is negative for "
|
||||
"unsigned range.");
|
||||
}
|
||||
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||||
|
||||
// Cast mantissa to signed type first (if T is a signed type) to avoid
|
||||
// unsigned integer overflow when multiplying by negative sign
|
||||
T signedMantissa = negative ? -static_cast<T>(mantissa) : static_cast<T>(mantissa);
|
||||
return std::make_pair(signedMantissa, exponent);
|
||||
auto const sign = negative ? -1 : 1;
|
||||
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
|
||||
}
|
||||
|
||||
constexpr Number
|
||||
|
||||
@@ -232,7 +232,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
|
||||
|
||||
/** The maximum amount of MPTokenIssuance */
|
||||
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
|
||||
static_assert(Number::largestMantissa >= maxMPTokenAmount);
|
||||
static_assert(Number::maxRep >= maxMPTokenAmount);
|
||||
|
||||
/** The maximum length of Data payload */
|
||||
std::size_t constexpr maxDataPayloadLength = 256;
|
||||
|
||||
@@ -540,8 +540,6 @@ STAmount::fromNumber(A const& a, Number const& number)
|
||||
return STAmount{asset, intValue, 0, negative};
|
||||
}
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
working.signum() >= 0, "xrpl::STAmount::fromNumber", "non-negative Number to normalize");
|
||||
auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
|
||||
|
||||
return STAmount{asset, mantissa, exponent, negative};
|
||||
|
||||
@@ -23,7 +23,7 @@ systemName()
|
||||
/** Number of drops in the genesis account. */
|
||||
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
|
||||
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
|
||||
static_assert(Number::largestMantissa >= INITIAL_XRP.drops());
|
||||
static_assert(Number::maxRep >= INITIAL_XRP.drops());
|
||||
|
||||
/** Returns true if the amount does not exceed the initial XRP in existence. */
|
||||
inline bool
|
||||
|
||||
160
package/README.md
Normal file
160
package/README.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Linux Packaging
|
||||
|
||||
This directory contains all files needed to build RPM and Debian packages for `xrpld`.
|
||||
|
||||
## Directory layout
|
||||
|
||||
```
|
||||
package/
|
||||
build_pkg.sh Staging and build script (called by CMake targets and CI)
|
||||
rpm/
|
||||
xrpld.spec RPM spec (xrpld_version/pkg_release passed via rpmbuild --define)
|
||||
debian/ Debian control files (control, rules, install, links, conffiles, ...)
|
||||
shared/
|
||||
xrpld.service systemd unit file (used by both RPM and DEB)
|
||||
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
|
||||
xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB)
|
||||
xrpld.logrotate logrotate config (installed to /opt/xrpld/bin/, user activates)
|
||||
update-xrpld.sh auto-update script (installed to /opt/xrpld/bin/)
|
||||
update-xrpld-cron cron entry for auto-update (installed to /opt/xrpld/bin/)
|
||||
test/
|
||||
smoketest.sh Package install smoke test
|
||||
check_install_paths.sh Verify install paths and compat symlinks
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Packaging targets and their container images are declared in
|
||||
[`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json)
|
||||
via a `"package": true` field on specific os entries. The package format
|
||||
(deb or rpm) is inferred at build time from the container's package manager
|
||||
(`apt-get` -> deb, `dnf`/`yum` -> rpm). The image tag is composed as
|
||||
`ghcr.io/xrplf/ci/{distro}-{version}:{compiler}-{cver}-sha-{image_sha}` —
|
||||
the same scheme used by `reusable-build-test.yml`. Bump `image_sha` in
|
||||
`linux.json` and both CI and local builds pick up the new image with no
|
||||
workflow edits.
|
||||
|
||||
| Package type | Image (derived from `linux.json`) | Tool required |
|
||||
| ------------ | ---------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| RPM | `ghcr.io/xrplf/ci/rhel-9:gcc-12-sha-<git_sha>` | `rpmbuild` |
|
||||
| DEB | `ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12-sha-<git_sha>` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` |
|
||||
|
||||
To print the exact image tags for the current `linux.json`:
|
||||
|
||||
```bash
|
||||
./.github/scripts/strategy-matrix/generate.py --packaging --config=.github/scripts/strategy-matrix/linux.json
|
||||
```
|
||||
|
||||
## Building packages
|
||||
|
||||
### Via CI
|
||||
|
||||
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
|
||||
`reusable-strategy-matrix.yml` with `mode: packaging` to generate the matrix of
|
||||
`{artifact_name, container_image}` entries, then fan out to
|
||||
`reusable-package.yml` per entry. That workflow downloads the pre-built `xrpld`
|
||||
binary artifact, detects the package format from the container, and calls
|
||||
`build_pkg.sh` directly — no CMake configure or build step is needed inside
|
||||
the packaging job.
|
||||
|
||||
### Locally (mirrors CI)
|
||||
|
||||
With an `xrpld` binary already built at `build/xrpld`, run the packaging step
|
||||
inside the same container CI uses. The image tag is derived from `linux.json`
|
||||
so you don't need to hardcode a SHA.
|
||||
|
||||
```bash
|
||||
# From the repo root. Pick any image flagged with `"package": true` in
|
||||
# linux.json; the package format is inferred from the container's package
|
||||
# manager. Example for the rpm-producing image:
|
||||
IMAGE=$(jq -r '
|
||||
.os | map(select(.package == true))[0] |
|
||||
"ghcr.io/xrplf/ci/\(.distro_name)-\(.distro_version):\(.compiler_name)-\(.compiler_version)-sha-\(.image_sha)"
|
||||
' .github/scripts/strategy-matrix/linux.json)
|
||||
|
||||
VERSION=2.4.0-local
|
||||
PKG_RELEASE=1
|
||||
|
||||
docker run --rm \
|
||||
-v "$(pwd):/src" \
|
||||
-w /src \
|
||||
"$IMAGE" \
|
||||
./package/build_pkg.sh . build "$VERSION" "$PKG_RELEASE"
|
||||
|
||||
# Output:
|
||||
# build/debbuild/*.deb (DEB + dbgsym .ddeb)
|
||||
# build/rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
### Via CMake (host-side target)
|
||||
|
||||
If you run CMake configure on a host that has `rpmbuild` or `dpkg-buildpackage`
|
||||
installed natively, you can use the CMake target directly — no container
|
||||
needed, but the host toolchain replaces the pinned CI image:
|
||||
|
||||
```bash
|
||||
cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=/opt/xrpld \
|
||||
-Dxrpld=ON \
|
||||
-Dxrpld_version=2.4.0-local \
|
||||
-Dtests=OFF \
|
||||
..
|
||||
|
||||
cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL
|
||||
```
|
||||
|
||||
The `cmake/XrplPackaging.cmake` module defines the target only if at least one
|
||||
of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then infers the
|
||||
package format from the host's package manager. `CMAKE_INSTALL_PREFIX` must
|
||||
be `/opt/xrpld`; if it is not, both targets are skipped with a `STATUS`
|
||||
message.
|
||||
|
||||
## How `build_pkg.sh` works
|
||||
|
||||
`build_pkg.sh <src_dir> <build_dir> [version] [pkg_release]` stages
|
||||
all files and invokes the platform build tool. It resolves `src_dir` and
|
||||
`build_dir` to absolute paths, then calls `stage_common()` to copy the binary,
|
||||
config files, and shared support files into the staging area. The package
|
||||
format is taken from the `PKG_TYPE` env var if set; otherwise it is inferred
|
||||
from the available package manager (`apt-get` -> deb, `dnf`/`yum` -> rpm).
|
||||
|
||||
### RPM
|
||||
|
||||
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
|
||||
2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`.
|
||||
3. Runs `rpmbuild -bb --define "xrpld_version ..." --define "pkg_release ..."`. The spec uses manual `install` commands to place files.
|
||||
4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
|
||||
|
||||
### DEB
|
||||
|
||||
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
|
||||
2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
|
||||
3. Copies `package/deb/debian/` control files into `debbuild/source/debian/`.
|
||||
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
|
||||
5. Generates a minimal `debian/changelog` (pre-release versions use `~` instead of `-`).
|
||||
6. Runs `dpkg-buildpackage -b --no-sign`. `debian/rules` uses manual `install` commands.
|
||||
7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package)
|
||||
|
||||
## Post-build verification
|
||||
|
||||
```bash
|
||||
# DEB
|
||||
dpkg-deb -c debbuild/*.deb | grep -E 'systemd|sysusers|tmpfiles'
|
||||
lintian -I debbuild/*.deb
|
||||
|
||||
# RPM
|
||||
rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
## Reproducibility
|
||||
|
||||
The following environment variables improve build reproducibility. They are not
|
||||
set automatically by `build_pkg.sh`; set them manually if needed:
|
||||
|
||||
```bash
|
||||
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
export TZ=UTC
|
||||
export LC_ALL=C.UTF-8
|
||||
export GZIP=-n
|
||||
export DEB_BUILD_OPTIONS="noautodbgsym reproducible=+fixfilepath"
|
||||
```
|
||||
134
package/build_pkg.sh
Executable file
134
package/build_pkg.sh
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build an RPM or Debian package from a pre-built xrpld binary.
|
||||
#
|
||||
# Usage: build_pkg.sh <src_dir> <build_dir> [version] [pkg_release]
|
||||
# src_dir : path to repository root
|
||||
# build_dir : directory containing the pre-built xrpld binary
|
||||
# version : package version string (e.g. 3.2.0-b1)
|
||||
# pkg_release : package release number (default: 1)
|
||||
#
|
||||
# The package format is taken from the PKG_TYPE env var if set; otherwise it
|
||||
# is inferred from the available package manager (apt-get -> deb, dnf/yum -> rpm).
|
||||
|
||||
SRC_DIR="$(cd "${1:?src_dir required}" && pwd)"
|
||||
BUILD_DIR="$(cd "${2:?build_dir required}" && pwd)"
|
||||
VERSION="${3:-$("${BUILD_DIR}/xrpld" --version | awk 'NR==1 {print $3}')}"
|
||||
PKG_RELEASE="${4:-1}"
|
||||
|
||||
if [[ -z "${PKG_TYPE:-}" ]]; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
PKG_TYPE=deb
|
||||
elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
|
||||
PKG_TYPE=rpm
|
||||
else
|
||||
echo "Cannot infer PKG_TYPE: no apt-get, dnf, or yum on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${SOURCE_DATE_EPOCH:-}" ]]; then
|
||||
if git -C "$SRC_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
SOURCE_DATE_EPOCH="$(git -C "$SRC_DIR" log -1 --format=%ct)"
|
||||
else
|
||||
SOURCE_DATE_EPOCH="$(date +%s)"
|
||||
fi
|
||||
fi
|
||||
export SOURCE_DATE_EPOCH
|
||||
CHANGELOG_DATE="$(date -u -R -d "@$SOURCE_DATE_EPOCH")"
|
||||
|
||||
# Split VERSION at the first '-' into base and optional pre-release suffix.
|
||||
# Examples: "3.2.0" -> ("3.2.0", ""); "3.2.0-b1" -> ("3.2.0", "b1").
|
||||
VER_BASE="${VERSION%%-*}"
|
||||
VER_SUFFIX="${VERSION#*-}"
|
||||
[[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX=""
|
||||
|
||||
SHARED="${SRC_DIR}/package/shared"
|
||||
DEBIAN_DIR="${SRC_DIR}/package/debian"
|
||||
|
||||
# Stage files that both packaging systems consume using the same filenames.
|
||||
stage_common() {
|
||||
local dest="$1"
|
||||
mkdir -p "${dest}"
|
||||
|
||||
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
|
||||
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
|
||||
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
|
||||
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"
|
||||
cp "${SRC_DIR}/README.md" "${dest}/README.md"
|
||||
|
||||
cp "${SHARED}/xrpld.service" "${dest}/xrpld.service"
|
||||
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
|
||||
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
|
||||
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
|
||||
cp "${SHARED}/update-xrpld.sh" "${dest}/update-xrpld.sh"
|
||||
cp "${SHARED}/update-xrpld-cron" "${dest}/update-xrpld-cron"
|
||||
cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service"
|
||||
cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer"
|
||||
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
|
||||
}
|
||||
|
||||
build_rpm() {
|
||||
local topdir="${BUILD_DIR}/rpmbuild"
|
||||
rm -rf "${topdir}"
|
||||
mkdir -p "${topdir}"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
|
||||
cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec"
|
||||
stage_common "${topdir}/SOURCES"
|
||||
|
||||
# RPM Version can't contain '-'. A pre-release goes in Release with a
|
||||
# leading "0." so 3.2.0-b1 sorts before the final 3.2.0-<pkg_release>.
|
||||
local rpm_release="${PKG_RELEASE}"
|
||||
[[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${VER_SUFFIX}.${PKG_RELEASE}"
|
||||
|
||||
set -x
|
||||
rpmbuild -bb \
|
||||
--define "_topdir ${topdir}" \
|
||||
--define "xrpld_version ${VER_BASE}" \
|
||||
--define "xrpld_release ${rpm_release}" \
|
||||
"${topdir}/SPECS/xrpld.spec"
|
||||
}
|
||||
|
||||
build_deb() {
|
||||
local staging="${BUILD_DIR}/debbuild/source"
|
||||
rm -rf "${staging}"
|
||||
mkdir -p "${staging}"
|
||||
|
||||
stage_common "${staging}"
|
||||
cp -r "${DEBIAN_DIR}" "${staging}/debian"
|
||||
|
||||
# Debhelper auto-discovers these only from debian/.
|
||||
cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service"
|
||||
cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers"
|
||||
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
|
||||
cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service"
|
||||
cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer"
|
||||
# Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0.
|
||||
local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}"
|
||||
|
||||
# Derive release channel from the version suffix:
|
||||
# (none) -> stable (tagged release)
|
||||
# b0 -> develop (develop-branch build)
|
||||
# b<N>, rc<N> -> unstable (pre-release)
|
||||
local deb_distribution
|
||||
case "${VER_SUFFIX}" in
|
||||
"") deb_distribution="stable" ;;
|
||||
b0) deb_distribution="develop" ;;
|
||||
*) deb_distribution="unstable" ;;
|
||||
esac
|
||||
|
||||
cat > "${staging}/debian/changelog" <<EOF
|
||||
xrpld (${deb_full_version}) ${deb_distribution}; urgency=medium
|
||||
* Release ${VERSION}.
|
||||
|
||||
-- XRPL Foundation <contact@xrplf.org> ${CHANGELOG_DATE}
|
||||
EOF
|
||||
|
||||
chmod +x "${staging}/debian/rules"
|
||||
|
||||
set -x
|
||||
( cd "${staging}" && dpkg-buildpackage -b --no-sign -d )
|
||||
}
|
||||
|
||||
"build_${PKG_TYPE}"
|
||||
23
package/debian/control
Normal file
23
package/debian/control
Normal file
@@ -0,0 +1,23 @@
|
||||
Source: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: XRPL Foundation <contact@xrplf.org>
|
||||
Rules-Requires-Root: no
|
||||
Build-Depends:
|
||||
debhelper-compat (= 13)
|
||||
Standards-Version: 4.7.0
|
||||
Homepage: https://github.com/XRPLF/rippled
|
||||
Vcs-Git: https://github.com/XRPLF/rippled.git
|
||||
Vcs-Browser: https://github.com/XRPLF/rippled
|
||||
|
||||
Package: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: any
|
||||
Depends:
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: XRP Ledger daemon
|
||||
Reference implementation of the XRP Ledger protocol. Participates
|
||||
in the peer-to-peer network, processes transactions, and maintains
|
||||
a local ledger copy.
|
||||
20
package/debian/copyright
Normal file
20
package/debian/copyright
Normal file
@@ -0,0 +1,20 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: rippled
|
||||
Source: https://github.com/XRPLF/rippled
|
||||
|
||||
Files: *
|
||||
Copyright: 2012-2026 Ripple Labs Inc.
|
||||
License: ISC
|
||||
|
||||
License: ISC
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
34
package/debian/rules
Normal file
34
package/debian/rules
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
export DH_OPTIONS = -v
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||
@:
|
||||
|
||||
override_dh_auto_install:
|
||||
install -Dm0755 xrpld debian/tmp/opt/xrpld/bin/xrpld
|
||||
install -Dm0644 xrpld.cfg debian/tmp/opt/xrpld/etc/xrpld.cfg
|
||||
install -Dm0644 validators.txt debian/tmp/opt/xrpld/etc/validators.txt
|
||||
install -Dm0644 xrpld.logrotate debian/tmp/opt/xrpld/bin/xrpld.logrotate
|
||||
install -Dm0755 update-xrpld.sh debian/tmp/opt/xrpld/bin/update-xrpld.sh
|
||||
install -Dm0644 update-xrpld-cron debian/tmp/opt/xrpld/bin/update-xrpld-cron
|
||||
install -Dm0644 README.md debian/tmp/usr/share/doc/xrpld/README.md
|
||||
install -Dm0644 LICENSE.md debian/tmp/usr/share/doc/xrpld/LICENSE.md
|
||||
|
||||
# update-xrpld.service is a Type=oneshot fired by update-xrpld.timer; installing
|
||||
# it without --no-start would run the update on package install.
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd xrpld.service
|
||||
dh_installsystemd --name=update-xrpld --no-enable --no-start update-xrpld.service update-xrpld.timer
|
||||
|
||||
execute_before_dh_installtmpfiles:
|
||||
dh_installsysusers
|
||||
|
||||
override_dh_installsysusers:
|
||||
|
||||
override_dh_dwz:
|
||||
@:
|
||||
1
package/debian/source/format
Normal file
1
package/debian/source/format
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (quilt)
|
||||
2
package/debian/xrpld.conffiles
Normal file
2
package/debian/xrpld.conffiles
Normal file
@@ -0,0 +1,2 @@
|
||||
/opt/xrpld/etc/xrpld.cfg
|
||||
/opt/xrpld/etc/validators.txt
|
||||
10
package/debian/xrpld.install
Normal file
10
package/debian/xrpld.install
Normal file
@@ -0,0 +1,10 @@
|
||||
opt/xrpld/bin/xrpld
|
||||
opt/xrpld/bin/xrpld.logrotate
|
||||
opt/xrpld/bin/update-xrpld.sh
|
||||
opt/xrpld/bin/update-xrpld-cron
|
||||
|
||||
opt/xrpld/etc/xrpld.cfg
|
||||
opt/xrpld/etc/validators.txt
|
||||
|
||||
usr/share/doc/xrpld/README.md
|
||||
usr/share/doc/xrpld/LICENSE.md
|
||||
10
package/debian/xrpld.links
Normal file
10
package/debian/xrpld.links
Normal file
@@ -0,0 +1,10 @@
|
||||
opt/xrpld/etc etc/opt/xrpld
|
||||
|
||||
opt/xrpld/bin/xrpld usr/bin/xrpld
|
||||
|
||||
# remove when "rippled" deprecated
|
||||
opt/xrpld/bin/xrpld opt/xrpld/bin/rippled
|
||||
opt/xrpld/bin/xrpld usr/local/bin/rippled
|
||||
opt/xrpld/etc/xrpld.cfg opt/xrpld/etc/rippled.cfg
|
||||
opt/xrpld opt/ripple
|
||||
etc/opt/xrpld etc/opt/ripple
|
||||
111
package/rpm/xrpld.spec
Normal file
111
package/rpm/xrpld.spec
Normal file
@@ -0,0 +1,111 @@
|
||||
Name: xrpld
|
||||
Version: %{xrpld_version}
|
||||
Release: %{xrpld_release}%{?dist}
|
||||
Summary: XRP Ledger daemon
|
||||
|
||||
License: ISC
|
||||
URL: https://github.com/XRPLF/rippled
|
||||
|
||||
ExclusiveArch: x86_64 aarch64
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
%undefine _debugsource_packages
|
||||
%debug_package
|
||||
|
||||
%build_mtime_policy clamp_to_source_date_epoch
|
||||
|
||||
%{?systemd_requires}
|
||||
%{?sysusers_requires_compat}
|
||||
|
||||
%description
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
|
||||
%prep
|
||||
:
|
||||
|
||||
%install
|
||||
rm -rf %{buildroot}
|
||||
|
||||
SRC=%{_sourcedir}
|
||||
|
||||
install -Dm0755 ${SRC}/xrpld %{buildroot}/opt/xrpld/bin/xrpld
|
||||
install -Dm0644 ${SRC}/xrpld.cfg %{buildroot}/opt/xrpld/etc/xrpld.cfg
|
||||
install -Dm0644 ${SRC}/validators.txt %{buildroot}/opt/xrpld/etc/validators.txt
|
||||
|
||||
mkdir -p %{buildroot}/etc/opt %{buildroot}/usr/bin %{buildroot}/usr/local/bin
|
||||
ln -s /opt/xrpld/etc %{buildroot}/etc/opt/xrpld
|
||||
ln -s /opt/xrpld/bin/xrpld %{buildroot}/usr/bin/xrpld
|
||||
|
||||
# TODO: remove when rippled deprecated
|
||||
ln -s xrpld %{buildroot}/opt/xrpld/bin/rippled
|
||||
ln -s /opt/xrpld/bin/xrpld %{buildroot}/usr/local/bin/rippled
|
||||
ln -s xrpld.cfg %{buildroot}/opt/xrpld/etc/rippled.cfg
|
||||
ln -s /opt/xrpld %{buildroot}/opt/ripple
|
||||
ln -s /etc/opt/xrpld %{buildroot}/etc/opt/ripple
|
||||
|
||||
install -Dm0644 ${SRC}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service
|
||||
install -Dm0644 ${SRC}/update-xrpld.service %{buildroot}%{_unitdir}/update-xrpld.service
|
||||
install -Dm0644 ${SRC}/update-xrpld.timer %{buildroot}%{_unitdir}/update-xrpld.timer
|
||||
|
||||
install -Dm0644 ${SRC}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf
|
||||
install -Dm0644 ${SRC}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf
|
||||
|
||||
install -Dm0644 ${SRC}/50-xrpld.preset %{buildroot}%{_presetdir}/50-xrpld.preset
|
||||
|
||||
install -Dm0755 ${SRC}/update-xrpld.sh %{buildroot}/opt/xrpld/bin/update-xrpld.sh
|
||||
install -Dm0644 ${SRC}/update-xrpld-cron %{buildroot}/opt/xrpld/bin/update-xrpld-cron
|
||||
install -Dm0644 ${SRC}/xrpld.logrotate %{buildroot}/opt/xrpld/bin/xrpld.logrotate
|
||||
|
||||
install -Dm0644 ${SRC}/LICENSE.md %{buildroot}/opt/xrpld/share/LICENSE.md
|
||||
install -Dm0644 ${SRC}/README.md %{buildroot}/opt/xrpld/share/README.md
|
||||
|
||||
%pre
|
||||
%sysusers_create_package xrpld %{_sourcedir}/xrpld.sysusers
|
||||
|
||||
%post
|
||||
systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%systemd_post xrpld.service update-xrpld.timer
|
||||
|
||||
%preun
|
||||
%systemd_preun xrpld.service update-xrpld.timer
|
||||
|
||||
%postun
|
||||
%systemd_postun_with_restart xrpld.service
|
||||
|
||||
%files
|
||||
%license /opt/xrpld/share/LICENSE.md
|
||||
%doc /opt/xrpld/share/README.md
|
||||
|
||||
%dir /opt/xrpld
|
||||
%dir /opt/xrpld/bin
|
||||
%dir /opt/xrpld/etc
|
||||
|
||||
/opt/xrpld/bin/xrpld
|
||||
/opt/xrpld/bin/xrpld.logrotate
|
||||
/opt/xrpld/bin/update-xrpld.sh
|
||||
/opt/xrpld/bin/update-xrpld-cron
|
||||
|
||||
/usr/bin/xrpld
|
||||
/etc/opt/xrpld
|
||||
|
||||
%config(noreplace) /opt/xrpld/etc/xrpld.cfg
|
||||
%config(noreplace) /opt/xrpld/etc/validators.txt
|
||||
|
||||
%{_unitdir}/xrpld.service
|
||||
%{_unitdir}/update-xrpld.service
|
||||
%{_unitdir}/update-xrpld.timer
|
||||
%{_presetdir}/50-xrpld.preset
|
||||
%{_sysusersdir}/xrpld.conf
|
||||
%{_tmpfilesdir}/xrpld.conf
|
||||
|
||||
%ghost %dir /var/lib/xrpld
|
||||
%ghost %dir /var/log/xrpld
|
||||
|
||||
# TODO: remove when rippled deprecated
|
||||
/opt/xrpld/bin/rippled
|
||||
/usr/local/bin/rippled
|
||||
/opt/xrpld/etc/rippled.cfg
|
||||
/etc/opt/ripple
|
||||
/opt/ripple
|
||||
4
package/shared/50-xrpld.preset
Normal file
4
package/shared/50-xrpld.preset
Normal file
@@ -0,0 +1,4 @@
|
||||
# /usr/lib/systemd/system-preset/50-xrpld.preset
|
||||
enable xrpld.service
|
||||
# Don't enable automatic updates
|
||||
disable update-xrpld.timer
|
||||
9
package/shared/update-xrpld-cron
Normal file
9
package/shared/update-xrpld-cron
Normal file
@@ -0,0 +1,9 @@
|
||||
# For automatic updates, symlink this file to /etc/cron.d/
|
||||
# Do not remove the newline at the end of this cron script
|
||||
|
||||
# bash required for use of RANDOM below.
|
||||
SHELL=/bin/bash
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin
|
||||
|
||||
# invoke check/update script with random delay up to 59 mins
|
||||
0 * * * * root sleep $((RANDOM*3540/32768)) && /opt/xrpld/bin/update-xrpld.sh
|
||||
16
package/shared/update-xrpld.service
Normal file
16
package/shared/update-xrpld.service
Normal file
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Check for and install xrpld package updates
|
||||
Documentation=man:systemd.service(5)
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
ConditionPathExists=/opt/xrpld/bin/update-xrpld.sh
|
||||
ConditionPathExists=/opt/xrpld/bin/xrpld
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/flock -n /run/lock/xrpld-update.lock /opt/xrpld/bin/update-xrpld.sh
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=update-xrpld
|
||||
TimeoutStartSec=30min
|
||||
PrivateTmp=true
|
||||
145
package/shared/update-xrpld.sh
Executable file
145
package/shared/update-xrpld.sh
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Optional: also write logs to a legacy file in addition to journald.
|
||||
# By default, this script logs to systemd/journald, viewable via:
|
||||
# journalctl -t update-xrpld
|
||||
#
|
||||
# Uncomment the line below if you need a flat file for compatibility with
|
||||
# external tooling, manual inspection, or environments where journald logs
|
||||
# are not persisted or easily accessible.
|
||||
#
|
||||
# Note: This duplicates all output (stdout/stderr) to both journald and the file.
|
||||
# It is generally not needed on modern systems and may cause log file growth
|
||||
# if left enabled long-term.
|
||||
#
|
||||
# Requires /var/log/xrpld/ to exist and be writable by the service (root).
|
||||
#
|
||||
# exec > >(tee -a /var/log/xrpld/update.log) 2>&1
|
||||
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
PKG_NAME=${PKG_NAME:-xrpld}
|
||||
|
||||
log() {
|
||||
# If running under systemd/journald, let it handle timestamps.
|
||||
if [[ -n "${JOURNAL_STREAM:-}" ]]; then
|
||||
printf '%s\n' "$*"
|
||||
else
|
||||
printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
|
||||
fi
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
|
||||
log "RESULT: failed reason=not-root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
get_installed_version() {
|
||||
if command -v dpkg-query >/dev/null 2>&1; then
|
||||
dpkg-query -W -f='${Version}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
|
||||
elif command -v rpm >/dev/null 2>&1; then
|
||||
rpm -q --qf '%{VERSION}-%{RELEASE}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
|
||||
else
|
||||
printf 'unknown'
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'log "RESULT: failed reason=script-error exit_code=$?"' ERR
|
||||
|
||||
apt_can_update() {
|
||||
apt-get update -qq
|
||||
apt-get -s --only-upgrade install "$PKG_NAME" 2>/dev/null | grep -q "^Inst ${PKG_NAME}\b"
|
||||
}
|
||||
|
||||
apt_apply_update() {
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
||||
-o Dpkg::Options::="--force-confdef" \
|
||||
-o Dpkg::Options::="--force-confold" \
|
||||
"$PKG_NAME"
|
||||
}
|
||||
|
||||
get_rpm_pm() {
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
printf 'dnf\n'
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
printf 'yum\n'
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_refresh_metadata() {
|
||||
local pm=$1
|
||||
if [[ "$pm" == "dnf" ]]; then
|
||||
dnf makecache --refresh -q >/dev/null
|
||||
else
|
||||
yum clean expire-cache -q >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_can_update() {
|
||||
local pm=$1
|
||||
|
||||
rpm_refresh_metadata "$pm"
|
||||
local rc=0
|
||||
set +e
|
||||
"$pm" check-update -q "$PKG_NAME" >/dev/null 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if [[ $rc -eq 100 ]]; then
|
||||
return 0
|
||||
elif [[ $rc -eq 0 ]]; then
|
||||
return 1
|
||||
else
|
||||
log "$pm check-update failed with exit code $rc"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_apply_update() {
|
||||
local pm=$1
|
||||
"$pm" update -y "$PKG_NAME"
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
systemctl restart "${PKG_NAME}.service"
|
||||
log "${PKG_NAME} service restarted successfully"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
log "Checking for ${PKG_NAME} updates via apt"
|
||||
if apt_can_update; then
|
||||
log "Update available; installing"
|
||||
apt_apply_update
|
||||
restart_service
|
||||
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
|
||||
else
|
||||
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
local rpm_pm=""
|
||||
if rpm_pm="$(get_rpm_pm)"; then
|
||||
log "Checking for ${PKG_NAME} updates via ${rpm_pm}"
|
||||
if rpm_can_update "$rpm_pm"; then
|
||||
log "Update available; installing"
|
||||
rpm_apply_update "$rpm_pm"
|
||||
restart_service
|
||||
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
|
||||
else
|
||||
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
log "RESULT: failed reason=no-package-manager"
|
||||
exit 1
|
||||
}
|
||||
|
||||
main "$@"
|
||||
10
package/shared/update-xrpld.timer
Normal file
10
package/shared/update-xrpld.timer
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Daily xrpld update check
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 00:00:00
|
||||
RandomizedDelaySec=1h
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
14
package/shared/xrpld.logrotate
Normal file
14
package/shared/xrpld.logrotate
Normal file
@@ -0,0 +1,14 @@
|
||||
/var/log/xrpld/*.log {
|
||||
daily
|
||||
minsize 200M
|
||||
rotate 7
|
||||
nocreate
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
compresscmd /usr/bin/gzip
|
||||
compressext .gz
|
||||
postrotate
|
||||
/opt/xrpld/bin/xrpld --conf /etc/opt/xrpld/xrpld.cfg logrotate
|
||||
endscript
|
||||
}
|
||||
22
package/shared/xrpld.service
Normal file
22
package/shared/xrpld.service
Normal file
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=XRP Ledger Daemon
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/xrpld/bin/xrpld --net --silent --conf /etc/opt/xrpld/xrpld.cfg
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
User=xrpld
|
||||
Group=xrpld
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
1
package/shared/xrpld.sysusers
Normal file
1
package/shared/xrpld.sysusers
Normal file
@@ -0,0 +1 @@
|
||||
u xrpld - "XRP Ledger daemon" /var/lib/xrpld /sbin/nologin
|
||||
2
package/shared/xrpld.tmpfiles
Normal file
2
package/shared/xrpld.tmpfiles
Normal file
@@ -0,0 +1,2 @@
|
||||
d /var/lib/xrpld 0750 xrpld xrpld -
|
||||
d /var/log/xrpld 0750 xrpld xrpld -
|
||||
45
package/test/check_install_paths.sh
Executable file
45
package/test/check_install_paths.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env sh
|
||||
# Validate installed paths and compat symlinks for xrpld packages.
|
||||
|
||||
set -e
|
||||
set -x
|
||||
trap 'test $? -ne 0 && touch /tmp/test_failed' EXIT
|
||||
|
||||
check() { test $1 "$2" || { echo "FAIL: $1 $2"; exit 1; }; }
|
||||
check_resolves_to() {
|
||||
actual=$(readlink -f "$1")
|
||||
[ "$actual" = "$2" ] || { echo "FAIL: $1 resolves to $actual, expected $2"; exit 1; }
|
||||
}
|
||||
|
||||
# compat directory symlinks — existence and resolved target
|
||||
check -L /opt/ripple
|
||||
check_resolves_to /opt/ripple /opt/xrpld
|
||||
|
||||
check -L /etc/opt/xrpld
|
||||
check_resolves_to /etc/opt/xrpld /opt/xrpld/etc
|
||||
|
||||
check -L /etc/opt/ripple
|
||||
check_resolves_to /etc/opt/ripple /opt/xrpld/etc
|
||||
|
||||
# config accessible via all expected paths
|
||||
check -f /opt/xrpld/etc/xrpld.cfg
|
||||
check -f /opt/xrpld/etc/rippled.cfg
|
||||
check -f /etc/opt/xrpld/xrpld.cfg
|
||||
check -f /etc/opt/xrpld/rippled.cfg
|
||||
check -f /etc/opt/ripple/xrpld.cfg
|
||||
check -f /etc/opt/ripple/rippled.cfg
|
||||
|
||||
if systemctl is-system-running >/dev/null 2>&1; then
|
||||
# service file sanity check
|
||||
SERVICE=$(systemctl cat xrpld)
|
||||
echo "$SERVICE" | grep -q 'ExecStart=/opt/xrpld/bin/xrpld' || { echo "FAIL: ExecStart wrong"; echo "$SERVICE"; exit 1; }
|
||||
echo "$SERVICE" | grep -q 'User=xrpld' || { echo "FAIL: User not xrpld"; echo "$SERVICE"; exit 1; }
|
||||
fi
|
||||
|
||||
# binary accessible via all expected paths
|
||||
/opt/xrpld/bin/xrpld --version
|
||||
/opt/xrpld/bin/rippled --version
|
||||
/opt/ripple/bin/xrpld --version
|
||||
/opt/ripple/bin/rippled --version
|
||||
/usr/bin/xrpld --version
|
||||
/usr/local/bin/rippled --version
|
||||
91
package/test/smoketest.sh
Executable file
91
package/test/smoketest.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install a locally-built package and run basic verification.
|
||||
#
|
||||
# Usage: smoketest.sh local
|
||||
# Expects packages in build/{dpkg,rpm}/packages/ or build/debbuild/ / build/rpmbuild/RPMS/
|
||||
|
||||
set -o pipefail
|
||||
set -x
|
||||
rm -f /tmp/test_failed /tmp/unittest_results
|
||||
trap 'test $? -ne 0 && touch /tmp/test_failed' EXIT
|
||||
|
||||
install_from=$1
|
||||
|
||||
. /etc/os-release
|
||||
case ${ID} in
|
||||
ubuntu|debian)
|
||||
pkgtype="dpkg"
|
||||
;;
|
||||
fedora|centos|rhel|rocky|almalinux)
|
||||
pkgtype="rpm"
|
||||
;;
|
||||
*)
|
||||
echo "unrecognized distro!"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${install_from}" != "local" ]; then
|
||||
echo "only 'local' install mode is supported"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install the package
|
||||
if [ "${pkgtype}" = "dpkg" ] ; then
|
||||
apt-get -y update
|
||||
# Find .deb files — check both possible output locations
|
||||
mapfile -t debs < <(find build/debbuild/ build/dpkg/packages/ -name '*.deb' ! -name '*dbgsym*' 2>/dev/null)
|
||||
if [ ${#debs[@]} -eq 0 ]; then
|
||||
echo "No .deb files found"
|
||||
exit 1
|
||||
fi
|
||||
dpkg --no-debsig -i "${debs[@]}" || apt-get -y install -f || { echo "DEB install failed"; exit 1; }
|
||||
elif [ "${pkgtype}" = "rpm" ] ; then
|
||||
# Find .rpm files — check both possible output locations
|
||||
mapfile -t rpms < <(find build/rpmbuild/RPMS/ build/rpm/packages/ -name '*.rpm' \
|
||||
! -name '*debug*' ! -name '*devel*' ! -name '*.src.rpm' 2>/dev/null)
|
||||
if [ ${#rpms[@]} -eq 0 ]; then
|
||||
echo "No .rpm files found"
|
||||
exit 1
|
||||
fi
|
||||
rpm -i "${rpms[@]}" || { echo "RPM install failed"; exit 1; }
|
||||
fi
|
||||
|
||||
# Verify installed version
|
||||
if ! VERSION_OUTPUT=$(/opt/xrpld/bin/xrpld --version); then
|
||||
echo "xrpld --version failed; binary not installed correctly"
|
||||
exit 1
|
||||
fi
|
||||
INSTALLED=$(echo "$VERSION_OUTPUT" | head -1 | awk '{print $NF}')
|
||||
echo "Installed version: ${INSTALLED}"
|
||||
|
||||
# Run unit tests
|
||||
if [ -n "${CI:-}" ]; then
|
||||
unittest_jobs=$(nproc)
|
||||
else
|
||||
unittest_jobs=16
|
||||
fi
|
||||
|
||||
(
|
||||
cd /tmp
|
||||
/opt/xrpld/bin/xrpld --unittest --unittest-jobs "${unittest_jobs}" > /tmp/unittest_results || true
|
||||
)
|
||||
|
||||
if [ ! -s /tmp/unittest_results ]; then
|
||||
echo "Unit test results file is empty — xrpld may have crashed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
num_failures=$(tail /tmp/unittest_results -n1 | grep -oP '\d+(?= failures)')
|
||||
if [ -z "$num_failures" ]; then
|
||||
echo "Could not parse unit test results — expected summary line not found"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${num_failures}" -ne 0 ]; then
|
||||
echo "$num_failures unit test(s) failed:"
|
||||
grep 'failed:' /tmp/unittest_results
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Compat path checks
|
||||
"$(dirname "${BASH_SOURCE[0]}")/check_install_paths.sh"
|
||||
@@ -10,17 +10,20 @@
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
|
||||
#endif
|
||||
|
||||
using uint128_t = xrpl::detail::uint128_t;
|
||||
using int128_t = xrpl::detail::int128_t;
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
using uint128_t = boost::multiprecision::uint128_t;
|
||||
using int128_t = boost::multiprecision::int128_t;
|
||||
#else // !defined(_MSC_VER)
|
||||
using uint128_t = __uint128_t;
|
||||
using int128_t = __int128_t;
|
||||
#endif // !defined(_MSC_VER)
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -59,6 +62,9 @@ Number::setMantissaScale(MantissaRange::mantissa_scale scale)
|
||||
// precision to an operation. This enables the final result
|
||||
// to be correctly rounded to the internal precision of Number.
|
||||
|
||||
template <class T>
|
||||
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
|
||||
|
||||
class Number::Guard
|
||||
{
|
||||
std::uint64_t digits_{0}; // 16 decimal guard digits
|
||||
@@ -92,7 +98,7 @@ public:
|
||||
round() const noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <detail::UnsignedMantissa T>
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundUp(
|
||||
bool& negative,
|
||||
@@ -100,22 +106,22 @@ public:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
std::string_view location);
|
||||
std::string location);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <detail::UnsignedMantissa T>
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
doRound(internalrep& drops, std::string_view location) const;
|
||||
doRound(rep& drops, std::string location) const;
|
||||
|
||||
private:
|
||||
void
|
||||
doPush(unsigned d) noexcept;
|
||||
|
||||
template <detail::UnsignedMantissa T>
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
};
|
||||
@@ -202,7 +208,7 @@ Number::Guard::round() const noexcept
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <detail::UnsignedMantissa T>
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::bringIntoRange(
|
||||
bool& negative,
|
||||
@@ -221,11 +227,13 @@ Number::Guard::bringIntoRange(
|
||||
{
|
||||
constexpr Number zero = Number{};
|
||||
|
||||
std::tie(negative, mantissa, exponent) = zero.toInternal();
|
||||
negative = zero.negative_;
|
||||
mantissa = zero.mantissa_;
|
||||
exponent = zero.exponent_;
|
||||
}
|
||||
}
|
||||
|
||||
template <detail::UnsignedMantissa T>
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundUp(
|
||||
bool& negative,
|
||||
@@ -233,7 +241,7 @@ Number::Guard::doRoundUp(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
std::string_view location)
|
||||
std::string location)
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
@@ -241,7 +249,7 @@ Number::Guard::doRoundUp(
|
||||
++mantissa;
|
||||
// Ensure mantissa after incrementing fits within both the
|
||||
// min/maxMantissa range and is a valid "rep".
|
||||
if (mantissa > maxMantissa)
|
||||
if (mantissa > maxMantissa || mantissa > maxRep)
|
||||
{
|
||||
mantissa /= 10;
|
||||
++exponent;
|
||||
@@ -252,7 +260,7 @@ Number::Guard::doRoundUp(
|
||||
Throw<std::overflow_error>(std::string(location));
|
||||
}
|
||||
|
||||
template <detail::UnsignedMantissa T>
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundDown(
|
||||
bool& negative,
|
||||
@@ -275,25 +283,26 @@ Number::Guard::doRoundDown(
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
Number::Guard::doRound(internalrep& drops, std::string_view location) const
|
||||
Number::Guard::doRound(rep& drops, std::string location) const
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
||||
{
|
||||
auto const& range = range_.get();
|
||||
if (drops >= range.max)
|
||||
if (drops >= maxRep)
|
||||
{
|
||||
static_assert(sizeof(internalrep) == sizeof(rep));
|
||||
// This should be impossible, because it's impossible to represent
|
||||
// "largestMantissa + 0.6" in Number, regardless of the scale. There aren't
|
||||
// enough digits available. You'd either get a mantissa of "largestMantissa"
|
||||
// or "largestMantissa / 10 + 1", neither of which will round up when
|
||||
// "maxRep + 0.6" in Number, regardless of the scale. There aren't
|
||||
// enough digits available. You'd either get a mantissa of "maxRep"
|
||||
// or "(maxRep + 1) / 10", neither of which will round up when
|
||||
// converting to rep, though the latter might overflow _before_
|
||||
// rounding.
|
||||
Throw<std::overflow_error>(std::string(location)); // LCOV_EXCL_LINE
|
||||
}
|
||||
++drops;
|
||||
}
|
||||
if (is_negative())
|
||||
drops = -drops;
|
||||
}
|
||||
|
||||
// Number
|
||||
@@ -308,6 +317,10 @@ Number::externalToInternal(rep mantissa)
|
||||
// If the mantissa is already positive, just return it
|
||||
if (mantissa >= 0)
|
||||
return mantissa;
|
||||
// If the mantissa is negative, but fits within the positive range of rep,
|
||||
// return it negated
|
||||
if (mantissa >= -std::numeric_limits<rep>::max())
|
||||
return -mantissa;
|
||||
|
||||
// If the mantissa doesn't fit within the positive range, convert to
|
||||
// int128_t, negate that, and cast it back down to the internalrep
|
||||
@@ -317,127 +330,10 @@ Number::externalToInternal(rep mantissa)
|
||||
return static_cast<internalrep>(-temp);
|
||||
}
|
||||
|
||||
/** Breaks down the number into components, potentially de-normalizing it.
|
||||
*
|
||||
* Ensures that the mantissa always has range_.log + 1 digits.
|
||||
*
|
||||
*/
|
||||
template <detail::UnsignedMantissa Rep>
|
||||
std::tuple<bool, Rep, int>
|
||||
Number::toInternal(MantissaRange const& range) const
|
||||
{
|
||||
auto exponent = exponent_;
|
||||
bool const negative = mantissa_ < 0;
|
||||
// It should be impossible for mantissa_ to be INT64_MIN, but use externalToInternal just in
|
||||
// case.
|
||||
Rep mantissa = static_cast<Rep>(externalToInternal(mantissa_));
|
||||
|
||||
auto const internalMin = range.internalMin;
|
||||
auto const minMantissa = range.min;
|
||||
|
||||
if (mantissa != 0 && mantissa >= minMantissa && mantissa < internalMin)
|
||||
{
|
||||
// Ensure the mantissa has the correct number of digits
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa >= internalMin && mantissa < internalMin * 10,
|
||||
"xrpl::Number::toInternal()",
|
||||
"Number is within reference range and has 'log' digits");
|
||||
}
|
||||
|
||||
return {negative, mantissa, exponent};
|
||||
}
|
||||
|
||||
/** Breaks down the number into components, potentially de-normalizing it.
|
||||
*
|
||||
* Ensures that the mantissa always has exactly range_.log + 1 digits.
|
||||
*
|
||||
*/
|
||||
template <detail::UnsignedMantissa Rep>
|
||||
std::tuple<bool, Rep, int>
|
||||
Number::toInternal() const
|
||||
{
|
||||
return toInternal(range_);
|
||||
}
|
||||
|
||||
/** Rebuilds the number from components.
|
||||
*
|
||||
* If "expectNormal" is true, the values are expected to be normalized - all
|
||||
* in their valid ranges.
|
||||
*
|
||||
* If "expectNormal" is false, the values are expected to be "near
|
||||
* normalized", meaning that the mantissa has to be modified at most once to
|
||||
* bring it back into range.
|
||||
*
|
||||
*/
|
||||
template <bool expectNormal, detail::UnsignedMantissa Rep>
|
||||
void
|
||||
Number::fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange)
|
||||
{
|
||||
if constexpr (std::is_same_v<std::bool_constant<expectNormal>, std::false_type>)
|
||||
{
|
||||
if (!pRange)
|
||||
throw std::runtime_error("Missing range to Number::fromInternal!");
|
||||
auto const& range = *pRange;
|
||||
|
||||
auto const maxMantissa = range.max;
|
||||
auto const minMantissa = range.min;
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa >= minMantissa, "xrpl::Number::fromInternal", "mantissa large enough");
|
||||
|
||||
if (mantissa > maxMantissa || mantissa < minMantissa)
|
||||
{
|
||||
normalize(negative, mantissa, exponent, range.min, maxMantissa);
|
||||
}
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||||
"xrpl::Number::fromInternal",
|
||||
"mantissa in range");
|
||||
}
|
||||
|
||||
// mantissa is unsigned, but it might not be uint64
|
||||
mantissa_ = static_cast<rep>(static_cast<internalrep>(mantissa));
|
||||
if (negative)
|
||||
mantissa_ = -mantissa_;
|
||||
exponent_ = exponent;
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
(pRange && isnormal(*pRange)) || isnormal(),
|
||||
"xrpl::Number::fromInternal",
|
||||
"Number is normalized");
|
||||
}
|
||||
|
||||
/** Rebuilds the number from components.
|
||||
*
|
||||
* If "expectNormal" is true, the values are expected to be normalized - all in
|
||||
* their valid ranges.
|
||||
*
|
||||
* If "expectNormal" is false, the values are expected to be "near normalized",
|
||||
* meaning that the mantissa has to be modified at most once to bring it back
|
||||
* into range.
|
||||
*
|
||||
*/
|
||||
template <bool expectNormal, detail::UnsignedMantissa Rep>
|
||||
void
|
||||
Number::fromInternal(bool negative, Rep mantissa, int exponent)
|
||||
{
|
||||
MantissaRange const* pRange = nullptr;
|
||||
if constexpr (std::is_same_v<std::bool_constant<expectNormal>, std::false_type>)
|
||||
{
|
||||
pRange = &Number::range_.get();
|
||||
}
|
||||
|
||||
fromInternal(negative, mantissa, exponent, pRange);
|
||||
}
|
||||
|
||||
constexpr Number
|
||||
Number::oneSmall()
|
||||
{
|
||||
return Number{
|
||||
false, Number::smallRange.internalMin, -Number::smallRange.log, Number::unchecked{}};
|
||||
return Number{false, Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
|
||||
};
|
||||
|
||||
constexpr Number oneSml = Number::oneSmall();
|
||||
@@ -445,86 +341,103 @@ constexpr Number oneSml = Number::oneSmall();
|
||||
constexpr Number
|
||||
Number::oneLarge()
|
||||
{
|
||||
return Number{
|
||||
false, Number::largeRange.internalMin, -Number::largeRange.log, Number::unchecked{}};
|
||||
return Number{false, Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
|
||||
};
|
||||
|
||||
constexpr Number oneLrg = Number::oneLarge();
|
||||
|
||||
Number
|
||||
Number::one(MantissaRange const& range)
|
||||
Number::one()
|
||||
{
|
||||
if (&range == &smallRange)
|
||||
if (&range_.get() == &smallRange)
|
||||
return oneSml;
|
||||
XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range");
|
||||
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
|
||||
return oneLrg;
|
||||
}
|
||||
|
||||
Number
|
||||
Number::one()
|
||||
{
|
||||
return one(range_);
|
||||
}
|
||||
|
||||
// Use the member names in this static function for now so the diff is cleaner
|
||||
// TODO: Rename the function parameters to get rid of the "_" suffix
|
||||
template <class T>
|
||||
void
|
||||
doNormalize(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
T& mantissa_,
|
||||
int& exponent_,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa)
|
||||
{
|
||||
auto constexpr minExponent = Number::minExponent;
|
||||
auto constexpr maxExponent = Number::maxExponent;
|
||||
auto constexpr maxRep = Number::maxRep;
|
||||
|
||||
using Guard = Number::Guard;
|
||||
|
||||
constexpr Number zero = Number{};
|
||||
auto const& range = Number::range_.get();
|
||||
if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
|
||||
if (mantissa_ == 0)
|
||||
{
|
||||
std::tie(negative, mantissa, exponent) = zero.toInternal(range);
|
||||
mantissa_ = zero.mantissa_;
|
||||
exponent_ = zero.exponent_;
|
||||
negative = zero.negative_;
|
||||
return;
|
||||
}
|
||||
|
||||
auto m = mantissa;
|
||||
while ((m < minMantissa) && (exponent > minExponent))
|
||||
auto m = mantissa_;
|
||||
while ((m < minMantissa) && (exponent_ > minExponent))
|
||||
{
|
||||
m *= 10;
|
||||
--exponent;
|
||||
--exponent_;
|
||||
}
|
||||
Guard g;
|
||||
if (negative)
|
||||
g.set_negative();
|
||||
while (m > maxMantissa)
|
||||
{
|
||||
if (exponent >= maxExponent)
|
||||
if (exponent_ >= maxExponent)
|
||||
throw std::overflow_error("Number::normalize 1");
|
||||
g.push(m % 10);
|
||||
m /= 10;
|
||||
++exponent;
|
||||
++exponent_;
|
||||
}
|
||||
if ((exponent < minExponent) || (m == 0))
|
||||
if ((exponent_ < minExponent) || (m < minMantissa))
|
||||
{
|
||||
std::tie(negative, mantissa, exponent) = zero.toInternal(range);
|
||||
mantissa_ = zero.mantissa_;
|
||||
exponent_ = zero.exponent_;
|
||||
negative = zero.negative_;
|
||||
return;
|
||||
}
|
||||
|
||||
XRPL_ASSERT_PARTS(m <= maxMantissa, "xrpl::doNormalize", "intermediate mantissa fits in int64");
|
||||
mantissa = m;
|
||||
|
||||
g.doRoundUp(negative, mantissa, exponent, minMantissa, maxMantissa, "Number::normalize 2");
|
||||
// When using the largeRange, "m" needs fit within an int64, even if
|
||||
// the final mantissa_ is going to end up larger to fit within the
|
||||
// MantissaRange. Cut it down here so that the rounding will be done while
|
||||
// it's smaller.
|
||||
//
|
||||
// Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807,
|
||||
// so "m" will be modified to 990,000,000,000,012,345. Then that value
|
||||
// will be rounded to 990,000,000,000,012,345 or
|
||||
// 990,000,000,000,012,346, depending on the rounding mode. Finally,
|
||||
// mantissa_ will be "m*10" so it fits within the range, and end up as
|
||||
// 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460.
|
||||
// mantissa() will return mantissa_ / 10, and exponent() will return
|
||||
// exponent_ + 1.
|
||||
if (m > maxRep)
|
||||
{
|
||||
if (exponent_ >= maxExponent)
|
||||
throw std::overflow_error("Number::normalize 1.5");
|
||||
g.push(m % 10);
|
||||
m /= 10;
|
||||
++exponent_;
|
||||
}
|
||||
// Before modification, m should be within the min/max range. After
|
||||
// modification, it must be less than maxRep. In other words, the original
|
||||
// value should have been no more than maxRep * 10.
|
||||
// (maxRep * 10 > maxMantissa)
|
||||
XRPL_ASSERT_PARTS(m <= maxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
|
||||
mantissa_ = m;
|
||||
|
||||
g.doRoundUp(negative, mantissa_, exponent_, minMantissa, maxMantissa, "Number::normalize 2");
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||||
mantissa_ >= minMantissa && mantissa_ <= maxMantissa,
|
||||
"xrpl::doNormalize",
|
||||
"final mantissa fits in range");
|
||||
XRPL_ASSERT_PARTS(
|
||||
exponent >= minExponent && exponent <= maxExponent,
|
||||
"xrpl::doNormalize",
|
||||
"final exponent fits in range");
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -563,20 +476,11 @@ Number::normalize<unsigned long>(
|
||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||||
}
|
||||
|
||||
void
|
||||
Number::normalize(MantissaRange const& range)
|
||||
{
|
||||
auto [negative, mantissa, exponent] = toInternal(range);
|
||||
|
||||
normalize(negative, mantissa, exponent, range.min, range.max);
|
||||
|
||||
fromInternal(negative, mantissa, exponent, &range);
|
||||
}
|
||||
|
||||
void
|
||||
Number::normalize()
|
||||
{
|
||||
normalize(range_);
|
||||
auto const& range = range_.get();
|
||||
normalize(negative_, mantissa_, exponent_, range.min, range.max);
|
||||
}
|
||||
|
||||
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||||
@@ -586,33 +490,21 @@ Number
|
||||
Number::shiftExponent(int exponentDelta) const
|
||||
{
|
||||
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
|
||||
|
||||
Number result = *this;
|
||||
|
||||
result.exponent_ += exponentDelta;
|
||||
|
||||
if (result.exponent_ >= maxExponent)
|
||||
auto const newExponent = exponent_ + exponentDelta;
|
||||
if (newExponent >= maxExponent)
|
||||
throw std::overflow_error("Number::shiftExponent");
|
||||
if (result.exponent_ < minExponent)
|
||||
if (newExponent < minExponent)
|
||||
{
|
||||
return Number{};
|
||||
}
|
||||
|
||||
Number const result{negative_, mantissa_, newExponent, unchecked{}};
|
||||
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::Number::shiftExponent", "result is normalized");
|
||||
return result;
|
||||
}
|
||||
|
||||
Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
|
||||
{
|
||||
auto const& range = range_.get();
|
||||
normalize(negative, mantissa, exponent, range.min, range.max);
|
||||
fromInternal(negative, mantissa, exponent, &range);
|
||||
}
|
||||
|
||||
Number&
|
||||
Number::operator+=(Number const& y)
|
||||
{
|
||||
auto const& range = range_.get();
|
||||
|
||||
constexpr Number zero = Number{};
|
||||
if (y == zero)
|
||||
return *this;
|
||||
@@ -627,8 +519,7 @@ Number::operator+=(Number const& y)
|
||||
return *this;
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
isnormal(range) && y.isnormal(range), "xrpl::Number::operator+=(Number) : is normal");
|
||||
XRPL_ASSERT(isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal");
|
||||
// *n = negative
|
||||
// *s = sign
|
||||
// *m = mantissa
|
||||
@@ -636,10 +527,13 @@ Number::operator+=(Number const& y)
|
||||
|
||||
// Need to use uint128_t, because large mantissas can overflow when added
|
||||
// together.
|
||||
auto [xn, xm, xe] = toInternal<uint128_t>(range);
|
||||
|
||||
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
|
||||
bool xn = negative_;
|
||||
uint128_t xm = mantissa_;
|
||||
auto xe = exponent_;
|
||||
|
||||
bool const yn = y.negative_;
|
||||
uint128_t ym = y.mantissa_;
|
||||
auto ye = y.exponent_;
|
||||
Guard g;
|
||||
if (xe < ye)
|
||||
{
|
||||
@@ -664,13 +558,14 @@ Number::operator+=(Number const& y)
|
||||
} while (xe > ye);
|
||||
}
|
||||
|
||||
auto const& range = range_.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
|
||||
if (xn == yn)
|
||||
{
|
||||
xm += ym;
|
||||
if (xm > maxMantissa)
|
||||
if (xm > maxMantissa || xm > maxRep)
|
||||
{
|
||||
g.push(xm % 10);
|
||||
xm /= 10;
|
||||
@@ -690,7 +585,7 @@ Number::operator+=(Number const& y)
|
||||
xe = ye;
|
||||
xn = yn;
|
||||
}
|
||||
while (xm < minMantissa)
|
||||
while (xm < minMantissa && xm * 10 <= maxRep)
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
@@ -699,8 +594,10 @@ Number::operator+=(Number const& y)
|
||||
g.doRoundDown(xn, xm, xe, minMantissa);
|
||||
}
|
||||
|
||||
normalize(xn, xm, xe, minMantissa, maxMantissa);
|
||||
fromInternal(xn, xm, xe, &range);
|
||||
negative_ = xn;
|
||||
mantissa_ = static_cast<internalrep>(xm);
|
||||
exponent_ = xe;
|
||||
normalize();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -735,8 +632,6 @@ divu10(uint128_t& u)
|
||||
Number&
|
||||
Number::operator*=(Number const& y)
|
||||
{
|
||||
auto const& range = range_.get();
|
||||
|
||||
constexpr Number zero = Number{};
|
||||
if (*this == zero)
|
||||
return *this;
|
||||
@@ -750,11 +645,15 @@ Number::operator*=(Number const& y)
|
||||
// *m = mantissa
|
||||
// *e = exponent
|
||||
|
||||
auto [xn, xm, xe] = toInternal(range);
|
||||
bool const xn = negative_;
|
||||
int const xs = xn ? -1 : 1;
|
||||
internalrep xm = mantissa_;
|
||||
auto xe = exponent_;
|
||||
|
||||
auto [yn, ym, ye] = y.toInternal(range);
|
||||
bool const yn = y.negative_;
|
||||
int const ys = yn ? -1 : 1;
|
||||
internalrep const ym = y.mantissa_;
|
||||
auto ye = y.exponent_;
|
||||
|
||||
auto zm = uint128_t(xm) * uint128_t(ym);
|
||||
auto ze = xe + ye;
|
||||
@@ -764,10 +663,11 @@ Number::operator*=(Number const& y)
|
||||
if (zn)
|
||||
g.set_negative();
|
||||
|
||||
auto const& range = range_.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
|
||||
while (zm > maxMantissa)
|
||||
while (zm > maxMantissa || zm > maxRep)
|
||||
{
|
||||
// The following is optimization for:
|
||||
// g.push(static_cast<unsigned>(zm % 10));
|
||||
@@ -784,17 +684,17 @@ Number::operator*=(Number const& y)
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||
negative_ = zn;
|
||||
mantissa_ = xm;
|
||||
exponent_ = xe;
|
||||
|
||||
normalize(zn, xm, xe, minMantissa, maxMantissa);
|
||||
fromInternal(zn, xm, xe, &range);
|
||||
normalize();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number&
|
||||
Number::operator/=(Number const& y)
|
||||
{
|
||||
auto const& range = range_.get();
|
||||
|
||||
constexpr Number zero = Number{};
|
||||
if (y == zero)
|
||||
throw std::overflow_error("Number: divide by 0");
|
||||
@@ -807,12 +707,17 @@ Number::operator/=(Number const& y)
|
||||
// *m = mantissa
|
||||
// *e = exponent
|
||||
|
||||
auto [np, nm, ne] = toInternal(range);
|
||||
bool const np = negative_;
|
||||
int const ns = (np ? -1 : 1);
|
||||
auto nm = mantissa_;
|
||||
auto ne = exponent_;
|
||||
|
||||
auto [dp, dm, de] = y.toInternal(range);
|
||||
bool const dp = y.negative_;
|
||||
int const ds = (dp ? -1 : 1);
|
||||
auto dm = y.mantissa_;
|
||||
auto de = y.exponent_;
|
||||
|
||||
auto const& range = range_.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
|
||||
@@ -824,7 +729,7 @@ Number::operator/=(Number const& y)
|
||||
// f can be up to 10^(38-19) = 10^19 safely
|
||||
static_assert(smallRange.log == 15);
|
||||
static_assert(largeRange.log == 18);
|
||||
bool const small = range.scale == MantissaRange::small;
|
||||
bool const small = Number::getMantissaScale() == MantissaRange::small;
|
||||
uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
|
||||
XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size");
|
||||
|
||||
@@ -874,8 +779,10 @@ Number::operator/=(Number const& y)
|
||||
}
|
||||
}
|
||||
normalize(zn, zm, ze, minMantissa, maxMantissa);
|
||||
fromInternal(zn, zm, ze, &range);
|
||||
XRPL_ASSERT_PARTS(isnormal(range), "xrpl::Number::operator/=", "result is normalized");
|
||||
negative_ = zn;
|
||||
mantissa_ = static_cast<internalrep>(zm);
|
||||
exponent_ = ze;
|
||||
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized");
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -883,36 +790,30 @@ Number::operator/=(Number const& y)
|
||||
Number::
|
||||
operator rep() const
|
||||
{
|
||||
auto const m = mantissa();
|
||||
internalrep drops = externalToInternal(m);
|
||||
|
||||
if (drops == 0)
|
||||
return drops;
|
||||
|
||||
rep drops = mantissa();
|
||||
int offset = exponent();
|
||||
Guard g;
|
||||
|
||||
if (m < 0)
|
||||
if (drops != 0)
|
||||
{
|
||||
g.set_negative();
|
||||
if (negative_)
|
||||
{
|
||||
g.set_negative();
|
||||
drops = -drops;
|
||||
}
|
||||
for (; offset < 0; ++offset)
|
||||
{
|
||||
g.push(drops % 10);
|
||||
drops /= 10;
|
||||
}
|
||||
for (; offset > 0; --offset)
|
||||
{
|
||||
if (drops > maxRep / 10)
|
||||
throw std::overflow_error("Number::operator rep() overflow");
|
||||
drops *= 10;
|
||||
}
|
||||
g.doRound(drops, "Number::operator rep() rounding overflow");
|
||||
}
|
||||
for (; offset < 0; ++offset)
|
||||
{
|
||||
g.push(drops % 10);
|
||||
drops /= 10;
|
||||
}
|
||||
for (; offset > 0; --offset)
|
||||
{
|
||||
if (drops >= largeRange.min)
|
||||
throw std::overflow_error("Number::operator rep() overflow");
|
||||
drops *= 10;
|
||||
}
|
||||
g.doRound(drops, "Number::operator rep() rounding overflow");
|
||||
|
||||
if (g.is_negative())
|
||||
return -drops;
|
||||
else
|
||||
return drops;
|
||||
return drops;
|
||||
}
|
||||
|
||||
Number
|
||||
@@ -936,22 +837,19 @@ Number::truncate() const noexcept
|
||||
std::string
|
||||
to_string(Number const& amount)
|
||||
{
|
||||
auto const& range = Number::range_.get();
|
||||
|
||||
// keep full internal accuracy, but make more human friendly if possible
|
||||
constexpr Number zero = Number{};
|
||||
if (amount == zero)
|
||||
return "0";
|
||||
|
||||
// The mantissa must have a set number of decimal places for this to work
|
||||
auto [negative, mantissa, exponent] = amount.toInternal(range);
|
||||
auto exponent = amount.exponent_;
|
||||
auto mantissa = amount.mantissa_;
|
||||
bool const negative = amount.negative_;
|
||||
|
||||
// Use scientific notation for exponents that are too small or too large
|
||||
auto const rangeLog = range.log;
|
||||
if (((exponent != 0 && amount.exponent() != 0) &&
|
||||
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
||||
auto const rangeLog = Number::mantissaLog();
|
||||
if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
||||
{
|
||||
// Remove trailing zeroes from the mantissa.
|
||||
while (mantissa != 0 && mantissa % 10 == 0 && exponent < Number::maxExponent)
|
||||
{
|
||||
mantissa /= 10;
|
||||
@@ -959,11 +857,8 @@ to_string(Number const& amount)
|
||||
}
|
||||
std::string ret = negative ? "-" : "";
|
||||
ret.append(std::to_string(mantissa));
|
||||
if (exponent != 0)
|
||||
{
|
||||
ret.append(1, 'e');
|
||||
ret.append(std::to_string(exponent));
|
||||
}
|
||||
ret.append(1, 'e');
|
||||
ret.append(std::to_string(exponent));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1051,11 +946,20 @@ power(Number const& f, unsigned n)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||||
|
||||
// This function, and power(Number f, unsigned n, unsigned d)
|
||||
// treat corner cases such as 0 roots as advised by Annex F of
|
||||
// the C standard, which itself is consistent with the IEEE
|
||||
// floating point standards.
|
||||
|
||||
Number
|
||||
Number::root(MantissaRange const& range, Number f, unsigned d)
|
||||
root(Number f, unsigned d)
|
||||
{
|
||||
constexpr Number zero = Number{};
|
||||
auto const one = Number::one(range);
|
||||
auto const one = Number::one();
|
||||
|
||||
if (f == one || d == 1)
|
||||
return f;
|
||||
@@ -1072,28 +976,21 @@ Number::root(MantissaRange const& range, Number f, unsigned d)
|
||||
if (f == zero)
|
||||
return f;
|
||||
|
||||
auto const [e, di] = [&]() {
|
||||
auto const exponent = std::get<2>(f.toInternal(range));
|
||||
|
||||
// Scale f into the range (0, 1) such that the scale change (e) is a
|
||||
// multiple of the root (d)
|
||||
auto e = exponent + range.log + 1;
|
||||
auto const di = static_cast<int>(d);
|
||||
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
||||
{
|
||||
int const k = (e >= 0 ? e : e - (di - 1)) / di;
|
||||
int const k2 = e - (k * di);
|
||||
if (k2 == 0)
|
||||
return 0;
|
||||
return di - k2;
|
||||
}();
|
||||
e += ex;
|
||||
f = f.shiftExponent(-e); // f /= 10^e;
|
||||
return std::make_tuple(e, di);
|
||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
||||
auto e = f.exponent_ + Number::mantissaLog() + 1;
|
||||
auto const di = static_cast<int>(d);
|
||||
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
||||
{
|
||||
int const k = (e >= 0 ? e : e - (di - 1)) / di;
|
||||
int const k2 = e - (k * di);
|
||||
if (k2 == 0)
|
||||
return 0;
|
||||
return di - k2;
|
||||
}();
|
||||
e += ex;
|
||||
f = f.shiftExponent(-e); // f /= 10^e;
|
||||
|
||||
XRPL_ASSERT_PARTS(e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d");
|
||||
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized");
|
||||
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized");
|
||||
bool neg = false;
|
||||
if (f < zero)
|
||||
{
|
||||
@@ -1126,33 +1023,15 @@ Number::root(MantissaRange const& range, Number f, unsigned d)
|
||||
|
||||
// return r * 10^(e/d) to reverse scaling
|
||||
auto const result = r.shiftExponent(e / di);
|
||||
XRPL_ASSERT_PARTS(
|
||||
result.isnormal(range), "xrpl::root(Number, unsigned)", "result is normalized");
|
||||
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root(Number, unsigned)", "result is normalized");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||||
|
||||
// This function, and power(Number f, unsigned n, unsigned d)
|
||||
// treat corner cases such as 0 roots as advised by Annex F of
|
||||
// the C standard, which itself is consistent with the IEEE
|
||||
// floating point standards.
|
||||
|
||||
Number
|
||||
root(Number f, unsigned d)
|
||||
{
|
||||
auto const& range = Number::range_.get();
|
||||
return Number::root(range, f, d);
|
||||
}
|
||||
|
||||
Number
|
||||
root2(Number f)
|
||||
{
|
||||
auto const& range = Number::range_.get();
|
||||
constexpr Number zero = Number{};
|
||||
auto const one = Number::one(range);
|
||||
auto const one = Number::one();
|
||||
|
||||
if (f == one)
|
||||
return f;
|
||||
@@ -1161,18 +1040,12 @@ root2(Number f)
|
||||
if (f == zero)
|
||||
return f;
|
||||
|
||||
auto const e = [&]() {
|
||||
auto const exponent = std::get<2>(f.toInternal(range));
|
||||
|
||||
// Scale f into the range (0, 1) such that f's exponent is a
|
||||
// multiple of d
|
||||
auto e = exponent + range.log + 1;
|
||||
if (e % 2 != 0)
|
||||
++e;
|
||||
f = f.shiftExponent(-e); // f /= 10^e;
|
||||
return e;
|
||||
}();
|
||||
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root2(Number)", "f is normalized");
|
||||
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
|
||||
auto e = f.exponent_ + Number::mantissaLog() + 1;
|
||||
if (e % 2 != 0)
|
||||
++e;
|
||||
f = f.shiftExponent(-e); // f /= 10^e;
|
||||
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized");
|
||||
|
||||
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
|
||||
auto const D = 105;
|
||||
@@ -1194,7 +1067,7 @@ root2(Number f)
|
||||
|
||||
// return r * 10^(e/2) to reverse scaling
|
||||
auto const result = r.shiftExponent(e / 2);
|
||||
XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
|
||||
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root2(Number)", "result is normalized");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1204,10 +1077,8 @@ root2(Number f)
|
||||
Number
|
||||
power(Number const& f, unsigned n, unsigned d)
|
||||
{
|
||||
auto const& range = Number::range_.get();
|
||||
|
||||
constexpr Number zero = Number{};
|
||||
auto const one = Number::one(range);
|
||||
auto const one = Number::one();
|
||||
|
||||
if (f == one)
|
||||
return f;
|
||||
@@ -1229,7 +1100,7 @@ power(Number const& f, unsigned n, unsigned d)
|
||||
d /= g;
|
||||
if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
|
||||
throw std::overflow_error("Number::power nan");
|
||||
return Number::root(range, power(f, n), d);
|
||||
return root(power(f, n), d);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -40,10 +40,9 @@ public:
|
||||
test_limits()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
auto const minMantissa = Number::minMantissa();
|
||||
|
||||
testcase << "test_limits " << to_string(scale) << ", " << minMantissa;
|
||||
testcase << "test_limits " << to_string(scale);
|
||||
bool caught = false;
|
||||
auto const minMantissa = Number::minMantissa();
|
||||
try
|
||||
{
|
||||
[[maybe_unused]] Number const x =
|
||||
@@ -68,9 +67,8 @@ public:
|
||||
__LINE__);
|
||||
test(Number{false, minMantissa, -32769, Number::normalized{}}, Number{}, __LINE__);
|
||||
test(
|
||||
// Use 1501 to force rounding up
|
||||
Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 +
|
||||
Number{false, 1'501, 32000, Number::normalized{}},
|
||||
Number{false, 1'500, 32000, Number::normalized{}},
|
||||
Number{false, minMantissa + 2, 32003, Number::normalized{}},
|
||||
__LINE__);
|
||||
// 9,223,372,036,854,775,808
|
||||
@@ -179,12 +177,8 @@ public:
|
||||
{Number{true, 9'999'999'999'999'999'999ULL, -37, Number::normalized{}},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}},
|
||||
{Number{Number::largestMantissa},
|
||||
Number{6, -1},
|
||||
Number{Number::largestMantissa / 10, 1}},
|
||||
{Number{Number::largestMantissa - 1},
|
||||
Number{1, 0},
|
||||
Number{Number::largestMantissa}},
|
||||
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep / 10, 1}},
|
||||
{Number{Number::maxRep - 1}, Number{1, 0}, Number{Number::maxRep}},
|
||||
// Test extremes
|
||||
{
|
||||
// Each Number operand rounds up, so the actual mantissa is
|
||||
@@ -194,18 +188,11 @@ public:
|
||||
Number{2, 19},
|
||||
},
|
||||
{
|
||||
// Does not round. Mantissas are going to be >
|
||||
// largestMantissa, so if added together as uint64_t's, the
|
||||
// result will overflow. With addition using uint128_t,
|
||||
// there's no problem. After normalizing, the resulting
|
||||
// mantissa ends up less than largestMantissa.
|
||||
Number{false, Number::largestMantissa, 0, Number::normalized{}},
|
||||
Number{false, Number::largestMantissa, 0, Number::normalized{}},
|
||||
Number{false, Number::largestMantissa * 2, 0, Number::normalized{}},
|
||||
},
|
||||
{
|
||||
// These mantissas round down, so adding them together won't
|
||||
// have any consequences.
|
||||
// Does not round. Mantissas are going to be > maxRep, so if
|
||||
// added together as uint64_t's, the result will overflow.
|
||||
// With addition using uint128_t, there's no problem. After
|
||||
// normalizing, the resulting mantissa ends up less than
|
||||
// maxRep.
|
||||
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
|
||||
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
|
||||
Number{false, 1'999'999'999'999'999'998ULL, 1, Number::normalized{}},
|
||||
@@ -298,16 +285,14 @@ public:
|
||||
{Number{1'000'000'000'000'000'001, -18},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{1'000'000'000'000'000'000, -36}},
|
||||
{Number{Number::largestMantissa},
|
||||
Number{6, -1},
|
||||
Number{Number::largestMantissa - 1}},
|
||||
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
|
||||
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep - 1}},
|
||||
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::largestMantissa / 10) + 1, 1}},
|
||||
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
|
||||
Number{(Number::maxRep / 10) + 1, 1}},
|
||||
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::largestMantissa}},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
|
||||
Number{Number::maxRep}},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::maxRep}},
|
||||
});
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z] : c)
|
||||
@@ -328,30 +313,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static std::uint64_t
|
||||
getMaxInternalMantissa()
|
||||
{
|
||||
return static_cast<std::uint64_t>(
|
||||
static_cast<std::int64_t>(power(10, Number::mantissaLog()))) *
|
||||
10 -
|
||||
1;
|
||||
}
|
||||
|
||||
void
|
||||
test_mul()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_mul " << to_string(scale);
|
||||
|
||||
// Case: Factor 1, Factor 2, Expected product, Line number
|
||||
using Case = std::tuple<Number, Number, Number, int>;
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z, line] : c)
|
||||
for (auto const& [x, y, z] : c)
|
||||
{
|
||||
auto const result = x * y;
|
||||
std::stringstream ss;
|
||||
ss << x << " * " << y << " = " << result << ". Expected: " << z;
|
||||
BEAST_EXPECTS(result == z, ss.str() + " line: " + std::to_string(line));
|
||||
BEAST_EXPECTS(result == z, ss.str());
|
||||
}
|
||||
};
|
||||
auto tests = [&](auto const& cSmall, auto const& cLarge) {
|
||||
@@ -365,97 +340,70 @@ public:
|
||||
}
|
||||
};
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
auto const maxInternalMantissa = getMaxInternalMantissa();
|
||||
|
||||
saveNumberRoundMode const save{Number::setround(Number::to_nearest)};
|
||||
{
|
||||
auto const cSmall = std::to_array<Case>({
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
Number{2000000000000000, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-2000000000000000, -15},
|
||||
__LINE__},
|
||||
Number{-2000000000000000, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
Number{2000000000000000, -15}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{1000000000000000, -14},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{1000000000000000, -14}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}},
|
||||
// Maximum mantissa range
|
||||
{Number{9'999'999'999'999'999, 0},
|
||||
Number{9'999'999'999'999'999, 0},
|
||||
Number{9'999'999'999'999'998, 16},
|
||||
__LINE__},
|
||||
Number{9'999'999'999'999'998, 16}},
|
||||
});
|
||||
auto const cLarge = std::to_array<Case>({
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items
|
||||
// from C with larger mantissa
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999862, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999862, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999862, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999862, -18}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
|
||||
__LINE__},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{0}},
|
||||
// Items from cSmall expanded for the larger mantissa,
|
||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||
// with higher precision
|
||||
{Number{1414213562373095049, -18},
|
||||
Number{1414213562373095049, -18},
|
||||
Number{2000000000000000001, -18},
|
||||
__LINE__},
|
||||
Number{2000000000000000001, -18}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999998, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999998, -18}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{1999999999999999999, -18},
|
||||
__LINE__},
|
||||
{Number{3214285714285714278, -18},
|
||||
Number{3111111111111111119, -18},
|
||||
Number{10, 0},
|
||||
__LINE__},
|
||||
// Maximum internal mantissa range - rounds up to 1e19
|
||||
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{1, 38},
|
||||
__LINE__},
|
||||
// Maximum actual mantissa range - same as int64 range
|
||||
Number{1999999999999999999, -18}},
|
||||
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
|
||||
// Maximum mantissa range - rounds up to 1e19
|
||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
Number{1, 38}},
|
||||
// Maximum int64 range
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'85, 19}},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -463,90 +411,66 @@ public:
|
||||
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero";
|
||||
{
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
Number{1999999999999999, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999, -15},
|
||||
__LINE__},
|
||||
Number{-1999999999999999, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
Number{1999999999999999, -15}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{9999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__}});
|
||||
Number{9999999999999999, -15}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
|
||||
auto const cLarge = std::to_array<Case>(
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items
|
||||
// from C with larger mantissa
|
||||
{
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999861, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999861, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999861, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999861, -18}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{false, 9999999999999999579ULL, -18, Number::normalized{}},
|
||||
__LINE__},
|
||||
Number{false, 9999999999999999579ULL, -18, Number::normalized{}}},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{0}},
|
||||
// Items from cSmall expanded for the larger mantissa,
|
||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||
// with higher precision
|
||||
{Number{1414213562373095049, -18},
|
||||
Number{1414213562373095049, -18},
|
||||
Number{2, 0},
|
||||
__LINE__},
|
||||
Number{2, 0}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999997, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999997, -18}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{1999999999999999999, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999999, -18}},
|
||||
{Number{3214285714285714278, -18},
|
||||
Number{3111111111111111119, -18},
|
||||
Number{10, 0},
|
||||
__LINE__},
|
||||
// Maximum internal mantissa range - rounds down to
|
||||
// maxMantissa/10e1
|
||||
Number{10, 0}},
|
||||
// Maximum mantissa range - rounds down to maxMantissa/10e1
|
||||
// 99'999'999'999'999'999'800'000'000'000'000'000'100
|
||||
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}},
|
||||
__LINE__},
|
||||
// Maximum actual mantissa range - same as int64
|
||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
Number{false, (maxMantissa / 10) - 1, 20, Number::normalized{}}},
|
||||
// Maximum int64 range
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'84, 19}},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -554,90 +478,66 @@ public:
|
||||
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward";
|
||||
{
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
Number{1999999999999999, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-2000000000000000, -15},
|
||||
__LINE__},
|
||||
Number{-2000000000000000, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
Number{1999999999999999, -15}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{9999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__}});
|
||||
Number{9999999999999999, -15}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
|
||||
auto const cLarge = std::to_array<Case>(
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items
|
||||
// from C with larger mantissa
|
||||
{
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999861, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999862, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999862, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999861, -18}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
|
||||
__LINE__},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{0}},
|
||||
// Items from cSmall expanded for the larger mantissa,
|
||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||
// with higher precision
|
||||
{Number{1414213562373095049, -18},
|
||||
Number{1414213562373095049, -18},
|
||||
Number{2, 0},
|
||||
__LINE__},
|
||||
Number{2, 0}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999998, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999998, -18}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{1999999999999999999, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999999, -18}},
|
||||
{Number{3214285714285714278, -18},
|
||||
Number{3111111111111111119, -18},
|
||||
Number{10, 0},
|
||||
__LINE__},
|
||||
// Maximum internal mantissa range - rounds down to
|
||||
// maxInternalMantissa/10-1
|
||||
Number{10, 0}},
|
||||
// Maximum mantissa range - rounds down to maxMantissa/10e1
|
||||
// 99'999'999'999'999'999'800'000'000'000'000'000'100
|
||||
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}},
|
||||
__LINE__},
|
||||
// Maximum external mantissa range - same as INT64_MAX (2^63-1)
|
||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
Number{false, (maxMantissa / 10) - 1, 20, Number::normalized{}}},
|
||||
// Maximum int64 range
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'84, 19}},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -645,89 +545,66 @@ public:
|
||||
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward";
|
||||
{
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
Number{2000000000000000, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999, -15},
|
||||
__LINE__},
|
||||
Number{-1999999999999999, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
Number{2000000000000000, -15}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{1000000000000000, -14},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__}});
|
||||
Number{1000000000000000, -14}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
|
||||
auto const cLarge = std::to_array<Case>(
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items
|
||||
// from C with larger mantissa
|
||||
{
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999862, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999861, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999861, -18}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
Number{1999999999999999862, -18}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{999999999999999958, -17},
|
||||
__LINE__},
|
||||
Number{999999999999999958, -17}},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{0}},
|
||||
// Items from cSmall expanded for the larger mantissa,
|
||||
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
|
||||
// with higher precision
|
||||
{Number{1414213562373095049, -18},
|
||||
Number{1414213562373095049, -18},
|
||||
Number{2000000000000000001, -18},
|
||||
__LINE__},
|
||||
Number{2000000000000000001, -18}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999997, -18},
|
||||
__LINE__},
|
||||
Number{-1999999999999999997, -18}},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{2, 0},
|
||||
__LINE__},
|
||||
Number{2, 0}},
|
||||
{Number{3214285714285714278, -18},
|
||||
Number{3111111111111111119, -18},
|
||||
Number{1000000000000000001, -17},
|
||||
__LINE__},
|
||||
// Maximum internal mantissa range - rounds up to
|
||||
// minMantissa*10 1e19*1e19=1e38
|
||||
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxInternalMantissa, 0, Number::normalized{}},
|
||||
Number{1, 38},
|
||||
__LINE__},
|
||||
// Maximum mantissa range - same as int64
|
||||
Number{1000000000000000001, -17}},
|
||||
// Maximum mantissa range - rounds up to minMantissa*10
|
||||
// 1e19*1e19=1e38
|
||||
{Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
Number{1, 38}},
|
||||
// Maximum int64 range
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'85, 19}},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -962,8 +839,6 @@ public:
|
||||
};
|
||||
*/
|
||||
|
||||
auto const maxInternalMantissa = getMaxInternalMantissa();
|
||||
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{2}, 2, Number{1414213562373095049, -18}},
|
||||
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
|
||||
@@ -975,16 +850,16 @@ public:
|
||||
{Number{0}, 5, Number{0}},
|
||||
{Number{5625, -4}, 2, Number{75, -2}}});
|
||||
auto const cLarge = std::to_array<Case>({
|
||||
{Number{false, maxInternalMantissa - 9, -1, Number::normalized{}},
|
||||
{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}},
|
||||
2,
|
||||
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
|
||||
{Number{false, maxInternalMantissa - 9, 0, Number::normalized{}},
|
||||
{Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}},
|
||||
2,
|
||||
Number{false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
|
||||
{Number{Number::largestMantissa},
|
||||
{Number{Number::maxRep},
|
||||
2,
|
||||
Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
|
||||
{Number{Number::largestMantissa},
|
||||
{Number{Number::maxRep},
|
||||
4,
|
||||
Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
|
||||
});
|
||||
@@ -1033,8 +908,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
Number const maxInternalMantissa = power(10, Number::mantissaLog()) * 10 - 1;
|
||||
|
||||
auto const cSmall = std::to_array<Number>({
|
||||
Number{2},
|
||||
Number{2'000'000},
|
||||
@@ -1044,10 +917,7 @@ public:
|
||||
Number{5, -1},
|
||||
Number{0},
|
||||
Number{5625, -4},
|
||||
Number{Number::largestMantissa},
|
||||
maxInternalMantissa,
|
||||
Number{Number::minMantissa(), 0, Number::unchecked{}},
|
||||
Number{Number::maxMantissa(), 0, Number::unchecked{}},
|
||||
Number{Number::maxRep},
|
||||
});
|
||||
test(cSmall);
|
||||
bool caught = false;
|
||||
@@ -1398,18 +1268,18 @@ public:
|
||||
case MantissaRange::large:
|
||||
// Test the edges
|
||||
// ((exponent < -(28)) || (exponent > -(8)))))
|
||||
test(Number::min(), "922337203685477581e-32768");
|
||||
test(Number::min(), "1e-32750");
|
||||
test(Number::max(), "9223372036854775807e32768");
|
||||
test(Number::lowest(), "-9223372036854775807e32768");
|
||||
{
|
||||
NumberRoundModeGuard const mg(Number::towards_zero);
|
||||
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL);
|
||||
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
|
||||
test(
|
||||
Number{false, maxMantissa, 0, Number::normalized{}}, "9223372036854775807");
|
||||
Number{false, maxMantissa, 0, Number::normalized{}}, "9999999999999999990");
|
||||
test(
|
||||
Number{true, maxMantissa, 0, Number::normalized{}}, "-9223372036854775807");
|
||||
Number{true, maxMantissa, 0, Number::normalized{}}, "-9999999999999999990");
|
||||
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
|
||||
@@ -1645,7 +1515,7 @@ public:
|
||||
Number const initalXrp{INITIAL_XRP};
|
||||
BEAST_EXPECT(initalXrp.exponent() > 0);
|
||||
|
||||
Number const maxInt64{Number::largestMantissa};
|
||||
Number const maxInt64{Number::maxRep};
|
||||
BEAST_EXPECT(maxInt64.exponent() > 0);
|
||||
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
|
||||
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22}));
|
||||
@@ -1662,213 +1532,21 @@ public:
|
||||
Number const initalXrp{INITIAL_XRP};
|
||||
BEAST_EXPECT(initalXrp.exponent() <= 0);
|
||||
|
||||
Number const maxInt64{Number::largestMantissa};
|
||||
Number const maxInt64{Number::maxRep};
|
||||
BEAST_EXPECT(maxInt64.exponent() <= 0);
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
|
||||
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19}));
|
||||
|
||||
NumberRoundModeGuard const mg(Number::towards_zero);
|
||||
|
||||
{
|
||||
auto const maxInternalMantissa = getMaxInternalMantissa();
|
||||
|
||||
// Rounds down to fit under 2^63
|
||||
Number const max = Number{false, maxInternalMantissa, 0, Number::normalized{}};
|
||||
// No alterations by the accessors
|
||||
BEAST_EXPECT(max.mantissa() == maxInternalMantissa / 10);
|
||||
BEAST_EXPECT(max.exponent() == 1);
|
||||
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
|
||||
// digits
|
||||
BEAST_EXPECT(
|
||||
(power(max, 2) ==
|
||||
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}}));
|
||||
}
|
||||
|
||||
{
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
Number const max = Number{false, maxMantissa, 0, Number::normalized{}};
|
||||
// No alterations by the accessors
|
||||
BEAST_EXPECT(max.mantissa() == maxMantissa);
|
||||
BEAST_EXPECT(max.exponent() == 0);
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38
|
||||
// digits
|
||||
BEAST_EXPECT(
|
||||
(power(max, 2) ==
|
||||
Number{false, 85'070'591'730'234'615'84, 19, Number::normalized{}}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNormalizeToRange()
|
||||
{
|
||||
// Test edge-cases of normalizeToRange
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "normalizeToRange " << to_string(scale);
|
||||
|
||||
auto test = [this](
|
||||
Number const& n,
|
||||
auto const rangeMin,
|
||||
auto const rangeMax,
|
||||
auto const expectedMantissa,
|
||||
auto const expectedExponent,
|
||||
auto const line) {
|
||||
auto const normalized = n.normalizeToRange(rangeMin, rangeMax);
|
||||
BEAST_EXPECTS(
|
||||
normalized.first == expectedMantissa,
|
||||
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
|
||||
". Expected mantissa:" + std::to_string(expectedMantissa) +
|
||||
", got: " + std::to_string(normalized.first) + " @ " + std::to_string(line));
|
||||
BEAST_EXPECTS(
|
||||
normalized.second == expectedExponent,
|
||||
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
|
||||
". Expected exponent:" + std::to_string(expectedExponent) +
|
||||
", got: " + std::to_string(normalized.second) + " @ " + std::to_string(line));
|
||||
};
|
||||
|
||||
std::int64_t constexpr iRangeMin = 100;
|
||||
std::int64_t constexpr iRangeMax = 999;
|
||||
|
||||
std::uint64_t constexpr uRangeMin = 100;
|
||||
std::uint64_t constexpr uRangeMax = 999;
|
||||
|
||||
constexpr static MantissaRange largeRange{MantissaRange::large};
|
||||
|
||||
std::int64_t constexpr iBigMin = largeRange.min;
|
||||
std::int64_t constexpr iBigMax = largeRange.max;
|
||||
|
||||
auto const testSuite = [&](Number const& n,
|
||||
auto const expectedSmallMantissa,
|
||||
auto const expectedSmallExponent,
|
||||
auto const expectedLargeMantissa,
|
||||
auto const expectedLargeExponent,
|
||||
auto const line) {
|
||||
test(n, iRangeMin, iRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
|
||||
test(n, iBigMin, iBigMax, expectedLargeMantissa, expectedLargeExponent, line);
|
||||
|
||||
// Only test non-negative. testing a negative number with an
|
||||
// unsigned range will assert, and asserts can't be tested.
|
||||
if (n.signum() >= 0)
|
||||
{
|
||||
test(n, uRangeMin, uRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
|
||||
test(
|
||||
n,
|
||||
largeRange.min,
|
||||
largeRange.max,
|
||||
expectedLargeMantissa,
|
||||
expectedLargeExponent,
|
||||
line);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
// zero
|
||||
Number const n{0};
|
||||
|
||||
testSuite(
|
||||
n,
|
||||
0,
|
||||
std::numeric_limits<int>::lowest(),
|
||||
0,
|
||||
std::numeric_limits<int>::lowest(),
|
||||
__LINE__);
|
||||
}
|
||||
{
|
||||
// Small positive number
|
||||
Number const n{2};
|
||||
|
||||
testSuite(n, 200, -2, 2'000'000'000'000'000'000, -18, __LINE__);
|
||||
}
|
||||
{
|
||||
// Negative number
|
||||
Number const n{-2};
|
||||
|
||||
testSuite(n, -200, -2, -2'000'000'000'000'000'000, -18, __LINE__);
|
||||
}
|
||||
{
|
||||
// Biggest valid mantissa
|
||||
Number const n{Number::largestMantissa, 0, Number::normalized{}};
|
||||
|
||||
if (scale == MantissaRange::small)
|
||||
// With the small mantissa range, the value rounds up. Because
|
||||
// it rounds up, when scaling up to the full int64 range, it
|
||||
// can't go over the max, so it is one digit smaller than the
|
||||
// full value.
|
||||
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||
else
|
||||
testSuite(n, 922, 16, Number::largestMantissa, 0, __LINE__);
|
||||
}
|
||||
{
|
||||
// Biggest valid mantissa + 1
|
||||
Number const n{Number::largestMantissa + 1, 0, Number::normalized{}};
|
||||
|
||||
if (scale == MantissaRange::small)
|
||||
// With the small mantissa range, the value rounds up. Because
|
||||
// it rounds up, when scaling up to the full int64 range, it
|
||||
// can't go over the max, so it is one digit smaller than the
|
||||
// full value.
|
||||
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||
else
|
||||
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
|
||||
}
|
||||
{
|
||||
// Biggest valid mantissa + 2
|
||||
Number const n{Number::largestMantissa + 2, 0, Number::normalized{}};
|
||||
|
||||
if (scale == MantissaRange::small)
|
||||
// With the small mantissa range, the value rounds up. Because
|
||||
// it rounds up, when scaling up to the full int64 range, it
|
||||
// can't go over the max, so it is one digit smaller than the
|
||||
// full value.
|
||||
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||
else
|
||||
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
|
||||
}
|
||||
{
|
||||
// Biggest valid mantissa + 3
|
||||
Number const n{Number::largestMantissa + 3, 0, Number::normalized{}};
|
||||
|
||||
if (scale == MantissaRange::small)
|
||||
// With the small mantissa range, the value rounds up. Because
|
||||
// it rounds up, when scaling up to the full int64 range, it
|
||||
// can't go over the max, so it is one digit smaller than the
|
||||
// full value.
|
||||
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
|
||||
else
|
||||
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
|
||||
}
|
||||
{
|
||||
// int64 min
|
||||
Number const n{std::numeric_limits<std::int64_t>::min(), 0};
|
||||
|
||||
if (scale == MantissaRange::small)
|
||||
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
|
||||
else
|
||||
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
|
||||
}
|
||||
{
|
||||
// int64 min + 1
|
||||
Number const n{std::numeric_limits<std::int64_t>::min() + 1, 0};
|
||||
|
||||
if (scale == MantissaRange::small)
|
||||
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
|
||||
else
|
||||
testSuite(n, -922, 16, -9'223'372'036'854'775'807, 0, __LINE__);
|
||||
}
|
||||
{
|
||||
// int64 min - 1
|
||||
// Need to cast to uint, even though we're dealing with a negative
|
||||
// number to avoid overflow and UB
|
||||
Number const n{
|
||||
true,
|
||||
-static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::min()) + 1,
|
||||
0,
|
||||
Number::normalized{}};
|
||||
|
||||
if (scale == MantissaRange::small)
|
||||
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
|
||||
else
|
||||
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
Number const max = Number{false, maxMantissa, 0, Number::normalized{}};
|
||||
BEAST_EXPECT(max.mantissa() == maxMantissa / 10);
|
||||
BEAST_EXPECT(max.exponent() == 1);
|
||||
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
|
||||
// digits
|
||||
BEAST_EXPECT(
|
||||
(power(max, 2) == Number{false, (maxMantissa / 10) - 1, 20, Number::normalized{}}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1899,7 +1577,6 @@ public:
|
||||
test_truncate();
|
||||
testRounding();
|
||||
testInt64();
|
||||
testNormalizeToRange();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user