Reduce memory footprint in the construction of a BigInt class
up vote
-1
down vote
favorite
I'm working on a concept at the moment and I'm writing the Pseudo code as I am asking this question. I'm thinking of making a fairly easy and simple to use class interface to represent BigInts. I'm thinking of making a couple of simple structures with basic properties and members that the BigInt class would use. For example instead of the BigInt class handling negative values directly it would contain a Sign struct and this struct would basically contain either a value of 0 or 1, or basically a Boolean type to designate if this BigInt is positive or negative. Upon Construction I intend to have the class generate a positive by default. I would also like to have a struct that represents the digits where there are two variants. The first variant has the digits 0-9 and the second which would inherit the original but also includes A-F. This way the class which would be a template class but only ever has two valid types would support use for Decimal and Hexadecimal. All of the mathematical operators would be defined outside of the class and depending its inferred type it will call and perform the appropriate version of the function. However the Hexadecimal part is still only concept as I'd like to get the Decimal version up and running first. These helper classes might look something like this:
class Sign {
private:
bool isNegative_ { false };
char sign_;
public:
Sign() : isNegative_( false ) {
sign_ = '+';
}
Sign( const bool isNegative ) : isNegative_( isNegative ) {
sign_ = isNegative_ ? '-' : '+';
};
Sign( const char sign ) {
if ( sign == '+' || sign == '' || sign == ' ' ) {
isNegative_ = false;
} else if ( sign == '-' ) {
isNegative_ = true;
} else {
// throw error improper character.
}
}
bool isPostive() const { return !isNegative_; }
bool isNegative() const { return !isNegative; }
char sign() const { return sign_; }
char negate() {
isNegative_ = !isNegative_;
sign_ = isNegative_ ? '+' : '-';
return sign_;
}
};
// NST = NumberSystemType
class enum NST { Decimal = 10, Hexadecimal = 16 };
template<class NT> // NT = NumberType
class Digit {
private:
NST nst_; // determines if Decimal or Hexadecimal
};
// Specialization for Decimal
template<NST::Decimal> // Should be same as template<10>
class Digit {
// would like some way to define 4 bits to represent 0-9; prefer not to
// use bitfields, but I think a char or unsigned char would be wasting
// memory using twice as much for what is needed. Not sure what to do here...
// maybe std::bitset<>...
};
template<NST::Hexadecimal> // Should be same as template<16>
class Digit : public Digit<Decimal> { // Also inherits all of The first specialization.
// same as above only needs 4 bits to represent values form 0-F
// A half char would be excellent but does not exist in c++... and most
// programming language's types.
// still thinking of maybe std::bitset<>...
};
The main distinction between the two is that the first specialization would only allow digits values from 0-9 and the digits themselves 0-9 where the second doesn't have that restriction, but also allows from a-f and or A-F either case is valid. I may also include a const char* to designate the Hex Prefix of 0x
that would be appended to any contained value for display.
I like this design approach as I'd like to keep the actual arithmetic functions and operators of the BigInt class as seperate function templates since the BigInt class can support both Decimal and Hexadecimal specialized template types. Also down the road if everything goes properly I'd also like to add the support to work with Complex numbers as well.
The BigInt class would be like this:
template<class NT>
BigInt {
private:
Sign sign_;
Digit<NT> carryDigit_;
std::vector<Digit<NT>> value_;
// May contain some functions such as setters and getters only
// Do not want the class to be modifying itself except for assignment only.
};
And as above this would be specialized as well for Decimal and Hexadecimal Types, however if someone creates an instance of BigInt<> myBigInt this should default to Decimal!
For the data that is contained in the vector. I'd like to store the digits in reverse order of what one reads. So if their is a number 345698
in BigInt's internal vector it would be stored as 896543
. The reason for this is when we do arithmetic operations in math we work from the least significant to the most significant starting at the right on the left side of the decimal point which is irrelevant since this is a BigInt only class and we work our way to the left. However if we store each digit that can only be 0-9 in each element of the above class's vector in the proper order and we use an outside operator+() function this would be challenging to do for one BigInt to another... example:
Basic Arithmetic R - L | Vector storing standard
12345 <1,2,3,4,5>
+ 678 <6,7,8>
------
13023
Here the indexes of <5> & <8> do not coincide so this makes it tough to figure out how to add a value with a few digits to one with many. My approach is that if we store the number in reverse order:
| Vector stored in reverse
<5,4,3,2,1>
<6,7,8>
Then the addition becomes simple! All we have to do is add each digit from both BigInt's vectors by same index value. And we have the use of the carry digit to carry over to the next field. The resulting BigInt would return with a size that is at least equal to or greater than the largest of the two BigInts. If the carryDigit has a value then the operation of addition on the next iteration will include 3 additions instead of two. Now when getting the BigInt for display we can return either a vector> except that when the user gets it this is not a "BigInt" it is vector of Digits and it is also in the correct order. It can even be returned by a string representation. This class can be constructed by a vector> and it will reverse the order when it stores it internally.
This is the overall concept of my BigInt class, I'm just wondering if this is a good design plan, if it would be considered efficient and I guess the main question that I have is about what to use to store the actual digits within the Digit's class... Would std::bitset<>
be appropriate to conserve the memory foot print or would it just be better to use a char
and not worry about the extra space with the pro of being simpler to implement?
c++ types biginteger template-specialization memory-footprint
|
show 11 more comments
up vote
-1
down vote
favorite
I'm working on a concept at the moment and I'm writing the Pseudo code as I am asking this question. I'm thinking of making a fairly easy and simple to use class interface to represent BigInts. I'm thinking of making a couple of simple structures with basic properties and members that the BigInt class would use. For example instead of the BigInt class handling negative values directly it would contain a Sign struct and this struct would basically contain either a value of 0 or 1, or basically a Boolean type to designate if this BigInt is positive or negative. Upon Construction I intend to have the class generate a positive by default. I would also like to have a struct that represents the digits where there are two variants. The first variant has the digits 0-9 and the second which would inherit the original but also includes A-F. This way the class which would be a template class but only ever has two valid types would support use for Decimal and Hexadecimal. All of the mathematical operators would be defined outside of the class and depending its inferred type it will call and perform the appropriate version of the function. However the Hexadecimal part is still only concept as I'd like to get the Decimal version up and running first. These helper classes might look something like this:
class Sign {
private:
bool isNegative_ { false };
char sign_;
public:
Sign() : isNegative_( false ) {
sign_ = '+';
}
Sign( const bool isNegative ) : isNegative_( isNegative ) {
sign_ = isNegative_ ? '-' : '+';
};
Sign( const char sign ) {
if ( sign == '+' || sign == '' || sign == ' ' ) {
isNegative_ = false;
} else if ( sign == '-' ) {
isNegative_ = true;
} else {
// throw error improper character.
}
}
bool isPostive() const { return !isNegative_; }
bool isNegative() const { return !isNegative; }
char sign() const { return sign_; }
char negate() {
isNegative_ = !isNegative_;
sign_ = isNegative_ ? '+' : '-';
return sign_;
}
};
// NST = NumberSystemType
class enum NST { Decimal = 10, Hexadecimal = 16 };
template<class NT> // NT = NumberType
class Digit {
private:
NST nst_; // determines if Decimal or Hexadecimal
};
// Specialization for Decimal
template<NST::Decimal> // Should be same as template<10>
class Digit {
// would like some way to define 4 bits to represent 0-9; prefer not to
// use bitfields, but I think a char or unsigned char would be wasting
// memory using twice as much for what is needed. Not sure what to do here...
// maybe std::bitset<>...
};
template<NST::Hexadecimal> // Should be same as template<16>
class Digit : public Digit<Decimal> { // Also inherits all of The first specialization.
// same as above only needs 4 bits to represent values form 0-F
// A half char would be excellent but does not exist in c++... and most
// programming language's types.
// still thinking of maybe std::bitset<>...
};
The main distinction between the two is that the first specialization would only allow digits values from 0-9 and the digits themselves 0-9 where the second doesn't have that restriction, but also allows from a-f and or A-F either case is valid. I may also include a const char* to designate the Hex Prefix of 0x
that would be appended to any contained value for display.
I like this design approach as I'd like to keep the actual arithmetic functions and operators of the BigInt class as seperate function templates since the BigInt class can support both Decimal and Hexadecimal specialized template types. Also down the road if everything goes properly I'd also like to add the support to work with Complex numbers as well.
The BigInt class would be like this:
template<class NT>
BigInt {
private:
Sign sign_;
Digit<NT> carryDigit_;
std::vector<Digit<NT>> value_;
// May contain some functions such as setters and getters only
// Do not want the class to be modifying itself except for assignment only.
};
And as above this would be specialized as well for Decimal and Hexadecimal Types, however if someone creates an instance of BigInt<> myBigInt this should default to Decimal!
For the data that is contained in the vector. I'd like to store the digits in reverse order of what one reads. So if their is a number 345698
in BigInt's internal vector it would be stored as 896543
. The reason for this is when we do arithmetic operations in math we work from the least significant to the most significant starting at the right on the left side of the decimal point which is irrelevant since this is a BigInt only class and we work our way to the left. However if we store each digit that can only be 0-9 in each element of the above class's vector in the proper order and we use an outside operator+() function this would be challenging to do for one BigInt to another... example:
Basic Arithmetic R - L | Vector storing standard
12345 <1,2,3,4,5>
+ 678 <6,7,8>
------
13023
Here the indexes of <5> & <8> do not coincide so this makes it tough to figure out how to add a value with a few digits to one with many. My approach is that if we store the number in reverse order:
| Vector stored in reverse
<5,4,3,2,1>
<6,7,8>
Then the addition becomes simple! All we have to do is add each digit from both BigInt's vectors by same index value. And we have the use of the carry digit to carry over to the next field. The resulting BigInt would return with a size that is at least equal to or greater than the largest of the two BigInts. If the carryDigit has a value then the operation of addition on the next iteration will include 3 additions instead of two. Now when getting the BigInt for display we can return either a vector> except that when the user gets it this is not a "BigInt" it is vector of Digits and it is also in the correct order. It can even be returned by a string representation. This class can be constructed by a vector> and it will reverse the order when it stores it internally.
This is the overall concept of my BigInt class, I'm just wondering if this is a good design plan, if it would be considered efficient and I guess the main question that I have is about what to use to store the actual digits within the Digit's class... Would std::bitset<>
be appropriate to conserve the memory foot print or would it just be better to use a char
and not worry about the extra space with the pro of being simpler to implement?
c++ types biginteger template-specialization memory-footprint
1
Is this as an excersice or for production? Because there's a whole bunch of tried and true bigint libraries out there with C++ interfaces, e.g. GMP.
– Darhuuk
Nov 8 at 9:03
2
If I was going to implement my own large-integer, I would use a built-in type such as 32-bits to hold each "digit"... essentially meaning my digit representation is base-2^32, rather than your proposal of base-10 or base-16. All my operations would be in binary and there would be no wasted space. So the entire number would just be something likestd::vector<uint32_t>
. This makes converting to/from base-10 for Input/Output slightly more involved, but the math would be fast and vectorizable... which to me is the whole point of big-int.
– paddy
Nov 8 at 9:17
1
"A half char would be excellent but does not exist in c++..." It doesn't exist in assembler either, because it makes no sense for processors designed to deal with 32 or 64 bits at a time with some internal convenience to deal with 8 bits at a time. Andstd::bitset
won't help you with that since it allocates storage in word-size blocks (at least in the GCC implementation).
– Nelfeal
Nov 8 at 9:18
2
Well, it looks to me like you've looked at the problem, chosen an inappropriate solution and then over-engineered it.
– paddy
Nov 8 at 9:24
1
@FrancisCugler - I agree with paddy. Distinct types for hex and decimal is terribly flawed design. One requirement of integral types is being able to assign and compare them (e.g. for equality) to each other. Your approach means the number of operations you need to implement is quadratic with number of bases you support - hard to maintain. Consider using a single class to efficiently represent an unsigned bigint value, then implement a signed bigint using that unsigned bigint type and abool
to represent sign. Handle bases at input or output, not with distinct representation in memory.
– Peter
Nov 8 at 10:33
|
show 11 more comments
up vote
-1
down vote
favorite
up vote
-1
down vote
favorite
I'm working on a concept at the moment and I'm writing the Pseudo code as I am asking this question. I'm thinking of making a fairly easy and simple to use class interface to represent BigInts. I'm thinking of making a couple of simple structures with basic properties and members that the BigInt class would use. For example instead of the BigInt class handling negative values directly it would contain a Sign struct and this struct would basically contain either a value of 0 or 1, or basically a Boolean type to designate if this BigInt is positive or negative. Upon Construction I intend to have the class generate a positive by default. I would also like to have a struct that represents the digits where there are two variants. The first variant has the digits 0-9 and the second which would inherit the original but also includes A-F. This way the class which would be a template class but only ever has two valid types would support use for Decimal and Hexadecimal. All of the mathematical operators would be defined outside of the class and depending its inferred type it will call and perform the appropriate version of the function. However the Hexadecimal part is still only concept as I'd like to get the Decimal version up and running first. These helper classes might look something like this:
class Sign {
private:
bool isNegative_ { false };
char sign_;
public:
Sign() : isNegative_( false ) {
sign_ = '+';
}
Sign( const bool isNegative ) : isNegative_( isNegative ) {
sign_ = isNegative_ ? '-' : '+';
};
Sign( const char sign ) {
if ( sign == '+' || sign == '' || sign == ' ' ) {
isNegative_ = false;
} else if ( sign == '-' ) {
isNegative_ = true;
} else {
// throw error improper character.
}
}
bool isPostive() const { return !isNegative_; }
bool isNegative() const { return !isNegative; }
char sign() const { return sign_; }
char negate() {
isNegative_ = !isNegative_;
sign_ = isNegative_ ? '+' : '-';
return sign_;
}
};
// NST = NumberSystemType
class enum NST { Decimal = 10, Hexadecimal = 16 };
template<class NT> // NT = NumberType
class Digit {
private:
NST nst_; // determines if Decimal or Hexadecimal
};
// Specialization for Decimal
template<NST::Decimal> // Should be same as template<10>
class Digit {
// would like some way to define 4 bits to represent 0-9; prefer not to
// use bitfields, but I think a char or unsigned char would be wasting
// memory using twice as much for what is needed. Not sure what to do here...
// maybe std::bitset<>...
};
template<NST::Hexadecimal> // Should be same as template<16>
class Digit : public Digit<Decimal> { // Also inherits all of The first specialization.
// same as above only needs 4 bits to represent values form 0-F
// A half char would be excellent but does not exist in c++... and most
// programming language's types.
// still thinking of maybe std::bitset<>...
};
The main distinction between the two is that the first specialization would only allow digits values from 0-9 and the digits themselves 0-9 where the second doesn't have that restriction, but also allows from a-f and or A-F either case is valid. I may also include a const char* to designate the Hex Prefix of 0x
that would be appended to any contained value for display.
I like this design approach as I'd like to keep the actual arithmetic functions and operators of the BigInt class as seperate function templates since the BigInt class can support both Decimal and Hexadecimal specialized template types. Also down the road if everything goes properly I'd also like to add the support to work with Complex numbers as well.
The BigInt class would be like this:
template<class NT>
BigInt {
private:
Sign sign_;
Digit<NT> carryDigit_;
std::vector<Digit<NT>> value_;
// May contain some functions such as setters and getters only
// Do not want the class to be modifying itself except for assignment only.
};
And as above this would be specialized as well for Decimal and Hexadecimal Types, however if someone creates an instance of BigInt<> myBigInt this should default to Decimal!
For the data that is contained in the vector. I'd like to store the digits in reverse order of what one reads. So if their is a number 345698
in BigInt's internal vector it would be stored as 896543
. The reason for this is when we do arithmetic operations in math we work from the least significant to the most significant starting at the right on the left side of the decimal point which is irrelevant since this is a BigInt only class and we work our way to the left. However if we store each digit that can only be 0-9 in each element of the above class's vector in the proper order and we use an outside operator+() function this would be challenging to do for one BigInt to another... example:
Basic Arithmetic R - L | Vector storing standard
12345 <1,2,3,4,5>
+ 678 <6,7,8>
------
13023
Here the indexes of <5> & <8> do not coincide so this makes it tough to figure out how to add a value with a few digits to one with many. My approach is that if we store the number in reverse order:
| Vector stored in reverse
<5,4,3,2,1>
<6,7,8>
Then the addition becomes simple! All we have to do is add each digit from both BigInt's vectors by same index value. And we have the use of the carry digit to carry over to the next field. The resulting BigInt would return with a size that is at least equal to or greater than the largest of the two BigInts. If the carryDigit has a value then the operation of addition on the next iteration will include 3 additions instead of two. Now when getting the BigInt for display we can return either a vector> except that when the user gets it this is not a "BigInt" it is vector of Digits and it is also in the correct order. It can even be returned by a string representation. This class can be constructed by a vector> and it will reverse the order when it stores it internally.
This is the overall concept of my BigInt class, I'm just wondering if this is a good design plan, if it would be considered efficient and I guess the main question that I have is about what to use to store the actual digits within the Digit's class... Would std::bitset<>
be appropriate to conserve the memory foot print or would it just be better to use a char
and not worry about the extra space with the pro of being simpler to implement?
c++ types biginteger template-specialization memory-footprint
I'm working on a concept at the moment and I'm writing the Pseudo code as I am asking this question. I'm thinking of making a fairly easy and simple to use class interface to represent BigInts. I'm thinking of making a couple of simple structures with basic properties and members that the BigInt class would use. For example instead of the BigInt class handling negative values directly it would contain a Sign struct and this struct would basically contain either a value of 0 or 1, or basically a Boolean type to designate if this BigInt is positive or negative. Upon Construction I intend to have the class generate a positive by default. I would also like to have a struct that represents the digits where there are two variants. The first variant has the digits 0-9 and the second which would inherit the original but also includes A-F. This way the class which would be a template class but only ever has two valid types would support use for Decimal and Hexadecimal. All of the mathematical operators would be defined outside of the class and depending its inferred type it will call and perform the appropriate version of the function. However the Hexadecimal part is still only concept as I'd like to get the Decimal version up and running first. These helper classes might look something like this:
class Sign {
private:
bool isNegative_ { false };
char sign_;
public:
Sign() : isNegative_( false ) {
sign_ = '+';
}
Sign( const bool isNegative ) : isNegative_( isNegative ) {
sign_ = isNegative_ ? '-' : '+';
};
Sign( const char sign ) {
if ( sign == '+' || sign == '' || sign == ' ' ) {
isNegative_ = false;
} else if ( sign == '-' ) {
isNegative_ = true;
} else {
// throw error improper character.
}
}
bool isPostive() const { return !isNegative_; }
bool isNegative() const { return !isNegative; }
char sign() const { return sign_; }
char negate() {
isNegative_ = !isNegative_;
sign_ = isNegative_ ? '+' : '-';
return sign_;
}
};
// NST = NumberSystemType
class enum NST { Decimal = 10, Hexadecimal = 16 };
template<class NT> // NT = NumberType
class Digit {
private:
NST nst_; // determines if Decimal or Hexadecimal
};
// Specialization for Decimal
template<NST::Decimal> // Should be same as template<10>
class Digit {
// would like some way to define 4 bits to represent 0-9; prefer not to
// use bitfields, but I think a char or unsigned char would be wasting
// memory using twice as much for what is needed. Not sure what to do here...
// maybe std::bitset<>...
};
template<NST::Hexadecimal> // Should be same as template<16>
class Digit : public Digit<Decimal> { // Also inherits all of The first specialization.
// same as above only needs 4 bits to represent values form 0-F
// A half char would be excellent but does not exist in c++... and most
// programming language's types.
// still thinking of maybe std::bitset<>...
};
The main distinction between the two is that the first specialization would only allow digits values from 0-9 and the digits themselves 0-9 where the second doesn't have that restriction, but also allows from a-f and or A-F either case is valid. I may also include a const char* to designate the Hex Prefix of 0x
that would be appended to any contained value for display.
I like this design approach as I'd like to keep the actual arithmetic functions and operators of the BigInt class as seperate function templates since the BigInt class can support both Decimal and Hexadecimal specialized template types. Also down the road if everything goes properly I'd also like to add the support to work with Complex numbers as well.
The BigInt class would be like this:
template<class NT>
BigInt {
private:
Sign sign_;
Digit<NT> carryDigit_;
std::vector<Digit<NT>> value_;
// May contain some functions such as setters and getters only
// Do not want the class to be modifying itself except for assignment only.
};
And as above this would be specialized as well for Decimal and Hexadecimal Types, however if someone creates an instance of BigInt<> myBigInt this should default to Decimal!
For the data that is contained in the vector. I'd like to store the digits in reverse order of what one reads. So if their is a number 345698
in BigInt's internal vector it would be stored as 896543
. The reason for this is when we do arithmetic operations in math we work from the least significant to the most significant starting at the right on the left side of the decimal point which is irrelevant since this is a BigInt only class and we work our way to the left. However if we store each digit that can only be 0-9 in each element of the above class's vector in the proper order and we use an outside operator+() function this would be challenging to do for one BigInt to another... example:
Basic Arithmetic R - L | Vector storing standard
12345 <1,2,3,4,5>
+ 678 <6,7,8>
------
13023
Here the indexes of <5> & <8> do not coincide so this makes it tough to figure out how to add a value with a few digits to one with many. My approach is that if we store the number in reverse order:
| Vector stored in reverse
<5,4,3,2,1>
<6,7,8>
Then the addition becomes simple! All we have to do is add each digit from both BigInt's vectors by same index value. And we have the use of the carry digit to carry over to the next field. The resulting BigInt would return with a size that is at least equal to or greater than the largest of the two BigInts. If the carryDigit has a value then the operation of addition on the next iteration will include 3 additions instead of two. Now when getting the BigInt for display we can return either a vector> except that when the user gets it this is not a "BigInt" it is vector of Digits and it is also in the correct order. It can even be returned by a string representation. This class can be constructed by a vector> and it will reverse the order when it stores it internally.
This is the overall concept of my BigInt class, I'm just wondering if this is a good design plan, if it would be considered efficient and I guess the main question that I have is about what to use to store the actual digits within the Digit's class... Would std::bitset<>
be appropriate to conserve the memory foot print or would it just be better to use a char
and not worry about the extra space with the pro of being simpler to implement?
c++ types biginteger template-specialization memory-footprint
c++ types biginteger template-specialization memory-footprint
edited Nov 8 at 9:07
asked Nov 8 at 8:58
Francis Cugler
4,00711227
4,00711227
1
Is this as an excersice or for production? Because there's a whole bunch of tried and true bigint libraries out there with C++ interfaces, e.g. GMP.
– Darhuuk
Nov 8 at 9:03
2
If I was going to implement my own large-integer, I would use a built-in type such as 32-bits to hold each "digit"... essentially meaning my digit representation is base-2^32, rather than your proposal of base-10 or base-16. All my operations would be in binary and there would be no wasted space. So the entire number would just be something likestd::vector<uint32_t>
. This makes converting to/from base-10 for Input/Output slightly more involved, but the math would be fast and vectorizable... which to me is the whole point of big-int.
– paddy
Nov 8 at 9:17
1
"A half char would be excellent but does not exist in c++..." It doesn't exist in assembler either, because it makes no sense for processors designed to deal with 32 or 64 bits at a time with some internal convenience to deal with 8 bits at a time. Andstd::bitset
won't help you with that since it allocates storage in word-size blocks (at least in the GCC implementation).
– Nelfeal
Nov 8 at 9:18
2
Well, it looks to me like you've looked at the problem, chosen an inappropriate solution and then over-engineered it.
– paddy
Nov 8 at 9:24
1
@FrancisCugler - I agree with paddy. Distinct types for hex and decimal is terribly flawed design. One requirement of integral types is being able to assign and compare them (e.g. for equality) to each other. Your approach means the number of operations you need to implement is quadratic with number of bases you support - hard to maintain. Consider using a single class to efficiently represent an unsigned bigint value, then implement a signed bigint using that unsigned bigint type and abool
to represent sign. Handle bases at input or output, not with distinct representation in memory.
– Peter
Nov 8 at 10:33
|
show 11 more comments
1
Is this as an excersice or for production? Because there's a whole bunch of tried and true bigint libraries out there with C++ interfaces, e.g. GMP.
– Darhuuk
Nov 8 at 9:03
2
If I was going to implement my own large-integer, I would use a built-in type such as 32-bits to hold each "digit"... essentially meaning my digit representation is base-2^32, rather than your proposal of base-10 or base-16. All my operations would be in binary and there would be no wasted space. So the entire number would just be something likestd::vector<uint32_t>
. This makes converting to/from base-10 for Input/Output slightly more involved, but the math would be fast and vectorizable... which to me is the whole point of big-int.
– paddy
Nov 8 at 9:17
1
"A half char would be excellent but does not exist in c++..." It doesn't exist in assembler either, because it makes no sense for processors designed to deal with 32 or 64 bits at a time with some internal convenience to deal with 8 bits at a time. Andstd::bitset
won't help you with that since it allocates storage in word-size blocks (at least in the GCC implementation).
– Nelfeal
Nov 8 at 9:18
2
Well, it looks to me like you've looked at the problem, chosen an inappropriate solution and then over-engineered it.
– paddy
Nov 8 at 9:24
1
@FrancisCugler - I agree with paddy. Distinct types for hex and decimal is terribly flawed design. One requirement of integral types is being able to assign and compare them (e.g. for equality) to each other. Your approach means the number of operations you need to implement is quadratic with number of bases you support - hard to maintain. Consider using a single class to efficiently represent an unsigned bigint value, then implement a signed bigint using that unsigned bigint type and abool
to represent sign. Handle bases at input or output, not with distinct representation in memory.
– Peter
Nov 8 at 10:33
1
1
Is this as an excersice or for production? Because there's a whole bunch of tried and true bigint libraries out there with C++ interfaces, e.g. GMP.
– Darhuuk
Nov 8 at 9:03
Is this as an excersice or for production? Because there's a whole bunch of tried and true bigint libraries out there with C++ interfaces, e.g. GMP.
– Darhuuk
Nov 8 at 9:03
2
2
If I was going to implement my own large-integer, I would use a built-in type such as 32-bits to hold each "digit"... essentially meaning my digit representation is base-2^32, rather than your proposal of base-10 or base-16. All my operations would be in binary and there would be no wasted space. So the entire number would just be something like
std::vector<uint32_t>
. This makes converting to/from base-10 for Input/Output slightly more involved, but the math would be fast and vectorizable... which to me is the whole point of big-int.– paddy
Nov 8 at 9:17
If I was going to implement my own large-integer, I would use a built-in type such as 32-bits to hold each "digit"... essentially meaning my digit representation is base-2^32, rather than your proposal of base-10 or base-16. All my operations would be in binary and there would be no wasted space. So the entire number would just be something like
std::vector<uint32_t>
. This makes converting to/from base-10 for Input/Output slightly more involved, but the math would be fast and vectorizable... which to me is the whole point of big-int.– paddy
Nov 8 at 9:17
1
1
"A half char would be excellent but does not exist in c++..." It doesn't exist in assembler either, because it makes no sense for processors designed to deal with 32 or 64 bits at a time with some internal convenience to deal with 8 bits at a time. And
std::bitset
won't help you with that since it allocates storage in word-size blocks (at least in the GCC implementation).– Nelfeal
Nov 8 at 9:18
"A half char would be excellent but does not exist in c++..." It doesn't exist in assembler either, because it makes no sense for processors designed to deal with 32 or 64 bits at a time with some internal convenience to deal with 8 bits at a time. And
std::bitset
won't help you with that since it allocates storage in word-size blocks (at least in the GCC implementation).– Nelfeal
Nov 8 at 9:18
2
2
Well, it looks to me like you've looked at the problem, chosen an inappropriate solution and then over-engineered it.
– paddy
Nov 8 at 9:24
Well, it looks to me like you've looked at the problem, chosen an inappropriate solution and then over-engineered it.
– paddy
Nov 8 at 9:24
1
1
@FrancisCugler - I agree with paddy. Distinct types for hex and decimal is terribly flawed design. One requirement of integral types is being able to assign and compare them (e.g. for equality) to each other. Your approach means the number of operations you need to implement is quadratic with number of bases you support - hard to maintain. Consider using a single class to efficiently represent an unsigned bigint value, then implement a signed bigint using that unsigned bigint type and a
bool
to represent sign. Handle bases at input or output, not with distinct representation in memory.– Peter
Nov 8 at 10:33
@FrancisCugler - I agree with paddy. Distinct types for hex and decimal is terribly flawed design. One requirement of integral types is being able to assign and compare them (e.g. for equality) to each other. Your approach means the number of operations you need to implement is quadratic with number of bases you support - hard to maintain. Consider using a single class to efficiently represent an unsigned bigint value, then implement a signed bigint using that unsigned bigint type and a
bool
to represent sign. Handle bases at input or output, not with distinct representation in memory.– Peter
Nov 8 at 10:33
|
show 11 more comments
2 Answers
2
active
oldest
votes
up vote
0
down vote
The smallest addressable unit of memory in C++ is a byte, and a byte is at least 8 bits (and usually exactly 8 bits). So your idea indeed wastes memory.
I also suspect you're not that familiar with math. What you call "NumberSystemType" is ordinarily called the base. So we talk about base-10 or base-16.
Now base-10 is only useful for human consumption, and thus pointless for this sort of bigints. Humans can't deal with numbers that have more than about 20 decimal digits anyway. So, pick a base that's useful for computers. And pick one. There's no need to support multiple bases. As paddy points out, base 2^32 is a perfectly reasonable base for computers.
I understand the distinction of what you are saying about base-10 and base-16 due to Logarithms. I was just giving it a friendly readable name As base-10 which is Decimal is a Number System and base-16 which is Decimal is also a Number System. I'm trying to only store in the Decimal version of BigInt's container for each element 0-9 and nothing else but as for the Hexadecimal version it will store 0-F. The reason for this is when writing the functions to do the addition - subtraction etc. they become quite easy if the digits are stored in the vector in reverse order!
– Francis Cugler
Nov 8 at 9:27
2
@FrancisCugler: Oh, the idea of storing digits in reverse order is not bad. But that still works when you choose a reasonable base like 2^8 or 2^32. Choosing a too-small base like 2^4 is just adding problems without any benefits whatsoever.
– MSalters
Nov 8 at 9:32
2
@FrancisCugler: Yeah, I already suspected you missed the necessary math basis.< 9, 2334343, 235, 3, 243 >
are 5 perfectly decent digits in base 2^32. Of course, there's no digit "03" there. If anything, it's0x00000003
.
– MSalters
Nov 8 at 9:52
1
To answer the above: yes, and maybe (not if you multiply as 64-bit for example, or use special instructions that store the overflow, useful for exactly these scenarios). This is one reason why I suggested you work in base-256, until you start to understand how to manage things like overflow in 32-bit integers. There's a clear knowledge gap here that's difficult to address as any kind of answer to your question. Your main issue was needing to be space-efficient. We've repeatedly told you how to achieve that, and all that remains is for you to go try it, and learn along the way.
– paddy
Nov 8 at 10:29
1
Yes, that's exactly what we're saying.
– paddy
Nov 8 at 10:45
|
show 13 more comments
up vote
0
down vote
-Update-
Okay I took some advise and this is what I have so far. I have not yet incorporated the concept of storing the values in reverse order which shouldn't be all too difficult. As it currently stands, I have eliminated all of the outside classes and my BigInt class signature looks like this:
class BigInt {
private:
short sign_;
uint32_t carry_{0};
std::vector<uint32_t> value_;
public:
BigInt() : sign_( 0 ) {}
BigInt( const uint32_t* value, const std::size_t arraySize, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), &value[0], &value[arraySize] );
std::reverse( value_.begin(), value_.end() );
}
BigInt( const std::vector<uint32_t>& value, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), value.begin(), value.end() );
std::reverse( value_.begin(), value_.end() );
}
BigInt( std::initializer_list<uint32_t> values, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), values.begin(), values.end() );
std::reverse( value_.begin(), value_.end() );
}
std::vector<uint32_t> operator()() {
//value_.push_back( static_cast<uint32_t>( sign_ ) ); // causes negative to be pushed into container multiple times...
std::reverse( value_.begin(), value_.end() );
return value_;
}
};
I think this is a good start: there are several ways to construct one which gives the caller some flexibility since you can create a BigInt with a pointer, an array, a vector, or even an initializer list. At first it may seem counter intuitive to have the sign at the end of the constructor but I want to be able to have the sign value to be a defaulted parameter when the value is positive. If anything other than 0
is passed in for the sign value it will convert it to -1
. A little more time and I'll have the signature of this class complete then I can move onto the operators that will work on it.
There are some minor pitfalls in the above code but I'm continuously working on them.
The smallest addressable storage unit ischar
. If you insist on templating your 'digit' type as a base-10 integral type, then you'll either need to accept wasting a lot of bits, or pack your numbers as BCD (i.e. two digits per byte) and index them in pairs. If your math is lightweight and the main processing requirement is converting to/from base 10 then carry on. If the main processing requirement is fast math and compact storage, then you are on the wrong track and you should use a base that is a power of 2 that can be packed into a native integral type.
– paddy
Nov 8 at 10:33
@paddy I updated my answer with working code: it's not complete and bug free yet, but it is a work in progress. This class will basically only store the contents, manage its element ordering and checking for valid input. All of the operations on this I am planning on implementing in outside functions that will be friends with this class.
– Francis Cugler
Nov 9 at 16:05
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
The smallest addressable unit of memory in C++ is a byte, and a byte is at least 8 bits (and usually exactly 8 bits). So your idea indeed wastes memory.
I also suspect you're not that familiar with math. What you call "NumberSystemType" is ordinarily called the base. So we talk about base-10 or base-16.
Now base-10 is only useful for human consumption, and thus pointless for this sort of bigints. Humans can't deal with numbers that have more than about 20 decimal digits anyway. So, pick a base that's useful for computers. And pick one. There's no need to support multiple bases. As paddy points out, base 2^32 is a perfectly reasonable base for computers.
I understand the distinction of what you are saying about base-10 and base-16 due to Logarithms. I was just giving it a friendly readable name As base-10 which is Decimal is a Number System and base-16 which is Decimal is also a Number System. I'm trying to only store in the Decimal version of BigInt's container for each element 0-9 and nothing else but as for the Hexadecimal version it will store 0-F. The reason for this is when writing the functions to do the addition - subtraction etc. they become quite easy if the digits are stored in the vector in reverse order!
– Francis Cugler
Nov 8 at 9:27
2
@FrancisCugler: Oh, the idea of storing digits in reverse order is not bad. But that still works when you choose a reasonable base like 2^8 or 2^32. Choosing a too-small base like 2^4 is just adding problems without any benefits whatsoever.
– MSalters
Nov 8 at 9:32
2
@FrancisCugler: Yeah, I already suspected you missed the necessary math basis.< 9, 2334343, 235, 3, 243 >
are 5 perfectly decent digits in base 2^32. Of course, there's no digit "03" there. If anything, it's0x00000003
.
– MSalters
Nov 8 at 9:52
1
To answer the above: yes, and maybe (not if you multiply as 64-bit for example, or use special instructions that store the overflow, useful for exactly these scenarios). This is one reason why I suggested you work in base-256, until you start to understand how to manage things like overflow in 32-bit integers. There's a clear knowledge gap here that's difficult to address as any kind of answer to your question. Your main issue was needing to be space-efficient. We've repeatedly told you how to achieve that, and all that remains is for you to go try it, and learn along the way.
– paddy
Nov 8 at 10:29
1
Yes, that's exactly what we're saying.
– paddy
Nov 8 at 10:45
|
show 13 more comments
up vote
0
down vote
The smallest addressable unit of memory in C++ is a byte, and a byte is at least 8 bits (and usually exactly 8 bits). So your idea indeed wastes memory.
I also suspect you're not that familiar with math. What you call "NumberSystemType" is ordinarily called the base. So we talk about base-10 or base-16.
Now base-10 is only useful for human consumption, and thus pointless for this sort of bigints. Humans can't deal with numbers that have more than about 20 decimal digits anyway. So, pick a base that's useful for computers. And pick one. There's no need to support multiple bases. As paddy points out, base 2^32 is a perfectly reasonable base for computers.
I understand the distinction of what you are saying about base-10 and base-16 due to Logarithms. I was just giving it a friendly readable name As base-10 which is Decimal is a Number System and base-16 which is Decimal is also a Number System. I'm trying to only store in the Decimal version of BigInt's container for each element 0-9 and nothing else but as for the Hexadecimal version it will store 0-F. The reason for this is when writing the functions to do the addition - subtraction etc. they become quite easy if the digits are stored in the vector in reverse order!
– Francis Cugler
Nov 8 at 9:27
2
@FrancisCugler: Oh, the idea of storing digits in reverse order is not bad. But that still works when you choose a reasonable base like 2^8 or 2^32. Choosing a too-small base like 2^4 is just adding problems without any benefits whatsoever.
– MSalters
Nov 8 at 9:32
2
@FrancisCugler: Yeah, I already suspected you missed the necessary math basis.< 9, 2334343, 235, 3, 243 >
are 5 perfectly decent digits in base 2^32. Of course, there's no digit "03" there. If anything, it's0x00000003
.
– MSalters
Nov 8 at 9:52
1
To answer the above: yes, and maybe (not if you multiply as 64-bit for example, or use special instructions that store the overflow, useful for exactly these scenarios). This is one reason why I suggested you work in base-256, until you start to understand how to manage things like overflow in 32-bit integers. There's a clear knowledge gap here that's difficult to address as any kind of answer to your question. Your main issue was needing to be space-efficient. We've repeatedly told you how to achieve that, and all that remains is for you to go try it, and learn along the way.
– paddy
Nov 8 at 10:29
1
Yes, that's exactly what we're saying.
– paddy
Nov 8 at 10:45
|
show 13 more comments
up vote
0
down vote
up vote
0
down vote
The smallest addressable unit of memory in C++ is a byte, and a byte is at least 8 bits (and usually exactly 8 bits). So your idea indeed wastes memory.
I also suspect you're not that familiar with math. What you call "NumberSystemType" is ordinarily called the base. So we talk about base-10 or base-16.
Now base-10 is only useful for human consumption, and thus pointless for this sort of bigints. Humans can't deal with numbers that have more than about 20 decimal digits anyway. So, pick a base that's useful for computers. And pick one. There's no need to support multiple bases. As paddy points out, base 2^32 is a perfectly reasonable base for computers.
The smallest addressable unit of memory in C++ is a byte, and a byte is at least 8 bits (and usually exactly 8 bits). So your idea indeed wastes memory.
I also suspect you're not that familiar with math. What you call "NumberSystemType" is ordinarily called the base. So we talk about base-10 or base-16.
Now base-10 is only useful for human consumption, and thus pointless for this sort of bigints. Humans can't deal with numbers that have more than about 20 decimal digits anyway. So, pick a base that's useful for computers. And pick one. There's no need to support multiple bases. As paddy points out, base 2^32 is a perfectly reasonable base for computers.
answered Nov 8 at 9:22
MSalters
132k8115267
132k8115267
I understand the distinction of what you are saying about base-10 and base-16 due to Logarithms. I was just giving it a friendly readable name As base-10 which is Decimal is a Number System and base-16 which is Decimal is also a Number System. I'm trying to only store in the Decimal version of BigInt's container for each element 0-9 and nothing else but as for the Hexadecimal version it will store 0-F. The reason for this is when writing the functions to do the addition - subtraction etc. they become quite easy if the digits are stored in the vector in reverse order!
– Francis Cugler
Nov 8 at 9:27
2
@FrancisCugler: Oh, the idea of storing digits in reverse order is not bad. But that still works when you choose a reasonable base like 2^8 or 2^32. Choosing a too-small base like 2^4 is just adding problems without any benefits whatsoever.
– MSalters
Nov 8 at 9:32
2
@FrancisCugler: Yeah, I already suspected you missed the necessary math basis.< 9, 2334343, 235, 3, 243 >
are 5 perfectly decent digits in base 2^32. Of course, there's no digit "03" there. If anything, it's0x00000003
.
– MSalters
Nov 8 at 9:52
1
To answer the above: yes, and maybe (not if you multiply as 64-bit for example, or use special instructions that store the overflow, useful for exactly these scenarios). This is one reason why I suggested you work in base-256, until you start to understand how to manage things like overflow in 32-bit integers. There's a clear knowledge gap here that's difficult to address as any kind of answer to your question. Your main issue was needing to be space-efficient. We've repeatedly told you how to achieve that, and all that remains is for you to go try it, and learn along the way.
– paddy
Nov 8 at 10:29
1
Yes, that's exactly what we're saying.
– paddy
Nov 8 at 10:45
|
show 13 more comments
I understand the distinction of what you are saying about base-10 and base-16 due to Logarithms. I was just giving it a friendly readable name As base-10 which is Decimal is a Number System and base-16 which is Decimal is also a Number System. I'm trying to only store in the Decimal version of BigInt's container for each element 0-9 and nothing else but as for the Hexadecimal version it will store 0-F. The reason for this is when writing the functions to do the addition - subtraction etc. they become quite easy if the digits are stored in the vector in reverse order!
– Francis Cugler
Nov 8 at 9:27
2
@FrancisCugler: Oh, the idea of storing digits in reverse order is not bad. But that still works when you choose a reasonable base like 2^8 or 2^32. Choosing a too-small base like 2^4 is just adding problems without any benefits whatsoever.
– MSalters
Nov 8 at 9:32
2
@FrancisCugler: Yeah, I already suspected you missed the necessary math basis.< 9, 2334343, 235, 3, 243 >
are 5 perfectly decent digits in base 2^32. Of course, there's no digit "03" there. If anything, it's0x00000003
.
– MSalters
Nov 8 at 9:52
1
To answer the above: yes, and maybe (not if you multiply as 64-bit for example, or use special instructions that store the overflow, useful for exactly these scenarios). This is one reason why I suggested you work in base-256, until you start to understand how to manage things like overflow in 32-bit integers. There's a clear knowledge gap here that's difficult to address as any kind of answer to your question. Your main issue was needing to be space-efficient. We've repeatedly told you how to achieve that, and all that remains is for you to go try it, and learn along the way.
– paddy
Nov 8 at 10:29
1
Yes, that's exactly what we're saying.
– paddy
Nov 8 at 10:45
I understand the distinction of what you are saying about base-10 and base-16 due to Logarithms. I was just giving it a friendly readable name As base-10 which is Decimal is a Number System and base-16 which is Decimal is also a Number System. I'm trying to only store in the Decimal version of BigInt's container for each element 0-9 and nothing else but as for the Hexadecimal version it will store 0-F. The reason for this is when writing the functions to do the addition - subtraction etc. they become quite easy if the digits are stored in the vector in reverse order!
– Francis Cugler
Nov 8 at 9:27
I understand the distinction of what you are saying about base-10 and base-16 due to Logarithms. I was just giving it a friendly readable name As base-10 which is Decimal is a Number System and base-16 which is Decimal is also a Number System. I'm trying to only store in the Decimal version of BigInt's container for each element 0-9 and nothing else but as for the Hexadecimal version it will store 0-F. The reason for this is when writing the functions to do the addition - subtraction etc. they become quite easy if the digits are stored in the vector in reverse order!
– Francis Cugler
Nov 8 at 9:27
2
2
@FrancisCugler: Oh, the idea of storing digits in reverse order is not bad. But that still works when you choose a reasonable base like 2^8 or 2^32. Choosing a too-small base like 2^4 is just adding problems without any benefits whatsoever.
– MSalters
Nov 8 at 9:32
@FrancisCugler: Oh, the idea of storing digits in reverse order is not bad. But that still works when you choose a reasonable base like 2^8 or 2^32. Choosing a too-small base like 2^4 is just adding problems without any benefits whatsoever.
– MSalters
Nov 8 at 9:32
2
2
@FrancisCugler: Yeah, I already suspected you missed the necessary math basis.
< 9, 2334343, 235, 3, 243 >
are 5 perfectly decent digits in base 2^32. Of course, there's no digit "03" there. If anything, it's 0x00000003
.– MSalters
Nov 8 at 9:52
@FrancisCugler: Yeah, I already suspected you missed the necessary math basis.
< 9, 2334343, 235, 3, 243 >
are 5 perfectly decent digits in base 2^32. Of course, there's no digit "03" there. If anything, it's 0x00000003
.– MSalters
Nov 8 at 9:52
1
1
To answer the above: yes, and maybe (not if you multiply as 64-bit for example, or use special instructions that store the overflow, useful for exactly these scenarios). This is one reason why I suggested you work in base-256, until you start to understand how to manage things like overflow in 32-bit integers. There's a clear knowledge gap here that's difficult to address as any kind of answer to your question. Your main issue was needing to be space-efficient. We've repeatedly told you how to achieve that, and all that remains is for you to go try it, and learn along the way.
– paddy
Nov 8 at 10:29
To answer the above: yes, and maybe (not if you multiply as 64-bit for example, or use special instructions that store the overflow, useful for exactly these scenarios). This is one reason why I suggested you work in base-256, until you start to understand how to manage things like overflow in 32-bit integers. There's a clear knowledge gap here that's difficult to address as any kind of answer to your question. Your main issue was needing to be space-efficient. We've repeatedly told you how to achieve that, and all that remains is for you to go try it, and learn along the way.
– paddy
Nov 8 at 10:29
1
1
Yes, that's exactly what we're saying.
– paddy
Nov 8 at 10:45
Yes, that's exactly what we're saying.
– paddy
Nov 8 at 10:45
|
show 13 more comments
up vote
0
down vote
-Update-
Okay I took some advise and this is what I have so far. I have not yet incorporated the concept of storing the values in reverse order which shouldn't be all too difficult. As it currently stands, I have eliminated all of the outside classes and my BigInt class signature looks like this:
class BigInt {
private:
short sign_;
uint32_t carry_{0};
std::vector<uint32_t> value_;
public:
BigInt() : sign_( 0 ) {}
BigInt( const uint32_t* value, const std::size_t arraySize, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), &value[0], &value[arraySize] );
std::reverse( value_.begin(), value_.end() );
}
BigInt( const std::vector<uint32_t>& value, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), value.begin(), value.end() );
std::reverse( value_.begin(), value_.end() );
}
BigInt( std::initializer_list<uint32_t> values, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), values.begin(), values.end() );
std::reverse( value_.begin(), value_.end() );
}
std::vector<uint32_t> operator()() {
//value_.push_back( static_cast<uint32_t>( sign_ ) ); // causes negative to be pushed into container multiple times...
std::reverse( value_.begin(), value_.end() );
return value_;
}
};
I think this is a good start: there are several ways to construct one which gives the caller some flexibility since you can create a BigInt with a pointer, an array, a vector, or even an initializer list. At first it may seem counter intuitive to have the sign at the end of the constructor but I want to be able to have the sign value to be a defaulted parameter when the value is positive. If anything other than 0
is passed in for the sign value it will convert it to -1
. A little more time and I'll have the signature of this class complete then I can move onto the operators that will work on it.
There are some minor pitfalls in the above code but I'm continuously working on them.
The smallest addressable storage unit ischar
. If you insist on templating your 'digit' type as a base-10 integral type, then you'll either need to accept wasting a lot of bits, or pack your numbers as BCD (i.e. two digits per byte) and index them in pairs. If your math is lightweight and the main processing requirement is converting to/from base 10 then carry on. If the main processing requirement is fast math and compact storage, then you are on the wrong track and you should use a base that is a power of 2 that can be packed into a native integral type.
– paddy
Nov 8 at 10:33
@paddy I updated my answer with working code: it's not complete and bug free yet, but it is a work in progress. This class will basically only store the contents, manage its element ordering and checking for valid input. All of the operations on this I am planning on implementing in outside functions that will be friends with this class.
– Francis Cugler
Nov 9 at 16:05
add a comment |
up vote
0
down vote
-Update-
Okay I took some advise and this is what I have so far. I have not yet incorporated the concept of storing the values in reverse order which shouldn't be all too difficult. As it currently stands, I have eliminated all of the outside classes and my BigInt class signature looks like this:
class BigInt {
private:
short sign_;
uint32_t carry_{0};
std::vector<uint32_t> value_;
public:
BigInt() : sign_( 0 ) {}
BigInt( const uint32_t* value, const std::size_t arraySize, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), &value[0], &value[arraySize] );
std::reverse( value_.begin(), value_.end() );
}
BigInt( const std::vector<uint32_t>& value, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), value.begin(), value.end() );
std::reverse( value_.begin(), value_.end() );
}
BigInt( std::initializer_list<uint32_t> values, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), values.begin(), values.end() );
std::reverse( value_.begin(), value_.end() );
}
std::vector<uint32_t> operator()() {
//value_.push_back( static_cast<uint32_t>( sign_ ) ); // causes negative to be pushed into container multiple times...
std::reverse( value_.begin(), value_.end() );
return value_;
}
};
I think this is a good start: there are several ways to construct one which gives the caller some flexibility since you can create a BigInt with a pointer, an array, a vector, or even an initializer list. At first it may seem counter intuitive to have the sign at the end of the constructor but I want to be able to have the sign value to be a defaulted parameter when the value is positive. If anything other than 0
is passed in for the sign value it will convert it to -1
. A little more time and I'll have the signature of this class complete then I can move onto the operators that will work on it.
There are some minor pitfalls in the above code but I'm continuously working on them.
The smallest addressable storage unit ischar
. If you insist on templating your 'digit' type as a base-10 integral type, then you'll either need to accept wasting a lot of bits, or pack your numbers as BCD (i.e. two digits per byte) and index them in pairs. If your math is lightweight and the main processing requirement is converting to/from base 10 then carry on. If the main processing requirement is fast math and compact storage, then you are on the wrong track and you should use a base that is a power of 2 that can be packed into a native integral type.
– paddy
Nov 8 at 10:33
@paddy I updated my answer with working code: it's not complete and bug free yet, but it is a work in progress. This class will basically only store the contents, manage its element ordering and checking for valid input. All of the operations on this I am planning on implementing in outside functions that will be friends with this class.
– Francis Cugler
Nov 9 at 16:05
add a comment |
up vote
0
down vote
up vote
0
down vote
-Update-
Okay I took some advise and this is what I have so far. I have not yet incorporated the concept of storing the values in reverse order which shouldn't be all too difficult. As it currently stands, I have eliminated all of the outside classes and my BigInt class signature looks like this:
class BigInt {
private:
short sign_;
uint32_t carry_{0};
std::vector<uint32_t> value_;
public:
BigInt() : sign_( 0 ) {}
BigInt( const uint32_t* value, const std::size_t arraySize, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), &value[0], &value[arraySize] );
std::reverse( value_.begin(), value_.end() );
}
BigInt( const std::vector<uint32_t>& value, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), value.begin(), value.end() );
std::reverse( value_.begin(), value_.end() );
}
BigInt( std::initializer_list<uint32_t> values, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), values.begin(), values.end() );
std::reverse( value_.begin(), value_.end() );
}
std::vector<uint32_t> operator()() {
//value_.push_back( static_cast<uint32_t>( sign_ ) ); // causes negative to be pushed into container multiple times...
std::reverse( value_.begin(), value_.end() );
return value_;
}
};
I think this is a good start: there are several ways to construct one which gives the caller some flexibility since you can create a BigInt with a pointer, an array, a vector, or even an initializer list. At first it may seem counter intuitive to have the sign at the end of the constructor but I want to be able to have the sign value to be a defaulted parameter when the value is positive. If anything other than 0
is passed in for the sign value it will convert it to -1
. A little more time and I'll have the signature of this class complete then I can move onto the operators that will work on it.
There are some minor pitfalls in the above code but I'm continuously working on them.
-Update-
Okay I took some advise and this is what I have so far. I have not yet incorporated the concept of storing the values in reverse order which shouldn't be all too difficult. As it currently stands, I have eliminated all of the outside classes and my BigInt class signature looks like this:
class BigInt {
private:
short sign_;
uint32_t carry_{0};
std::vector<uint32_t> value_;
public:
BigInt() : sign_( 0 ) {}
BigInt( const uint32_t* value, const std::size_t arraySize, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), &value[0], &value[arraySize] );
std::reverse( value_.begin(), value_.end() );
}
BigInt( const std::vector<uint32_t>& value, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), value.begin(), value.end() );
std::reverse( value_.begin(), value_.end() );
}
BigInt( std::initializer_list<uint32_t> values, short sign = 0 ) :
sign_( sign ) {
if( sign_ != 0 ) sign_ = -1;
value_.insert( value_.end(), values.begin(), values.end() );
std::reverse( value_.begin(), value_.end() );
}
std::vector<uint32_t> operator()() {
//value_.push_back( static_cast<uint32_t>( sign_ ) ); // causes negative to be pushed into container multiple times...
std::reverse( value_.begin(), value_.end() );
return value_;
}
};
I think this is a good start: there are several ways to construct one which gives the caller some flexibility since you can create a BigInt with a pointer, an array, a vector, or even an initializer list. At first it may seem counter intuitive to have the sign at the end of the constructor but I want to be able to have the sign value to be a defaulted parameter when the value is positive. If anything other than 0
is passed in for the sign value it will convert it to -1
. A little more time and I'll have the signature of this class complete then I can move onto the operators that will work on it.
There are some minor pitfalls in the above code but I'm continuously working on them.
edited Nov 9 at 1:35
answered Nov 8 at 10:13
Francis Cugler
4,00711227
4,00711227
The smallest addressable storage unit ischar
. If you insist on templating your 'digit' type as a base-10 integral type, then you'll either need to accept wasting a lot of bits, or pack your numbers as BCD (i.e. two digits per byte) and index them in pairs. If your math is lightweight and the main processing requirement is converting to/from base 10 then carry on. If the main processing requirement is fast math and compact storage, then you are on the wrong track and you should use a base that is a power of 2 that can be packed into a native integral type.
– paddy
Nov 8 at 10:33
@paddy I updated my answer with working code: it's not complete and bug free yet, but it is a work in progress. This class will basically only store the contents, manage its element ordering and checking for valid input. All of the operations on this I am planning on implementing in outside functions that will be friends with this class.
– Francis Cugler
Nov 9 at 16:05
add a comment |
The smallest addressable storage unit ischar
. If you insist on templating your 'digit' type as a base-10 integral type, then you'll either need to accept wasting a lot of bits, or pack your numbers as BCD (i.e. two digits per byte) and index them in pairs. If your math is lightweight and the main processing requirement is converting to/from base 10 then carry on. If the main processing requirement is fast math and compact storage, then you are on the wrong track and you should use a base that is a power of 2 that can be packed into a native integral type.
– paddy
Nov 8 at 10:33
@paddy I updated my answer with working code: it's not complete and bug free yet, but it is a work in progress. This class will basically only store the contents, manage its element ordering and checking for valid input. All of the operations on this I am planning on implementing in outside functions that will be friends with this class.
– Francis Cugler
Nov 9 at 16:05
The smallest addressable storage unit is
char
. If you insist on templating your 'digit' type as a base-10 integral type, then you'll either need to accept wasting a lot of bits, or pack your numbers as BCD (i.e. two digits per byte) and index them in pairs. If your math is lightweight and the main processing requirement is converting to/from base 10 then carry on. If the main processing requirement is fast math and compact storage, then you are on the wrong track and you should use a base that is a power of 2 that can be packed into a native integral type.– paddy
Nov 8 at 10:33
The smallest addressable storage unit is
char
. If you insist on templating your 'digit' type as a base-10 integral type, then you'll either need to accept wasting a lot of bits, or pack your numbers as BCD (i.e. two digits per byte) and index them in pairs. If your math is lightweight and the main processing requirement is converting to/from base 10 then carry on. If the main processing requirement is fast math and compact storage, then you are on the wrong track and you should use a base that is a power of 2 that can be packed into a native integral type.– paddy
Nov 8 at 10:33
@paddy I updated my answer with working code: it's not complete and bug free yet, but it is a work in progress. This class will basically only store the contents, manage its element ordering and checking for valid input. All of the operations on this I am planning on implementing in outside functions that will be friends with this class.
– Francis Cugler
Nov 9 at 16:05
@paddy I updated my answer with working code: it's not complete and bug free yet, but it is a work in progress. This class will basically only store the contents, manage its element ordering and checking for valid input. All of the operations on this I am planning on implementing in outside functions that will be friends with this class.
– Francis Cugler
Nov 9 at 16:05
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53204355%2freduce-memory-footprint-in-the-construction-of-a-bigint-class%23new-answer', 'question_page');
}
);
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
Is this as an excersice or for production? Because there's a whole bunch of tried and true bigint libraries out there with C++ interfaces, e.g. GMP.
– Darhuuk
Nov 8 at 9:03
2
If I was going to implement my own large-integer, I would use a built-in type such as 32-bits to hold each "digit"... essentially meaning my digit representation is base-2^32, rather than your proposal of base-10 or base-16. All my operations would be in binary and there would be no wasted space. So the entire number would just be something like
std::vector<uint32_t>
. This makes converting to/from base-10 for Input/Output slightly more involved, but the math would be fast and vectorizable... which to me is the whole point of big-int.– paddy
Nov 8 at 9:17
1
"A half char would be excellent but does not exist in c++..." It doesn't exist in assembler either, because it makes no sense for processors designed to deal with 32 or 64 bits at a time with some internal convenience to deal with 8 bits at a time. And
std::bitset
won't help you with that since it allocates storage in word-size blocks (at least in the GCC implementation).– Nelfeal
Nov 8 at 9:18
2
Well, it looks to me like you've looked at the problem, chosen an inappropriate solution and then over-engineered it.
– paddy
Nov 8 at 9:24
1
@FrancisCugler - I agree with paddy. Distinct types for hex and decimal is terribly flawed design. One requirement of integral types is being able to assign and compare them (e.g. for equality) to each other. Your approach means the number of operations you need to implement is quadratic with number of bases you support - hard to maintain. Consider using a single class to efficiently represent an unsigned bigint value, then implement a signed bigint using that unsigned bigint type and a
bool
to represent sign. Handle bases at input or output, not with distinct representation in memory.– Peter
Nov 8 at 10:33