This question is a follow up of Header only bigint library written in c++20.
I've made all (or almost all) the corrections suggested in the answers, plus some minor change here and there and a modification in the division algorithm. Instead of a lot of constructors, now there are only two template construcors, limited by concepts. The reason I do this instead of just defining a constructor for the biggest types is because I want the bigint class to work seamlessly with all the builtin integer types.
I made a follow-up question for two reasons:
- One of the people who replied said that he/she's "sure there is more".
- If everything else is mostly correct, I would like some comments on the algoritms that I used (If there are better choices, if they can be optimized in some places etc.), particularly for
stobiand the division algorithm (but also for the other stuff).
WARNING: don't use GCC 11.1 to compile this, the compilation will stop with an internal compiler error because of this bug. Use GCC 10 instead, or GCC 11.2 when it will come out.
#include <algorithm>
#include <cctype>
#include <cmath>
#include <compare>
#include <concepts>
#include <cstdint>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
using std::uint_least64_t;
using std::uint32_t;
using std::uintmax_t;
using size_type = uint_least64_t;
class bigint {
static const uint_least64_t BASE=static_cast<uint_least64_t>(std::numeric_limits<uint32_t>::max())+1;
static const uint_least64_t DIGITS10BASE=static_cast<uint_least64_t>(std::numeric_limits<uint32_t>::digits10)+1;
bool negative=false;
std::vector<uint32_t> container;
uint_least64_t getDigit(size_type k) const{
if(k>=container.size()){
return 0;
}
return container[k];
}
void normalize(){
while(container.size()!=0 && container.back()==0){
container.pop_back();
}
if(container.size()==0){
container.push_back(0);
negative=false;
}
container.shrink_to_fit();
}
public:
// Constructors
bigint() : container{0} {}
template<std::integral T>
bigint(T n){
if(n==0){
container.push_back(0);
return;
}
uintmax_t l;
if(n<0){
negative=true;
l=static_cast<uintmax_t>(-(n+1))+1;
} else {
l=n;
}
while(l>0){
container.push_back(l%BASE);
l/=BASE;
}
}
template<std::floating_point T>
explicit bigint(T n){
if(n>-1 && n<1){
container.push_back(0);
return;
}
long double l=n;
if(l<0){
negative=true;
l=-l;
}
l=std::floor(l);
while(l>=1){
container.push_back(std::fmod(l,BASE));
l=std::floor(l/BASE);
}
}
// Unary arithmetic operators
bigint operator+() const {return *this;}
inline bigint operator-() const;
friend inline bigint biabs(bigint n) {n.negative=false; return n;}
// Comparison operators
friend bool operator==(const bigint &a,const bigint &b) = default;
friend std::strong_ordering operator<=>(const bigint &a,const bigint &b);
// Compound assignment operators
bigint& operator+=(const bigint &b);
bigint& operator-=(const bigint &b);
bigint& operator*=(const bigint &b);
bigint& operator/=(const bigint &b);
bigint& operator%=(const bigint &b);
// Increment/decrement
inline bigint& operator++();
inline bigint& operator--();
bigint operator++(int) {bigint old=*this; ++*this; return old;}
bigint operator--(int) {bigint old=*this; --*this; return old;}
// Conversion functions
inline explicit operator bool() const;
explicit operator std::string() const;
friend bigint stobi(const std::string &n);
// Debug
void dump() const{
if(negative){
std::cout << "-_";
}
for(size_type con=container.size(); con>0; con--){
std::cout << container[con-1] << "_";
}
std::cout << "[" << container.size() << "," << BASE << "," << DIGITS10BASE << "]" << "\n" << std::flush;
}
};
bigint stobi(const std::string &str){
bigint res;
std::string::const_iterator msd=std::find_if_not(str.begin(),str.end(),[](char d){return std::isspace(d);});
if(*msd=='+'){
msd++;
} else if(*msd=='-'){
res.negative=true;
msd++;
}
if(!std::isdigit(*msd)){
throw std::invalid_argument("stobi");
}
msd=std::find_if(msd,str.end(),[](char d){return d!='0';});
if(!std::isdigit(*msd)){
res.negative=false;
return res;
}
std::string::const_iterator alsd=std::find_if_not(msd,str.end(),[](char d){return std::isdigit(d);});
res.container.clear();
std::string n(msd,alsd);
while(n.size()>bigint::DIGITS10BASE || std::stoull(std::string(n,0,bigint::DIGITS10BASE))>=bigint::BASE){
std::string quot;
size_type con=bigint::DIGITS10BASE;
uint_least64_t partdivid=std::stoull(std::string(n,0,bigint::DIGITS10BASE));
if(partdivid<bigint::BASE){
partdivid=partdivid*10+(n[con]-'0');
con++;
}
while(con<n.size()){
quot+=partdivid/bigint::BASE+'0';
partdivid=(partdivid%bigint::BASE)*10+(n[con]-'0');
con++;
}
quot+=partdivid/bigint::BASE+'0';
partdivid%=bigint::BASE;
res.container.push_back(partdivid);
n=quot;
}
res.container.push_back(std::stoull(n));
return res;
}
inline bigint operator"" _bi (const char *n){
std::string str=n;
if(str.size()<=std::numeric_limits<unsigned long long>::digits10){
return bigint(std::stoull(str));
}
return stobi(str);
}
inline bigint bigint::operator-() const{
bigint flip=*this;
if(flip!=0_bi){
flip.negative=!(flip.negative);
}
return flip;
}
inline bigint& bigint::operator++(){
*this+=1_bi;
return *this;
}
inline bigint& bigint::operator--(){
*this-=1_bi;
return *this;
}
std::strong_ordering operator<=>(const bigint &a,const bigint &b){
if(a.negative!=b.negative){
return b.negative<=>a.negative;
}
if(a.negative==true){
if(a.container.size()!=b.container.size()){
return b.container.size()<=>a.container.size();
}
return std::lexicographical_compare_three_way(b.container.rbegin(),b.container.rend(),a.container.rbegin(),a.container.rend());
}
if(a.container.size()!=b.container.size()){
return a.container.size()<=>b.container.size();
}
return std::lexicographical_compare_three_way(a.container.rbegin(),a.container.rend(),b.container.rbegin(),b.container.rend());
}
inline bigint::operator bool() const{
return *this!=0_bi;
}
inline bigint operator+(bigint a,const bigint &b){
a+=b;
return a;
}
inline bigint operator-(bigint a,const bigint &b){
a-=b;
return a;
}
inline bigint operator*(bigint a,const bigint &b){
a*=b;
return a;
}
inline bigint operator/(bigint a,const bigint &b){
a/=b;
return a;
}
inline bigint operator%(bigint a,const bigint &b){
a%=b;
return a;
}
bigint& bigint::operator+=(const bigint &b){
if(this==&b){
*this*=2_bi;
return *this;
}
if(b==0_bi){
return *this;
}
if(negative!=b.negative){
*this-=-b;
return *this;
}
size_type digits=container.size();
if(digits<b.container.size()){
digits=b.container.size();
}
uint_least64_t rem=0;
for(size_type k=0; k<digits; k++){
uint_least64_t sum=rem+getDigit(k)+b.getDigit(k);
rem=sum/BASE;
sum%=BASE;
if(k<container.size()){
container[k]=sum;
} else {
container.push_back(sum);
}
}
if(rem!=0){
container.push_back(rem);
}
return *this;
}
bigint& bigint::operator-=(const bigint &b){
if(this==&b){
*this=0_bi;
return *this;
}
if(b==0_bi){
return *this;
}
if(negative!=b.negative){
*this+=-b;
return *this;
}
if(biabs(*this)<biabs(b)){
*this=-(b-*this);
return *this;
}
size_type digits=container.size();
uint_least64_t rem=0;
for(size_type k=0; k<digits; k++){
uint_least64_t diff=BASE+getDigit(k)-b.getDigit(k)-rem;
rem=1;
if(diff>=BASE){
diff-=BASE;
rem=0;
}
container[k]=diff;
}
normalize();
return *this;
}
bigint& bigint::operator*=(const bigint &b){
if(*this==0_bi){
return *this;
}
if(b==0_bi){
*this=0_bi;
return *this;
}
bool sign=(negative!=b.negative);
bigint sum=0_bi;
for(size_type k=0; k<b.container.size(); k++){
bigint part;
part.container=std::vector<uint32_t>(k,0);
uint_least64_t rem=0;
for(size_type j=0; j<container.size() || rem!=0; j++){
uint_least64_t prod=(b.getDigit(k)*getDigit(j))+rem;
rem=prod/BASE;
prod%=BASE;
part.container.push_back(prod);
}
part.normalize();
sum+=part;
}
*this=sum;
negative=sign;
return *this;
}
bigint& bigint::operator/=(const bigint &b){
if(b==0_bi){
throw std::domain_error("bigint: Division by zero");
}
if(biabs(*this)<biabs(b)){
*this=0_bi;
return *this;
}
bool sign=(negative!=b.negative);
bigint quot,partdivid;
size_type con=b.container.size();
quot.container.clear();
partdivid.container=std::vector<uint32_t>(container.end()-con,container.end());
con++;
if(partdivid<b){
partdivid.container.insert(partdivid.container.begin(),*(container.end()-con));
con++;
}
while(con<=container.size()){
uint_least64_t min=0;
uint_least64_t max=BASE-1;
while(max-min>1){
uint_least64_t mid=min+(max-min)/2;
if(partdivid-b*mid<0_bi){
max=mid-1;
} else {
min=mid;
}
}
uint_least64_t partquot;
if(partdivid-b*max<0_bi){
partquot=min;
} else {
partquot=max;
}
partdivid-=b*partquot;
quot.container.push_back(partquot);
partdivid.container.insert(partdivid.container.begin(),*(container.end()-con));
partdivid.normalize();
con++;
}
uint_least64_t min=0;
uint_least64_t max=BASE-1;
while(max-min>1){
uint_least64_t mid=min+(max-min)/2;
if(partdivid-b*mid<0_bi){
max=mid-1;
} else {
min=mid;
}
}
uint_least64_t partquot;
if(partdivid-b*max<0_bi){
partquot=min;
} else {
partquot=max;
}
quot.container.push_back(partquot);
std::reverse(quot.container.begin(),quot.container.end());
*this=quot;
negative=sign;
return *this;
}
bigint& bigint::operator%=(const bigint &b){
*this=*this-(*this/b)*b;
return *this;
}
bigint::operator std::string() const{
std::string str;
if(*this==0_bi){
str+='0';
return str;
}
bigint n=*this;
n.negative=false;
while(n>0_bi){
str+=(n%10_bi).container[0]+'0';
n/=10_bi;
}
if(negative){
str+='-';
}
std::reverse(str.begin(),str.end());
return str;
}
std::ostream& operator<<(std::ostream &os, const bigint &n){
os << static_cast<std::string>(n);
return os;
}
std::istream& operator>>(std::istream &is, bigint &n){
std::string str;
is >> str;
try{
n=stobi(str);
} catch(std::invalid_argument&){
is.setstate(std::ios::failbit);
}
return is;
}