Discussion:
What is your opinion about unsigned int u = -2 ?
(too old to reply)
Thiago Adams
2024-07-31 13:55:21 UTC
Permalink
What is your opinion about this:

unsigned int u1 = -1;

Generally -1 is used to get the maximum value.
Is this guaranteed to work?

How about this one?

unsigned int u2 = -2;
Does it makes sense? Maybe a warning here?
Ben Bacarisse
2024-07-31 19:29:48 UTC
Permalink
Post by Thiago Adams
unsigned int u1 = -1;
Generally -1 is used to get the maximum value.
Yes, that's a common usage, though I prefer either -1u or ~0u.
Post by Thiago Adams
Is this guaranteed to work?
How about this one?
unsigned int u2 = -2;
Does it makes sense? Maybe a warning here?
Warnings are almost always good, especially if they can be configured.
For example you can ask gcc to warn about converting -1 to unsigned
while leaving -1u and ~0u alone.
--
Ben.
Tim Rentsch
2024-08-11 19:33:11 UTC
Permalink
Post by Ben Bacarisse
Post by Thiago Adams
unsigned int u1 = -1;
Generally -1 is used to get the maximum value.
Yes, that's a common usage, though I prefer either -1u or ~0u.
Post by Thiago Adams
Is this guaranteed to work?
How about this one?
unsigned int u2 = -2;
Does it makes sense? Maybe a warning here?
Warnings are almost always good, especially if they can be configured.
For example you can ask gcc to warn about converting -1 to unsigned
while leaving -1u and ~0u alone.
Ick. That choice is exactly backwards IMO. Converting -1 to
an unsigned type always sets all the bits. Converting -1u to
an unsigned type can easily do the wrong thing, depending
on the target type.
Vir Campestris
2024-08-11 20:08:45 UTC
Permalink
Post by Tim Rentsch
Ick. That choice is exactly backwards IMO. Converting -1 to
an unsigned type always sets all the bits. Converting -1u to
an unsigned type can easily do the wrong thing, depending
on the target type.
"Converting -1 to an unsigned type always sets all the bits"

In any normal twos complement architecture that's the case. But there
are a few oddballs out there where -1 is +1, except that the dedicated
sign bit is set.

Andy
Richard Damon
2024-08-11 20:45:12 UTC
Permalink
Post by Vir Campestris
Ick.  That choice is exactly backwards IMO.  Converting -1 to
an unsigned type always sets all the bits.  Converting -1u to
an unsigned type can easily do the wrong thing, depending
on the target type.
"Converting -1 to an unsigned type always sets all the bits"
In any normal twos complement architecture that's the case. But there
are a few oddballs out there where -1 is +1, except that the dedicated
sign bit is set.
Andy
But, when that -1 value is converted to an unsigned type, that VALUE
will be adjusted modulo the appropriate power of two.

signed to unsigned conversion works on VALUE, not bit pattern, so is
invariant with the representation of the negative values.

Yes, in a union with a signed and unsigned, the type punning will let
you see the representation of the types, but assignment works on values.
James Kuyper
2024-08-11 20:53:20 UTC
Permalink
On 8/11/24 16:08, Vir Campestris wrote:
...
Post by Vir Campestris
"Converting -1 to an unsigned type always sets all the bits"
In any normal twos complement architecture that's the case. But there
are a few oddballs out there where -1 is +1, except that the dedicated
sign bit is set.
There may be hardware where that is true, but a conforming
implementation of C targeting that hardware cannot use the hardware's
result. It must fix up the result produced by the hardware to match the
result required by the C standard.
Vir Campestris
2024-08-12 10:47:11 UTC
Permalink
Post by James Kuyper
...
Post by Vir Campestris
"Converting -1 to an unsigned type always sets all the bits"
In any normal twos complement architecture that's the case. But there
are a few oddballs out there where -1 is +1, except that the dedicated
sign bit is set.
There may be hardware where that is true, but a conforming
implementation of C targeting that hardware cannot use the hardware's
result. It must fix up the result produced by the hardware to match the
result required by the C standard.
But, when that -1 value is converted to an unsigned type, that VALUE
will be adjusted modulo the appropriate power of two.
signed to unsigned conversion works on VALUE, not bit pattern, so is
invariant with the representation of the negative values.
Yes, in a union with a signed and unsigned, the type punning will let
you see the representation of the types, but assignment works on values.
Ah, thank you both. It's academic interest only of course!

Andy
Tim Rentsch
2024-08-12 06:07:32 UTC
Permalink
Post by Vir Campestris
Post by Tim Rentsch
Ick. That choice is exactly backwards IMO. Converting -1 to
an unsigned type always sets all the bits. Converting -1u to
an unsigned type can easily do the wrong thing, depending
on the target type.
"Converting -1 to an unsigned type always sets all the bits"
In any normal twos complement architecture that's the case. But there
are a few oddballs out there where -1 is +1, except that the dedicated
sign bit is set.
What you say is right if the transformation occurs by means of
type punning, as for example if we had a union with both a
signed int member and an unsigned int member. Reading the
unsigned int member after having assigned to the signed int
member depends on whether signed int uses ones' complement,
two's complement, or signed magnitude.

My comment though is about conversion, not about type punning.
The rules for conversion (both explicit conversion, when a cast
operator is used, and implicit conversion, such as when an
assignment is performed (and lots of other places)) are defined
in terms of values, not representations. Thus, in this code

signed char minus_one = -1;
unsigned u = minus_one;
unsigned long bigu = minus_one;
unsigned long long biggeru = minus_one;
printf( " printing unsigned : %u\n", (unsigned) minus_one );

in every case we get unsigned values (of several lengths) with
all value bits set, because of the rules for how values are
converted between a signed type (such as signed char) and an
unsigned type.
James Kuyper
2024-07-31 21:17:15 UTC
Permalink
Post by Thiago Adams
unsigned int u1 = -1;
Generally -1 is used to get the maximum value.
Is this guaranteed to work?
Yes.
"... the value is converted by repeatedly adding or subtracting
one more than the maximum value that can be represented in the new type
until the value is in the range of the new type." (6.3.1.3p2).

In practice, there's better ways than repeated addition, but that's how
the conversion is defined.
Post by Thiago Adams
How about this one?
unsigned int u2 = -2;
That is guaranteed to produce 1 less than the maximum value.
Blue-Maned_Hawk
2024-08-01 06:34:21 UTC
Permalink
Post by Thiago Adams
unsigned int u1 = -1;
Generally -1 is used to get the maximum value.
Is this guaranteed to work?
Whether or not it is, i would prefer to use the UINT_MAX macro to make the
code clearer.
Post by Thiago Adams
How about this one?
unsigned int u2 = -2;
Does it makes sense? Maybe a warning here?
I cannot think of any situations where that would make sense, but i also
cannot guarantee that there are not any.
--
Blue-Maned_Hawk│shortens to Hawk│/blu.mɛin.dʰak/│he/him/his/himself/Mr.
blue-maned_hawk.srht.site
I was so amused with it that i did it twenty-three more times.
Ben Bacarisse
2024-08-01 11:02:24 UTC
Permalink
...
Post by Blue-Maned_Hawk
Post by Thiago Adams
How about this one?
unsigned int u2 = -2;
Does it makes sense? Maybe a warning here?
I cannot think of any situations where that would make sense, but i also
cannot guarantee that there are not any.
Some of the multi-byte conversion functions (like mbrtowc) return either
(size_t)-1 or (size_t)-2 to indicate different kinds of failure so it's
not inconceivable that someone might write

