blob: 1e4ffbfe9ce96990e81a3e26b402711f180138b3 [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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <ostream>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
namespace stg {
namespace {
bool Negative(const std::string& limbs) {
return !limbs.empty() && (limbs.back() & 0x80) != 0;
}
void Negate(std::string& limbs) {
// negate in 2's complement
bool carry = true;
for (char& limb : limbs) {
limb = ~limb;
if (carry) {
limb = static_cast<unsigned char>(limb) + 1;
carry = limb == 0;
}
}
}
void Trim(std::string& limbs) {
const bool negative = Negative(limbs);
while (!limbs.empty()
&& (limbs.back() == 0 || limbs.back() == static_cast<char>(-1))) {
const bool new_negative = limbs.size() > 1
&& (limbs[limbs.size() - 2] & 0x80) != 0;
if (new_negative != negative) {
break;
}
limbs.pop_back();
}
}
} // namespace
template<typename T>
Number::Number(T n) {
static_assert(std::is_integral_v<T>);
if constexpr (std::is_signed_v<T>) {
// This works for both signed and unsigned T, but with explicit template
// instantiation it triggers Clang signed / unsigned comparison warnings.
while (n != 0 && n != -1) {
limbs_.push_back(n);
n = n >> 8;
}
// sign extend, if needed
if (Negative(limbs_) != (n < 0)) {
limbs_.push_back(n);
}
} else {
while (n != 0) {
limbs_.push_back(n);
n = n >> 8;
}
// zero extend, if needed
if (Negative(limbs_)) {
limbs_.push_back(0);
}
}
}
std::ostream& Number::Print(std::ostream& os) const {
std::string limbs = limbs_;
if (Negative(limbs)) {
Negate(limbs);
os << '-';
}
std::string digits;
while (!limbs.empty()) {
char carry = 0;
for (auto it = limbs.rbegin(); it != limbs.rend(); ++it) {
auto& value = *it;
const unsigned int intermediate
= static_cast<uint8_t>(value) | (static_cast<uint8_t>(carry) << 8);
value = intermediate / 10;
carry = intermediate % 10;
}
digits.push_back('0' + carry);
if (limbs.back() == 0) {
limbs.pop_back();
}
}
if (digits.empty()) {
return os << '0';
}
std::reverse(digits.begin(), digits.end());
return os << digits;
}
int64_t Number::HashValue() const {
// abbreviated ToChunks<uint64_t>, for backwards compatibility
uint64_t result = 0;
const char extension = Negative(limbs_) ? -1 : 0;
for (size_t ix = 0; ix < 8; ++ix) {
const uint8_t value = ix < limbs_.size() ? limbs_[ix] : extension;
result |= static_cast<uint64_t>(value) << (8 * ix);
}
return static_cast<int64_t>(result);
}
template<typename T>
std::vector<T> Number::ToChunks(const Number& number) {
static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>);
std::vector<T> chunks;
const auto& limbs = number.limbs_;
const char extension = Negative(limbs) ? -1 : 0;
for (size_t ix = 0; ix < limbs.size(); ix += sizeof(T)) {
T chunk = 0;
for (size_t iy = 0; iy < sizeof(T); ++iy) {
const size_t byte = ix + iy;
const uint8_t limb = byte < limbs.size() ? limbs[byte] : extension;
chunk |= static_cast<T>(limb) << (8 * iy);
}
chunks.push_back(chunk);
}
return chunks;
}
template<typename T>
Number Number::FromChunks(const std::vector<T>& chunks) {
static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>);
Number number;
auto& limbs = number.limbs_;
for (size_t ix = 0; ix < chunks.size(); ++ix) {
T value = chunks[ix];
for (size_t iy = 0; iy < sizeof(T); ++iy) {
limbs.push_back(value);
value = value >> 8;
}
}
// remove excess sign extension
Trim(limbs);
return number;
}
Number Number::FromBytes(bool is_signed, std::string_view bytes) {
Number number;
auto& limbs = number.limbs_;
limbs.reserve(bytes.size());
for (auto byte : bytes) {
limbs.push_back(byte);
}
if (is_signed) {
Trim(limbs);
} else {
// need to avoid a false negative if the highest bit is set
if (Negative(limbs)) {
limbs.push_back(0);
} else {
Trim(limbs);
}
}
return number;
}
template Number::Number(int8_t);
template Number::Number(int16_t);
template Number::Number(int32_t);
template Number::Number(int64_t);
template Number::Number(uint8_t);
template Number::Number(uint16_t);
template Number::Number(uint32_t);
template Number::Number(uint64_t);
template std::vector<uint8_t> Number::ToChunks(const Number&);
template std::vector<uint16_t> Number::ToChunks(const Number&);
template std::vector<uint32_t> Number::ToChunks(const Number&);
template std::vector<uint64_t> Number::ToChunks(const Number&);
template Number Number::FromChunks(const std::vector<uint8_t>&);
template Number Number::FromChunks(const std::vector<uint16_t>&);
template Number Number::FromChunks(const std::vector<uint32_t>&);
template Number Number::FromChunks(const std::vector<uint64_t>&);
} // namespace stg