2

In software engineering, often developers will utilize three different states of a signed integer, as a trilean:

This tends to be quite typical:

-1 - Represents something akin to a null pointer, as in it has no initiation or use or meaning
0  - Represents zero or false
1+ - Represents a valid value

In this case of QIODevice::waitForBytesWritten():

-1 - Wait Forever
0  - Do not wait at all
1+ - Wait for n

Given this, I have wondered about the viability and usefulness of an integral type whose bottom range was -1, and what value that would represent. I would imagine it would be something like,

unsigned char quint8(0)
h    :: 0
0000 0000

unsigned char quint8(1)
h    :: 1
0000 0001

unsigned char quint8(2)
h    :: 2
0000 0010

unsigned char quint8(253)
h    :: 253
1111 1101

unsigned char quint8(254)
h    :: 254
1111 1110

unsigned char quint8(255)
h    :: -1 // The change being here
1111 1111

unsigned char quint8(256)
h    :: 0
0000 0000

1: Does an integral type like this already exist somewhere?

Has it been implemented? Initially I had thought that ssize_t had operated something like this, but I recall being mistaken on that presumption.

2: What are its advantages?

The obvious advantages would be a higher cap, at 254 as opposed to 128 The other main advantage I would see, would be a greater ability to be explicit when writing code. If a parameter for example accepts an integral type like this, then that tells me before hand that -1 has a particular meaning.

I also imagine that there would be some advantages as far as bitwise operators were concerned, but I have not ruminated enough to think of any.

3: What are its disadvantages?

4: Is it simply too complex or difficult or inefficient to implement, and is that fundamentally why it does not exist as a common type?

5
  • 2
    It is not uncommon that values outside the normal range indicate errors (because where else can you put them). This will however mean that you always, ALWAYS have duality about whether it is a valid value or an error value and need to check each and every time you want to use it and the amount of code to do so counters the space saved by putting two values into one. Any reason you cannot split this into a pair or an object? Feb 26, 2021 at 18:08
  • I'm having trouble understanding what your question is. Note the singular: question, not questions. Questions on Stack Exchange must be narrowly focused on a single question for which a single, canonical, objectively correct answer exists. Feb 26, 2021 at 20:33
  • I believe ADA allows the programmer to specify a valid range for the ints it stores. In Computer Science these are called Bounded/Range int types (for X..Y) or offset int types for a 0..2^(8*x)+-Y allowing for example to store -1000..-745 in a byte offset by -1000. As for why these are not common? Silicon is the fastest, and most silicon implement unsigned, two's complement, and/or one's complement. Everything else is slower because its done in software.
    – Kain0_0
    Feb 27, 2021 at 1:45
  • It is not appropriate to use code formatting for content that is not code. Can you fix it? Feb 27, 2021 at 2:05
  • 1
    No special type is needed. You can represent a number -1..254 with an uint8_t. With the modular arithmetic on unsigned numbers, 255 = -1.
    – amon
    Feb 27, 2021 at 8:57

6 Answers 6

8