size_t processed_but_incomplete = -2;
--
Ben.
Thiago Adams
2024-08-02 13:36:59 UTC
Permalink
Post by Ben Bacarisse
...
Post by Blue-Maned_Hawk
Post by Thiago Adams
How about this one?
unsigned int u2 = -2;
Does it makes sense? Maybe a warning here?
I cannot think of any situations where that would make sense, but i also
cannot guarantee that there are not any.
Some of the multi-byte conversion functions (like mbrtowc) return either
(size_t)-1 or (size_t)-2 to indicate different kinds of failure so it's
not inconceivable that someone might write
size_t processed_but_incomplete = -2;
I also notice that overflow is not an error for unsigned .

For instance:

unsigned long long a = ULLONG_MAX + ULLONG_MAX;

But it is for signed

long long a = LLONG_MAX + LLONG_MAX;

So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
Kenny McCormack
2024-08-02 14:33:31 UTC
Permalink
In article <v8inds$2qpqh$***@dont-email.me>,
Thiago Adams <***@gmail.com> wrote:
...
Post by Thiago Adams
So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
I think it is because it wants to (still) support representations other
than 2s complement. I think POSIX requires 2s complement, and I expect the
C standard to (eventually) follow suit.
--
Res ipsa loquitur.
Bart
2024-08-02 14:48:15 UTC
Permalink
Post by Kenny McCormack
...
Post by Thiago Adams
So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
I think it is because it wants to (still) support representations other
than 2s complement. I think POSIX requires 2s complement, and I expect the
C standard to (eventually) follow suit.
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.

But even if well-defined (eg. that UB was removed so that overflow just
wraps as it does with unsigned), some here, whose initials may or may
not be DB, consider such overflow Wrong and a bug in a program.

However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.

But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
Ben Bacarisse
2024-08-02 15:17:29 UTC
Permalink
Post by Kenny McCormack
...
Post by Thiago Adams
So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
I think it is because it wants to (still) support representations other
than 2s complement. I think POSIX requires 2s complement, and I expect the
C standard to (eventually) follow suit.
C23 assumes 2s complement. However overflow on signed integers will still
be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow just
wraps as it does with unsigned), some here, whose initials may or may not
be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because I've
chosen a u32 type instead of u64, that's just as much a bug as using i32
instead of i64.
I don't think *anyone* considers a program that produces the wrong
result to be have any less buggy simply because of the types used. You
are ascribing to other views that I have never seen anyone express.
--
Ben.
Thiago Adams
2024-08-02 18:39:03 UTC
Permalink
Post by Bart
Post by Kenny McCormack
...
Post by Thiago Adams
So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
I think it is because it wants to (still) support representations other
than 2s complement.  I think POSIX requires 2s complement, and I
expect the
C standard to (eventually) follow suit.
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow just
wraps as it does with unsigned), some here, whose initials may or may
not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
Also consider

#include <stdio.h>
#include <limits.h>
int main()
{
//ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX is 1
printf("%ull", (ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX));
}



I am not Rust fan, but I prefer rust here. (compile time error)

https://godbolt.org/z/TP8WjThdE

in C it prints 0

https://godbolt.org/z/KWc8nhajc
Thiago Adams
2024-08-02 18:58:22 UTC
Permalink
Post by Thiago Adams
Post by Bart
Post by Kenny McCormack
...
Post by Thiago Adams
So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
I think it is because it wants to (still) support representations other
than 2s complement.  I think POSIX requires 2s complement, and I
expect the
C standard to (eventually) follow suit.
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow
just wraps as it does with unsigned), some here, whose initials may or
may not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
Also consider
#include <stdio.h>
#include <limits.h>
int main()
{
    //ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX is 1
    printf("%ull", (ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX));
}
I am not Rust fan, but I prefer rust here. (compile time error)
https://godbolt.org/z/TP8WjThdE
in C it prints 0
https://godbolt.org/z/KWc8nhajc
It also overflow in compile time in Go
https://godbolt.org/z/n94no73P8
Keith Thompson
2024-08-02 18:48:38 UTC
Permalink
Bart <***@freeuk.com> writes:
[...]
Post by Bart
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow
just wraps as it does with unsigned), some here, whose initials may or
may not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
There is a difference in that unsigned "overflow" might give
(consistent) results you didn't want, but signed overflow has undefined
behavior.

There are perhaps infinitely many ways to get wrong results. It doesn't
necessarily matter whether you get those wrong answers via code that has
well defined behavior or code that has unspecified or undefined
behavior.

The standard had to say *something* about the behavior of arithmetic
operations that yield results outside the range of the relevant type.
It happens to say that the behavior is undefined for signed integer
types and well defined for unsigned integer types. It is to a large
extent an arbitrary choice (though there are historical reasons for it),
but it means that programmers are free to take advantage of things like
`-1u`. It also means they can be bitten by that behavior if they're not
careful.

Incidentally, what the standard says is that unsigned operations do not
overflow at all. It could equivalently have said that they do overflow
and the behavior of overflow is well defined.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
James Kuyper
2024-08-02 19:21:09 UTC
Permalink
Post by Keith Thompson
[...]
Post by Bart
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow
just wraps as it does with unsigned), some here, whose initials may or
may not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
There is a difference in that unsigned "overflow" might give
(consistent) results you didn't want, but signed overflow has undefined
behavior.
When David was expressing the opinion Bart is talking about above, he
was talking about whether it was desirable for unsigned overflow to have
undefined behavior, not about the fact that, in C, it does have
undefined behavior. He argued that signed overflow almost always is the
result of a logical error, and the typical behavior when it does
overflow, is seldom the desired way of handling those cases. Also, he
pointed out that making it undefined behavior enables some convenient
optimizations.

For instance, the expression (num*2)/2 always has the same value as
'num' itself, except when the multiplication overflows. If overflow has
undefined behavior, the cases where it does overflow can be ignored,
permitting (num*2)/2 to be optimized to simply num.
Thiago Adams
2024-08-03 01:03:51 UTC
Permalink
Post by James Kuyper
Post by Keith Thompson
[...]
Post by Bart
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow
just wraps as it does with unsigned), some here, whose initials may or
may not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
There is a difference in that unsigned "overflow" might give
(consistent) results you didn't want, but signed overflow has undefined
behavior.
When David was expressing the opinion Bart is talking about above, he
was talking about whether it was desirable for unsigned overflow to have
undefined behavior, not about the fact that, in C, it does have
undefined behavior. He argued that signed overflow almost always is the
result of a logical error, and the typical behavior when it does
overflow, is seldom the desired way of handling those cases. Also, he
pointed out that making it undefined behavior enables some convenient
optimizations.
For instance, the expression (num*2)/2 always has the same value as
'num' itself, except when the multiplication overflows. If overflow has
undefined behavior, the cases where it does overflow can be ignored,
permitting (num*2)/2 to be optimized to simply num.
I am doing experiments with constexpr. What happens with overflow in
compile time? The answer is not so clear. Sometimes it does not compile
and sometimes it works as in runtime.

constexpr int i = 1073741823;
constexpr int i2 = i*3/3; /*does not compile*/

But this compiles and outputs

#include <stdio.h>

