Search code examples
c++bigintegerradix

BigInteger: Way to change base to 2 when writing to file using ofstream in C++?


I'm using the BigInteger Library in C++ to multiply some large numbers and write them to file. However, I want to change the base of the numbers before writing them.

I've tried using std::setbase() however it only changes it to base 8, 10 and 16. For example trying setbase(2) gives an error.

Here's an example:

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include "BigIntegerLibrary.hh"
#include "BigIntegerUtils.hh"
#include "BigIntegerAlgorithms.hh"
#include "BigUnsigned.hh"
#include "BigUnsignedInABase.hh"
#include "BigInteger.hh"
#include <stdio.h>      /* printf, NULL */
#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */
...
        str1=randomStrGen(k);
        str2=randomStrGen(j);

        BigInteger s1 = stringToBigInteger(str1);
        BigInteger s2 = stringToBigInteger(str2);

        myfile<<str1<<" "<<str2<<" "<<"10"<<" "<<setbase(2)<<s1+s2<<" "<<s1*s2<<"\n";
...

Gives the error:

terminate called after throwing an instance of 'char const*'

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
[Inferior 1 (process 5156) exited with code 03]

BigInteger Library downloaded from https://mattmccutchen.net/bigint/, 2010.04.30 Release.


Solution

  • The problem is that std::setbase(2) does not switch to a binary representation, see here on cppreference:

    Values of base other than 8, 10, or 16 reset basefield to zero, which corresponds to decimal output and prefix-dependent input.

    Here is an own implementation to get a binary representation of a BigUnsigned which will be stored into a std::string, note that it assumes that your machine stores values in little endian representation.

    The function convertToBinary() expects a BigUnsigned because this type can perform more mathematical operations such as getting bits. This also means that it can only work for unsigned numbers.

    Further you should consider about using an other library as this library will no longer be maintained (last release from 2010) as stated here:

    I have lost interest in maintaining this library and will not respond to most development or support inquiries.

    Full code:

    #include "BigIntegerLibrary.hh"    
    #include <iostream>
    #include <string>
    
    std::string convertToBinary(const BigUnsigned& bigUns)
    {
       std::string binary;
       for (size_t idx = 0; idx < bigUns.bitLength(); ++idx)
       {
          binary += (bigUns.getBit(bigUns.bitLength() - 1 - idx) ? "1" : "0");
       }
    
       return binary;
    }
    
    int main()
    {
       std::string str1 = "12";
       std::string str2 = "18446744073709551615"; /* uint64 max */
       BigInteger s1 = stringToBigInteger(str1);
       BigInteger s2 = stringToBigInteger(str2);
       s2 = s2 * 2;
    
       std::cout << s1 << ": " << convertToBinary(s1.getMagnitude()) << std::endl;
       std::cout << s2 << ": " << convertToBinary(s2.getMagnitude()) << std::endl;
    
       return 0;
    }
    

    Output:

    12: 1100
    36893488147419103230: 11111111111111111111111111111111111111111111111111111111111111110
    

    For native types you can get the binary representation as said in the comments and already answered here on SO.


    Edit #1:

    As the OP talked about a general conversion with any base in the comments below here is a version that will convert a BigUnsigned to any base returning a std::string, the function name is convertToAnyBase(). It is inspired by this snippet. But this works just with unsigned numbers like the solution before.
    To check those big number conversion there is a great online tool called numberfacts.

    Full code (contains also the code from above):

    #include "BigIntegerLibrary.hh"    
    #include <iostream>
    #include <string>
    #include <vector>
    
    std::string convertToBinary(const BigUnsigned& bigUns)
    {
       std::string binary;
       for (size_t idx = 0; idx < bigUns.bitLength(); ++idx)
       {
          binary += (bigUns.getBit(bigUns.bitLength() - 1 - idx) ? "1" : "0");
       }
    
       return binary;
    }
    
    std::string convertToAnyBase(const BigUnsigned& bigUns_P, unsigned short base)
    {
       static const char baseDigits[16] =
          { '0', '1', '2', '3', '4', '5', '6', '7',
            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    
       BigUnsigned bigUns = bigUns_P;
    
       std::string anyBaseString;
       std::vector<size_t> convertedNumber;
       convertedNumber.reserve(bigUns.bitLength());
    
       /* convert to the indicated base */
       while (bigUns != 0)
       {
          convertedNumber.push_back((bigUns % base).toUnsignedInt());
          bigUns = bigUns / base;
       }
    
       /* store result in reverse order */
       for (std::vector<size_t>::const_reverse_iterator curNumber = convertedNumber.rbegin();
            curNumber != convertedNumber.rend();
            ++curNumber)
       {
          anyBaseString += baseDigits[*curNumber];
       }
    
       return anyBaseString;
    }
    
    int main()
    {
       std::string str1 = "12";
       std::string str2 = "18446744073709551615"; /* uint64 max */
       BigInteger s1 = stringToBigInteger(str1);
       BigInteger s2 = stringToBigInteger(str2);
       s2 = s2 * 2;
    
       std::cout << s1 << " binary: " << convertToBinary(s1.getMagnitude()) << "\n"
                 << s2 << " binary: " << convertToBinary(s2.getMagnitude()) << "\n"
                 << std::endl;
    
       std::cout << s1 << " binary: " << convertToAnyBase(s1.getMagnitude(), 2) << "\n"
                 << s2 << " binary: " << convertToAnyBase(s2.getMagnitude(), 2) << "\n"
                 << std::endl;
    
       std::cout << s1 << " octal: "  << convertToAnyBase(s1.getMagnitude(), 8) << "\n"
                 << s2 << " binary: " << convertToAnyBase(s2.getMagnitude(), 8) << "\n"
                 << std::endl;
    
       return 0;
    }
    

    Output:

    12 binary: 1100
    36893488147419103230 binary: 11111111111111111111111111111111111111111111111111111111111111110
    
    12 binary (any base function): 1100
    36893488147419103230 binary (any base function): 11111111111111111111111111111111111111111111111111111111111111110
    
    12 octal (any base function): 14
    36893488147419103230 octal (any base function): 3777777777777777777776
    

    Edit #2:

    An easier approach to convert to any base is to use the capabilities that the BigInteger Library provides. It provides a BigUnsignedInABase class which supports the conversion to a std::string, but also this just works for unsigned numbers:

    std::string convertToAnyBase(const BigUnsigned& bigUns, unsigned short base)
    {
       BigUnsignedInABase bigBase(bigUns, base);
       return static_cast<std::string> (bigBase);
    }
    

    If you want to work also with signed numbers you have to work with the Two's complement. There is no operator ! (Two's complement) or operator ~ (One's complement) in the BigInteger Library available so you have to do it on the bits and add one for your own.

    But this would only work for the binary (base 2) conversion. The other bases are suffering from the fact that the BigInteger Library saves the BigInteger as sign + BigUnsigned as aggregated number, so that there isn't any other binary representation of negative numbers here.

    Another possibility would be to write the sign (BigInteger::getSign()) in front of the string and keep the positive number e.g.:

    -12base{10} = -1100base{2} = -14base{8} 
    

    This is an easy code change as you just have to pass BigInteger instead of BigUnsigned and use the appropiate method.