blob: cc12e9327438b365c3a44c442153f09329c51c00 [file]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2025 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
// https://llvm.org/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Giuliano Procida
#include "number.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <sstream>
#include <vector>
#include <catch2/catch.hpp>
namespace {
using stg::Number;
template<typename T>
void CheckStrings(T n) {
const Number number(n);
std::ostringstream os1;
os1 << (n + 0); // promote char to int
const auto n_as_string = os1.str();
std::ostringstream os2;
os2 << number;
CHECK(!os2.fail());
const auto number_as_string = os2.str();
CHECK(n_as_string == number_as_string);
}
template<typename T>
void CheckAllStrings() {
auto min = std::numeric_limits<T>::min();
auto max = std::numeric_limits<T>::max();
for (auto n = min; n < max; ++n) {
CheckStrings(n);
}
CheckStrings(max);
}
template<typename T>
void CheckLimitStrings() {
auto min = std::numeric_limits<T>::min();
auto max = std::numeric_limits<T>::max();
CheckStrings(min);
CheckStrings(max);
}
TEST_CASE("all int8_t via Number to string") {
CheckAllStrings<int8_t>();
}
TEST_CASE("all uint8_t via Number to string") {
CheckAllStrings<uint8_t>();
}
TEST_CASE("all int16_t via Number to string") {
CheckAllStrings<int16_t>();
}
TEST_CASE("all uint16_t via Number to string") {
CheckAllStrings<uint16_t>();
}
TEST_CASE("min/max int32_t via Number to string") {
CheckLimitStrings<int32_t>();
}
TEST_CASE("min/max uint32_t via Number to string") {
CheckLimitStrings<uint32_t>();
}
TEST_CASE("min/max int64_t via Number to string") {
CheckLimitStrings<int64_t>();
}
TEST_CASE("min/max uint64_t via Number to string") {
CheckLimitStrings<uint64_t>();
}
template<typename T>
void CheckZeroChunks() {
const Number zero;
const Number four(4);
for (size_t ix = 0; ix < 12; ++ix) {
std::vector<T> chunks;
chunks.resize(ix);
const auto number = Number::FromChunks(chunks);
CHECK(number == zero);
}
for (size_t ix = 1; ix < 12; ++ix) {
std::vector<T> chunks;
chunks.resize(ix);
chunks[0] = 4;
const auto number = Number::FromChunks(chunks);
CHECK(number == four);
}
}
TEST_CASE("zero chunks") {
CheckZeroChunks<uint8_t>();
CheckZeroChunks<uint16_t>();
CheckZeroChunks<uint32_t>();
CheckZeroChunks<uint64_t>();
}
TEST_CASE("Number to chunks and back") {
for (int64_t a = 0; a < 2; ++a) {
for (int64_t b = 0; b < 2; ++b) {
for (int64_t c = 0; c < 2; ++c) {
for (int64_t d = 0; d < 2; ++d) {
for (int64_t e = 0; e < 2; ++e) {
for (int64_t f = 0; f < 2; ++f) {
for (int64_t g = 0; g < 2; ++g) {
const int64_t n = (a << 56) + (b << 55) + (c << 32)
+ (d << 31) + (e << 8) + (f << 7) + g;
GIVEN(n) {
const Number number(n);
const auto chunks8 = Number::ToChunks<uint8_t>(number);
const auto chunks16 = Number::ToChunks<uint16_t>(number);
const auto chunks32 = Number::ToChunks<uint32_t>(number);
const auto chunks64 = Number::ToChunks<uint64_t>(number);
const Number number8 = Number::FromChunks(chunks8);
const Number number16 = Number::FromChunks(chunks16);
const Number number32 = Number::FromChunks(chunks32);
const Number number64 = Number::FromChunks(chunks64);
CHECK(number8 == number);
CHECK(number16 == number);
CHECK(number32 == number);
CHECK(number64 == number);
CHECK(chunks64.size() == (n ? 1 : 0));
if (!chunks64.empty()) {
CHECK(static_cast<int64_t>(chunks64[0]) == n);
}
}
}
}
}
}
}
}
}
}
template<typename T>
void CheckChunks(const std::vector<T>& chunks) {
const Number number = Number::FromChunks(chunks);
std::ostringstream os;
os << number;
auto chunks1 = Number::ToChunks<T>(number);
const T zeros = 0;
const T ones = ~zeros;
const T top = 1ULL << (sizeof(T) * 8 - 1);
const T extension
= chunks1.empty() || (chunks1.back() & top) == 0 ? zeros : ones;
while (chunks1.size() < chunks.size()) {
chunks1.push_back(extension);
}
CHECK(chunks1 == chunks);
}
TEST_CASE("1-byte chunks to Number and back") {
for (size_t length = 0; length <= 10; ++length) {
std::vector<uint8_t> chunks;
chunks.resize(length);
for (size_t bits = 0; bits < (1ULL << (2 * length)); ++bits) {
for (size_t probe = 0; probe < length; ++probe) {
uint8_t chunk = 0;
if (bits & (1ULL << (2 * probe))) {
chunk = chunk | 0x7f;
}
if (bits & (1ULL << (2 * probe + 1))) {
chunk = chunk | 0x80;
}
chunks[probe] = chunk;
}
CheckChunks(chunks);
}
}
}
TEST_CASE("2-byte chunk to Number and back") {
for (size_t length = 0; length <= 5; ++length) {
std::vector<uint16_t> chunks;
chunks.resize(length);
for (size_t bits = 0; bits < (1ULL << (4 * length)); ++bits) {
for (size_t probe = 0; probe < length; ++probe) {
uint16_t chunk = 0;
if (bits & (1ULL << (4 * probe))) {
chunk = chunk | 0x7f;
}
if (bits & (1ULL << (4 * probe + 1))) {
chunk = chunk | 0x80;
}
if (bits & (1ULL << (4 * probe + 2))) {
chunk = chunk | 0x7f00;
}
if (bits & (1ULL << (4 * probe + 3))) {
chunk = chunk | 0x8000;
}
chunks[probe] = chunk;
}
CheckChunks(chunks);
}
}
}
TEST_CASE("4-byte chunk to Number and back") {
for (size_t length = 0; length <= 3; ++length) {
std::vector<uint32_t> chunks;
chunks.resize(length);
for (size_t bits = 0; bits < (1ULL << (8 * length)); ++bits) {
for (size_t probe = 0; probe < length; ++probe) {
uint32_t chunk = 0;
if (bits & (1ULL << (8 * probe))) {
chunk = chunk | 0x7f;
}
if (bits & (1ULL << (8 * probe + 1))) {
chunk = chunk | 0x80;
}
if (bits & (1ULL << (8 * probe + 2))) {
chunk = chunk | 0x7f00;
}
if (bits & (1ULL << (8 * probe + 3))) {
chunk = chunk | 0x8000;
}
if (bits & (1ULL << (8 * probe + 4))) {
chunk = chunk | 0x7f0000;
}
if (bits & (1ULL << (8 * probe + 5))) {
chunk = chunk | 0x800000;
}
if (bits & (1ULL << (8 * probe + 6))) {
chunk = chunk | 0x7f000000;
}
if (bits & (1ULL << (8 * probe + 7))) {
chunk = chunk | 0x80000000;
}
chunks[probe] = chunk;
}
CheckChunks(chunks);
}
}
}
TEST_CASE("8-byte chunk to Number and back") {
for (size_t length = 0; length <= 2; ++length) {
std::vector<uint64_t> chunks;
chunks.resize(length);
for (size_t bits = 0; bits < (1ULL << (8 * length)); ++bits) {
for (size_t probe = 0; probe < length; ++probe) {
uint64_t chunk = 0;
if (bits & (1ULL << (8 * probe))) {
chunk = chunk | 0x7f;
}
if (bits & (1ULL << (8 * probe + 1))) {
chunk = chunk | 0x80;
}
if (bits & (1ULL << (8 * probe + 2))) {
chunk = chunk | 0x7f00;
}
if (bits & (1ULL << (8 * probe + 3))) {
chunk = chunk | 0x8000;
}
if (bits & (1ULL << (8 * probe + 4))) {
chunk = chunk | 0x7fffffffff0000;
}
if (bits & (1ULL << (8 * probe + 5))) {
chunk = chunk | 0x80000000000000;
}
if (bits & (1ULL << (8 * probe + 6))) {
chunk = chunk | 0x7f00000000000000;
}
if (bits & (1ULL << (8 * probe + 7))) {
chunk = chunk | 0x8000000000000000;
}
chunks[probe] = chunk;
}
CheckChunks(chunks);
}
}
}
constexpr std::array kPowersOf2 = {
"1",
"2",
"4",
"8",
"16",
"32",
"64",
"128",
"256",
"512",
"1024",
"2048",
"4096",
"8192",
"16384",
"32768",
"65536",
"131072",
"262144",
"524288",
"1048576",
"2097152",
"4194304",
"8388608",
"16777216",
"33554432",
"67108864",
"134217728",
"268435456",
"536870912",
"1073741824",
"2147483648",
"4294967296",
"8589934592",
"17179869184",
"34359738368",
"68719476736",
"137438953472",
"274877906944",
"549755813888",
"1099511627776",
"2199023255552",
"4398046511104",
"8796093022208",
"17592186044416",
"35184372088832",
"70368744177664",
"140737488355328",
"281474976710656",
"562949953421312",
"1125899906842624",
"2251799813685248",
"4503599627370496",
"9007199254740992",
"18014398509481984",
"36028797018963968",
"72057594037927936",
"144115188075855872",
"288230376151711744",
"576460752303423488",
"1152921504606846976",
"2305843009213693952",
"4611686018427387904",
"9223372036854775808",
"18446744073709551616",
"36893488147419103232",
"73786976294838206464",
"147573952589676412928",
"295147905179352825856",
"590295810358705651712",
"1180591620717411303424",
"2361183241434822606848",
"4722366482869645213696",
"9444732965739290427392",
"18889465931478580854784",
"37778931862957161709568",
"75557863725914323419136",
"151115727451828646838272",
"302231454903657293676544",
"604462909807314587353088",
"1208925819614629174706176",
"2417851639229258349412352",
"4835703278458516698824704",
"9671406556917033397649408",
"19342813113834066795298816",
"38685626227668133590597632",
"77371252455336267181195264",
"154742504910672534362390528",
"309485009821345068724781056",
"618970019642690137449562112",
"1237940039285380274899124224",
"2475880078570760549798248448",
"4951760157141521099596496896",
"9903520314283042199192993792",
"19807040628566084398385987584",
"39614081257132168796771975168",
"79228162514264337593543950336",
"158456325028528675187087900672",
"316912650057057350374175801344",
"633825300114114700748351602688",
"1267650600228229401496703205376",
"2535301200456458802993406410752",
"5070602400912917605986812821504",
"10141204801825835211973625643008",
"20282409603651670423947251286016",
"40564819207303340847894502572032",
"81129638414606681695789005144064",
"162259276829213363391578010288128",
"324518553658426726783156020576256",
"649037107316853453566312041152512",
"1298074214633706907132624082305024",
"2596148429267413814265248164610048",
"5192296858534827628530496329220096",
"10384593717069655257060992658440192",
"20769187434139310514121985316880384",
"41538374868278621028243970633760768",
"83076749736557242056487941267521536",
"166153499473114484112975882535043072",
"332306998946228968225951765070086144",
"664613997892457936451903530140172288",
"1329227995784915872903807060280344576",
"2658455991569831745807614120560689152",
"5316911983139663491615228241121378304",
"10633823966279326983230456482242756608",
"21267647932558653966460912964485513216",
"42535295865117307932921825928971026432",
"85070591730234615865843651857942052864",
"170141183460469231731687303715884105728",
"340282366920938463463374607431768211456",
"680564733841876926926749214863536422912",
"1361129467683753853853498429727072845824",
"2722258935367507707706996859454145691648",
"5444517870735015415413993718908291383296",
"10889035741470030830827987437816582766592",
"21778071482940061661655974875633165533184",
"43556142965880123323311949751266331066368",
"87112285931760246646623899502532662132736",
"174224571863520493293247799005065324265472",
"348449143727040986586495598010130648530944",
"696898287454081973172991196020261297061888",
"1393796574908163946345982392040522594123776",
"2787593149816327892691964784081045188247552",
"5575186299632655785383929568162090376495104",
"11150372599265311570767859136324180752990208",
"22300745198530623141535718272648361505980416",
"44601490397061246283071436545296723011960832",
"89202980794122492566142873090593446023921664",
"178405961588244985132285746181186892047843328",
"356811923176489970264571492362373784095686656",
"713623846352979940529142984724747568191373312",
"1427247692705959881058285969449495136382746624",
"2854495385411919762116571938898990272765493248",
"5708990770823839524233143877797980545530986496",
"11417981541647679048466287755595961091061972992",
"22835963083295358096932575511191922182123945984",
"45671926166590716193865151022383844364247891968",
"91343852333181432387730302044767688728495783936",
"182687704666362864775460604089535377456991567872",
"365375409332725729550921208179070754913983135744",
"730750818665451459101842416358141509827966271488",
"1461501637330902918203684832716283019655932542976",
};
TEST_CASE("unsigned bytes powers") {
for (size_t ix = 0; ix < kPowersOf2.size(); ++ix) {
const auto& decimal = kPowersOf2[ix];
const auto byte = ix / 8;
const auto bit = ix % 8;
std::vector<char> bytes(byte + 1, 0);
bytes[byte] = 1 << bit;
const auto number = Number::FromUnsignedBytes({bytes.data(), bytes.size()});
std::ostringstream os;
os << number;
CHECK(os.str() == decimal);
bytes.push_back(0);
const auto padded = Number::FromUnsignedBytes({bytes.data(), bytes.size()});
CHECK(padded == number);
}
}
TEST_CASE("signed bytes powers") {
for (size_t ix = 0; ix < kPowersOf2.size(); ++ix) {
const auto& decimal = kPowersOf2[ix];
const auto byte = ix / 8;
const auto bit = ix % 8;
std::vector<char> bytes(byte + (bit == 7 ? 2 : 1), 0);
bytes[byte] = 1 << bit;
const auto number = Number::FromSignedBytes({bytes.data(), bytes.size()});
std::ostringstream os;
os << number;
CHECK(os.str() == decimal);
bytes.push_back(0);
const auto padded = Number::FromUnsignedBytes({bytes.data(), bytes.size()});
CHECK(padded == number);
}
}
TEST_CASE("negative bytes powers") {
for (size_t ix = 0; ix < kPowersOf2.size(); ++ix) {
const auto& decimal = kPowersOf2[ix];
const auto byte = ix / 8;
const auto bit = ix % 8;
std::vector<char> bytes(byte + 1, 0);
for (auto extension_bit = bit; extension_bit < 8; ++extension_bit) {
bytes[byte] |= 1 << extension_bit;
}
const auto number = Number::FromSignedBytes({bytes.data(), bytes.size()});
std::ostringstream os;
os << number;
std::ostringstream decimal_os;
decimal_os << '-' << decimal;
CHECK(os.str() == decimal_os.str());
bytes.push_back(-1);
const auto padded = Number::FromSignedBytes({bytes.data(), bytes.size()});
CHECK(padded == number);
}
}
} // namespace