int main()
{
constexpr signed char i = -2;
constexpr unsigned long long u = 0ull+i;
printf("%ull", u); /*prints 4294967294*/
}
Thiago Adams
2024-08-03 01:12:04 UTC
Permalink
Post by Thiago Adams
Post by James Kuyper
Post by Keith Thompson
[...]
Post by Bart
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow
just wraps as it does with unsigned), some here, whose initials may or
may not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
There is a difference in that unsigned "overflow" might give
(consistent) results you didn't want, but signed overflow has undefined
behavior.
When David was expressing the opinion Bart is talking about above, he
was talking about whether it was desirable for unsigned overflow to have
undefined behavior, not about the fact that, in C, it does have
undefined behavior. He argued that signed overflow almost always is the
result of a logical error, and the typical behavior when it does
overflow, is seldom the desired way of handling those cases. Also, he
pointed out that making it undefined behavior enables some convenient
optimizations.
For instance, the expression (num*2)/2 always has the same value as
'num' itself, except when the multiplication overflows. If overflow has
undefined behavior, the cases where it does overflow can be ignored,
permitting (num*2)/2 to be optimized to simply num.
I am doing experiments with constexpr. What happens with overflow in
compile time? The answer is not so clear. Sometimes it does not compile
and sometimes it works as in runtime.
constexpr int i = 1073741823;
constexpr int i2 = i*3/3;  /*does not compile*/
But this compiles and outputs
#include <stdio.h>
int main()
{
    constexpr signed char i = -2;
    constexpr unsigned long long u = 0ull+i;
    printf("%ull", u); /*prints 4294967294*/
}
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
Keith Thompson
2024-08-03 01:31:25 UTC
Permalink
Thiago Adams <***@gmail.com> writes:
[...]
Post by Thiago Adams
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
I'm not sure what you mean by "Compilers extend to work with
unsigned long long.".

