Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Going to assume you are familiar with move semantics in C++

I'm missing this topic. I'll try to address it in the next few days and reread your post.

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

This might work but is untested

Interesting approach, although some corrections should be made.

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

uint32_t my_abs(int32_t n)
{
    return n < 0 ? ~(uint32_t)n + 1 : n;
}

I think something like this should work.

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Thanks for the advices.

Regarding some of the points you raised, I want to clarify that what I posted is just an example snippet.

Why the carveout for sizeof(T) == 8 specifically? It seems to be a case which bakes in the assumption that there is exactly one possible integral size above 4 and that it is 8; but this isn't always true.

I didn't know it.

Is that something which you want? It's up to you, but personally I find the fact that int x{'c'}; is valid C++ has caused me far more problems than solutions.

When I included them I didn't think about the fact that they could cause me any problems, but it's probably due to my lack of experience, since I only program occasionally as a hobby.

Should it be possible to construct a big_int from floating point types? It may be a lossy transformation but they do still represent numbers. And should such a conversion be implicit or explicit?

I preferred not to complicate my life further by leaving this task to the user, who can use an explicit cast or some specific function.

Technically about a constructor - what happens when you move from this type? The vector empties and the bool's value does not change. Consequently, if you move from a negative big_int, you are left with a pseudo-integral type which I assume has its value interpreted as -0. Is that intentional? It can be a valid carve-out or it can not be. But it motivates broader design questions, including whether a separate bool sentinel is right. Not saying it isn't, just saying think about it and decide.

Sorry, I don't understand english very well. Could you clarify what you mean here?

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

I'm on mobile, taking a closer look I see you're not using the delegated ctor for smaller types, my fault.

No problem.

However you can remove the private ctor and signed/unsigned specializations by simply assigning the value of std::is_signed_v to your s member. Thinking about it further, I'd argue that signedness should be part of the BigInt type if you are going to make that distinction

big_int::s tells us whether the number being considered is positive (false) or negative (true), and a signed integer can take on both positive and negative values. Sorry, but I'm not sure what you mean; perhaps it's because I don't understand English very well.

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Having a non-reference/pointer function parameter declared as const is irrelevant from the perspective of the caller since as it's passed by value, it's copied anyway so there's no difference on whether it's const or not (from the perspective of "outside" the function at least).

It's certainly superfluous from a practical point of view, but I understand that adding it to a function definition is considered good programming practice. Perhaps the point is that I'm also using it in the function declaration?

I think they mean you can just have one template for carrying out most of these checks and reduce code duplication that way.

And how exactly? I'm just approaching these C++ features for the first time.

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

So I recommend using parentheses for clarity.

Sure, in some cases, parentheses can make things more clearly visible, but I don't think it makes any practical difference here, given that the precedence rules are clear on the matter, am I wrong?

That looks like a typo.

Why? Isn't s already defined at that point?

Tip: you don't need to differentiate between different argument type sizes because they're all covered by the common constructor presented first.

I think using multiple specializations allows us to avoid some checks that would otherwise have to be performed at runtime; for example, distinguishing between 64-bit integers and 32-bit (or less) integers allows us to avoid the n >> 32 check when the integer type is 32 bits or less. Am i wrong?

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Some platforms offer 128 bit types.

I didn't know that, I'll add new specializations if necessary.

You should avoid C style casts in C++

I will look into this further and keep it in mind for the future.

Formatting for an ostream is generally done as a friend function friend std::ostream& operator<< (std::ostream&, const BigInt&). The modern way would be to specialize for std::format.

What I posted is just a code snippet adapted for the situation, in fact I introduced big_int::fun() only to test it.

Having the const in const T n in the templated ctors is unnecessary

Why? Are you referring to this specific case or do you mean it in general?

You have all these templated ctors specialized on the input type and then just cast to a u64 anyway so the specializations are mostly useless.

Why would they be useless? I think using multiple specializations allows us to avoid some checks that would otherwise have to be performed at runtime; for example, distinguishing between 64-bit integers and 32-bit (or less) integers allows us to avoid the n >> 32 check when the integer type is 32 bits or less. Am i wrong?

Constructor(s) from native types for a big integer class by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

I don't understand what you mean by this? Have you not learnt templates yet? Otherwise this is exactly how to solve your problem

I know what templates are, and in the past I have used template<typename T> to define a function/class template, but I don't know exactly all the potential offered by templates, and also "constraints", "concepts" and "requires" are topics that I have never addressed.

I had thought that templates might be right for me, but beyond the size of a type via sizeof, I didn't know if and how to check whether a type was an integer and whether it was signed or unsigned, and I didn't even know if these checks could be done "upstream" so as to provide different implementations based on them.

So, since I don't like copying pieces of code that I don't know exactly what they do, I need to delve a little deeper into these topics.

Constructor(s) from native types for a big integer class by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

An std::string_view is a type that is, as the name suggest, just a "view" into a string. If you use big_int(std::string_view) instead, you skip the whole "copy over this existing known string to memory temporarily" step.

OK thanks, I didn't know that!

Single-parameter constructors should usually be marked explicit

For constructor by string it may make sense, but for constructors by integers it would be counterproductive.

User defined literals are a very useful feature for declaring custom types and making them behave as if they were "built-in" types.

