-1

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

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 的实例,则默认为十进制!

对于向量中包含的数据。我想以与阅读内容相反的顺序存储数字。因此,如果它们是345698BigInt 内部向量中的一个数字,它将存储为896543. 这样做的原因是,当我们在数学中进行算术运算时,我们从小数点左侧的右侧开始从最低有效位到最高有效位,这是无关紧要的,因为这是一个仅限 BigInt 的类,我们以自己的方式工作左边。但是,如果我们以正确的顺序在上述类的向量的每个元素中存储每个只能是 0-9 的数字,并且我们使用外部 operator+() 函数,这对于一个 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 个加法而不是 2 个。现在,当获取 BigInt 进行显示时,我们可以返回一个向量>,除了当用户获取它时,这不是“BigInt”,它是数字向量,并且它的顺序也是正确的。它甚至可以通过字符串表示形式返回。这个类可以由一个vector>构造,它在内部存储时会颠倒顺序。

这是我的 BigInt 类的总体概念,我只是想知道这是否是一个好的设计计划,是否会被认为是有效的,我想我的主要问题是关于使用什么来存储实际数字Digit 的类...是否适合保存内存占用空间,或者使用 a而不用担心额外的空间std::bitset<>会更好,因为 pro 更易于实现?char

4

2 回答 2

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. 再多一点时间,我将完成这个类的签名,然后我可以转到将处理它的运算符。

上面的代码中有一些小缺陷,但我一直在努力解决它们。

于 2018-11-08T10:13:43.170 回答
0

C++ 中最小的可寻址内存单元是一个字节,一个字节至少有8 位(通常正好是 8 位)。所以你的想法确实浪费内存。

我还怀疑你对数学不太熟悉。您所说的“NumberSystemType”通常称为base。所以我们谈论base-10或base-16。

现在 base-10 仅对人类消费有用,因此对于这种 bigints 毫无意义。无论如何,人类无法处理超过 20 位十进制数字的数字。所以,选择一个对计算机有用的基础。并选择一个。无需支持多个基地。正如 paddy 指出的那样,base 2^32 是计算机的一个完全合理的基础。

于 2018-11-08T09:22:55.493 回答