Preprocessing expressions (used in #if conditions) are evaluated in
intmax_t or uintmax_t, but that's not the case for constant expressions
in general. For example, in :

static const signed char c = -2;

the initializer is required to be a constant expression. -2 is of type
int, which is implicitly converted to char. There are no constants of
types narrower than int, but you can write:

static const signed char c = (signed char)-2;

where `(signed char)-2` is a constant expression of type signed char.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Thiago Adams
2024-08-03 01:50:11 UTC
Permalink
Post by Keith Thompson
[...]
Post by Thiago Adams
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
I'm not sure what you mean by "Compilers extend to work with
unsigned long long.".
enum {C = 18446744073709551615 -1 };
// ~~~~~~~~~~~~~~~~~~~~
// ^ warning: integer constant is so large that it is unsigned

https://godbolt.org/z/K7hzczETP

This part "shall only have operands that are integer constants"

From C23

"An integer constant expression132) shall have integer type and shall
only have operands that are
integer constants, named and compound literal constants of integer type,
character constants,
sizeof expressions whose results are integer constants, alignof
expressions, and floating, named,
or compound literal constants of arithmetic type that are the immediate
operands of casts. Cast
operators in an integer constant expression shall only convert
arithmetic types to integer types,
except as part of an operand to the typeof operators, sizeof operator,
or alignof operator."
Keith Thompson
2024-08-03 02:29:27 UTC
Permalink
Post by Thiago Adams
Post by Keith Thompson
[...]
Post by Thiago Adams
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
I'm not sure what you mean by "Compilers extend to work with
unsigned long long.".
enum {C = 18446744073709551615 -1 };
// ~~~~~~~~~~~~~~~~~~~~
// ^ warning: integer constant is so large that it is unsigned
https://godbolt.org/z/K7hzczETP
Since 18446744073709551615 is 2**64-1, it's outside the range of any
signed integer type in typical implementations. Since unsuffixed
integer constants are of some signed type (since C99), that constant is
likely to cause problems. You could write 18446744073709551615ull.

That's a rather odd warning. In C90, an unsuffixed integer constant's
type was the first of (int, long int, unsigned long int) in which its
value would fit. In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type.
Since 18446744073709551615 exceeds ULLONG_MAX (assuming 64 bits),
apparently gcc treats it as having an unsigned type as an extension.

(My quick experiment indicates that, at least on my system,
18446744073709551615 is of type __int128 and, bizarrely,
18446744073709551616 is of type int with the value 0. This seems
like a bug.)
Post by Thiago Adams
This part "shall only have operands that are integer constants"
From C23
"An integer constant expression132) shall have integer type and shall
only have operands that are
integer constants, named and compound literal constants of integer
type, character constants,
sizeof expressions whose results are integer constants, alignof
expressions, and floating, named,
or compound literal constants of arithmetic type that are the
immediate operands of casts. Cast
operators in an integer constant expression shall only convert
arithmetic types to integer types,
except as part of an operand to the typeof operators, sizeof operator,
or alignof operator."
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Thiago Adams
2024-08-03 12:36:23 UTC
Permalink
-------- Mensagem encaminhada --------
Assunto: Re: What is your opinion about unsigned int u = -2 ?
Data: Fri, 02 Aug 2024 19:29:27 -0700
De: Keith Thompson <Keith.S.Thompson+***@gmail.com>
Organização: None to speak of
Grupos de notícias: comp.lang.c
Post by Thiago Adams
Post by Keith Thompson
[...]
Post by Thiago Adams
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
I'm not sure what you mean by "Compilers extend to work with
unsigned long long.".
enum {C = 18446744073709551615 -1 };
// ~~~~~~~~~~~~~~~~~~~~
// ^ warning: integer constant is so large that it is unsigned
https://godbolt.org/z/K7hzczETP
Since 18446744073709551615 is 2**64-1, it's outside the range of any
signed integer type in typical implementations. Since unsuffixed
integer constants are of some signed type (since C99), that constant is
likely to cause problems. You could write 18446744073709551615ull.

That's a rather odd warning. In C90, an unsuffixed integer constant's
type was the first of (int, long int, unsigned long int) in which its
value would fit. In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type.
Since 18446744073709551615 exceeds ULLONG_MAX (assuming 64 bits),
apparently gcc treats it as having an unsigned type as an extension.

(My quick experiment indicates that, at least on my system,
18446744073709551615 is of type __int128 and, bizarrely,
18446744073709551616 is of type int with the value 0. This seems
like a bug.)
Post by Thiago Adams
This part "shall only have operands that are integer constants"
From C23
"An integer constant expression132) shall have integer type and shall
only have operands that are
integer constants, named and compound literal constants of integer
type, character constants,
sizeof expressions whose results are integer constants, alignof
expressions, and floating, named,
or compound literal constants of arithmetic type that are the
immediate operands of casts. Cast
operators in an integer constant expression shall only convert
arithmetic types to integer types,
except as part of an operand to the typeof operators, sizeof operator,
or alignof operator."
I realized now I read "integer constants" as "int constants" that is
very diferent! Thanks for the explanation.

----

I checked and 18446744073709551615 is not unsigned long long

static_assert(TYPE_IS(18446744073709551615, unsigned long long));

https://godbolt.org/z/vnzWWxvjr
Post by Thiago Adams
"In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type."
I think this should be reviewed before they constexpr was added in C.

I am fixing my constant expression evaluation in cake and I will share
the code of "old" constant expressions and new constexpr. So constexpr
does not have restriction on signed types only.
Keith Thompson
2024-08-03 23:10:14 UTC
Permalink
You must have done something odd when posting your followup. The
attributions are messed up.
Post by Thiago Adams
-------- Mensagem encaminhada --------
Assunto: Re: What is your opinion about unsigned int u = -2 ?
Data: Fri, 02 Aug 2024 19:29:27 -0700
Organização: None to speak of
Grupos de notícias: comp.lang.c
Post by Thiago Adams
Post by Keith Thompson
[...]
Post by Thiago Adams
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
I'm not sure what you mean by "Compilers extend to work with
unsigned long long.".
enum {C = 18446744073709551615 -1 };
// ~~~~~~~~~~~~~~~~~~~~
// ^ warning: integer constant is so large that it is unsigned
https://godbolt.org/z/K7hzczETP
Since 18446744073709551615 is 2**64-1, it's outside the range of any
signed integer type in typical implementations. Since unsuffixed
integer constants are of some signed type (since C99), that constant is
likely to cause problems. You could write 18446744073709551615ull.
That's a rather odd warning. In C90, an unsuffixed integer constant's
type was the first of (int, long int, unsigned long int) in which its
value would fit. In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type.
Since 18446744073709551615 exceeds ULLONG_MAX (assuming 64 bits),
apparently gcc treats it as having an unsigned type as an extension.
KT> (My quick experiment indicates that, at least on my system,
KT> 18446744073709551615 is of type __int128 and, bizarrely,
KT> 18446744073709551616 is of type int with the value 0. This seems
KT> like a bug.)

[...]

TA> I checked and 18446744073709551615 is not unsigned long long
TA>
TA> static_assert(TYPE_IS(18446744073709551615, unsigned long long));
TA>
TA> https://godbolt.org/z/vnzWWxvjr

Right. As I said, gcc gives it type __int128. This is to be expected
if __int128 is an *extended integer type*, but in fact it's an
extension, and gcc's behavior appears reasonable on that basis. In the
absence of integer types wider than 64 bits, 18446744073709551615
(2**64-1) has no type. If you need that particular value in your code,
I suggest finding a different way to express it. 18446744073709551615u
or 0xffffffffffffffff are possibilities; so is ULLONG_MAX if long long
is 64 bits and that expresses your intent more clearly.
Post by Thiago Adams
Post by Thiago Adams
"In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type."
I think this should be reviewed before they constexpr was added in C.
That's not going to happen, and I'm not sure why it should.
Post by Thiago Adams
I am fixing my constant expression evaluation in cake and I will share
the code of "old" constant expressions and new constexpr. So constexpr
does not have restriction on signed types only.
constexpr has never been restricted to signed types. Unsuffixed integer
constants are. That's why there are suffixed integer constants.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Thiago Adams
2024-08-04 01:46:42 UTC
Permalink
Post by Keith Thompson
You must have done something odd when posting your followup. The
attributions are messed up.
Post by Thiago Adams
-------- Mensagem encaminhada --------
Assunto: Re: What is your opinion about unsigned int u = -2 ?
Data: Fri, 02 Aug 2024 19:29:27 -0700
Organização: None to speak of
Grupos de notícias: comp.lang.c
Post by Thiago Adams
Post by Keith Thompson
[...]
Post by Thiago Adams
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
I'm not sure what you mean by "Compilers extend to work with
unsigned long long.".
enum {C = 18446744073709551615 -1 };
// ~~~~~~~~~~~~~~~~~~~~
// ^ warning: integer constant is so large that it is unsigned
https://godbolt.org/z/K7hzczETP
Since 18446744073709551615 is 2**64-1, it's outside the range of any
signed integer type in typical implementations. Since unsuffixed
integer constants are of some signed type (since C99), that constant is
likely to cause problems. You could write 18446744073709551615ull.
That's a rather odd warning. In C90, an unsuffixed integer constant's
type was the first of (int, long int, unsigned long int) in which its
value would fit. In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type.
Since 18446744073709551615 exceeds ULLONG_MAX (assuming 64 bits),
apparently gcc treats it as having an unsigned type as an extension.
KT> (My quick experiment indicates that, at least on my system,
KT> 18446744073709551615 is of type __int128 and, bizarrely,
KT> 18446744073709551616 is of type int with the value 0. This seems
KT> like a bug.)
[...]
TA> I checked and 18446744073709551615 is not unsigned long long
TA>
TA> static_assert(TYPE_IS(18446744073709551615, unsigned long long));
TA>
TA> https://godbolt.org/z/vnzWWxvjr
Right. As I said, gcc gives it type __int128. This is to be expected
if __int128 is an *extended integer type*, but in fact it's an
extension, and gcc's behavior appears reasonable on that basis. In the
absence of integer types wider than 64 bits, 18446744073709551615
(2**64-1) has no type. If you need that particular value in your code,
I suggest finding a different way to express it. 18446744073709551615u
or 0xffffffffffffffff are possibilities; so is ULLONG_MAX if long long
is 64 bits and that expresses your intent more clearly.
Post by Thiago Adams
Post by Thiago Adams
"In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type."
I think this should be reviewed before they constexpr was added in C.
That's not going to happen, and I'm not sure why it should.
Post by Thiago Adams
I am fixing my constant expression evaluation in cake and I will share
the code of "old" constant expressions and new constexpr. So constexpr
does not have restriction on signed types only.
constexpr has never been restricted to signed types. Unsuffixed integer
constants are. That's why there are suffixed integer constants.
Everything is becoming a little clear in my mind. (Some of my previous
comments are wrong)
Thanks for helping clarify the concepts.
Thiago Adams
2024-08-04 02:04:45 UTC
Permalink
Post by Thiago Adams
You must have done something odd when posting your followup.  The
attributions are messed up.
Post by Thiago Adams
-------- Mensagem encaminhada --------
Assunto: Re: What is your opinion about unsigned int u = -2 ?
Data: Fri, 02 Aug 2024 19:29:27 -0700
Organização: None to speak of
Grupos de notícias: comp.lang.c
Post by Thiago Adams
Post by Keith Thompson
[...]
Post by Thiago Adams
It is interesting to compare constexpr with the existing constant
expression in C that works with integers.Compilers extend to work with
unsigned long long.
constexpr works with the sizes as defined , for instance char.
I'm not sure what you mean by "Compilers extend to work with
unsigned long long.".
enum {C = 18446744073709551615 -1 };
//        ~~~~~~~~~~~~~~~~~~~~
//        ^ warning: integer constant is so large that it is unsigned
https://godbolt.org/z/K7hzczETP
Since 18446744073709551615 is 2**64-1, it's outside the range of any
signed integer type in typical implementations.  Since unsuffixed
integer constants are of some signed type (since C99), that constant is
likely to cause problems.  You could write 18446744073709551615ull.
That's a rather odd warning.  In C90, an unsuffixed integer constant's
type was the first of (int, long int, unsigned long int) in which its
value would fit.  In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type.
Since 18446744073709551615 exceeds ULLONG_MAX (assuming 64 bits),
apparently gcc treats it as having an unsigned type as an extension.
KT> (My quick experiment indicates that, at least on my system,
KT> 18446744073709551615 is of type __int128 and, bizarrely,
KT> 18446744073709551616 is of type int with the value 0.  This seems
KT> like a bug.)
[...]
TA> I checked and 18446744073709551615 is not unsigned long long
TA>
TA> static_assert(TYPE_IS(18446744073709551615, unsigned long long));
TA>
TA> https://godbolt.org/z/vnzWWxvjr
Right.  As I said, gcc gives it type __int128.  This is to be expected
if __int128 is an *extended integer type*, but in fact it's an
extension, and gcc's behavior appears reasonable on that basis.  In the
absence of integer types wider than 64 bits, 18446744073709551615
(2**64-1) has no type.  If you need that particular value in your code,
I suggest finding a different way to express it.  18446744073709551615u
or 0xffffffffffffffff are possibilities; so is ULLONG_MAX if long long
is 64 bits and that expresses your intent more clearly.
Post by Thiago Adams
Post by Thiago Adams
"In C99 and later, an unsuffixed integer constant is of
type int, long int, or long long int, *never* of any unsigned type."
I think this should be reviewed before they constexpr was added in C.
That's not going to happen, and I'm not sure why it should.
Post by Thiago Adams
I am fixing my constant expression evaluation in cake and I will share
the code of "old" constant expressions and new constexpr. So constexpr
does not have restriction on signed types only.
constexpr has never been restricted to signed types.  Unsuffixed integer
constants are.  That's why there are suffixed integer constants.
Everything is becoming a little clear in my mind. (Some of my previous
comments are wrong)
Thanks for helping clarify the concepts.
This will be my test case in cake. In brief, number without prefix
should be signed integers, int when possible.

If int is too small then it is long (if long is bigger than int)
otherwise long long.

If long long is no enough then , we don´t respect what standard that
says that should be signed integer and cakes use unsigned + warning just
like clang. (GCC uses int128)

Cake simulates gcc on linux and msvc on windows, so long is 64 on linux
and 32 on windows.


#include <limits.h>

#define TYPE_IS(E, T) _Generic(E, T: true, default: false)

// less <= than i32 max
static_assert(TYPE_IS(1, int));

// max i32
static_assert(TYPE_IS(2147483647, int));
#if LONG_MAX > INT_MAX
// max i32 + 1
static_assert(TYPE_IS(2147483648, long));

// maximum i64
static_assert(TYPE_IS(9223372036854775807, long));

// maximum i64 + 1, no choice but use unsigned and warning
static_assert(TYPE_IS(9223372036854775808, unsigned long long));
#else
// max i32 + 1
static_assert(TYPE_IS(2147483648, long long));

// maximum i64
static_assert(TYPE_IS(9223372036854775807, long long));

// maximum i64 + 1, no choice but use unsigned and warning
static_assert(TYPE_IS(9223372036854775808, unsigned long long));
#endif

int main() {}
Keith Thompson
2024-08-03 01:25:30 UTC
Permalink
Thiago Adams <***@gmail.com> writes:
[...]
Post by Thiago Adams
I am doing experiments with constexpr. What happens with overflow in
compile time? The answer is not so clear. Sometimes it does not
compile and sometimes it works as in runtime.
Your first example fails due to signed integer overflow. Your second
example is well defined because there is no such thing as unsigned
integer overflow.
Post by Thiago Adams
constexpr int i = 1073741823;
constexpr int i2 = i*3/3; /*does not compile*/
The `constexpr` means that the initializer must be a constant
expression. `i*3/3` is not a constant expression, because `i*3` is not
a constant expression, because its result is not in the range of
representable values for its type, violating the constraint in N3220
6.6p4. (Assuming 32-bit int; if int is wider than 32 bits there's no
overflow and i2 is 1073741823 .)
Post by Thiago Adams
But this compiles and outputs
#include <stdio.h>
int main()
{
constexpr signed char i = -2;
constexpr unsigned long long u = 0ull+i;
printf("%ull", u); /*prints 4294967294*/
}
No, the printf has undefined behavior; it prints "4294967294ll" on my
system. The format for unsigned long long is "%llu", not "%ull". You
have a "%u" for unsigned int followed by a literal "ll". It's probably
printing the low-order 32 bits of the 64-bit value. (You should also
have a newline at the end of the output.)

Fixing that problem :

#include <stdio.h>
int main() {
constexpr signed char i = -2;
constexpr unsigned long long u = 0ull+i;
printf("%llu\n", u); /*prints 4294967294*/
}

The output is "18446744073709551614" (assuming unsigned long long is 64
bits, which it is in every implementation I'm aware of).

There's no overflow. `0ull` is of type `unsigned long`, value
zero. `i` is of type `signed char`, value -2. For the addition, the
usual arithmetic conversions cause the right operand to be converted
from signed char to unsigned long long, yielding ULLONG_MAX-2,
or 18446744073709551614, or 0xfffffffffffffffe .
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Thiago Adams
2024-08-03 01:44:31 UTC
Permalink
Post by Keith Thompson
[...]
Post by Thiago Adams
I am doing experiments with constexpr. What happens with overflow in
compile time? The answer is not so clear. Sometimes it does not
compile and sometimes it works as in runtime.
Your first example fails due to signed integer overflow. Your second
example is well defined because there is no such thing as unsigned
integer overflow.
I need a new name other than overflow.

The expression

ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX

has the result 1 if calculated with infinite precision.

But the calculated value here is 0

#include <stdio.h>

int main()
{
constexpr unsigned long long ULLONG_MAX = 18446744073709551615;
constexpr unsigned long long r =
ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX;
printf("%llu", r);
}
Keith Thompson
2024-08-03 02:04:45 UTC
Permalink
Post by Thiago Adams
Post by Keith Thompson
[...]
Post by Thiago Adams
I am doing experiments with constexpr. What happens with overflow in
compile time? The answer is not so clear. Sometimes it does not
compile and sometimes it works as in runtime.
Your first example fails due to signed integer overflow. Your second
example is well defined because there is no such thing as unsigned
integer overflow.
I need a new name other than overflow.
Wraparound, or unsigned wraparound if you want to be a little more
precise.

C23 adds this entry to the glossary (3.28):

wraparound

the process by which a value is reduced modulo 2^N, where N is the
width of the resulting type

The standard uses a superscript, which I can't easily reproduce here.
I hope it's sufficiently obvious that I'm not using ^ to mean bitwise xor.

N3220 6.2.5p11 (same or similar wording appears in earlier editions) :

A computation involving unsigned operands can never produce an
overflow, because arithmetic for the unsigned type is performed
modulo 2^N.
Post by Thiago Adams
The expression
ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX
has the result 1 if calculated with infinite precision.
But the calculated value here is 0
Right. The subexpression ULLONG_MAX*ULLONG_MAX in infinite precision
would be 340282366920938463426481119284349108225 in infinite precision,
but assuming 64-bit unsigned long long the actual result is that value
modulo 2^64, or 1. Dividing 1 by ULLONG_MAX yields 0 (since integer
division truncates); dividing again by ULLONG_MAX yields 0 again.

The reduction modulo 2^64 happens on each arithmitic operation (here one
multiplication and two divisions), not on the expression as a whole.
Since ULLONG_MAX*ULLONG_MAX is 1 (more precisely 1ull), the expression
ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX
is equivalent to
1ull/ULLONG_MAX/ULLONG_MAX
Post by Thiago Adams
#include <stdio.h>
int main()
{
constexpr unsigned long long ULLONG_MAX = 18446744073709551615;
constexpr unsigned long long r =
ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX;
printf("%llu", r);
}
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Thiago Adams
2024-08-03 02:27:18 UTC
Permalink
Post by Keith Thompson
Post by Thiago Adams
Post by Keith Thompson
[...]
Post by Thiago Adams
I am doing experiments with constexpr. What happens with overflow in
compile time? The answer is not so clear. Sometimes it does not
compile and sometimes it works as in runtime.
Your first example fails due to signed integer overflow. Your second
example is well defined because there is no such thing as unsigned
integer overflow.
I need a new name other than overflow.
Wraparound, or unsigned wraparound if you want to be a little more
precise.
wraparound
the process by which a value is reduced modulo 2^N, where N is the
width of the resulting type
The standard uses a superscript, which I can't easily reproduce here.
I hope it's sufficiently obvious that I'm not using ^ to mean bitwise xor.
A computation involving unsigned operands can never produce an
overflow, because arithmetic for the unsigned type is performed
modulo 2^N.
Post by Thiago Adams
The expression
ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX
has the result 1 if calculated with infinite precision.
But the calculated value here is 0
Right. The subexpression ULLONG_MAX*ULLONG_MAX in infinite precision
would be 340282366920938463426481119284349108225 in infinite precision,
but assuming 64-bit unsigned long long the actual result is that value
modulo 2^64, or 1. Dividing 1 by ULLONG_MAX yields 0 (since integer
division truncates); dividing again by ULLONG_MAX yields 0 again.
The reduction modulo 2^64 happens on each arithmitic operation (here one
multiplication and two divisions), not on the expression as a whole.
Since ULLONG_MAX*ULLONG_MAX is 1 (more precisely 1ull), the expression
ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX
is equivalent to
1ull/ULLONG_MAX/ULLONG_MAX
Post by Thiago Adams
#include <stdio.h>
int main()
{
constexpr unsigned long long ULLONG_MAX = 18446744073709551615;
constexpr unsigned long long r =
ULLONG_MAX*ULLONG_MAX/ULLONG_MAX/ULLONG_MAX;
printf("%llu", r);
}
Thanks.
Here a sample with signed int that has a overflow warning.


#include <stdio.h>

int main()
{
constexpr int a = 2147483647;
constexpr int b = 1;
constexpr int c = a+b;
}

https://godbolt.org/z/ca31r8EMK


I think both cases (overflow and wraparound) should have warnings.

Comparing with __builtin_add_overflow it also reports wraparound.

#include <stdio.h>

int main()
{
unsigned int r;
if (__builtin_add_overflow(0,-1, &r) != 0)
{
printf("fail");
}
}
Keith Thompson
2024-08-03 02:40:32 UTC
Permalink
Thiago Adams <***@gmail.com> writes:
[...]
Post by Thiago Adams
Here a sample with signed int that has a overflow warning.
#include <stdio.h>
int main()
{
constexpr int a = 2147483647;
constexpr int b = 1;
constexpr int c = a+b;
}
https://godbolt.org/z/ca31r8EMK
It's reasonable to warn about a+b, since it has undefined behavior.
In fact gcc warns about the expression a+b, since it has undefined
behavior, and issues a fatal error message about its use in a context
requiring a constant expression, since that's a constraint violation.
Post by Thiago Adams
I think both cases (overflow and wraparound) should have warnings.
You're free to think that, of course, but wraparound behavior is well
defined and unambiguous. I wouldn't mind an *optional* warning, but
plenty of programmers might deliberately write something like

const unsigned int max = -1;

with the reasonable expectation that it will set max to INT_MAX.
Post by Thiago Adams
Comparing with __builtin_add_overflow it also reports wraparound.
#include <stdio.h>
int main()
{
unsigned int r;
if (__builtin_add_overflow(0,-1, &r) != 0)
{
printf("fail");
}
}
Of course __builtin_add_overflow is a non-standard gcc extension. The
documentation says:

-- Built-in Function: bool __builtin_add_overflow (TYPE1 a, TYPE2 b,
TYPE3 *res)
...
These built-in functions promote the first two operands into
infinite precision signed type and perform addition on those
promoted operands. The result is then cast to the type the third
pointer argument points to and stored there. If the stored result
is equal to the infinite precision result, the built-in functions
return 'false', otherwise they return 'true'. As the addition is
performed in infinite signed precision, these built-in functions
have fully defined behavior for all argument values.

It returns true if the result is equal to what would be computed in
infinite signed precision, so it treats both signed overflow and
unsigned wraparound as "overflow". It looks like a useful function, and
if you use it with an unsigned target, it's because you *want* to detect
wraparound.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
David Brown
2024-08-03 17:43:14 UTC
Permalink
Post by Keith Thompson
[...]
Post by Thiago Adams
Here a sample with signed int that has a overflow warning.
#include <stdio.h>
int main()
{
constexpr int a = 2147483647;
constexpr int b = 1;
constexpr int c = a+b;
}
https://godbolt.org/z/ca31r8EMK
It's reasonable to warn about a+b, since it has undefined behavior.
In fact gcc warns about the expression a+b, since it has undefined
behavior, and issues a fatal error message about its use in a context
requiring a constant expression, since that's a constraint violation.
Post by Thiago Adams
I think both cases (overflow and wraparound) should have warnings.
You're free to think that, of course, but wraparound behavior is well
defined and unambiguous. I wouldn't mind an *optional* warning, but
plenty of programmers might deliberately write something like
const unsigned int max = -1;
with the reasonable expectation that it will set max to INT_MAX.
Post by Thiago Adams
Comparing with __builtin_add_overflow it also reports wraparound.
#include <stdio.h>
int main()
{
unsigned int r;
if (__builtin_add_overflow(0,-1, &r) != 0)
{
printf("fail");
}
}
Of course __builtin_add_overflow is a non-standard gcc extension. The
-- Built-in Function: bool __builtin_add_overflow (TYPE1 a, TYPE2 b,
TYPE3 *res)
...
These built-in functions promote the first two operands into
infinite precision signed type and perform addition on those
promoted operands. The result is then cast to the type the third
pointer argument points to and stored there. If the stored result
is equal to the infinite precision result, the built-in functions
return 'false', otherwise they return 'true'. As the addition is
performed in infinite signed precision, these built-in functions
have fully defined behavior for all argument values.
It returns true if the result is equal to what would be computed in
infinite signed precision, so it treats both signed overflow and
unsigned wraparound as "overflow". It looks like a useful function, and
if you use it with an unsigned target, it's because you *want* to detect
wraparound.
C23 provides ckd_add() that is identical to __builtin_add_overflow()
except for the order of the operands.
Thiago Adams
2024-08-03 17:46:00 UTC
Permalink
Post by David Brown
Post by Keith Thompson
[...]
Here a sample with signed int that has a overflow  warning.
#include <stdio.h>
int main()
{
    constexpr int a = 2147483647;
    constexpr int b = 1;
    constexpr int c = a+b;
}
https://godbolt.org/z/ca31r8EMK
It's reasonable to warn about a+b, since it has undefined behavior.
In fact gcc warns about the expression a+b, since it has undefined
behavior, and issues a fatal error message about its use in a context
requiring a constant expression, since that's a constraint violation.
I think both cases (overflow and wraparound) should have warnings.
You're free to think that, of course, but wraparound behavior is well
defined and unambiguous.  I wouldn't mind an *optional* warning, but
plenty of programmers might deliberately write something like
     const unsigned int max = -1;
with the reasonable expectation that it will set max to INT_MAX.
Comparing with __builtin_add_overflow it also reports wraparound.
#include <stdio.h>
int main()
{
    unsigned int r;
    if (__builtin_add_overflow(0,-1, &r) != 0)
    {
      printf("fail");
    }
}
Of course __builtin_add_overflow is a non-standard gcc extension.  The
  -- Built-in Function: bool __builtin_add_overflow (TYPE1 a, TYPE2 b,
           TYPE3 *res)
  ...
      These built-in functions promote the first two operands into
      infinite precision signed type and perform addition on those
      promoted operands.  The result is then cast to the type the third
      pointer argument points to and stored there.  If the stored result
      is equal to the infinite precision result, the built-in functions
      return 'false', otherwise they return 'true'.  As the addition is
      performed in infinite signed precision, these built-in functions
      have fully defined behavior for all argument values.
It returns true if the result is equal to what would be computed in
infinite signed precision, so it treats both signed overflow and
unsigned wraparound as "overflow".  It looks like a useful function, and
if you use it with an unsigned target, it's because you *want* to detect
wraparound.
C23 provides ckd_add() that is identical to __builtin_add_overflow()
except for the order of the operands.
I used __builtin_add_overflow because i dint know if the new header was
available.
I am also implementing ckd_add etc for my own use in msvc.
Keith Thompson
2024-08-03 23:52:00 UTC
Permalink
Thiago Adams <***@gmail.com> writes:
[...]
Post by Thiago Adams
I used __builtin_add_overflow because i dint know if the new header
was available.
I am also implementing ckd_add etc for my own use in msvc.
gcc 14.1.0 and llvm/clang 18.1.0 both provide the <stdckdint.h> header.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
d***@comcast.net
2024-08-25 20:52:30 UTC
Permalink
On Fri, 02 Aug 2024 19:40:32 -0700, Keith Thompson
Post by Keith Thompson
Post by Thiago Adams
I think both cases (overflow and wraparound) should have warnings.
You're free to think that, of course, but wraparound behavior is well
defined and unambiguous. I wouldn't mind an *optional* warning, but
plenty of programmers might deliberately write something like
const unsigned int max = -1;
with the reasonable expectation that it will set max to INT_MAX.
(cough) UINT_MAX (cough)
Keith Thompson
2024-08-25 21:28:34 UTC
Permalink
Post by d***@comcast.net
On Fri, 02 Aug 2024 19:40:32 -0700, Keith Thompson
Post by Keith Thompson
Post by Thiago Adams
I think both cases (overflow and wraparound) should have warnings.
You're free to think that, of course, but wraparound behavior is well
defined and unambiguous. I wouldn't mind an *optional* warning, but
plenty of programmers might deliberately write something like
const unsigned int max = -1;
with the reasonable expectation that it will set max to INT_MAX.
(cough) UINT_MAX (cough)
Quite right, thanks.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
David Brown
2024-08-03 17:34:54 UTC
Permalink
Post by James Kuyper
Post by Keith Thompson
[...]
Post by Bart
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow
just wraps as it does with unsigned), some here, whose initials may or
may not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
There is a difference in that unsigned "overflow" might give
(consistent) results you didn't want, but signed overflow has undefined
behavior.
When David was expressing the opinion Bart is talking about above, he
was talking about whether it was desirable for unsigned overflow to have
undefined behavior, not about the fact that, in C, it does have
undefined behavior. He argued that signed overflow almost always is the
result of a logical error, and the typical behavior when it does
overflow, is seldom the desired way of handling those cases. Also, he
pointed out that making it undefined behavior enables some convenient
optimizations.
For instance, the expression (num*2)/2 always has the same value as
'num' itself, except when the multiplication overflows. If overflow has
undefined behavior, the cases where it does overflow can be ignored,
permitting (num*2)/2 to be optimized to simply num.
Yes, that is all correct.

IMHO - and I realise it is an opinion not shared by everyone - I think
it would be best for a language of the level and aims of C to leave all
integer overflows as undefined behaviour. It is helpful for
implementations to have debug or sanitizing modes that generate run-time
checks and run-time errors for overflows, to aid in debugging. (clang
and gcc both provide such features - no doubt other compilers do too.)

And you do need additional features to get modulo effects on the
occasions when these are needed. I think you could come a long way with
the ckd_ macros from C23 :

#include <stdckdint.h>
bool ckd_add(type1 *result, type2 a, type3 b);
bool ckd_sub(type1 *result, type2 a, type3 b);
bool ckd_mul(type1 *result, type2 a, type3 b);


(Of course, C is the way it is, for many reasons - and I am not
suggesting it be changed!)
Thiago Adams
2024-08-03 17:51:23 UTC
Permalink
Post by David Brown
Post by James Kuyper
Post by Keith Thompson
[...]
Post by Bart
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow
just wraps as it does with unsigned), some here, whose initials may or
may not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
There is a difference in that unsigned "overflow" might give
(consistent) results you didn't want, but signed overflow has undefined
behavior.
When David was expressing the opinion Bart is talking about above, he
was talking about whether it was desirable for unsigned overflow to have
undefined behavior, not about the fact that, in C, it does have
undefined behavior. He argued that signed overflow almost always is the
result of a logical error, and the typical behavior when it does
overflow, is seldom the desired way of handling those cases. Also, he
pointed out that making it undefined behavior enables some convenient
optimizations.
For instance, the expression (num*2)/2 always has the same value as
'num' itself, except when the multiplication overflows. If overflow has
undefined behavior, the cases where it does overflow can be ignored,
permitting (num*2)/2 to be optimized to simply num.
Yes, that is all correct.
IMHO - and I realise it is an opinion not shared by everyone - I think
it would be best for a language of the level and aims of C to leave all
integer overflows as undefined behaviour.  It is helpful for
implementations to have debug or sanitizing modes that generate run-time
checks and run-time errors for overflows, to aid in debugging.  (clang
and gcc both provide such features - no doubt other compilers do too.)
And you do need additional features to get modulo effects on the
occasions when these are needed.  I think you could come a long way with
#include <stdckdint.h>
bool ckd_add(type1 *result, type2 a, type3 b);
bool ckd_sub(type1 *result, type2 a, type3 b);
bool ckd_mul(type1 *result, type2 a, type3 b);
(Of course, C is the way it is, for many reasons - and I am not
suggesting it be changed!)
Consider this sample