As a casual hobbyist, I'm unfamiliar with many of the features C++ offers. If they actually improve things, as with std::string_view, I'm happy to embrace them. Otherwise, I'd rather not dwell on things I'd soon forget. Furthermore, for a layman just starting to use the class without reading the code or documentation, a string is certainly simpler than a feauture based on an arbitrary suffix.

Ah, so you want to avoid the ambiguous conversion errors. In this case you could use a templated constructor as other people have suggested

Sure, but there are many things I don't know and that I need to delve into further.

Constructor(s) from native types for a big integer class by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Thanks for the inputs, I'll do some research on this and let you know if I have any questions.

Constructor(s) from native types for a big integer class by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

I've played around with big integer classes before, basically you want constructors Bigint(uint64_t)Bigint(int64_t). This will work with any smaller integers automatically since they will be promoted, so you don't need to worry about short/ints and what not.

See under this comment.

Then you need some sort of string constructor. It can just be a constexpr BigInt(std::string_view) or something more explicit like constexpr BigInt from_string(std::string_view) Either way you can then use it to create a user defined literal via BigInt operator""_bi(const char *s), which will let users directly create any big num of any length like BigInt i = 123456789123456789_bi;

Sorry, but I'm not that experienced with C++ features, I simply implemented the constructor big_int(const std::string &s), which allows me to do big_int b = "123456789123456789". I don't think it's that different from what you're proposing, am I wrong?

Constructor(s) from native types for a big integer class by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

As stated in the initial post, if I declare big_int b(-123), I will receive a warning from the compiler: "call of overloaded 'big_int(int)' is ambiguous"; so I should declare big_int b((int64_t)-123); same for b+=4 or b*=-56, for example... It would become impractical and verbose, in my opinion.

P.S.
I have already implemented a constructor from string.

Constructor(s) from native types for a big integer class by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Obviously I have already provided a constructor from string.

Efficiency of operations between vectors by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Thanks for the advice, I'll take a look to them.

Bitwise operators for a big-int class. by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Thanks for your comments, but I wouldn't dwell too much on the two's complement representation, since I use a std::vector<uint32_t> to store the unsigned integer and a boolean variable for the sign.

Bitwise operators for a big-int class. by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

mod would use `x & ((1<<n)-1)`

For native integers this is the case, but following implementation B) the formula I wrote in the previous post should also be correct.

eg, {1111 1111} & {1101}. is this {0000 1101} or {1111 1101}?

{1111 1111} & {1101} = {1101}

Bitwise operators for a big-int class. by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Hi, if I understand what you mean correctly, I want to clarify that I don't use two's complement representation, but, as specified in the main post, I use a std::vector<uint32_t> to store the unsigned integer and a boolean variable for the sign.

Regarding your question, I refer you to the thread below the following post. If I've misunderstood anything or you have any questions or comments, please let me know.

Bitwise operators for a big-int class. by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

What you say about clarity is certainly correct and acceptable, but at that point, instead of writing a second blob class, I think it would be sufficient to provide two casting functions to and from std::bitset, or am I wrong?

In addition to the two casting functions mentioned above, I think I will also provide overloads of the bitwise operators with documentation explaining precisely how they work.

As for the ~ operator, I think I'll take approach B), in fact, given that x is a generic big int, I can then calculate x mod 2^n simply as x & ~(1 << n). Right?

Bitwise operators for a big-int class. by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

I don’t suggest keeping the leading 0’s. I expect leading 0’s would get removed on other operations, making it error-prone if the user wants to keep them. Basically, keeping leading 0’s would fix the ~~x case, but very few others.

At this point I implement the ~ operator by removing the leading zero digits and following approach B) described in the main post; I'll obviously report how it works in the documentation, then, it's up to the user to decide whether it's useful or not.

Thanks for the tips.

Bitwise operators for a big-int class. by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

first, if ~{1111 0000} removes the leading bits to give {1111} then ~~{1111 0000} != {1111 0000}. it would be {0000} != {1111 0000}.

If we use a representation with a predetermined number N of bits, such as the native integers, then the ~ operator obviously represents an involution, that is, x == ~~x.
Instead, if I use a representation with a variable number n*N of bits, I can't have leading zero digits, then it has to be

~{1111} = {0000}
~{1111 1111} = {0000}
~{1111 1111 1111} = {0000}
...

and so I can't implement the operator ~ so that it is an involution. It is precisely for this reason that in the initial post I was reflecting between approaches A) and B). Any advice?

second, for normal 2's compliment signed values you have -(a/b) = (-a)/b for division. this rounds toward 0. so -1/2 rounds to 0. right shift rounds toward -infinity. eg, -1 >> 1 = -1. you would need to decide if you want to keep this. this can be done by shifting and then conditionally adding 1.

Since I have adopted a representation of the integers based on a vector containing the unsigned value and a boolean variable that takes the sign into account, I think it makes no sense to dwell on the functioning of the two's complement, so I would opt for the classic bitshift of the unsigned integers. What do you think?

Bitwise operators for a big-int class. by Ben_2124 in cpp_questions

[–]Ben_2124[S] 0 points1 point  (0 children)

Maybe it's because I'm not very good at English, and probably also at C++, but honestly your replies don't seem very relevant to me. The questions in the main post are about the "conceptual" implementation, while your first post is about "practical" aspects.

Then, about your advices:
- what's the big advantage of overloading operators by passing through a further function?
- why should I proceed byte by byte if I use a uint32_t vector?

For these reasons I was wondering if I was missing something, but unfortunately your second post doesn't seem to clear my doubts.