Files
create/src/Base/UnlimitedUnsigned.h
Luz Paz ee4fa234c9 Fix various typos
Missed by the codespell CI not being functional for a period of time.
2025-05-15 10:59:48 -05:00

236 lines
9.4 KiB
C++

/***************************************************************************
* Copyright (c) 2025 Kevin Martin <kpmartin@papertrail.ca> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef SRC_BASE_UNLIMITEDUNSIGNED_H_
#define SRC_BASE_UNLIMITEDUNSIGNED_H_
#ifndef FC_GLOBAL_H
#include <FCGlobal.h>
#endif
#include <vector>
#include <string>
#include <stdexcept>
// ----------------------------------------------------------------------------
namespace Base
{
class UnlimitedUnsigned
{
private:
// This should be a signed type so we can get transient negative values during calculations
// without resorting to a larger type
using PartType = int32_t;
// The following must be a type that limits the value to >= 0 and < maxPartPlusOne
using SmallDeltaType = unsigned char;
explicit UnlimitedUnsigned(std::vector<PartType>&& partsVector)
: parts(std::move(partsVector))
{}
// We take a really cheap floor(bitcount * log10(max PartType)). 3/10 is just a bit less
// than log10(2) The value should be sizeof(PartType) * CHAR_BIT * 3 / 10 but we can't
// calculate the corresponding maxPartPlusOne in a static const initializer, so we just wire
// in the values for 4-byte PartType.
static const size_t partDigitCount = 9;
static const PartType maxPartPlusOne =
1000000000; // (PartType)pow(10, partDigitCount); but can't call pow in a const ctor.
public:
explicit UnlimitedUnsigned(SmallDeltaType value)
: parts(std::vector<PartType>(1, value))
{}
// Parse a decimal digit string into an UnlimitedUnsigned. There should be no white space,
// only digits. a zero-length string parses as zero.
static UnlimitedUnsigned fromString(const std::string& text)
{
std::vector<PartType> result((text.size() + partDigitCount - 1) / partDigitCount);
size_t minimumSize = 0;
size_t lastStartPosition = text.size();
// Parse chunks of digits starting with the least significant
for (size_t i = 0; i < result.size(); i++) {
if (partDigitCount >= lastStartPosition) {
// partial chunk of leading digits
result[i] = static_cast<PartType>(std::stoul(text.substr(0, lastStartPosition)));
}
else {
lastStartPosition -= partDigitCount;
result[i] = static_cast<PartType>(
std::stoul(text.substr(lastStartPosition, partDigitCount)));
}
if (result[i] != 0) {
minimumSize = i + 1;
}
}
result.resize(minimumSize);
return UnlimitedUnsigned(std::move(result));
}
std::string toString()
{
std::string result;
result.reserve(parts.size() * partDigitCount);
// Format the chunks from most- to least-significant
for (auto it = parts.rbegin(); it != parts.rend(); it++) {
std::string partDigits = std::to_string(*it);
if (!result.empty()) {
size_t padding = partDigitCount - partDigits.size();
if (padding > 0) {
result += std::string(padding, '0');
}
}
result += partDigits;
}
return result;
}
// TODO: Some allocation of vector contents could be avoided by defining methods like
// static UnlimitedUnsigned operator+(UnlimitedUnsigned&& left, const UnlimitedUnsigned&
// right); static UnlimitedUnsigned operator+(const UnlimitedUnsigned& left,
// UnlimitedUnsigned&& right); static UnlimitedUnsigned operator+(UnlimitedUnsigned&& left,
// UnlimitedUnsigned&& right); which would reuse left.parts or right.parts after possibly
// growing it. The last one would use the larger of the two buffers.
UnlimitedUnsigned operator+(const UnlimitedUnsigned& right) const
{
size_t resultSize = std::max(parts.size(), right.parts.size());
std::vector<PartType> result(resultSize);
PartType carry = 0;
for (size_t i = 0; i < resultSize; i++) {
auto newPart = (i < parts.size() ? parts[i] : 0)
+ (i < right.parts.size() ? right.parts[i] : 0) + carry;
if (newPart < maxPartPlusOne) {
carry = 0;
}
else {
carry = 1;
newPart -= maxPartPlusOne;
}
result[i] = newPart;
}
if (carry > 0) {
result.resize(resultSize + 1);
result[resultSize] = carry;
}
return UnlimitedUnsigned(std::move(result));
}
UnlimitedUnsigned operator+(SmallDeltaType right) const
{
size_t resultSize = parts.size();
std::vector<PartType> result(resultSize);
PartType carry = right;
for (size_t i = 0; i < resultSize; i++) {
auto newPart = parts[i] + carry;
if (newPart < maxPartPlusOne) {
carry = 0;
}
else {
carry = 1;
newPart -= maxPartPlusOne;
}
result[i] = newPart;
}
if (carry > 0) {
result.resize(resultSize + 1);
result[resultSize] = carry;
}
return UnlimitedUnsigned(std::move(result));
}
UnlimitedUnsigned operator-(const UnlimitedUnsigned& right) const
{
size_t resultSize = std::max(parts.size(), right.parts.size());
std::vector<PartType> result(resultSize);
PartType borrow = 0;
size_t lastNonZero = 0;
for (size_t i = 0; i < resultSize; i++) {
auto newPart = (i < parts.size() ? parts[i] : 0)
- (i < right.parts.size() ? right.parts[i] : 0) - borrow;
if (newPart >= 0) {
borrow = 0;
}
else {
borrow = 1;
newPart += maxPartPlusOne;
}
result[i] = newPart;
if (newPart != 0) {
lastNonZero = i + 1;
}
}
if (borrow > 0) {
throw std::overflow_error("UnlimitedUnsigned arithmetic produced a negative result");
}
result.resize(lastNonZero);
return UnlimitedUnsigned(std::move(result));
}
UnlimitedUnsigned operator-(SmallDeltaType right) const
{
size_t resultSize = parts.size();
std::vector<PartType> result(resultSize);
PartType borrow = right;
for (size_t i = 0; i < resultSize; i++) {
auto newPart = parts[i] - borrow;
if (newPart >= 0) {
borrow = 0;
}
else {
borrow = 1;
newPart += maxPartPlusOne;
}
result[i] = newPart;
}
if (borrow > 0) {
throw std::overflow_error("UnlimitedUnsigned arithmetic produced a negative result");
}
// Note that by here resultSize > 0, otherwise the original this == 0 and we would have
// the above exception.
if (result[resultSize - 1] == 0) {
result.resize(resultSize - 1);
}
return UnlimitedUnsigned(std::move(result));
}
bool operator<=(const UnlimitedUnsigned& right) const
{
// lexicographical_compare is logically a < operation, so we reverse the operands and
// invert the result to get <= and we compare the most significant chunks first.
return parts.size() < right.parts.size()
|| (parts.size() == right.parts.size()
&& !std::lexicographical_compare(right.parts.rbegin(),
right.parts.rend(),
parts.rbegin(),
parts.rend()));
}
bool operator>(const UnlimitedUnsigned& right) const
{
return !(*this <= right);
}
bool operator==(const UnlimitedUnsigned& right) const
{
return parts == right.parts;
}
bool operator==(SmallDeltaType right) const
{
return right == 0 ? parts.empty() : (parts.size() == 1 && parts[0] == right);
}
private:
std::vector<PartType> parts;
};
} // namespace Base
#endif // SRC_BASE_UNLIMITEDUNSIGNED_H_