#include <stdio.h>
#define MINUSONE -1

int main(){

unsigned int a = 0;
unsigned long long b = 0;

unsigned long long r ;
r = a + MINUSONE; /*4294967295*/
printf("%llu\n", r);
r = b + MINUSONE;
printf("%llu\n", r); /*18446744073709551615*/
}

We have a invisible conversion from -1 to unsigned int or unsigned
long long. The value used at the expression is not -1.

I think I will add a warning in cake for this situation. A cast can
remove the warning.
For instance

r = a + ((unsigned long)MINUSONE);
Thiago Adams
2024-08-03 18:30:05 UTC
Permalink
More samples..
max uint64 + 1 is signed 128 bits in gcc and unsigned long long in clang

#ifdef __clang__
static_assert(TYPE_IS(9223372036854775808, unsigned long long ));
#else
static_assert(TYPE_IS(9223372036854775808, __int128));
#endif

https://godbolt.org/z/hveY44ov4
Keith Thompson
2024-08-03 23:46:50 UTC
Permalink
Post by Thiago Adams
More samples..
max uint64 + 1 is signed 128 bits in gcc and unsigned long long in clang
#ifdef __clang__
static_assert(TYPE_IS(9223372036854775808, unsigned long long ));
#else
static_assert(TYPE_IS(9223372036854775808, __int128));
#endif
https://godbolt.org/z/hveY44ov4
9223372036854775808 is 2**63, or INT64_MAX-1, not UINT64_MAX-1.