You're taking the use specifically of -1 too literally here. APIs like this didn't really pick -1; they're either picking any negative number, or they're picking an integer with all of its bits set (which in two's complement is -1).

So if that's what you want, simply take an unsigned number and use the maximum value as the equivalent of the "-1" condition. There isn't any need for a specialized type here.

It should also be noted that waitForBytesWritten only has two states. 0 isn't special; it's simply waiting for 0 time units.

9
  • 1
    In this example, most would use 255 for the special case "wait forever"
    – user949300
    Feb 26, 2021 at 19:14
  • 3
    @Akiva: "if during debugging, I happened to see for example the number 4294967295" But that's what -1 is (when interpreted as an unsigned number). If you're going to send values with such wildly divergent meaning through the same interface, then you're going to have to deal with the fact that you're going to have to pay attention to those wildly divergent meanings. Feb 26, 2021 at 19:32
  • 5
    @Akiva: More to the point, I don't understand why it's OK for a signed number with all the bits set to have a special meaning, but somehow the exact same bitpattern in the unsigned case is ambiguous. Either way, you have to remember that a specific number has a specific meaning. Feb 26, 2021 at 19:33
  • 2
    @NicolBolas Its not just a special meaning, its a easily recognizable value. 4294967295 may or may not have a special meaning, because in 64 bits, it would just be another value. It does not have a recognizable value either except in binary. -1 on the other hand inherently has a special meaning, because it is the first iteration of a negative. It also has a recognizable value.
    – Anon
    Feb 26, 2021 at 20:06
  • 4
    @Akiva, depends who is doing the "recognizing". In any source code you would be comparing to 0xFFFFFFFF which is easily recognizable.
    – user949300
    Feb 26, 2021 at 20:57
3

I think what you are asking are valid questions - a little bit broad, but these questions are hard to separate from each other.

1: Does an integral type like this already exist somewhere? Has it been implemented?

I have never heard of a programming language (or a standard lib in a major programming language) which provides this as a standard number type - and I assume that is what you were asking about here. However, most certainly some programmer somewhere on the world over last decades in one of the gazillions of programs which were written had the requirement to pack values of that range into a single byte.

Hence actually I am convinced the chances are very high that someone in programming history must have created such a type. But get me right - the chances are also high other ranges like [-2 ... 253] or [-42 ... 213] in a single byte have been required and implemented somewhere by someone. But having a special, general datatype in a language or library for each possible range does not really look sensible to me.

2: What are its advantages?

The one and only "advantage" is that is you get a type for exactly that range from -1 to 254 which requires not more than a single byte. No more, no less. One gets a "wrap around" for overflowing exactly from 254 to -1. And since you asked - I fail to see any advantage for bitwise operations - bitwise operations do usually not interact with the number range into which a byte is mapped.

3: What are its disadvantages?

The main disadvantage I see is that standard CPU hardware does not support it directly, and I would not expect this to happen in the near future - the real-world use cases for this very special type are most probably too few to justfify the effort of implementing it in hardware.

4: Is it simply too complex or difficult or inefficient to implement, and is that fundamentally why it does not exist as a common type?

Not every special kind of numbertype, even when it would be simple and efficient to implement, finds its way into a standard lib, a programming language, or (even less likely) into the CPU hardware. For this, the benefits over using an existing type, or over implementing a custom type, must clearly be apparent, and the new number type's real-world use cases must occur with a certain frequency.

To make the suggested type necessary, the following combination of four requirements must all come together:

  • one needs a type for values in a range from [-1,x]

  • there must exist a real reason why using a type with more than one byte is not acceptable

  • x must be bigger than 127 (otherwise a signed byte type would be sufficient)

  • converting from a byte representation into a type with a larger range, when the potentially signed values are required, and vice versa, when the byte is required again, must be too cumbersome

I would not expect this combination of requirements to occur in the real world very frequently.

So in short, I think that is what is probably missing here: enough real world applications which could so much benefit from this enough to justify the effort for providing this very specialized type as part of some standard library or language.

2

We have types that solve this problem. They don’t have the long history of use that the -1 flag or the null pointer have but they work if you know how to use them.

Want to show an unsigned integer isn’t initialized yet? Want to use -1 to mean -1? Fine. Shove the int into a collection when it exists! Before that you have an empty collection.

For those that can’t stand the idea of a collection that only ever has 0 or 1 elements in it, they created the maybe monad. It doesn’t really add that much, but it might make you feel better since it can’t have 2 or more elements. 0 or 1 is it.

This is a newer idea, but that doesn’t make it a bad idea.

2

Biased integral types are sometimes used in embedded sensor applications with limited storage and sensor protocols with limited bandwidth, where a number within a certain range needs to be transmitted or stored as compact as possible.

For example, for a medical device that measures a patient's temperature, it doesn't make sense to use a full octet going from -128 to +127 or 0 to 255. Anything below 10°C and above 50°C means the patient is dead anyway. So, we could devise a scale that is in steps of 1/6 degrees and measures from 60 (meaning 10°C) to 315 (meaning 52.5°C). This allows us to store and send the body temperature to a reasonable degree (heh!) of accuracy in just one single octet.

Without this biasing, a lot of the 256 possible values of an octet would have been wasted and without the scaling, the precision would have been much worse.

Remember, an octet can store 256 distinct "things", but how you interpret those 256 "things" is up to you.

A different avenue are subrange types as they exist in Pascal and many of its descendants like Modula-2, Modula-3, Ada, or Nim.

A subrange type allows the programmer to declare a new type as a subrange of an existing type.

1

Of course the Pascal language did it, in the subrange type: var x: -1..254;

Notice also that the pure language did not explicitly specify how the variable would be implemented or stored: it is an abstract specification. It simply stated that "the acceptable range of values is in this inclusive range." The language also provided the packed [record] specification to vaguely request that "the storage should require the minimum number of bytes."

1

Jörg W Mittag and Mike Robinson's answers mention languages with numeric ranges types. I will focus on the aspect of encoding a result or error in the type system.

In languages with basic type systems like C sentinel values such as -1 are the common idiom for signaling errors. High-level languages with sufficiently advanced type systems generally take another approach by using either an optional or result type.

Result types are discriminated union types which encloses either a result or error. They generally support pattern matching to apply different behaviour based on the underlying type. This eliminates a requirement for compatible result and error types, making it harder to accidentally misuse one as the other.

An example of this is Rust's std::result. A Result<T, E> contains either a success value of type T or an error of type E.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.