The origin of zero-based array indexing
exple.tive.org
exple.tive.org
* Zero is the first unsigned integer. If you start at 1, you're wasting one
* p[0] == *p, and it makes no difference whether pointer arithmetic had been invented, because pointers are just memory addresses, and that's what the cpu works with
The best reason for one-indexing:* Some people think it's less confusing
>> Over and over again tools meant to make it easier for humans to approach big problems are discarded in favor of tools that are easier to teach to computers, and that decision is described as an inevitability.
That's exactly how it should be. What's easier for humans is subjective. What's easier for computers is objective, and going with the grain of the machine tends to yield solutions that work, whereas going against the grain tends to cause over-complicated and buggy implementations. That's the true take-home of "Worse is Better." The Unix approach to syscalls worked, and Unix is alive and well. The MIT approach was quite possibly impossible to implement, and the system they were building is extinct. If you want to use a computer well, you have to learn a little about computers and set aside your preconceived ideas. What's so unreasonable about that?
It's a trade-off, if we were bending entirely to the computer's will we'd all be programming machine code, whereas there's overhead in using more natural language. Honestly I've bought this 0-indexing is the Right Way for a long time but the more I use R the more a 1-index seems to make sense for high-level languages.
No ones are involved either. I don't have a "first" hand and when you request a first orange, you're just asking for the head of the ordered set.
Your orange takes the zeroth index becoming the first orange: http://i.imm.io/1lWRO.png
People understand the concept of zero perfectly. They know the day doesn't begin at 1am, they know babies aren't born 1 years old. This isn't a computer-specific concept in any way.
This is true, and an interesting point, but also nobody describes a baby as being zero years old, or the time as being zero-o'clock. People avoid zero in these situations and find alternatives - i.e. 3-months or 12.30am.
Personally I see both sides but the point is that whenever you make something divisible, the first unit is 1 and that's what people call it.
Alternatively we could just switch 12PM and 12AM, but that would mess with midnight == start of the day.
Unfortunately I think I'm doomed to suffer through this inconsistency, since it doesn't seem to bother anyone else.
I guess for me personally the perfect solution would be a 12-hour clock that operates on the same principles as the European 24-hour (which I think is the same as the military clock?). So 00:00 to 11:59, twice per day.
But honestly my whole argument is pretty academic, so I'll probably just put up with the clock starting at 12.
It's the difference between ordinals and cardinals. In human languages I see ordinals as a pure shorthand: you could just as well say "give me orange number zero", except it's more cumbersome. I honestly can't think of a context (in human languages) where ordinals would be something more than a mere convenience. I believe the existence of ordinals is a technically unnecessary historical artifact: counting was invented before zero.
PS: I doubt people number their hands. A person would say "in your left hand", not in "your first hand".
Just want to point out that perhaps we humans are not as subjective as our intuition indicates. The third rear brake light make car's stopping distance shorter. There are human (psychological) reasons for that. I mean you could do objective approach all you want to make traffic safer, like build better brakes, but ultimately, humans control cars, humans have biased (arguably fixed) way of thinking, and there are ways to create interfaces that makes use of that bias. 1-based indexing makes ton of sense when your tool is meant for human use, because most of us is biased towards start counting at 1, objectively at least.
Neurological in that the line detecting algorithm is more excited by seeing a triangle of three lines than a horizontal bar of just one line. We have the technology to stick a HDTV on the bumper of each car and replace "brake lights" with a giant glowing stop sign which would work even better. But its basically the same argument.
In fact, it would be an interesting neuroscientifical research to measure the neurological activity that the symbol "1" and "1ˢᵗ" trigger in a programmers brain inside the context of a prose or written English language, and compare it to the activity the symbol `a[0]` triggers inside the context of code.
People are certainly not consistent in preferring 1-indexing. People expect decades and millenia to start on years ending in zero (even though they actually don't, because of 1-indexing).
The "MIT approach" in Worse is Better discussed the extant PCLSRing scheme on ITS (not a theoretical one) and the different approach that Unix took. Saying that it was "impossible to implement" is just flat out wrong.
ITS is an ancient time sharing system that predates Unix. It was written in assembly, ran on PDP-6s and later on PDP-10s, and had fallen into disuse when Gabriel wrote that essay (MIT would stop using it the following year). The success of Unix had nothing to do with its solution to the PCLSRing problem.
Reasons which I feel are too C-like and high level already. The article falls into the same trap and says this as a counter argument:
> It’s not about * i = a + n * sizeof(x) because pointers and structs didn’t exist.
But I actually read this as an argument in favor of zero-based index origin: since pointers and structs did not exist, any form of memory addressing and data storing/retrieving had to be done at some lower level, and if you did it by hand, you will for sure align the start of your in memory handmade array-like structure at the address of it, then merely add to the register holding the address an offset maybe itself stored in a register, and certainly you would not add 1 again to those, because the idea would not even cross your mind to lose one and one byte/word of memory and one cycle, as it would just make no sense at that level.
Subsequently, C (or B or A or whatever) evolves the process and embeds into the language such arithmetics, abstracting it under monikers such as "pointer", "struct" and "array" and adding utility functions like "sizeof", so that you don't have to remember if this or that CPU/asm uses ADD or ADL, or is foo-aligned, or uses bar calling convention.
* Ordinal and cardinal numbers both start from 1 in all major human languages
* Humans didn't use the number zero until relatively recently in mathematical history - this illustrates that it's intuitively hard for humans to grasp.
* It's important to separate the cases of 0 and null, which is easier if you use 1-indexing. This has got to save a lot of tricky bugs.
High-level programming languages are designed for humans. Humans 1-index everything, so these would be better 1-indexed.
Low-level programming languages are designed for computers. You can 0-index there, for the reasons that you stated.
As programmers, we had to explicitly learn 0-indexing. 0-indexing creates an artificial barrier to enter programming, when there are so many other barriers that we should be trying to relieve.
For instance, could you rewrite this function where int count = 1; and have it still be intuitive?
int count_occurrences(std::vector<int> vec, int value) {
int count = 0;
for(int i = 0; i != vec.size(); ++i) { // using 0-based index
if(vec[i] == value)
++count;
}
return count;
}
My attempt is: (look how ugly!) int count_occurrences(std::vector<int> vec, int value) {
if(vec.empty())
return 0;
count = 1;
for(int i = 2; i <= vec.size(); ++i) { // using 1-based index
if(vec[i] == value)
++count;
}
return count;
}`count` is a quantity, not an index, and there are such things as "zero apples". But you start counting them from the first one.
int count_occurrences(std::vector<int> vec, int value) {
int count = 0;
for(int i = 1; i <= vec.size(); ++i) { // using 1-based index
if(vec[i] == value)
++count;
}
return count;
}
Off the top of my head, this has a number of benefits in higher level languages. For instance in Javascript, here are some common inconveniences caused by zero-based indices: var lastEl = arr[arr.length - 1];
if(arr.indexOf(someEl) !== -1) {
// do something knowing that someEl is in the array
}
and if we lived in a one-based world, here's what they would look like: var lastEl = arr[arr.length];
if(arr.indexOf(someEl)) {
// do something knowing that someEl is in the array
}programming is math, and all the math works better if 0-index arrays. Leave programming language design to the adults.
* Zero is the first unsigned integer. If you start at 1, you're wasting one
Counter-argument: If you start indexing at 1, it allows you to encode an exceptional condition (e.g., element of a search not found) as 0 when the result is an unsigned integer. * p[0] == *p, and it makes no difference whether pointer arithmetic had been invented, because pointers are just memory addresses, and that's what the cpu works with
Counter-argument: This only holds for fixed-length arrays, strings, and the C/C++ idiosyncrasy that conflates pointers with arrays (at the expense of runtime safety). If you have variable length arrays or strings, then the beginning of the actual array/string contents will be at an offset (allowing for length/capacity/etc. fields). In fact, the offset may be exactly one (if you have a length field the size of each element), making one-based indexing the more efficient choice.You can make up ANY number of arguments for zero- or one-based indexing, and it will depend entirely on how much weight you assign to each of them.
Or, alternatively, you can use the Pascal approach, where you can specify the beginning index on a per-array basis.
WRT strings with metadata, if you construct your data structure so that the length and other metadata are at negative offsets from the pointer value that gets passed around, then the array data can be at offset zero again, so it's even more efficient. So zero still wins :).
But the arguments for 0-based indexing are also deeper than C arrays. C arrays are just one place where it's clearly visible.
The Pascal approach unfortunately doesn't solve the problem either. "Everyone can do what they like" is fine as long as no one ever has to read or interface with code written by other people.
Personally, I think it's cool that there are elements of mid/high-level programming languages which still reflect very low-level operational concepts.
I can't see anything about the computational cost of the indexing in there: Does the author have another source for that? It doesn't seem to be supported by what he quotes. The anecdotes about the IBM 7094 are nice, but I don't see much evidence for the kind of connections the author claims.
It went from "I'm Dr. Richard, and BCPL supports pointer arithmetic starting from zero" to "I'm the author, and the IBM 7094 handles all the pointer arithmetic during compile time, and time is limited; therefore, Dr Richard added zero-based indexing to speed up compile time." When really, his intentions are assumed by the author. Dr. Richard sounds like a very pure computer scientist. He may have added that feature without consideration of when pointer arithmetic is calculated simply because he wanted it in his BCPL language.
Really, Dr. Richard seems to sum up the issue nicely, "I can see no sensible reason why the first element of a BCPL array should have subscript one."
Also, wasn't the original question why zero vs one, not, why we started using pointer arithmetic at all?
for (i = 0; i < width*height; i++) {
arr2d[i / width][i % width] = array[i];
}
If you use one-based arrays, you have to write for (i = 1; i <= width*height; i++) {
arr2d[(i-1) / width + 1][(i - 1) % width + 1] = array[i];
}
Yes, you can write two loops, but this example shows how mapping one array onto another is easier if you use zero-based array.Mike Hoye: "C inherited its array semantics from B, which inherited them in turn from BCPL, and though BCPL arrays are zero-origin, the language doesn’t support pointer arithmetic, much less data structures."
Martin Richards: "If a BCPL variable represents a pointer, it points to one or more consecutive words of memory. These words are the same size as BCPL variables. Just as machine code allows address arithmetic so does BCPL, so if p is a pointer p+1 is a pointer to the next word after the one p points to. Naturally p+0 has the same value as p."
It isn't, because anybody who wants to publish in an IEEE conferences has to pay a fee X as a member or Y as a non-member, where X + IEEE membership fee < Y.
So young graduate students just join because our advisers tell us we must to save money.
Of course, we get mail advertiesments from people (like insurers) who IEEE sells our membership information to.
I think everybody in my demographic (young grad students) views this organization as basically operating a racket. I bet they actually do lots of really great stuff. But they've never told me anything about that. It's not like there was an information session when I "joined" (paid up). So it's sad. IEEE has a real image problem.
The author also addresses the importance of history. Voodoo knowledge _does_ pervade modern computer science, and I for one am happy to see something different.
But is it really? I'm inclined towards anonymouz's and adamnemecek's idea here: Dr Richard clearly says indices are offsets, no? It's nice the author gets all sentimental about it, but that doesn't make him right.
Given that, his arguments support 1-indexing better.
The equivalent statement in probability theory says that a probability is uniquely determined by its cumulative distribution function, which are sometimes nicer to take limits of. You can also get a probability from a cumulative distribution function, provided your function is right-continuous [1] (which is directly related to half-open intervals). For more examples of half-open intervals in probability, you can look at stochastic processes. See, for example, càdlàgs [2] and Skorohod spaces; they capture the notion that processes that "decide to jump" (think of Markov chains changing states if you like) are right-continuous.
IMO, half-open intervals are just nicer whenever you have to union them or intersect them, and are no worse in other aspects when compared to closed intervals. Also, I think the author makes a big cultural blunder when he dismisses "mathematical aesthetics" as a valid reason. A significant number of mathematicians think of elegance as the ultimate goal in mathematics; as Hardy famously said, "There is no permanent place in the world for ugly mathematics".
[0] http://en.wikipedia.org/wiki/Carath%C3%A9odory%27s_extension...
[1] Well, we also need the obvious conditions: Correct limits at infinity and monotonicity.
Yes, but we are talking about indexing discrete arrays. All of your examples are continuous, and are not analogous.
As to the "and [thus] are not analogous" part, I recommend the Concrete Mathematics book. You'll certainly see at least a hundred examples of continuous examples alongside with their discrete counterparts, as you can imagine by looking at the book title. Half-open intervals feature prominently when doing discrete calculus in the Sums chapter.
The crux of the issue is treating indices as offsets rather than identifiers, which is perfectly reasonable if and only if one is directly working with sequential memory.
I consider the idea that we should follow human intuition to itself "not rise to the level of wrong". There is nothing intuitive about programming, it is probably the second least intuitive activity known to man behind only pure mathematics. Your intuition needs to be trained from what is basically scratch, not blinding copied from much friendly, human domains. We know what that leads to, we've been doing it for decades; some people seem to argue "Oh, if only we'd try more human-friendliness it would all be better" as if we've never tried it, but we have, repeatedly. We get Applescript and COBOL and other languages that strive to be "human friendly" and produce an impenetrable morass of "human friendliness" that become incredibly hard to use as the size of the project exceeds trivial. As counter-intuitive as it may be, the track record is pretty clear at this point that the best languages are those that do not try to cloak themselves in false human skin.
(Which is of course not to say that we should all be programming assembler. Don't strawman me there. But the task of a language should be seen as a bridge between the human world and the computer world; it is not the job of the language to actually be in the human world itself.)
For the love of avoiding mutation, let's start pitching indexing in the dustbin of history when it comes to programming languages for general use. The fact that the justifications are coming from pointers and arrays ought to be a warning flag that we are looking backward rather than designing the future.
for i = 0, i <= myarray.length - 1, i++
do foo(myarray[i])
end
I'm not suggesting that foreach is singing, dancing and all-powerful, so of course it doesn't serve for accessing arrays by position. What I am suggesting is that the syntax used for arrays was not inscribed on stone tablets upon Mount Olympus, and that myarray.get(6) //return the sixth element
myarray.foreach('foo 6 19) //operate on part of the array
May be better abstractions for general purpose programming. Syntax should facilitate the programmer.We are always going to need this stuff for certain tasks. For example, I'd love to see a simply 3x3 filter operation on an image without using indices. I am also a bit biased; I'm a systems programmer, I don't write UI's and web apps. I tend to work more in the trenches, where abstraction gets in your way almost as often as it helps you get things done more quickly.
array[ M mod N + 1 ]
to get the right index. This is at best clumsy.
The reality is that, whether people realize it or not, we actually start counting most things from zero. My bank account doesn't start at 1, nor does any stock price, nor number of cattle or NSA agents hiding in bushes. Zero is a perfectly reasonable speed to go (especially at a stop sign). None of these are "at least one".
The stupid part of all this is that typically if you request the length of the array of our rocks, you will get five. But request the index of the last entry and you will get four. The simple fact that to refer to the last index in the array you have to use something like array.length - 1 points out the silliness of the whole thing.
EDIT: After a moment I felt the need to add that I'm not necessarily saying that all arrays need to start at one over zero, it's just that the whole thing is silly on first appearance. If the reason to start at zero is technical then fine, but as frustrated developers we have to have something to complain about.
Likewise, stocks first come to market with an initial price And a stock with a price of zero or zero shares is for all practical purposes in the first case and existentially in the second case, not a stock.
And while yes there might be zero NSA agents in the bushes, that means that the list or set of NSA agents in the bushes is empty, I.e. that there is no first agent, and the value at index of the first agent is the same as the value at the second - nil, void, undefined or an error...and hopefully not the latter since there are more than enough of them already.
My favourite offshoot is this tragic LWN post about a fairly amazing sounding debugging tracer that "three people in the entire world have ever run": http://lwn.net/1999/0121/a/mec.html
float b[4], *bb;
bb=b-1;
Voila! I have a single array that is both zero-indexed and one-indexed, in C.This destroys the argument about pointer arithmetic. Both arrays use exactly the same calculation to access.
Think this looks like a bad idea? That's opinion and convention, and nothing more. This code snippet comes from "Numerical Recipes in C, 2nd Ed.", in which they use one-based arrays in C frequently. In that edition, they also advocated switching on the fly :O. Later editions switched to zero-indexed arrays due to dominant convention.
"It is sometimes convenient to use zero-offset vectors, and sometimes convenient to use unit-offset vectors in algorithms. The choice should be whichever is most natural to the problem at hand." [NR, 2nd ed. pg 18. emphasis mine]
Context is the key to knowing which one is better, and neither one is best in all contexts. Saying zero indexed arrays are always better is like saying scrambled eggs are always better, it doesn't make sense without context, and its imposing an opinion that others may not share.
"For example, the coefficients of a polynomial a0 + a1 x + a2 x2 + . . . + an xn clearly cry out for the zero-offset a[0..n], while a vector of N data points x i , i = 1 . . . N calls for a unit-offset x[1..N]"
The polynomial cries out for zero for a real mathematical reason. Zero really is more natural there. The vector of N data points "calls" for a unit-offset because, um, convention?
I think ordinality reflecting cardinality is, while a convention, more than a purely arbitrary convention. And even if it is a purely arbitrary convention, its one that is so pervasive in dealing with collections outside of computing that it is a cognitive burden not to match it in computing (its a cognitive burden that you get used to dealing with pretty quickly when you've been immersed in languages that don't support it naturally, but its a cognitive burden nonetheless.)
We are violently agreeing, we don't really need to twist words or attempt to disprove what might just be a poorly worded statement, right? We are all educated engineers here, and we can afford each other the benefit of the doubt, no? We both get it.
The important part of the point is that all the other arguments here about which choice is inherently more "natural" is somewhat bogus. Neither is inherently more natural, what is more natural depends on what you're trying to do.
In some cases, there isn't an obvious choice, that's where convention plays the biggest role, but having a default choice made for you doesn't make the convention better or more natural, it just makes it an arbitrary choice that everyone is used to.
And I agree with the sentiment of using the right tool for the job, in principle.
However, I disagree that we're in full agreement :). I actually do think that there is a choice here that is inherently more natural for the sufficient majority of general-purpose tasks. The main arguments I'm aware of against it are arguments purely from human convention, which I find are not always as "natural" as one might hope.
Personally, even though I'm not used to it, I could probably be convinced that one-based might be the right "general purpose" answer for looping through an array when not doing any math on the index. Often when doing any math on the index, zero-based is much better. I do a lot of index based math, and yet if I'm being honest, I'd say the vast majority of my loops and the loops in code around me probably don't play index tricks and could use either indexing scheme.
Funny enough, I do a lot of graphics and image processing, and the OP referenced a joke about splitting the difference and using 0.5 -- I used 0.5 indexed arrays all the time! Logically, not syntactically -- I mean I take pixel samples starting from the center of a pixel, which is indexed by (ix+0.5,iy+0.5). If there were such a thing as a 0.5 index, I would use it. :P
However, I do agree with you that in the vast majority of loops there aren't any interesting index tricks. For this reason, I actually think language constructs like "foreach" and iterators and so on are better than counting from either 0 or 1 in most cases. In high-level languages, use high-level looping constructs, and let the compiler figure out the indices for you :-).
So: the technical reason we started counting arrays at
zero is that in the mid-1960′s, you could shave a few
cycles off of a program’s compilation time on an IBM 7094.
The social reason is that we had to save every cycle we
could, because if the job didn’t finish fast it might not
finish at all and you never know when you’re getting
bumped off the hardware because the President of IBM just
called and fuck your thesis, it’s yacht-racing time.(not harping on you, just stating for the TL/DR's looking for a summary)
"So: the technical reason we started counting arrays at zero is that in the mid-1960′s, you could shave a few cycles off of a program’s compilation time on an IBM 7094. The social reason is that we had to save every cycle we could, because if the job didn’t finish fast it might not finish at all and you never know when you’re getting bumped off the hardware because the President of IBM just called and fuck your thesis, it’s yacht-racing time."
I thought the reason is that the index is simply a memory offset. :\
a[0] always meant something like "get me the data whre a is pointing to"
a[1] meant "get me the data 1-offset next to where a is pointing to"
One based indexes are just going to confuse the noobs even worse. "So a[2] means get me the data offset by one from a" "uh what, don't you mean offset by two?" "No somebody said pointer arithmetic would be simpler if you had to memorize a bunch of different times to add or subtract one, just as a barrier to entry". "Oh well that sucks. Someone should invent a zero indexed array language..."
I always thought about arrays as pointers (at least in C), like it's just syntactical sugar for specific kind of pointer.
There is no argument for 1-indexed arrays other than, "so-and-so can't be arsed to learn 0-indexed."
I can't describe the theory in any more detail than that; I don't even know what I'd have to study to describe the issue using technical vocabulary. But it just clicked for me.
The point of the article is the impressive research.
its obvious that you can make 'human friendly' (i don't agree with that at all) one based indexing into zero based indices at the compiler stage by doing a lot of -1's (because it makes sense for memory addressing - thats how memory addresses work). those -1s obviously cost something...
that it slows down run-time code is utterly unthinkable in the context of old school computing - whilst it might make sense today if you are using some high level tool built on piles and piles of other tools, and so far from the metal that you have no idea what is going on... even then i struggle to understand why this would be considered.
off loading run-time costs to compile time is standard optimisation practice... although it was massively more important in the past than it is now. the case from the story shows how language design optimised the compiler design, so that it would optimise the final run-time.
its just optimisation - any programmer assigned this task in this context is highly likely to come up with the same solution because its straightforward and works.
https://plus.google.com/115212051037621986145/posts/YTUxbXYZ...
There are myriad reasons why zero-based indexing is the correct approach. Djikstra's commentary on the subject gets to the heart of the issue, but let's take the roundabout route.
Consider languages with pointers and pointer arithmetic. In these languages, anything outside of zero-based indexing is confusing and error-prone. The equivalence:
p[i] is *(p + i)
Only holds with zero based indexing, and it is a crucial property to maintain. The crucial understanding of |i| in this context is that it's not simply a number, but that it is a _vector_ from one location to another. The nullary vector (0) |p| takes you to |p|, or |p == &p[0]|. To get the vector between two pointers, we simply have to subtract the origin from the destination: v(p, q) = q - p
So: v(p, &p[i]) = &p[i] - p = (p + i) - p == i
These vectors fall into a natural algebraic relationship. Consider two indexes |i| and |j|, and the vectors between |p|, |&p[i]| and |&p[j]|. To help with readability, I'll alias |pi = &p[i]| and |pj = &p[j]|.The vector from |p| to |pi| is |i|. The vector from |p| to |pj| is |j|. The vector from |pi| to |pj| is:
v(pi, pj) = pj - pi = (p + j) - (p + i) = j - i
Plug this vector back in as an index from |pi|, and we get back to |pj| naturally: pi[v(pi, pj)] = (pi + v(pi, pj)) = (p + i) + (j - i) = p + j = pj
With one-based indexing, this algrebraic consistency breaks down. I'll use caps to distinguish the one-based indexing formulas from the zero-based indexing formulas. In the one-based scheme, |&P[I] == (P + (I - 1))|. Following from that: V(PI, PJ) = PJ - PI = (P + (J - 1)) - (P + (I - 1)) = (J - 1) - (I - 1) = J - I
So far so good. The vector calculation yields the same number! Great! Now let's plug it back in: PI[V(PI, PJ)] = (PI + (V(PI, PJ) - 1)) = (P + (I - 1)) + ((J - I) - 1) = P + J - 2 = PJ - 1
Nope! Indexing from |PI| with the vector from |PI| to |PJ| no longer takes you to |PJ|. Rather, it takes you to one element _before_ |PJ|.The consistent, clear relationship between pointers and the vectors between them no longer holds. Vectors no longer compose. Composing two vectors no longer works, as the |-1| term compounds over each composition.
This is no small thing to lose. Algebras are extremely powerful ways of coherently describing the behaviour of systems. Losing these algebras means you lose their powerful regularity. Adding two vectors no longer yields the true composite vector. Multiplying a vector by a scalar no longer yields a scaled vector. At every vector addition or scaling, the programmer will be forced to adjust their result vectors to obtain the true composite vector. This is an enormous burden on the programmer.
Any programming language that imposes this burden on the programmer is simply wrong. We make languages so that it's easier for us, people, to reason about the behaviour of our programs. Destroying the algebraic relationship between pointers and the vectors between them is inexcusable. We may have experimented it during the early years of language development, but we know better now.
The above is just _one_ reason why zero-based indexing is, without a doubt, the correct approach. The other major reason has to do with the superiority of open intervals over that of closed intervals, and of the algebras over those intervals. I might write another comment on that if I get time, but the gist of it is the following (this is the thrust of Djikstra's argument in favour of zero-based indexing):
Zero-based indexing supports a notion of integer intervals that fall into a coherent algebraic framework of open intervals.
Adding to Dijkstra's argument, this notion of an algebra over open intervals is powerful because not only dose it capture integer intervals (array and pointer arithmetic in C and C++), but also can be extended to ranges over a variety of ordered containers (e.g. collections of arbitrary values), and of ordered infinite sets (e.g. the abstract set of all strings, or all JSON values, etc.). The most prominent implementation of this is C++'s STL ranges (there's a very good reason |container.end()| in C++ returns an iterator "past" the last item in the collection). Suffice it to say that for algebras over intervals, open intervals win over closed intervals.
However, I don't have time to write that novel right now ;)
*'this' referring to the debate in thread, not in the article.
arr[I + 0] = arr[I]
arr[II + 3] = arr[V]
arr[I - 10] = undefinedThis has been one of these occasions.