I suggest not doing *anything* that relies on the value of any decimal
constant greater than 2**63-1, unless you absolutely have to. (Since
you're working on a C23 transpiler, the latter probably does apply to
you.) 9223372036854775808 has no type unless there is some (possibly
extended) integer type wider than 64 bits, which is not the case for
either gcc or clang. Both compilers have made different decisions (and
I personally don't agree with either of them).

Incidentally, both gcc and clang reject 9223372036854775808 with a fatal
error with "-std=c23 -pedantic-errors".
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Tim Rentsch
2024-08-11 20:57:07 UTC
Permalink
Post by Keith Thompson
Post by Thiago Adams
More samples..
max uint64 + 1 is signed 128 bits in gcc and unsigned long long in clang
#ifdef __clang__
static_assert(TYPE_IS(9223372036854775808, unsigned long long ));
#else
static_assert(TYPE_IS(9223372036854775808, __int128));
#endif
https://godbolt.org/z/hveY44ov4
9223372036854775808 is 2**63, or INT64_MAX-1, not UINT64_MAX-1.
Of course you meant INT64_MAX + 1 (and presumably UINT64_MAX + 1).
Keith Thompson
2024-08-11 21:53:50 UTC
Permalink
Post by Tim Rentsch
Post by Keith Thompson
Post by Thiago Adams
More samples..
max uint64 + 1 is signed 128 bits in gcc and unsigned long long in clang
#ifdef __clang__
static_assert(TYPE_IS(9223372036854775808, unsigned long long ));
#else
static_assert(TYPE_IS(9223372036854775808, __int128));
#endif
https://godbolt.org/z/hveY44ov4
9223372036854775808 is 2**63, or INT64_MAX-1, not UINT64_MAX-1.
Of course you meant INT64_MAX + 1 (and presumably UINT64_MAX + 1).
Yes, thanks.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Keith Thompson
2024-08-03 23:19:45 UTC
Permalink
Thiago Adams <***@gmail.com> writes:
[...]
Post by Thiago Adams
#define MINUSONE -1
Needs parentheses:

#define MINUSONE (-1)
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Bonita Montero
2024-08-09 18:10:09 UTC
Permalink
Post by Keith Thompson
Incidentally, what the standard says is that unsigned operations do not
overflow at all. It could equivalently have said that they do overflow
and the behavior of overflow is well defined.
No, it says that signed operation don't overflow.
Unsigned operations have a specified wrap-around.
David Brown
2024-08-03 17:17:19 UTC
Permalink
Post by Bart
Post by Kenny McCormack
...
Post by Thiago Adams
So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
I think it is because it wants to (still) support representations other
than 2s complement.  I think POSIX requires 2s complement, and I
expect the
C standard to (eventually) follow suit.
C23 assumes 2s complement. However overflow on signed integers will
still be considered UB: too many compilers depend on it.
But even if well-defined (eg. that UB was removed so that overflow just
wraps as it does with unsigned), some here, whose initials may or may
not be DB, consider such overflow Wrong and a bug in a program.
However they don't consider overflow of unsigned values wrong at all,
simply because C allows that behaviour.
But I don't get it. If my calculation gives the wrong results because
I've chosen a u32 type instead of u64, that's just as much a bug as
using i32 instead of i64.
You don't get it because you never pay attention to what I write - you'd
rather jump to conclusions without reading.

In almost all cases, wrapping signed integer overflow would give the
incorrect (in terms of what makes sense for the code) result even if it
is fully defined by the compiler.

In almost all cases, wrapping unsigned integer overflow would give the
incorrect (in terms of what makes sense for the code) result regardless
of the fact that C gives a definition for the behaviour.

There are, of course, exceptions - situations where you really do want
modulo arithmetic. But in most cases you want integer types that model
real mathematical integers to the extent possible with efficient
practical implementations. Using 16-bit int because the numbers are
easier, if you have 65535 apples in a pile and you add an apple, you do
not expect to have 0 apples. That would be almost as silly as having
32767 apples, adding one more, and having -32768 apples.

Outside of the occasional rare case, code that relies on overflow
behaviour of integers - signed or unsigned, defined by the
language/implementation or not - is logically incorrect code.

That is in addition to some cases (signed integer overflow for C) being
undefined in the language.

It's helpful that the language provides a way to get modulo arithmetic
for the cases that need it. But just because C defines the behaviour of
unsigned integer overflow, does not make it makes sense in code.
Defined incorrect results are just as wrong as undefined incorrect results.
Bonita Montero
2024-08-04 18:29:21 UTC
Permalink
Post by Kenny McCormack
...
Post by Thiago Adams
So it seams that anything is ok for unsigned but not for signed.
Maybe because all computer gave same answer for unsigned but this is not
true for signed?
I think it is because it wants to (still) support representations other
than 2s complement. I think POSIX requires 2s complement, and I expect the
C standard to (eventually) follow suit.
Since the mid of the 70s all new machines work all with 2s complement.
There will be never computers with different notations since the 2s
complement makes the circuit design easier.
Lawrence D'Oliveiro
2024-08-07 23:48:55 UTC
Permalink
Post by Bonita Montero
Since the mid of the 70s all new machines work all with 2s complement.
There will be never computers with different notations since the 2s
complement makes the circuit design easier.
This may be hard to believe, but I think in the early days 2s-complement
arithmetic was seen as something exotic, like advanced mathematics or
something. To some, sign-magnitude seemed more “intuitive”.

As for ones-complement ... I don’t know to explain that.
David Brown
2024-08-08 17:47:14 UTC
Permalink
Post by Lawrence D'Oliveiro
Post by Bonita Montero
Since the mid of the 70s all new machines work all with 2s complement.
There will be never computers with different notations since the 2s
complement makes the circuit design easier.
This may be hard to believe, but I think in the early days 2s-complement
arithmetic was seen as something exotic, like advanced mathematics or
something. To some, sign-magnitude seemed more “intuitive”.
As for ones-complement ... I don’t know to explain that.
Think about negating a value. For two's complement, that means
inverting each bit and then adding 1. For sign-magnitude, you invert
the sign bit. For ones' complement, you invert each bit.
Bonita Montero
2024-08-09 18:08:31 UTC
Permalink
Think about negating a value.  For two's complement, that means
inverting each bit and then adding 1.  For sign-magnitude, you
invert the sign bit. For ones' complement, you invert each bit.
But with one's complement you have the same circuits for ading
and substracting like with unsigned values.
David Brown
2024-08-09 18:19:59 UTC
Permalink
Post by Bonita Montero
Think about negating a value.  For two's complement, that means
inverting each bit and then adding 1.  For sign-magnitude, you
invert  the sign bit. For ones' complement, you invert each bit.
But with one's complement you have the same circuits for ading
and substracting like with unsigned values.
If you are trying to say that for two's complement, "a + b" and "a - b"
use the same circuits regardless of whether you are doing signed or
unsigned arithmetic, then that is correct. It is one of the reasons why
two's complement became the dominant format.
Bonita Montero
2024-08-09 19:18:01 UTC
Permalink
Post by David Brown
Post by Bonita Montero
Think about negating a value.  For two's complement, that means
inverting each bit and then adding 1.  For sign-magnitude, you
invert  the sign bit. For ones' complement, you invert each bit.
But with one's complement you have the same circuits for ading
and substracting like with unsigned values.
If you are trying to say that for two's complement, "a + b" and "a - b"
use the same circuits regardless of whether you are doing signed or
unsigned arithmetic, then that is correct.  It is one of the reasons why
two's complement became the dominant format.
... and you've got one more value since there's no negative and
positive zero.
David Brown
2024-08-09 19:40:06 UTC
Permalink
Post by Bonita Montero
Post by David Brown
Post by Bonita Montero
Think about negating a value.  For two's complement, that means
inverting each bit and then adding 1.  For sign-magnitude, you
invert  the sign bit. For ones' complement, you invert each bit.
But with one's complement you have the same circuits for ading
and substracting like with unsigned values.
If you are trying to say that for two's complement, "a + b" and "a -
b" use the same circuits regardless of whether you are doing signed or
unsigned arithmetic, then that is correct.  It is one of the reasons
why two's complement became the dominant format.
... and you've got one more value since there's no negative and
positive zero.
It's rarely significant that there's an extra value for signed integers
- it's just one more out of 4 billion for 32-bit ints. (It's vital that
you have all possible patterns for unsigned data.)

