I'm implementing signed and unsigned integers in my language. They are represented in C as signed long
and unsigned long
respectively.
struct value {
enum type type; / SINT, UINT, ... */
union {
signed long sint;
unsigned long uint;
/* more types... */
} as;
};
Basic arithmetic operations such as +
and -
may operate on integers of mixed signedness depending on the types of the operands:
struct value accumulator, argument;
/* ... */
/* accumulator.type == UINT && argument.type == SINT */
accumulator.as.unsigned_integer += argument.as.signed_integer;
This code causes the C compiler to emit warnings like:
warning: implicit conversion changes signedness: 'long' to 'unsigned long' [-Wsign-conversion]
Which makes me worried about the correctness of this implementation.
I considered simply making it an error to operate on values of mixed signedness. I feel this would make the language too annoying to use though since the programmer would be required to defensively convert the integers every single time.
I currently handle overflows by letting the values wrap around. I assume two's complement integer representation and compile with -fwrapv
. I plan to implement overflow checks with transparent promotion to arbitrary precision integers in the future. I want to make integers work seamlessly just like they do in Python and Ruby.
So is there a correct way to implement this? Any pitfalls and footguns I should be considering? How do other languages do it?
-fwrapv
but I plan to implement overflow checks with transparent promotion to arbitrary precision integers in the future, like in Python and Ruby. $\endgroup$Small_Int
on this page). $\endgroup$x.uint
rather thanx.as.uint
. $\endgroup$as
idiom I've come to prefer it. Short, correct, reads like natural english, distinguishes union fields from regular types and makes the type punning explicit. $\endgroup$