diff options
-rw-r--r-- | bigint.cpp | 61 | ||||
-rw-r--r-- | bigint.h | 41 | ||||
-rw-r--r-- | gf28.cpp | 4 |
3 files changed, 59 insertions, 47 deletions
@@ -14,12 +14,13 @@ Bigint Bigint::one(1); Bigint Bigint::two(2); Bigint::Bigint() - :sign(1){} + :sign(1){} Bigint::Bigint(Bigint &&o) - :digits(move(o.digits)),sign(o.sign){ - o.normalise(); + :digits(move(o.digits)),sign(o.sign){ + o.sign=1; checkconsistent(); + o.checkconsistent(); } Bigint::Bigint(const string &repr){ @@ -28,7 +29,7 @@ Bigint::Bigint(const string &repr){ } Bigint::Bigint(slongdigit_t v) - :digits(1,abs(v)),sign(v>=0?1:-1){ + :digits(1,abs(v)),sign(v>=0?1:-1){ static_assert(sizeof(longdigit_t)==2*sizeof(digit_t), "longdigit_t should be twice as large as digit_t"); v=abs(v); @@ -41,14 +42,14 @@ Bigint::Bigint(slongdigit_t v) } Bigint::Bigint(longdigit_t v) - :digits(1,(digit_t)v),sign(1){ + :digits(1,(digit_t)v),sign(1){ if(v>digits[0])digits.push_back(v>>digit_bits); else if(v==0)digits.clear(); checkconsistent(); } Bigint::Bigint(sdigit_t v) - :digits(1,abs(v)),sign(v>=0?1:-1){ + :digits(1,abs(v)),sign(v>=0?1:-1){ if(v==0){ digits.clear(); sign=1; @@ -57,11 +58,12 @@ Bigint::Bigint(sdigit_t v) } Bigint::Bigint(digit_t v) - :digits(1,v),sign(1){ + :digits(1,v),sign(1){ if(v==0)digits.clear(); checkconsistent(); } +//ignores sign of arguments void Bigint::add(Bigint &a,const Bigint &b){ if(a.digits.size()<b.digits.size())a.digits.resize(b.digits.size()); int sz=a.digits.size(); @@ -77,12 +79,13 @@ void Bigint::add(Bigint &a,const Bigint &b){ a.checkconsistent(); } +//ignores sign of arguments void Bigint::subtract(Bigint &a,const Bigint &b){ if(a.digits.size()<b.digits.size()){ a.digits.resize(b.digits.size()); //adds zeros } assert(a.digits.size()>=b.digits.size()); - if(a.digits.size()==0){ + if(a.digits.size()==0){ //then a==b==0 a.checkconsistent(); return; } @@ -97,7 +100,7 @@ void Bigint::subtract(Bigint &a,const Bigint &b){ carry=(bdig||carry)&&res>=adig; a.digits[i]=res; } - if(carry){ + if(carry){ //Apparently, a<b //we do a fake 2s complement, sort of carry=0; for(int i=0;i<sz;i++){ @@ -112,6 +115,10 @@ void Bigint::subtract(Bigint &a,const Bigint &b){ a.checkconsistent(); } +//This is simple O(n^2) multiplication, with no optimisation for a==b. Also, no Karatsuba here. +//This works and is simple (KISS), but doesn't perform that well. In fact, it seems to be the +//bottleneck of the entire bigint implementation (through its use divmod, which is used A LOT +//for modulo in RSA). Bigint Bigint::product(const Bigint &a,const Bigint &b){ int asz=a.digits.size(),bsz=b.digits.size(); if(asz==0||bsz==0)return Bigint(); @@ -155,9 +162,10 @@ void Bigint::checkconsistent(){ Bigint& Bigint::operator=(Bigint &&o){ sign=o.sign; digits=move(o.digits); - o.normalise(); + o.sign=1; normalise(); checkconsistent(); + o.checkconsistent(); return *this; } @@ -207,7 +215,7 @@ Bigint& Bigint::operator=(digit_t v){ } Bigint& Bigint::operator+=(const Bigint &o){ - if(&o==this){ //*this + *this + if(&o==this){ //*this + *this = *this<<1 operator<<=(1); return *this; } @@ -223,7 +231,7 @@ Bigint& Bigint::operator+=(const Bigint &o){ } Bigint& Bigint::operator-=(const Bigint &o){ - if(&o==this){ // *this - *this + if(&o==this){ // *this - *this = 0 sign=1; digits.clear(); return *this; @@ -253,8 +261,8 @@ Bigint& Bigint::operator*=(slongdigit_t n){return *this*=Bigint(n);} Bigint& Bigint::operator<<=(int sh){ if(sh==0)return *this; if(digits.size()==0)return *this; - if(sh<0)return *this>>=-sh; - if(sh/digit_bits>0){ + if(sh<0)return *this>>=-sh; //we support negative shifting + if(sh/digit_bits>0){ //first shift by a multiple of our digit size, since that's easy digits.insert(digits.begin(),sh/digit_bits,0); sh%=digit_bits; if(sh==0){ @@ -262,7 +270,7 @@ Bigint& Bigint::operator<<=(int sh){ return *this; } } - digits.push_back(0); + digits.push_back(0); //afterwards, shift by the remaining amount for(int i=digits.size()-2;i>=0;i--){ digits[i+1]|=digits[i]>>(digit_bits-sh); digits[i]<<=sh; @@ -276,8 +284,8 @@ Bigint& Bigint::operator<<=(int sh){ Bigint& Bigint::operator>>=(int sh){ if(sh==0)return *this; if(digits.size()==0)return *this; - if(sh<0)return *this<<=-sh; - if(sh/digit_bits>0){ + if(sh<0)return *this<<=-sh; //we support negative shifting + if(sh/digit_bits>0){ //first shift by a multiple of our digit size, since that's easy if(sh/digit_bits>=(int)digits.size()){ digits.clear(); sign=1; @@ -291,7 +299,7 @@ Bigint& Bigint::operator>>=(int sh){ return *this; } } - digits[0]>>=sh; + digits[0]>>=sh; //afterwards, shift by the remaining amount int sz=digits.size(); for(int i=1;i<sz;i++){ digits[i-1]|=digits[i]<<(digit_bits-sh); @@ -334,7 +342,6 @@ Bigint Bigint::operator>>(int sh) const { } pair<Bigint,Bigint> Bigint::divmod(const Bigint &div) const { - // cerr<<"divmod("<<hex<<*this<<','<<div<<dec<<')'<<endl; int bitcdiff=bitcount()-div.bitcount(); if(bitcdiff<0)bitcdiff=0; pair<Bigint,Bigint> p=divmod(div,1,bitcdiff/29+10); //ignores all signs @@ -373,9 +380,11 @@ pair<Bigint,Bigint> Bigint::divmod(const Bigint &div) const { } //ignores all signs, and always returns positive numbers! +//This function is opaque and way more complicated than it should be. Sorry for that. +//Strategy: find a quotient (and guess=quotient*div) such that guess<=*this, but *this-guess +//is small. Then subtract guess from *this, and repeat. pair<Bigint,Bigint> Bigint::divmod(const Bigint &div,int depth,int maxdepth) const { - if(depth>maxdepth)assert(false); - // cerr<<"divmod("<<hex<<*this<<','<<div<<dec<<") depth="<<depth<<" maxdepth="<<maxdepth<<endl; + if(depth>maxdepth)assert(false); //Sanity check if(div.digits.size()==0)throw domain_error("Bigint divide by zero"); if(digits.size()==0)return make_pair(Bigint::zero,Bigint::zero); @@ -399,7 +408,7 @@ pair<Bigint,Bigint> Bigint::divmod(const Bigint &div,int depth,int maxdepth) con return make_pair( Bigint((slongdigit_t)(thisnum/divnum)), Bigint((slongdigit_t)(thisnum%divnum))); - } else if(divbtc>=digit_bits){ //the large case + } else if(divbtc>=digit_bits){ //both *this and div are large //take 2*digit_bits of *this and 1*digit_bits of div; quotient gives a good guess int spill=__builtin_clz(digits.back()); longdigit_t thishead2=((longdigit_t)digits.back()<<(spill+digit_bits))|((longdigit_t)digits[digits.size()-2]<<spill); @@ -508,6 +517,7 @@ bool Bigint::odd() const { return !even(); } +//Produces a string with the bytes of the mantissa in little-endian order. string Bigint::serialiseMantissa() const { string s; s.resize(digits.size()*sizeof(digit_t)); @@ -520,6 +530,7 @@ string Bigint::serialiseMantissa() const { return s; } +//Inverse of serialiseMantissa void Bigint::deserialiseMantissa(const string &s){ if(s.size()%sizeof(digit_t)!=0)throw invalid_argument("Not a serialised Bigint"); sign=1; @@ -550,10 +561,6 @@ vector<bool> Bigint::bits() const { return v; } -Bigint::digit_t Bigint::_digit(int idx) const { - return digits.at(idx); -} - istream& operator>>(istream &is,Bigint &b){ while(isspace(is.peek()))is.get(); if(!is)return is; @@ -567,7 +574,7 @@ istream& operator>>(istream &is,Bigint &b){ bool acted=false; if(is.peek()=='0'){ is.get(); - if(is.peek()=='x'){ + if(is.peek()=='x'){ //hex value is.get(); acted=false; while(true){ @@ -6,24 +6,30 @@ #include <utility> #include <cstdint> +//A bigint implementation based on unsigned 32-bit digits. +//The implementation relies on having native 64-bit arithmetic (or specifically, +//(2*digit_bits)-bit arithmetic). class Bigint{ public: typedef uint32_t digit_t; - typedef int32_t sdigit_t; - typedef uint64_t longdigit_t; - typedef int64_t slongdigit_t; + typedef int32_t sdigit_t; //signed version + typedef uint64_t longdigit_t; //should be twice as large as digit_t + typedef int64_t slongdigit_t; //signed version static const int digit_bits=8*sizeof(digit_t); private: - std::vector<digit_t> digits; - int sign; + std::vector<digit_t> digits; //little-endian order + int sign; //-1 for negative, 1 for positive; -0 is not a valid state static void add(Bigint&,const Bigint&); //ignores sign of arguments - static void subtract(Bigint&,const Bigint&); //ignores sign of arguments; assumes a>=b + static void subtract(Bigint&,const Bigint&); //ignores sign of arguments static Bigint product(const Bigint&,const Bigint&); - void shrink(); - void normalise(); + void shrink(); //prune unnecessary zero digits on top + void normalise(); //make sure -0 doesn't occur + + //This function is called from all relevant return points of the member functions. It's + //basically a debug check, but it can't hurt to have it since it takes few cpu cycles. void checkconsistent(); std::pair<Bigint,Bigint> divmod(const Bigint&,int depth,int maxdepth) const; //ignores all signs @@ -32,8 +38,9 @@ public: Bigint(); Bigint(const Bigint&)=default; Bigint(Bigint&&); - explicit Bigint(const std::string&); - explicit Bigint(sdigit_t); + + explicit Bigint(const std::string&); //Initialise with a large value + explicit Bigint(sdigit_t); //Initialise with a small value explicit Bigint(digit_t); explicit Bigint(slongdigit_t); explicit Bigint(longdigit_t); @@ -54,6 +61,8 @@ public: Bigint& operator<<=(int); Bigint& operator>>=(int); Bigint& negate(); + //Division and modulo operators are explicitly NOT included, to promote clever usage + //of the divmod function, which combines both. Bigint operator+(const Bigint&) const; Bigint operator-(const Bigint&) const; @@ -93,22 +102,18 @@ public: bool odd() const; std::string serialiseMantissa() const; //stores everything but the sign - - //restores non-negative number; can throw invalid_argument - void deserialiseMantissa(const std::string&); - std::vector<bool> bits() const; + void deserialiseMantissa(const std::string&); //restores non-negative number; can throw invalid_argument + std::vector<bool> bits() const; //Stores bits in little-endian order friend std::istream& operator>>(std::istream&,Bigint&); friend std::ostream& operator<<(std::ostream&,Bigint); - digit_t _digit(int idx) const; - - static Bigint mone; + static Bigint mone; //Some small values to save a constructor call static Bigint zero; static Bigint one; static Bigint two; }; std::istream& operator>>(std::istream&,Bigint&); -std::ostream& operator<<(std::ostream&,Bigint); +std::ostream& operator<<(std::ostream&,Bigint); //The output operator supports the `hex` modifier @@ -28,10 +28,10 @@ uint8_t GF28::multiply(uint8_t x,uint8_t y){ } GF28::GF28() - :value(0){} + :value(0){} GF28::GF28(int v) - :value(reduce(v,modulus)){} + :value(reduce(v,modulus)){} GF28::operator uint8_t() const { return value; |