It is, I would say, nice that you don't have two different zero
representations.

But sometimes it would be nice if INT_MIN were equal to -INT_MAX, and
that there was an extra pattern available for an invalid or special
value. It could provide a very compact representation for a type that
is either a valid integer or a "No result" indicator, much like a NaN
for floating point. Of course, it's possible to use two's complement
and reserve 0x8000'0000 (or whatever number of zeros are needed) for the
purpose.
Richard Damon
2024-08-10 01:16:19 UTC
Permalink
Post by David Brown
Post by Bonita Montero
Think about negating a value.  For two's complement, that means
inverting each bit and then adding 1.  For sign-magnitude, you
invert  the sign bit. For ones' complement, you invert each bit.
But with one's complement you have the same circuits for ading
and substracting like with unsigned values.
If you are trying to say that for two's complement, "a + b" and "a - b"
use the same circuits regardless of whether you are doing signed or
unsigned arithmetic, then that is correct.  It is one of the reasons why
two's complement became the dominant format.
No, a two's complement subtractor needs to invert the second operand,
and inject a carry into the bottom bit. A one's complement subtractor
just need to invert the second operand.

The fact that you want that carry input in the logic anyway for
add-with-carry to make bigger additions, says that this doesn't really
cost anything.
Bonita Montero
2024-08-04 16:16:02 UTC
Permalink
Post by Blue-Maned_Hawk
Post by Thiago Adams
unsigned int u1 = -1;
Generally -1 is used to get the maximum value.
Is this guaranteed to work?
Whether or not it is, i would prefer to use the UINT_MAX macro to make the
code clearer.
Post by Thiago Adams
How about this one?
unsigned int u2 = -2;
Does it makes sense? Maybe a warning here?
I cannot think of any situations where that would make sense, but i also
cannot guarantee that there are not any.
With ...
u &= -2;
... you reset the lowest bit.

Similar with ...
u &= -256;
... you reset the lowest eight bits.
Loading...