在构造BigInt类时减少内存占用

时间:2018-11-08 08:58:25

标签: c++ types biginteger template-specialization memory-footprint

此刻我正在研究一个概念,并在问这个问题的同时编写伪代码。我正在考虑使用一个非常容易和简单的类接口来表示BigInts。我正在考虑使用BigInt类使用几个具有基本属性和成员的简单结构。例如,不是BigInt类直接处理负值,而是包含一个Sign结构,该结构基本上包含0或1的值,或者基本上是布尔类型来指定此BigInt是正还是负。在构建之后,我打算让该类在默认情况下生成一个肯定值。我还希望有一个表示两个变体形式的数字的结构。第一个变体具有数字0-9,第二个变体将继承原始数字,但也包括A-F。这样,将成为模板类但只有两个有效类型的类将支持使用Decimal和Hexadecimal。所有数学运算符都将在类之外定义,并且根据其推断的类型,它将调用并执行函数的适当版本。但是,十六进制部分仍然只是概念,因为我想首先启动并运行Decimal版本。这些帮助程序类可能看起来像这样:

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 == '\0' || 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<>...

};

两者之间的主要区别在于,第一个专业化只允许数字值从0-9到数字本身0-9,而第二个专业化没有这种限制,但也允许af和AF已验证。我还可以包含一个const char *来指定0x的十六进制前缀,该前缀将附加到任何包含的值上进行显示。

我喜欢这种设计方法,因为我想将BigInt类的实际算术函数和运算符保留为单独的函数模板,因为BigInt类可以同时支持Decimal和Hexadecimal专用模板类型。同样,如果一切顺利,我也想添加支持以处理复数。

BigInt类如下:

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.
};

并且如上所述,它也将专门用于十进制和十六进制类型,但是,如果有人创建BigInt <> myBigInt的实例,则该默认值应为Decimal!

用于向量中包含的数据。我想按与读物相反的顺序存储数字。因此,如果它们是BigInt内部向量中的数字345698,它将存储为896543。这样做的原因是,当我们在数学中进行算术运算时,我们从小数点的左侧的最低有效位到最高有效位开始工作,这是无关紧要的,因为这是仅BigInt的类,因此我们以左边。但是,如果我们以适当的顺序在上述类的向量的每个元素中存储只能为0-9的每个数字,并且我们使用外部operator +()函数,那么对于一个BigInt到另一个BigInt来说将是一个挑战。例如:

Basic Arithmetic R - L    | Vector storing standard
12345                       <1,2,3,4,5>
+ 678                       <6,7,8>
------  
13023

此处<5>和<8>的索引不一致,因此很难弄清楚如何将一个数位的值加到一个数位。我的方法是,如果我们以相反的顺序存储数字:

                         | Vector stored in reverse
                           <5,4,3,2,1>
                           <6,7,8>

然后添加变得简单!我们要做的就是将两个BigInt向量的每个数字相加相同的索引值。而且我们可以使用进位数字结转到下一个字段。生成的BigInt将返回的大小至少等于或大于两个BigInt中最大的大小。如果carryDigit有一个值,则下一次迭代的加法运算将包括3次加法而不是两次。现在,当获取要显示的BigInt时,我们可以返回一个vector>,不同之处在于,当用户获取它不是“ BigInt”时,它是Digits的向量,并且其顺序也正确。它甚至可以由字符串表示形式返回。此类可以通过vector>构造,并且在内部存储时将颠倒顺序。

这是我的BigInt类的整体概念,我只是想知道这是否是一个好的设计计划,是否可以认为它是有效的,我想我的主要问题是关于使用什么来存储实际的Digit类中的数字... std::bitset<>是否适合节省内存占用空间?还是最好使用char而不用担心简单的优点而担心额外的空间实施?

2 个答案:

答案 0 :(得分:0)

C ++中最小的可寻址内存单元是一个字节,一个字节至少为 8位(通常为8位)。因此,您的想法确实浪费了内存。

我还怀疑您对数学不是那么熟悉。您所谓的“ NumberSystemType”通常称为 base 。因此,我们谈论以10为底或以16为底。

现在base-10仅对人类食用有用,因此对于这种bigints毫无意义。无论如何,人类不能处理超过20个十进制数字的数字。因此,选择一个对计算机有用的基础。然后选一个。无需支持多个基地。正如paddy所指出的,以2 ^ 32为基数对于计算机来说是一个非常合理的基数。

答案 1 :(得分:0)

-更新-

好吧,我接受了一些建议,这就是我到目前为止所掌握的。我还没有合并以相反顺序存储值的概念,这应该不太困难。就目前情况而言,我已经消除了所有外部类,而我的BigInt类签名看起来像这样:

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_;
    }
};

我认为这是一个好的开始:有多种方法可以构造一种使调用者具有灵活性的方法,因为您可以使用指针,数组,向量甚至是初始化列表创建BigInt。刚开始时,在构造函数的末尾加上符号可能看起来很不直观,但我希望当值为正时,能够将符号值作为默认参数。如果传入0以外的符号值,它将转换为-1。再花一点时间,我将完成该类的签名,然后我可以继续进行操作。

上面的代码中有一些小缺陷,但我一直在不断努力。