2
\$\begingroup\$

I made this project to practice object-oriented programming. I have implemented operator+ and operator+=. The rationale behind struct cache are

  1. I don't necessarily need to traverse the entire container to display the value.
  2. Using a struct cache makes it possible to modify its value even in a const method

I also considered eliminating the container entirely and representing BigInt as a string, that might have been a better approach.

Here is my code

BigInt.h

#ifndef BIGINTEGER_H_
#define BIGINTEGER_H_

#include <iostream>
#include <deque>
#include <string>

namespace MyBigInteger
{ 
    struct cache
    {
        bool has_changed{true};
        std::string cache_value{""};
    };

    class BigInt
    {
        friend std::ostream& operator<<(std::ostream& os, const BigInt& bigint);
        friend std::istream& operator>>(std::istream& is, BigInt& bigint);
        public:
            BigInt();
            explicit BigInt(const std::string &str_number);
            BigInt& operator+=(const BigInt& rhs);
            BigInt operator+(const BigInt& lhs) const;
            size_t size() const;
        private:
            cache* string_rep;
            size_t bigint_size;
            std::deque<int8_t> elems;

            std::string get_new_cache_value() const;
    };

    int8_t to_digit(const char chr);
    char to_char(const int value);
}

#endif

BigInt.cpp

#include "BigInt.h"

#include <algorithm>

namespace MyBigInteger
{
    BigInt::BigInt()
        : string_rep{new cache{false, ""}} {}

    BigInt::BigInt(const std::string& str_number)
        : string_rep{new cache{false, str_number}}, bigint_size{str_number.size()}
        {
            for(const auto chr : str_number)
                elems.push_back(to_digit(chr));
        }
        
    int8_t to_digit(const char chr)
    {
        return chr - '0';
    }

    char to_char(const int value)
    {
        return value + '0';
    }

    std::ostream& operator<<(std::ostream& os, const BigInt& bigint)
    {
        if(bigint.string_rep->has_changed)
        {
            bigint.string_rep->cache_value = bigint.get_new_cache_value();
            bigint.string_rep->has_changed = false;
        }

        os << bigint.string_rep->cache_value;

        return os;
    }

    std::istream& operator>>(std::istream& is, BigInt& bigint)
    {
        is >> bigint.string_rep->cache_value;

        return is;
    }

    std::string BigInt::get_new_cache_value() const
    {
        std::string temp_str;
        std::for_each(elems.cbegin(), elems.cend(), [&](const int x)
        { temp_str += to_char(x); });

        return temp_str;
    }

    BigInt& BigInt::operator+=(const BigInt& rhs)
    {
        *this = std::move(*this + rhs);
        return *this;
    }

    BigInt BigInt::operator+(const BigInt& rhs) const
    {
        BigInt res;
        res.string_rep = new cache{true, ""};
        unsigned total = 0;
        unsigned carry = 0;

        auto beg = elems.crbegin(), rhs_beg = rhs.elems.crbegin();
        for(; beg != elems.crend() && rhs_beg != rhs.elems.crend();
            ++beg, ++rhs_beg)
        {
            total = *beg + *rhs_beg + carry;
            carry = total / 10;
            res.elems.push_front(total % 10);
        }
        
        if(beg == elems.crend() && rhs_beg == rhs.elems.crend())
            return res;

        else if(beg == elems.crend())
        {
            for(; rhs_beg != rhs.elems.crend(); ++rhs_beg)
            {
                total = *rhs_beg + carry;
                carry = total / 10;
                res.elems.push_front(total % 10);
            }
        }
        else
        {
            for(; beg != elems.crend(); ++beg)
            {
                total = *beg + carry;
                carry = total / 10;
                res.elems.push_front(total % 10);
            }
        }
        if(carry)
            res.elems.push_front(carry);

        return res;
    }

    size_t BigInt::size() const
    {
        return bigint_size;
    }
}

main.cpp

#include "BigInt.h"

#include <iostream>

using namespace MyBigInteger;

int main()
{
    BigInt my_bigint{"123456789123456789123456789"};
    BigInt my_bigint2{"24682468246824682468"};
    std::cout << "my_bigint + my_bigint2: " << my_bigint + my_bigint2 << '\n'; 
    my_bigint += my_bigint2;
    std::cout << "my_bigint after my_bigint += my_bigint2: " << my_bigint << '\n';

    BigInt my_bigint3{};
    std::cout << "default my_bigint3: " << my_bigint3 << '\n';
    std::cin >> my_bigint3;
    std::cout << "my_bigint3 after input: " << my_bigint3 << '\n';
    BigInt my_bigint4{my_bigint3};
    std::cout << "After copy constructor: " << my_bigint4 << '\n';
    my_bigint4 = my_bigint;
    std::cout << "After copy assignment: " << my_bigint4 << '\n';
}
```
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Use constexpr

These functions

    int8_t to_digit(const char chr)
    {
        return chr - '0';
    }

    char to_char(const int value)
    {
        return value + '0';
    }

can be marked constexpr. You are telling that it is possible to calculate the value at compile-time. constexpr also implies inline, resulting in faster execution time. Note that after marking it constexpr you must define it with the declaration.


Use string_view

As your class name suggests, you are going to be working with big numbers, i.e big strings will come. In that case, copying can be very bad and slow.

In your class constructor

     BigInt::BigInt(const std::string& str_number)
        : string_rep{new cache{false, str_number}}, bigint_size{str_number.size()}
        {
            for(const auto chr : str_number)
                elems.push_back(to_digit(chr));
        }

I refactored it for this example

struct cache
{
    std::string str;
};

struct BigInt
{
    cache c;

    BigInt(const std::string& str)
        : c{ str.data() }
}

Assuming there is no small string optimization, you will be calling new 2 times, once for constructing str, and the other time for copying into c.str.

However, if you use string_view

struct cache
{
    std::string str;
};

struct BigInt
{
    cache c;

    BigInt(std::string_view str)
        : c{str.data()}
    {}

};

You will be calling new only once, to construct the string and that same string into the member.


TODO

Here are some things that you left out as far as I can remember

  • Operators - -,*,/,%,^,&,~,|, ==, ++, --
  • Casting - Decide whether your data type should be able to cast to the primitive ones, or the opposite
\$\endgroup\$
3
  • \$\begingroup\$ I would implement those operators, I just wanted review of what I have done so far. \$\endgroup\$ Dec 7, 2020 at 16:10
  • \$\begingroup\$ Adding inline is reductant, the compiler should be smart enough to select what to inline, but great review. \$\endgroup\$ Dec 7, 2020 at 16:12
  • \$\begingroup\$ @theProgrammer Yes surely, but the compiler can't inline your original function since the declaration and definition are split \$\endgroup\$
    – user228914
    Dec 7, 2020 at 16:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.