Why numbering should start at zero (1982)
cs.utexas.edu
cs.utexas.edu
The article mentions Mesa's interval syntax, which gave programmers all the possible options:
[1..100] (0 .. 101) [1 .. 101) [1 .. 101)
were all the same interval, 1 to 100 inclusive. Bad idea. Great way to get off-by-one errors.
Another bad idea from that era - Wirth's insistence that you shouldn't be allowed compile-time arithmetic in Pascal. Like.
const
tabsize = 100;
var
tab: array [0..tabsize-1] of integer; { NOT ALLOWED }
No, you had to have a separate named constant for "tabsize-1", and it couldn't be initialized as "tabsize-1", either.I would think that depends on whether that gets taught at schools early in life.
Intervals that are closed at the low and open at the high end also are convenient when using zero-based indexing (you want an easy way to express 0,1,2,…,n-1, and if you’re used to the notation [0,n) is nicer than [0,n-1])
I would think overloading the meaning of the various parentheses makes parsing and generating clear error messages harder, though (but not as hard as when one would follow ISO 31-11 and allow such things a ]3,7] for an half-open interval excluding 3, but including 7)
I think the desire to not overload parentheses with yet another meaning is why ‘Modern’ languages tend to use an infix operator for ranges, e.g. ‘1 to 5’ or ‘1...5’, with Swift having the half-open variant ‘1..<5’ (IMO ugly, but clear, so I guess one would get used to it).
I'm partial to the viciously set-theoretic[0]:
for i in 5: print i # 0;1;2;3;4
0: finite ordinals[1] are 0={},1={{}}={0},2={{},{{}}}={0,1},3={{},{{}},{{},{{}}}}={0,1,2},Sx=x∪{x}={0,...,x}1: aka naturals or nonnegative/unsigned integers
A popular way to partition an array at i and j is to do:
arr[0..<i], arr[i..<j], arr[j...-1] a0,a1,a2 = arr.splitat({i,j})
maybe? It avoids repeating i and j twice and arr thrice, at least. Admittedly, there probably are cases where you really do want a range [x..^y] for arbitrary x and y, but it seems like most cases aren't.After having many fencepost errors, I finally came to the conclusion that all my code shall henceforth be zero-based. I'm much happier not having those errors anymore. I.e.:
for (i = 1; i <= N; ++i)
The <= is always a huge red flag for me, and I rewrite it as: for (i = 0; i < N; ++i)
and add a `1` in the body if I must.For example:
1. increment x
2. take the value of x, then increment x
(1) is better form if you don't need the value of x.
1. increment x, then take the value of x
2. take the value of x, then increment x
Both are expressions in Java returning a value. My C is very rusty but I remember it being the same in C.
Given the author, I assume that this is also common in D.
The argument I've heard for why in C++ prefix is preferred is that compilers have a harder time optimizing out the temporary object cruft (especially older compilers).
You can, but if there are side effects in the object's implementation then it can be less efficient.
You run into issues when using 1-based counting as well as measuring.
Our calendar years use 1-based counting, which results in things like "2020" being the "20th year" of the "21st century". If we used 0-based counting instead, we would call the current year "2019" and it would be "year 19" of "century 20".
Explanation of conventional year counting:
1st century began in year 1
2nd century began in year 101 (100 years later)
..
21st century began in year 2001
If you run the same sequence as above, but start counting from 0, you get seemingly much more sensible numbers (0, 0; 1, 100; 20, 2000; x, x*100...). I suspect most people probably think that the 21st century began in 2000 anyway.It should also be noted that the use of the number 0 in general is relatively recent. The Roman numeral system (which was initially used for our year numbering) has no representation for it, as it was only used conceptually hundreds of years later. It seems to me that the main reason people count from 1 is historical.
But then 2000 becomes year 0 of century 20, which is confusing English. But "century 20" isn't great English anyway. Calendars should be 0-based like any other measuring tool. The 3rd minute of the 2nd hour of a marathon is 1:02. The 3rd day of the 2nd month of my marathon is 1/02. If that happens to be the 3rd day of the second month of the year, it's 2/03?
I think you meant to say the "20th" year (off-by-one error!), since 2000 would be the "1st" year under your grammatical assumptions.
I think it's safe to assume that if we conventionally counted from 0 instead of 1, we wouldn't refer to the initial thing as "first" (or "1st"). If we did maintain that construction, we would probably just refer to the initial century as the "0th century" and then 2019 would be the "19th year of the 20th century".
> But then 2000 becomes year 0 of century 20, which is confusing English.
I think it's only confusing because it's unconventional, not because there's something inherently more confusing about it. There have been plenty of languages that haven't even had counting systems that do anything more than "alone, pair, many" .. sure, you can find contexts where counting in general is confusing.
> The 3rd minute of the 2nd hour of a marathon is 1:02.
Right, under current 1-based counting convention, but the point of this discussion is that this inconsistency exists for basically no reason (or rather, historical reasons—look up the history of "0"). When people want to do arithmetic on ordinal numbers, they end up subtracting 1 to turn them into a zero-based natural number, then add 1 again to turn them back into an ordinal:
century(year) = floor((year - 1)/100) + 1
century(2020) = 21
You can reduce the likelihood of off-by-one errors by just changing your representation of ordinals such that they start from 0: century(year) = floor(year/100)
century(2019) = 20
I suspect most off-by-one errors can indeed be seen as due to this inconsistency between conventional ordinals and the more arithmetic-friendly zero-based naturals. Again, since the former is simply convention, I claim it would be better if our convention were different.The language is flexible enough to also let you define a<..b and a<.<b to complete the quartet, but I don’t know if anyone who ever did.
Apart from that, I agree that it depends whether you are counting [1] or doing something else.
[1] That's why I do HN references like this.
This restriction has since long been relaxed in Wirth's later languages Modula-2 and Oberon which both supersede Pascal.
People should actually stop using late 70's Pascal examples, specially when ISO Extended Pascal in 1990 fixed most of them, not to mention famous dialects like UCSD, Apple's Object Pascal and Turbo Pascal, or its modern variants.
With half-open intervals, that case proved to be cryptic and error-prone, so we added reversed() to flip the direction of iteration. That allowed people to easily bridge from the comfortable and common case of looping forwards. Instead of range(n-1, -1, -1) we can write reversed(range(n)).
$ python3 -c 'print(reversed(range(10)))'
<range_iterator object at 0x7fdb53ae96f0>
It turns out that reversed() only works on objects that expose the __len__ and __getitem__ methods:https://www.python.org/dev/peps/pep-0322/
If you try to use reversed() with your own generator it will fail with "TypeError: argument to reversed() must be a sequence", until you wrap the generator invocation in an explicit list().
Very nice.
python3 -c 'print(range(10)[::-1])'
range(9, -1, -1)http://python-history.blogspot.com/2013/10/why-python-uses-0...
See Guido's comment on calendar :)
Well, clearly they (people who prefer 1-based indexing) don't consider the amount of damage they've done enough.
What's so pernicious about them? They are short, clear, unambiguous, easy to type. Why don't we just get our computer languages to understand what "2, 3, ..., 12" means? Is there any other argument to start counting at 0 other than not defining a range using the three dots? If not, maybe starting at 1 (like everyone does outside of computing) is the better option.
Some languages already do: for example, in Ruby you can use (-5..-1) to get [-5, -4, -3, -2, -1]. It's lazy evaluation, so you can even do stuff like:
Infinity = 1.0/0.0
(1..Infinity).step(2).take(5) #=> [0, 2, 4, 6, 8]For programming, I imagine it's better for it to be infinity than 0 since having 0 in the denominator implies that it's changing (who would write a constant 0 in a denominator?), and so as the denominator is approaching 0, the fraction is getting closer and closer to pos/neg infinity, not 0. Making it evaluate to zero would imply a change of direction for whatever the fraction represents.
So, if it's a position, as the position goes 1000/1000, 1000/500, 1000/100, 1000/1, etc. it's getting closer to infinity. Having it suddenly go to 0 would break the pattern of movement.
Mathematically, there is no x that satisfies x = 1/0 -> x * 0 = 1, since anything multipled by 0 is 0, so it's undefined.
P.S. the link you provided is exactly the one.
This isn’t so compelling in today’s world of mostly people who live in high level programming languages with no real notion of what goes on in the hardware. But backwards compatibility is a thing, and “following conventions” as well, so I see zero based indexing used in modern high level languages as a logical extension of where this all came from.
What’s hard, really, isn’t using zero based indexing, it’s switching back and forth between systems.
Array indexing is less compelling on higher level languages, so I think the needs of those languages can be safely ignored here anyway.
Whereas C and descendents see arrays as chunks of memory.
In C arrays it makes sense to think of the index as an offset (0 elements away from the start, etc.).
In math its more consistent with classic matrix notation.
0. Subtracting the lower bound from the upper yields the length of the sequence.
1. For adjacent sub-sequences, the upper bound of the lower sequence will equal the upper bound of the lower sequence.
One could just as easily argue for option C (a ≤ i ≤ b) by pointing out that
0. It's much closer to natural language ("all the numbers from a to b"), and thus less likely to be casually misunderstood.
1. It uses the same inequality symbol twice, making it quicker and easier to understand.
2. If either of the bounds of one sub-sequence is inside the bounds of another sub-sequence, those two sub-sequences overlap.
His logic is that we should cater to the empty sequence case, and it would be "unnatural" to write 2 <= x <= 1, so we have to write 2 <= x < 2. That is just not an improvement. Ask somebody to write down an empty sequence, starting at 2, they'll think you're crazy. The Common Case is give the First and Last.
Option C is, to me, obviously best. How you can replace 2 .. 12 and not use 2 & 12 is beyond me. (/s Dijkstra's opinions considered harmful /s)
What does that mean? 2,3,5,8,12?
> starting at 1 (like everyone does outside of computing
Non-computing science quite frequently uses offset-based indexing starting at zero, just as is frequently done in computing.
Note also that, in maths, both the cardinal and the ordinal numbers start at 0 - defined as the cardinality of the empty set and the set containing the empty set, respectively.
a = "abc"
b = [" "]*10
With 0-based indexing you would do: for i in range(0,10):
b[i]=a[i%3]
In a 1-based language that becomes: for i in range(1,11):
b[i]=a[(i-1)%3+1]
So you need to shift the index twice. This is because modulo arithmetic needs 0 to form a ring. As a result, in situations where the difference between 0 and 1 based indexing makes a difference it's ususally 0-based indexing that leads to simpler code.Assume positive b for a moment. We want a * (a/b) + (a%b) = a. If a%b is to always be within [0..b), then a/b has to round toward -infinity. C99 instead chose round towards 0.
Source: C99 Rationale v5.10 sec. 6.5.5p10 (http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10...)
(Haskell has both kinds of integer division, called div and quot, which is nice.)
Like, 1000/1000 + 1000%1000 = 1000? That sounds like undesirable behavior for division to me.
> Don't you mean we want "(a/b) + (a%b) = a"?
No, they mean "b * (a/b) + (a%b) = a". (Also I'm not convinced that's actually useful, but that's a different discussion.)
In my cases of numerical code 1 based indexing causes fewer ±1 than 0 based indexing. (Nitpick: In modulo arithmetic 0 = N so having the index run from 1 to N is a ring just as well. The problem is the conventional representation of the equivalence class -N,0,N,2N,3N… in the numbers being 0 (which arises from arithmetics definition of modulo))
I'd argue that we start at 0 and repeatedly add one for each item counted.
Likewise, in multiplying we start at 1, particularly in this sense: For n>0, n^0 = 1. Multiply 1 by something zero times leaves 1.
And in python you could just go:
import itertools as it
a = ''.join(it.islice(it.cycle("abc"),10))
Also you don't need 0 to form a ring. Modulo arithmetic forms a ring no matter which representatives you pick. Languages just tend to implement one that includes 0 because it's more convenient.Some things are more naturally numbered from 1, others from zero. Fence posts versus fence spans.
Pointer arithmetic is a great example of a situation well adapted to 0 based numbering, and was quite relevant to many programmers when this was written. However, far fewer programmers nowadays interact with raw pointers directly.
Personally, I run into more cases where I’d rather have 1 based numbering than zero, but your mileage may vary. It’s valuable to have a language which can support both.
It doesn't have to be literally a memory pointer to run into the same phenomenon. For example, offsets into a file. I can think of an example when I was doing work on a file format, and 0 based indexing certainly helped simplify some logic.
I have no idea what indexing style is more appropriate to the hypothetical ‘average’ programmer, but I find myself preferring mostly 1 to N the most, followed next by -N to N and finally 0 to N.
As I said originally, your mileage may vary, but I think it’s important that we use languages where many different indexing styles can be supported ergonomically, and in a way that avoids silly errors due to using the wrong index style.
Type systems and generic functions help a lot with this. I think of it a lot like say, signed versus unsigned integers. Multiple useful ways of looking at the same bitwise data. Type systems generally save us from nasty errors with the various types of integers. They can do it with arrays too.
A[2:5]
I find that much much more convenient and user friendly than the alternative.Or, I suppose, you could start counting at 1, but use an optional type and use None for that case. But that makes doing arithmetic on those numbers ("widgets per pizza") harder than if you'd just used 0 to begin with.
A computer should start counting from 0, there's no reason to special case the empty set.
Lets consider a measuring tool for fluids which is based on volume. What is a useful range for the index on the side that measures the currently non-air capacity?
Many such devices, E.G. a measuring cup I've got at home in the kitchen, use a series of tick marks within a bounded scale of accuracy range ] with marks on both the top (of course) and also the bottom (it looks nice).
While 0 isn't expressly labeled on many scales, it is part of the inclusive range by implicit nature. Thus as you point out the case of 0 units, and 'empty set' are one and the same in this real world example.
Many of our existing conventions derived from military, science, and academic applications. Business applications came along later, emphasizing discrete counting and categorizing things along the lines of set theory, such as in the set or not in the set, not half in. (Money has decimals, but you don't typically increment through it with indexes.)
(Although COBOL was published around 1960, it took roughly 5 years for computers to get powerful enough to make it practical and widespread. The earliest COBOL compilers were dog-slow resource hogs relative to the hardware of the day.)
It's not a practical problem for most coding. If you disagree, can you demonstrate a problem caused by indexing arrays starting at one in a typical code situation/scenario?
The 1-based counting would be to index or label them like item 1, item 2, item 3, ... which is quite natural to humans. If you had zero items, you'd express that by your list being empty, not by having a number 0 somewhere. It's not a special case.
If I have five pizzas, would anyone argue that the variable storing that fact should have a value of 0x0004? On the other hand, if there’s a stack of five pizzas, they’re either numbered 0..4 or 1..5 depending on your religion.
All of this because some arrogant programmers (or ex-programmers) think that they know better than everyone else and that changing legacy and/or logically correct conventions is good because of their religious beliefs that "0-based is better for everyone and every task".
It could be they lack real-world experience or work in "esoteric" domains. Theorem proving is a different animal than making Boss Bob's billing summary reports come out right.
for 1 <= i <= 10:
do stuff
for 0 <= i < 10:
do stuff
for 1 <= i < j <= 10:
do stuff
(In the last case, the exact order in which the 45 iterations happen should maybe be left unspecified; at any rate, it would be bad style to depend on it.)Downward iteration:
for 10 >= i >= 1:
do stuff
Obviously this doesn't cover every case of "arithmetic for loop" that would be useful: sometimes you want a stepsize that's neither 0 nor 1. I'd be quite happy with a language in which I had to do that using a more general iterate-over-an-arbitrary-sequence construction; I'm tempted by options like for 100 <= i <= 1000 where i%3==1:
do stuff
but it's probably too clever by half; either you only support the simple "specify the value of the variable mod something that doesn't change while iterating" case, in which case users will complain that some obvious generalizations fail to work, or you support arbitrary predicates, in which case you have to choose between making the "easy" cases efficient and the "hard" cases not (in which case users will be confused when what seem like small changes have drastic effects on the runtime of their code) and making all of them inefficient (in which case users will be confused by how slowly their code runs in some cases). (loop for i from 1 upto 10 ..)
(loop for i from 1 below 11 ..)
(loop for i from 10 downto 1 ..)
(loop for i from 10 above 0 ..)
(loop for i from 3.5 to 7.5 by 0.25 ..)
It does much more than that, I should mention; it's a whole mini-language/DSL (like format).But it doesn't have the elegance of just specifying the inequalities you want your loop variables to satisfy :-).
Relying on something unspecified isn't just bad style; it's a bug.
Why would you introduce requirements into a higher level language that leave important aspects unspecified, opening the door to bugs?
1. It might be best not to specify what happens, because
2. if it were specified then people would rely on the specific order, which would be bad style because it would make code harder to read and be more liable to errors if anyone misremembers what order the iteration happens in.
(But it's true that people might rely on the observable behaviour even if it weren't officially specified, and that would be even worse. Which is why I said "should maybe be left unspecified"; it isn't obvious to me which way is better overall. If it's unspecified then fewer people will rely on it but the consequences each time will be worse.)
-- Stan Kelly-Bootle
This is, of course, a terrible idea, and that feature is now deprecated. $[ is always 0 and setting it to any other value is an error.
¹ https://archive.org/search.php?query=creator%3A%22Unix+Revie...
² https://web.archive.org/web/20161024171031/http://www.sarche...
Many CPUs have a flag that is automatically raised if a value is 0.
This means you don't have to execute a compare instruction to test if a value is 0, because the "Zero" flag will be set as soon as the 0 value is loaded.
That means you can load a value, and directly go to a BEQ (which is really shorthand for "if zero flag = 0") and save a few cycles by avoiding the CMP instruction.
So this is why numbering starts at zero. Testing if your list is empty, which is probably a common thing if you loop through each element, is slightly quicker.
If you are anyone who doesn't want to care (too much) about how things are implemented, then it's a lot less error prone to mark the 1st element of an array a[1]. Having taught and written fairly complex scientific code in both 0 and 1 indexed languages, there really is no good reason to do anything else.
for a in array[3:6]
Should iterate over the third to sixth element of the array.Seriously though you save yourself a lot of trouble if you don't worry that much about what range your indices have but rather what they represent. The fact that they're integers starting at 0 is just an implementation detail.
Me too. But the problem is, maybe they'll give you a few. Probably not more than 10 or 20? Say 20 to be safe. So I'd keep an empty box ready.
I alluded to this in another comment, but what astonishes me about my son is how rapidly he grows and changes. Every night, my wife and I talk about how amazing he is, etc. and I can't help but think about the fact that 1 year ago, he was barely able to roll over, and that he's now running around, "reading," eating solid foods, and a full-blown chatterbox. If he's doing this much now, what the heck is he going to be doing next year?
It's forced me to reevaluate my own life. If my son can accomplish all of that in one year with proper support, nurturing, and guidance, and we are genetically related (i.e. 50% of him is me), then what could I do in the same amount of time?
I've resolved to find that out.
(Although kids can pick up on memorization very quickly)
That's extremely amazing for a kid that is exactly one years old, rather than, say, 1.x years old for high values of x.
For any U, p(P₂ U) ≤ q(U) < p(P₁ U)
He actually is a pretty amazing kid. He's miles ahead of other kids his age, even some who are older. I wasn't expecting to be able to teach him counting until next year, so I'm being very relaxed about it right now and making it fun. He's 1, not 5, as far as I'm concerned, he can do what he wants, but if he's into it, I'll teach him.
I couldn't tell you offhand if the logic you speak of is distinct to Sheets or if it's Sheets maintaining the same interfaces other spreadsheet software defined for those macro names long ago.
Having a kid I've been thinking a lot about counting, and what it really is. It seems totally wrapped up in repetition, and so I'm wondering if teaching counting as a function of, say, circular motion doesn't give a better intuition than the usual "count this clump of things" approach. (Counting clumps requires the person to simultaneously introduce an ordering and then implement a kind of internalized repetition as they point and count rhythmically. My kid seems to struggle with the ordering part, and no wonder: N objects have N! orderings.)
Between 0 and 10 there is 10 one-sized intervals touching 11 connecting/borderline points (integers). You cannot make this fact go away, no matter which language you choose.
In a for loop, if we start with 0, then the terminating condition is i less than n.
However, ifvit starts at 1, then the terminating condition is i less than or equal to n.
What mental trick do you use to remember that?
"In corporate religions as in others, the heretic must be cast out not because of the probability that he is wrong but because of the possibility that he is right." Antony Jay
There's a parallel argument for being inclusive in the upper bound: it lets you specify a range which is the entire size of your integer type.
I can see that that might seem like a poor trade for losing the ability to represent an empty range starting at zero, but it seems a shame he didn't mention that this is the tradeoff you're making.
However for high-level programming, starting at one has advantages. For instance what index to use to represent insertion of an element at the beginning? Being able to use zero for this is cognitively easier.
Also I wonder if it's highly dependent on whether the domain is largely algorithmic (e.g. video codecs) or business logic (e.g. supply chain).
And how much is simply the language being "misused", e.g. writing "<= n - 1" rather than "< n".
> e.g. writing "<= n - 1" rather than "< n"
Wouldn't such errors further increase the ratio?
But if you ran such an analysis across public GitHub repositories per-language and wrote the results up in a blog post, I'm sure HN would love to see it. Definitely front-page material.
> But if you ran such an analysis across public GitHub repositories per-language and wrote the results up in a blog post, I'm sure HN would love to see it.
I'll see what I can do. I do have some repos in sight that could be used for it.
But even then this won't be as straightforward as you say since different languages have different applications (eg cpp for games, julia for scientific computing). This would require writing the same code in both indexing patterns and then comparing them.
95% of cases it does not matter or make things more elegant or simple, but when it does it's always zero-indexing that is more elegant.
And in both cases I always run into an issue where I think, "This would be so much easier if we just started at [the other index]".
When I'm programming in 1-based languages, I'm almost always happy with that choice, because they're almost always languages designed for solving problems in a domain that were standardized on 1-based counting before Charles Babbage was born.
When I'm programming in 0-based languages, I'm sometimes very happy with that. Those times are when I'm working in C or C++ . When I'm using other languages, I don't care much maybe 1/3 of the time. And the majority of the time I still wish it were 1-based, because I'm using that language to solve problems in a domain that had standardized on 1-based counting before Charles Babbage was born.
Case: indexing an array in ASM, C, C++, Swift,…
Case: using an offset
0 <= i < N
Case: indexing an array in Pascal, Ada, …
Case: writing a math paper
Case: talking with a non-programmer
1 <= i <= NIt is true that most matrix theorems are formulated with 1-based indices. But mathematical notation is full of unfortunate historical accidents (like defining π to be half the circumference of the unit circle), so one should be cautious when drawing inspiration from it. And for what it's worth, set theorists start ordinal numbers from zero, and each ordinal a half-open interval of numbers which precede it:
https://en.m.wikipedia.org/wiki/Ordinal_number#Von_Neumann_d...
This is numbering being talked about. Guy finishes a marathon in first place, he would be the zeroth runner and one runner has finished. Most people would intuitively like those numbers to line up.
Of course, when you build the natural numbers on a computer and use those as indices to arrays, it makes sense that the first index is zero. So the first (common usage) element would then naturally be called the zeroth element. This, of course, gives you the problem that array.size == x means that array.last_index == x - 1 and all the off by one errors that entails.
Before any runners have completed the race, there are 0 runners in the set of runners that have completed the race (empty set/null set).
As soon as the first runner crosses the finish line 1 runner has finished the race, increasing the counter by one increment and it is customary to assign them the designation of the upper end of the unit range rather than the lower number. Thus, they are the First (1st) runner to have completed the race.
We count rooms the same way probably out of a similar convention, the first room on a floor is numbered 1, rather than 0, because it is more useful to annotate the end of the range than the start. It makes a lot of math easier too. If there are 10 rooms on a floor and 10 guests, there are 0 rooms free.
// Start the position at one, because that's how we
// assign number labels to finishers in a race.
let position = 1
// Create a callback that will assign a label to
// each competitor when that competitor finishes
// the race.
let assignLabel = (competitor) => {
competitor.label = position
position = position + 1
}
// Begin the race, and pass it a callback that will
// be run each time a competitor finishes the race
// (in the order they finish).
race.begin(assignLabel)
Note that this "label" really is just a label. It happens to be an integer here, but we could also use a library that outputs English strings like "first", "second", "third", etc. This debate is focused more on how elements in an ordered collection are accessed.https://en.m.wikipedia.org/wiki/Radix
Nothing to do with starting number. And base 0/base 1 makes no sense.
But yes please explain to me the very thing we're talking about which is exactly what I was referencing when pointing out that using 0 as an identifier rather than a value is moderately counterintuitive.