blob: 17a2d0359ad78661e6fe6f4c2d6a59f5ba4a2c38 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_BYTE_SIZE_H_
#define BASE_BYTE_SIZE_H_
#include <concepts>
#include <cstdint>
#include <iosfwd>
#include "base/base_export.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
namespace base {
// ByteSize (unsigned, 64-bit) and ByteSizeDelta (signed, 64-bit) each represent
// an integral number of bytes. They support arithmetic operations and
// conversions to/from KiB, MiB, GiB, TiB, PiB, and EiB.
//
// The range of ByteSize is [0...max(int64_t)] so that it's a strict subset of
// ByteSizeDelta, to simplify conversion rules. Any operation that overflows
// (including converting a negative ByteSizeDelta to ByteSize) will result in a
// crash and thus this should only be used for trusted inputs.
//
// Sample usage:
//
// // Do not reinvent conversion between units.
// constexpr ByteSize kBufferSize = MiBU(1);
// std::vector<char> buffer(kBufferSize.InBytes());
//
// // Enforce that correct units are used across APIs at compile time.
// ByteSize quota = GetQuota();
// SetMetadataSize(KiBU(10));
// ByteSizeDelta remaining_quota = quota - KiBU(10);
// SetDatabaseSize(remaining_quota.AsByteSize());
//
// KiBU()/KiBS(), MiBU()/MiBS(), etc. can take float parameters. This will
// return the nearest integral number of bytes, rounding towards zero.
namespace internal {
// Shared code between ByteSize and ByteSizeDelta.
//
// This wraps a number of bytes stored as int64_t, which covers the full range
// of both ByteSize ([0...max(int64_t)]) and ByteSizeDelta.
class ByteSizeBase {
public:
~ByteSizeBase() = default;
// Comparators and hashers.
constexpr friend bool operator==(ByteSizeBase a, ByteSizeBase b) = default;
constexpr friend auto operator<=>(ByteSizeBase a, ByteSizeBase b) = default;
template <typename H>
friend H AbslHashValue(H h, ByteSizeBase b) {
return H::combine(std::move(h), b.bytes_);
}
protected:
// Can only be constructed by subclasses.
constexpr ByteSizeBase() = default;
template <typename T>
constexpr explicit ByteSizeBase(T bytes)
: bytes_(checked_cast<int64_t>(bytes)) {}
// Conversion helper functions.
template <typename ReturnType>
constexpr ReturnType InBytesImpl() const {
return checked_cast<ReturnType>(bytes_);
}
template <typename ReturnType>
constexpr ReturnType InKiBImpl() const {
return InBytesImpl<ReturnType>() / 1024;
}
template <typename ReturnType>
constexpr ReturnType InMiBImpl() const {
return InBytesImpl<ReturnType>() / 1024 / 1024;
}
template <typename ReturnType>
constexpr ReturnType InGiBImpl() const {
return InBytesImpl<ReturnType>() / 1024 / 1024 / 1024;
}
template <typename ReturnType>
constexpr ReturnType InTiBImpl() const {
return InBytesImpl<ReturnType>() / 1024 / 1024 / 1024 / 1024;
}
template <typename ReturnType>
constexpr ReturnType InPiBImpl() const {
return InBytesImpl<ReturnType>() / 1024 / 1024 / 1024 / 1024 / 1024;
}
template <typename ReturnType>
constexpr ReturnType InEiBImpl() const {
return InBytesImpl<ReturnType>() / 1024 / 1024 / 1024 / 1024 / 1024 / 1024;
}
// Returns the wrapped value as a CheckedNumeric. Always use int64_t for
// checked math so that intermediate steps can handle the full range of
// ByteSizeDelta. The ByteSize constructor will CHECK if a final ByteSize
// result is negative.
constexpr CheckedNumeric<int64_t> AsChecked() const { return bytes_; }
// Helpers to implement math operations, storing the result in ResultType.
// These all modify a CheckedNumeric in-place because
// `operator+(CheckedNumeric<int64_t>, uint64_t)` will return a
// CheckedNumeric<uint64_t>, failing on any negative value.
template <typename ResultType>
requires std::derived_from<ResultType, ByteSizeBase>
constexpr ResultType AddImpl(ByteSizeBase other) const {
auto checked_bytes = AsChecked();
checked_bytes += other.bytes_;
return ResultType(checked_bytes);
}
template <typename ResultType>
requires std::derived_from<ResultType, ByteSizeBase>
constexpr ResultType SubImpl(ByteSizeBase other) const {
auto checked_bytes = AsChecked();
checked_bytes -= other.bytes_;
return ResultType(checked_bytes);
}
template <typename ResultType, typename T>
requires std::derived_from<ResultType, ByteSizeBase>
constexpr ResultType MulImpl(T value) const {
auto checked_bytes = AsChecked();
checked_bytes *= value;
return ResultType(checked_bytes);
}
template <typename ResultType, typename T>
requires std::derived_from<ResultType, ByteSizeBase>
constexpr ResultType DivImpl(T value) const {
auto checked_bytes = AsChecked();
checked_bytes /= value;
return ResultType(checked_bytes);
}
private:
// The wrapped number of bytes. Not stored as a CheckedNumeric because it only
// needs to be checked on modification. It doesn't carry around errors. This
// is private so that it can't be modified by subclasses without going through
// the constructor, which range-checks it.
int64_t bytes_ = 0;
};
} // namespace internal
class ByteSizeDelta;
// A non-negative number of bytes, in the range [0...max(int64_t)].
class BASE_EXPORT ByteSize : public internal::ByteSizeBase {
public:
constexpr ByteSize() = default;
// Constructs a ByteSize from an unsigned integer. CHECK's that the value is
// in range.
template <typename T>
requires std::unsigned_integral<T>
constexpr explicit ByteSize(T bytes)
// The inherited constructor ensures `bytes` is also in range for int64_t.
: ByteSizeBase(checked_cast<uint64_t>(bytes)) {}
// Constructs a ByteSize from a compile-time constant with signed type, for
// convenience. Constants that are out of range will fail to compile.
//
// To construct a ByteSize from a signed integer at runtime, cast explicitly
// or use FromByteSizeDelta().
template <typename T>
requires std::signed_integral<T>
consteval explicit ByteSize(T bytes)
// The inherited constructor ensures `bytes` is also in range for int64_t.
: ByteSizeBase(checked_cast<uint64_t>(bytes)) {}
// Converts ByteSize to and from a signed ByteSizeDelta. Converting from a
// delta CHECK's that it's in range (ie. non-negative). Converting to a delta
// always succeeds.
static constexpr ByteSize FromByteSizeDelta(ByteSizeDelta delta);
constexpr ByteSizeDelta AsByteSizeDelta() const;
// Returns a value corresponding to the "maximum" number of bytes possible.
// Useful as a constant to mean "unlimited".
static constexpr ByteSize Max() {
return ByteSize(std::numeric_limits<int64_t>::max());
}
constexpr bool is_zero() const { return InBytes() == 0; }
constexpr bool is_max() const { return *this == Max(); }
// Conversion to integral values.
constexpr uint64_t InBytes() const { return InBytesImpl<uint64_t>(); }
constexpr uint64_t InKiB() const { return InKiBImpl<uint64_t>(); }
constexpr uint64_t InMiB() const { return InMiBImpl<uint64_t>(); }
constexpr uint64_t InGiB() const { return InGiBImpl<uint64_t>(); }
constexpr uint64_t InTiB() const { return InTiBImpl<uint64_t>(); }
constexpr uint64_t InPiB() const { return InPiBImpl<uint64_t>(); }
constexpr uint64_t InEiB() const { return InEiBImpl<uint64_t>(); }
// Conversion to floating point values.
constexpr double InBytesF() const { return InBytesImpl<double>(); }
constexpr double InKiBF() const { return InKiBImpl<double>(); }
constexpr double InMiBF() const { return InMiBImpl<double>(); }
constexpr double InGiBF() const { return InGiBImpl<double>(); }
constexpr double InTiBF() const { return InTiBImpl<double>(); }
constexpr double InPiBF() const { return InPiBImpl<double>(); }
constexpr double InEiBF() const { return InEiBImpl<double>(); }
// Math operators. Addition and subtraction deliberately support only ByteSize
// and ByteSizeDelta, to make sure all values are constructed with explicit
// units.
constexpr ByteSize& operator+=(ByteSize other) {
return *this = AddImpl<ByteSize>(other);
}
constexpr ByteSize& operator+=(ByteSizeDelta delta);
constexpr ByteSize& operator-=(ByteSize other) {
return *this = SubImpl<ByteSize>(other);
}
constexpr ByteSize& operator-=(ByteSizeDelta delta);
template <typename T>
constexpr ByteSize& operator*=(T value) {
return *this = MulImpl<ByteSize>(value);
}
template <typename T>
constexpr ByteSize& operator/=(T value) {
return *this = DivImpl<ByteSize>(value);
}
// Returns the sum of two ByteSizes.
friend constexpr ByteSize operator+(ByteSize left, ByteSize right) {
return left.AddImpl<ByteSize>(right);
}
// Returns the delta between two ByteSizes.
friend constexpr ByteSizeDelta operator-(ByteSize left, ByteSize right);
// Modifies a ByteSize by a delta, and returns the result.
friend constexpr ByteSize operator+(ByteSize left, ByteSizeDelta right);
friend constexpr ByteSize operator+(ByteSizeDelta left, ByteSize right);
friend constexpr ByteSize operator-(ByteSize left, ByteSizeDelta right);
// Scales a ByteSize by a numeric value, and returns the result.
template <typename T>
friend constexpr ByteSize operator*(ByteSize left, T right) {
return left.MulImpl<ByteSize>(right);
}
template <typename T>
friend constexpr ByteSize operator*(T left, ByteSize right) {
return right * left;
}
template <typename T>
friend constexpr ByteSize operator/(ByteSize left, T right) {
return left.DivImpl<ByteSize>(right);
}
private:
// Allow ByteSizeBase::AddImpl, etc. to construct from the math result.
friend class ByteSizeBase;
constexpr explicit ByteSize(CheckedNumeric<int64_t> checked_bytes)
: ByteSizeBase(checked_cast<uint64_t>(checked_bytes.ValueOrDie())) {}
};
// A signed number of bytes, in the range [min(int64_t)...max(int64_t)].
class BASE_EXPORT ByteSizeDelta : public internal::ByteSizeBase {
public:
constexpr ByteSizeDelta() = default;
// Constructs a ByteSizeDelta from a signed integer.
template <typename T>
requires std::signed_integral<T>
constexpr explicit ByteSizeDelta(T bytes) : ByteSizeBase(bytes) {}
// Constructs a ByteSizeDelta from a compile-time constant with unsigned type,
// for convenience. Constants that are out of range will fail to compile.
//
// To construct a ByteSizeDelta from an unsigned integer at runtime, cast
// explicitly or use FromByteSize().
template <typename T>
requires std::unsigned_integral<T>
consteval explicit ByteSizeDelta(T bytes)
// The inherited constructor ensures `bytes` is in range for int64_t.
: ByteSizeBase(bytes) {}
// Converts ByteSizeDelta to and from an unsigned ByteSize. Converting from a
// delta CHECK's that it's in range (ie. non-negative). Converting to a delta
// always succeeds.
static constexpr ByteSizeDelta FromByteSize(ByteSize size) {
return size.AsByteSizeDelta();
}
constexpr ByteSize AsByteSize() const {
return ByteSize(checked_cast<uint64_t>(InBytes()));
}
// Returns a value corresponding to the "maximum" (positive) number of bytes
// possible. Useful as a constant to mean "unlimited" in the positive
// direction.
static constexpr ByteSizeDelta Max() {
return ByteSizeDelta(std::numeric_limits<int64_t>::max());
}
// Returns a value corresponding to the "minimum" (or maximum negative) number
// of bytes possible. Useful as a constant to mean "unlimited" in the negative
// direction.
static constexpr ByteSizeDelta Min() {
return ByteSizeDelta(std::numeric_limits<int64_t>::min());
}
constexpr bool is_positive() const { return InBytes() > 0; }
constexpr bool is_zero() const { return InBytes() == 0; }
constexpr bool is_negative() const { return InBytes() < 0; }
constexpr bool is_max() const { return *this == Max(); }
constexpr bool is_min() const { return *this == Min(); }
// Conversion to integral values.
constexpr int64_t InBytes() const { return InBytesImpl<int64_t>(); }
constexpr int64_t InKiB() const { return InKiBImpl<int64_t>(); }
constexpr int64_t InMiB() const { return InMiBImpl<int64_t>(); }
constexpr int64_t InGiB() const { return InGiBImpl<int64_t>(); }
constexpr int64_t InTiB() const { return InTiBImpl<int64_t>(); }
constexpr int64_t InPiB() const { return InPiBImpl<int64_t>(); }
constexpr int64_t InEiB() const { return InEiBImpl<int64_t>(); }
// Conversion to floating point values.
constexpr double InBytesF() const { return InBytesImpl<double>(); }
constexpr double InKiBF() const { return InKiBImpl<double>(); }
constexpr double InMiBF() const { return InMiBImpl<double>(); }
constexpr double InGiBF() const { return InGiBImpl<double>(); }
constexpr double InTiBF() const { return InTiBImpl<double>(); }
constexpr double InPiBF() const { return InPiBImpl<double>(); }
constexpr double InEiBF() const { return InEiBImpl<double>(); }
// Returns the absolute value, as a ByteSizeDelta. CHECK's that the absolute
// value is in range (ie. not Min(), since two's complement minimums have no
// corresponding positive value in range.)
constexpr ByteSizeDelta Abs() const {
return ByteSizeDelta(AsChecked().Abs());
}
// Returns the absolute value, as a ByteSize. CHECK's that the absolute value
// is in range for a ByteSize.
constexpr ByteSize Magnitude() const { return Abs().AsByteSize(); }
// Math operators. Addition and subtraction deliberately support only
// ByteSizeDelta, to make sure all values are constructed with explicit units.
constexpr ByteSizeDelta operator+() const { return *this; }
constexpr ByteSizeDelta operator-() const {
return ByteSizeDelta(-AsChecked());
}
constexpr ByteSizeDelta& operator+=(ByteSizeDelta other) {
return *this = AddImpl<ByteSizeDelta>(other);
}
constexpr ByteSizeDelta& operator-=(ByteSizeDelta other) {
return *this = SubImpl<ByteSizeDelta>(other);
}
template <typename T>
constexpr ByteSizeDelta& operator*=(T value) {
return *this = MulImpl<ByteSizeDelta>(value);
}
template <typename T>
constexpr ByteSizeDelta& operator/=(T value) {
return *this = DivImpl<ByteSizeDelta>(value);
}
friend constexpr ByteSizeDelta operator+(ByteSizeDelta left,
ByteSizeDelta right) {
return left.AddImpl<ByteSizeDelta>(right);
}
friend constexpr ByteSizeDelta operator-(ByteSizeDelta left,
ByteSizeDelta right) {
return left.SubImpl<ByteSizeDelta>(right);
}
template <typename T>
friend constexpr ByteSizeDelta operator*(ByteSizeDelta left, T right) {
return left.MulImpl<ByteSizeDelta>(right);
}
template <typename T>
friend constexpr ByteSizeDelta operator*(T left, ByteSizeDelta right) {
return right * left;
}
template <typename T>
friend constexpr ByteSizeDelta operator/(ByteSizeDelta left, T right) {
return left.DivImpl<ByteSizeDelta>(right);
}
private:
// Allow ByteSizeBase::AddImpl, etc. to construct from the math result.
friend class ByteSizeBase;
constexpr explicit ByteSizeDelta(CheckedNumeric<int64_t> checked_bytes)
: ByteSizeBase(checked_bytes.ValueOrDie()) {}
};
// Templated functions to construct from various types. Note that integers must
// be converted to CheckedNumeric BEFORE multiplying to detect overflows, while
// floats must be converted AFTER multiplying to avoid premature truncation.
//
// TODO(crbug.com/448661443): After all uses of KiB, etc, are migrated to
// explicit signed/ unsigned, rename KiBU to KiB.
template <typename T>
requires std::integral<T>
constexpr ByteSize KiBU(T kib) {
return ByteSize(kib) * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSize KiBU(T kib) {
return ByteSize(checked_cast<uint64_t>(kib * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSizeDelta KiBS(T kib) {
return ByteSizeDelta(kib) * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSizeDelta KiBS(T kib) {
return ByteSizeDelta(checked_cast<int64_t>(kib * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSize MiBU(T mib) {
return ByteSize(mib) * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSize MiBU(T mib) {
return ByteSize(checked_cast<uint64_t>(mib * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSizeDelta MiBS(T mib) {
return ByteSizeDelta(mib) * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSizeDelta MiBS(T mib) {
return ByteSizeDelta(checked_cast<int64_t>(mib * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSize GiBU(T gib) {
return ByteSize(gib) * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSize GiBU(T gib) {
return ByteSize(checked_cast<uint64_t>(gib * 1024.0 * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSizeDelta GiBS(T gib) {
return ByteSizeDelta(gib) * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSizeDelta GiBS(T gib) {
return ByteSizeDelta(checked_cast<int64_t>(gib * 1024.0 * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSize TiBU(T tib) {
return ByteSize(tib) * 1024 * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSize TiBU(T tib) {
return ByteSize(
checked_cast<uint64_t>(tib * 1024.0 * 1024.0 * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSizeDelta TiBS(T tib) {
return ByteSizeDelta(tib) * 1024 * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSizeDelta TiBS(T tib) {
return ByteSizeDelta(
checked_cast<int64_t>(tib * 1024.0 * 1024.0 * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSize PiBU(T pib) {
return ByteSize(pib) * 1024 * 1024 * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSize PiBU(T pib) {
return ByteSize(
checked_cast<uint64_t>(pib * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSizeDelta PiBS(T pib) {
return ByteSizeDelta(pib) * 1024 * 1024 * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSizeDelta PiBS(T pib) {
return ByteSizeDelta(
checked_cast<int64_t>(pib * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSize EiBU(T eib) {
return ByteSize(eib) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSize EiBU(T eib) {
return ByteSize(checked_cast<uint64_t>(eib * 1024.0 * 1024.0 * 1024.0 *
1024.0 * 1024.0 * 1024.0));
}
template <typename T>
requires std::integral<T>
constexpr ByteSizeDelta EiBS(T eib) {
return ByteSizeDelta(eib) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
}
template <typename T>
requires std::floating_point<T>
constexpr ByteSizeDelta EiBS(T eib) {
return ByteSizeDelta(checked_cast<int64_t>(eib * 1024.0 * 1024.0 * 1024.0 *
1024.0 * 1024.0 * 1024.0));
}
// Stream operators for logging and testing.
BASE_EXPORT std::ostream& operator<<(std::ostream& os, ByteSize size);
BASE_EXPORT std::ostream& operator<<(std::ostream& os, ByteSizeDelta delta);
// Implementation.
// static
constexpr ByteSize ByteSize::FromByteSizeDelta(ByteSizeDelta delta) {
return delta.AsByteSize();
}
constexpr ByteSizeDelta ByteSize::AsByteSizeDelta() const {
return ByteSizeDelta(checked_cast<int64_t>(InBytes()));
}
constexpr ByteSize& ByteSize::operator+=(ByteSizeDelta delta) {
return *this = AddImpl<ByteSize>(delta);
}
constexpr ByteSize& ByteSize::operator-=(ByteSizeDelta delta) {
return *this = SubImpl<ByteSize>(delta);
}
constexpr ByteSizeDelta operator-(ByteSize left, ByteSize right) {
return left.SubImpl<ByteSizeDelta>(right);
}
constexpr ByteSize operator+(ByteSize left, ByteSizeDelta right) {
return left.AddImpl<ByteSize>(right);
}
constexpr ByteSize operator+(ByteSizeDelta left, ByteSize right) {
return right + left;
}
constexpr ByteSize operator-(ByteSize left, ByteSizeDelta right) {
return left.SubImpl<ByteSize>(right);
}
} // namespace base
#endif // BASE_BYTE_SIZE_H_