# Why do computers count from zero?

Computers traditionally tally numerical values starting from zero. For example, arrays in C-based programming languages start from index zero.

What historical reasons exist for this, and what practical advantages does counting from zero have over counting from one?

Note: This question asks for well-explained technical answers, not merely opinions, and is intended to cover computers in general rather than just programming. This question expands upon the Programmers question "Why are structs/arrays zero-based?".

-
Djkstra opinion – Bakuriu Apr 5 '13 at 7:11
There have been more than a few examples of computer languages that used 1-origin arrays. – Daniel R Hicks Apr 5 '13 at 11:15
Why don't humans count from 0? – Untitled Apr 5 '13 at 13:36
Woah, woah, no one counts from zero, we index from zero. No one says the "zeroth" element. We say the "first" element at index 0. Think of the index as how far an element is offset from the first position. Well, the first element is at the first position, so it's not offset at all, so its index is 0. The second element as one element before it, so it's offset 1 element and is at index 1 – mowwwalker Apr 5 '13 at 14:32
@Ramhound No, it isn't. Zero-based indexing is completely unrelated to using binary. – Peter Olson Apr 5 '13 at 16:41

Counting arrays from 0 simplifies the computation of the memory address of each element.

If an array is stored at a given position in memory (it's called the address) the position of each element can be computed as

element(n) = address + n * size_of_the_element

If you consider the first element the first, the computation becomes

element(n) = address + (n-1) * size_of_the_element

Not a huge different but it adds an unnecessary subtraction for each access.

Edit

• The usage of the array index as an offset is not a requirement but just an habit. The offset of the first element could be hidden by the system and taken into consideration when allocating and referencing element.

• Dijkstra published a paper "Why numbering should start at zero" (pdf) where he explains why starting with 0 is a better choice. Starting at zero allows a better representation of ranges.

-
+1 for correct answer. Note that 0-based indexing is just a (very common) convention of the language being used; it's not universal. For instance, Lua uses 1-based indexing. The "unnecessary subtraction" may have been the reasoning behind 0-based indexing in the old days, but now most languages use it simply because it's what everyone is already used to (largely thanks to C), and there's no compelling reason to change that convention. – BlueRaja - Danny Pflughoeft Apr 5 '13 at 15:35
This makes no sense. The position of each element can always be computed as address + n * size_of_element so long as the "address" is the address of the zeroth element. This works perfectly whether the zeroth element exists as an element of the array or not. The question is why the zeroth element exists, not why we store addresses as the address of the (possibly notional) zeroth element. (Which this answers.) – David Schwartz Apr 5 '13 at 17:42
@DavidSchwartz Let's take an old language as C. If allocate memory you get an address where the memory starts. If a compiler sees something like v[n] it has to compute the address of the expression. If the indexes start a 0 the computation is v+x*size. If at 1 the computation is v+(x-1)*size. E.g., v[1] will correspond to v + (1-1)*size that is v. – Matteo Apr 5 '13 at 19:12
@David: In C (the language that really popularized 0-based indexing), arrays and pointers are largely interchangable, so it is important for a number of reasons that *array actually refers to the first element. One example: if we have array point to the memory location before the first element, casting to an array of a different type would be troublesome eg. the position of the second byte in an array of ints would become dependent on the word-size; on a 32-bit machine, it would be at ((char*)intArray + 5)!! – BlueRaja - Danny Pflughoeft Apr 5 '13 at 19:39
No, this is not an issue of whether the array has a zeroth element. Because, you see, there is also scaling. If I have an array of 8 byte objects, and I overlay that with a byte array, what is the byte index of object [42]? Why it is simple: 42 * 8. The problem with 1 based is that this offset of 1 is 1 byte when I look at the byte array, and it is 8 bytes when I look at the overlaid 8-byte-unit array. – Kaz Apr 6 '13 at 2:47

While the principles below apply to decimal as well any other base, Counting from 0 in computers can be easily understood naturally from the fixed-digit binary system of representing numbers used in computers. If you have 8 bits, then there are 256 possible combinations of 1s and 0s that can be expressed. You could use these 8-bit to express the numbers 1-256, but this would leave out 0 which is useful in mathematics as a number in and of itself, so they are used to express the numbers 0-255.

This already sets a precedent of a natural order starting from 0 (all 0's in the binary representation) to 255 (all 1's in an 8-bit number). Considering the system of representing numbers, starting from 0 makes sense because 0 is the "first" number in the system, so 1 is the "second" number, and so forth.

An additional reason why starting from 0 in computers is so convenient is due to the concept of offsets. An offset is a number representing the distance from a location in memory or hard disk or any other "addressable" medium. In computers, practically all data is stored linearly, meaning that there is an order to the data, a first a byte, a second byte, etc. It is convenient to express location of "areas" of data via an offset. What is the first byte in a block of data? It is at offset '0', which means it is found 0 bytes after the first byte in the block of data. While it is possible to have "1" designate the first byte, this creates complications in the representation of the data for several reasons:

• By the exclusion of 0 from being used to address data, you reduce the number of things you can address with an 8-bit number by one.
• To calculate the offset, which is necessary at the hardware level of data access, at some point you have to subtract one from the numbering, which introduces a complexity.
• Pointers to a block of data always point to the first block, so arithmetic is straightforward when you start from 0. (ie, the 1st byte in the first block of the first cluster of data is 0 + 0 + 0 when you start from 0, it is 1 + 1 + 1 - 1 -1 when you start from 1.) The arithmetic for this when you start from 1 with nested datastructures like this example can be confusing.
-
Has nothing to do with binary representation. Both binary and decimal numbers begins from 0. – Matteo Apr 5 '13 at 6:16
If you begin counting from 0 you don't reduce the number of addresses you could (in theory) go from 1 to 257. – Matteo Apr 5 '13 at 6:20
@Matteo not in a single byte you couldn't – OrangeDog Apr 5 '13 at 8:49
@Dougvj Zero-based counting has absolutely nothing to do with binary. The point you are making is about making use of every number in a fixed-digit representation, which is a concern regardless of whether you are using base 2, base 10, or base 23517. – Peter Olson Apr 5 '13 at 15:08
-1 It has absolutely nothing to do with binary representation. – BlueRaja - Danny Pflughoeft Apr 5 '13 at 15:28

Never thought an opportunity for an armchair philosopher such as myself would come along on Superuser. There is a fundamental misconception at heart here, because non-philosophers tend to skip over the minute details. In short: Computers do not count from zero, but denomination of positions starts from zero.

There is nothing confusing about this perceived inconsistency between computer and human (any) counting techniques. Let's decompose the question.

Why do computers count from zero?

• They do not count from zero

Computers tally values starting from zero. For example, arrays in C.

• The index (indicator of position, tally) starts from zero. The count of elements in an array where there is a single element at index zero is one

Zero is practical to represent a void of something or the middle point of a scale. It is not practical for counting anything because it is impossible by definition of zero.

In the same sense as the middle point of a scale, the zero can be used to represent the very edge (absolute beginning) of a collection. The question is meaningless because it is inconsistent between "tally values" and "count from zero".

So yes, computers do tally from zero, but they count from one. The two words bear different meaning.

# tal·ly [tal-ee]

noun

1. an account or reckoning; a record of debit and credit, of the score of a game, or the like.
2. anything on which a score or account is kept..
3. a number or group of items recorded.

# count [kount]

verb (used with object)

1. to check over (the separate units or groups of a collection) one by one to determine the total number; add up; enumerate: He counted his tickets and found he had ten.
2. to reckon up; calculate; compute.
3. to list or name the numerals up to: Close your eyes and count ten.

(dictionary.com)

The practical reasons are adequately described by Dougvj, I have nothing to add there. If only we could have a CS professor (from the 60s) to give a historical account...

-
To be completely pedantic, you're comparing a verb with a noun. I think "tally" and "count" really are synonymous, and both can be used as either a verb or noun. – Brian Apr 5 '13 at 16:25
@Brian A fair observation and and my intention is to illustrate (in a pedantic manner) that the confusion stems from a misinterpretation of terms. There isn't really a difference between "the 1st element" and "the element at position 0". They are both element one. The first, not "zeroth". There is no such thing as counting from zero. Enumeration starts at one by definition, while addressing may be a->1, b->2. c->3 or 0->1, 1->2, 2->3. The most common example of "counting from zero" can be found in middle school math books in the form of {x₀, x₁, x₂} - but the subscript is an index. – Ярослав Рахматуллин Apr 5 '13 at 18:01
It's just that designers actually wandered around quite a bit before they settled on the current scheme. What seems "obvious" now wasn't. And likely a somewhat different scheme could have been chosen and would now seem more "obvious" than what we have. – Daniel R Hicks Apr 7 '13 at 21:04

I think this has been covered before by "prof.dr. Edsger W. Dijkstra" - Burroughs Research Fellow in a letter dated 11 August 1982: c.f. EWD831

Titled: Why numbering should start at zero. "Are there reasons to prefer one convention to the other? Yes, there are...."

Note also that Dijkstra was on the ALGOL 68 design team late until 1968. Algol68 permits arrays either from 0, 1 or any number the programmer deems appropriate for the algorithm. c.f. ("The Making of Algol 68" recounts ' “Can you define triangular arrays?” someone (Tony Hoare?) interrupted. “Not just triangular, but even elliptical” replied Aad, and showed how. ')

Specifically, in Algol68, when arrays (& matrices) are sliced they get an index @1, so there is a bias towards [1:...] arrays. But the "1st" lower bound can be moved to start at the "0th" position by specifying "@0", e.g. vector x[4:99@2], matrix y[4:99@1,4:99@0]. Similarly there is a default/bias of from 1 in do ~ od loops (unless "from 0" is explicitly stated), and from 1 for the integer case i in ~,~,~ esac and \$c(~,~,~)\$ choice clauses.

It seems that Dijkstra's comments about the March 1968 Draft Report(MR93 ) and his insistences provoked what is arguably a pre-usenet flame war: "there are writings which are lovable although ungrammatical, and there are other writings which are extremely grammatical, but are disgusting. This is something that I cannot explain to superficial persons." EWD230

The Algol 68 Final Report(FR) came out on 20 Dec 1968 when it was resented at Munich Meeting and then adopted by the Working Group. Subsequently the report approved by the General Assembly of UNESCO's IFIP for publication.

Around Dec 23(?) 1968 Dijkstra, Duncan, Garwick, Hoare, Randell, Seegmuller, Turski, Woodger and Garwick signed the AB31.1.1.1 "Minority Report", page 7 (Published 1970).

-

The distance analogy someone else brought up lends itself to a very practical illustration:

"How far is your house from the nearest gas station?"

"1 mile."

"You live at the gas station?"

"No, if I lived at the gas station it would be 0 miles"

"Why are you counting from zero instead of from one?"

Another good example would be birthdays - we don't say someone's is one year old the day they are born, we say it's a year later.

We say leap years or US presidential elections are every four years, even though if you count from one: 2000, 2001, 2002, 2003, 2004 is five years. (Incidentally, the Romans did screw this up for a while, and had leap years too close together)

My point is, we "count" from zero all the time in the real world - "How many positions after [start of array] is the element you want" simply happens to be the question you are answering with a count-from-zero in many computer programs. You wouldn't say that the first element is one position after the start, would you? It is the start.

-
Your math concerning the elections is off by a year. Your example contains 2 election years within a 5 year span; the correct illustration would be that 4 years pass from one election to the next, i.e. 2000 -> 2001 (a 1 year span), 2001 -> 2002, 2002 -> 2003, 2003 -> 2004. – Jimmy Apr 5 '13 at 16:12
@Jimmy That was my point - if people "counted from one" in the sense they want computers to, they would count 2000 as one instead of as zero. This is, incidentally, how the ancient Romans actually did it (and would indeed describe a cycle like "2000, 2004, 2008" as a five-year cycle). – Random832 Apr 6 '13 at 23:08
Your birthday example is not universally true. For example, in South Korea the first year of life is counted as one instead of zero. – BennyMcBenBen Apr 9 '13 at 21:05

As already said by others computers do not count from zero.

Some languages index from 0. Indexing from 0 has two main advantages:

1. It converts to assembly in a natural fashion because it can be interpreted as an offset from a pointer to the first position.

2. You don't get weirdness when you want negatives. How many years between 1BC and 1AD? None. Because although BC is effectively negative dates, there's no year zero. Had there been 0AD there would be not any problem here. You see the same problem all over the place in science where people have naively defined the first element in a set as +1.

-
Also, if "1 mile" means "right here", then since a mile is 1760 feet, it means that "1760 feet" also means "right here", right? Wrong, "1 foot" means right here, oops! In this one based stupidity, "right here" is one foot, one inch, one centimeter, etc. – Kaz Apr 6 '13 at 3:10

### Counting naturally begins at zero

Here is the algorithm for counting apples in a basket:

count := 0

count := count + 1

After the execution of the above, count holds the number of apples. It may be zero, because baskets can be empty.

If you don't use your credit card for an entire month, do you get a bill of 1 dollar? Or 1 cent?

When you reset the trip meter on your car's odometer, does it go to 0001 or 0000?

### Arrays can provide multiple views of the same data

Consider an array of 32 bit structures d, which are each made of 16 bit words w. Each word is made up of two 8 bit bytes b. Under zero indexing, the overlay looks very convenient:

d: |   0   |   1   |
w: | 0 | 1 | 2 | 3 |
b: |0|1|2|3|4|5|6|7|

The 32 bit object d[1] as at the word address w[2] which is easily computed by multiplying the index by 2, which is the ratio of the sizes of the 32 and 16 bit object. Furthermore, in byte addressing, it is b[4].

This works because zero is zero, in every unit of measurement: byte, word, double word and so on.

Look at the above diagram: it looks much like a ruler, where unit conversions are intuitive.

With one based indexing, it breaks:

d: |   1   |   2   |
w: | 1 | 2 | 3 | 4 |
b: |1|2|3|4|5|6|7|8|

Now we cannot simply multiply the d index by 2 to get the w index, or by 4 to get the b index. The conversion between units becomes clumsy. For instance to go from d[2] to b[4], we have to calculate ((2 - 1) * 4) + 1 = 5.

We have to subtract out that pesky 1 bias in the d units, then do the scaling in the natural zero-based coordinate system, and then add back the pesky 1 in b units. Note that it's not the same 1! We subtract one double word width, but then add in one byte width.

Converting between different views of the data becomes something like Celsius-Fahrenheit conversion.

Those who say that one-based arrays are easy to deal with at the implementation level, because there is just a simple subtraction of 1 are fooling themselves, and you. This is true only if we do not do any scaling calculations among different data types. Such calculations happen in any program that has a flexible view on data (e.g. a multi-dimensional array also accessed as a one-dimensional one) or that manipulates storage: for example, a memory allocator, file system, or video frame buffer library.

### Minimizing Digits

In any base, if we want to use the fewest digits to implement a range of values which is a power of the base, we must start from zero. For instance, in base ten, three digits is enough to give us a thousand distinct values from 0 to 999. If we start from 1, we overflow by just one value, and we need four digits.

This is important in computers, because the number of digits in binary translates to hardware address lines. For instance a ROM chip with 256 words in it can be addressed from 0 to 255, which requires 8 bits: 00000000 to 11111111. If it is addressed from 1 to 256, then nine bits are needed. We have to wastefully add one more address trace to the circuit board or integrated circuit. So what would possibly happen in practice would be that 0 would just be called 1 at the software API level for accessing that chip. A request for word 1 would actually put 00000000 on the 8 bit address bus. Or else, a request for 1 would translate to address 00000001, as expected, but a request for 256 would map to the otherwise unused 8 bit address 00000000 rather than the 9 bit address 100000000. Both of these bag-biting kludges are really solutions in search of a problem, and are avoided entirely by consistently using 0 to 255 at the hardware, in the software and in all user interfaces and documentation.

### One-based displacements are fundamentally stupid

Consider Western music theory for instance. We have diatonic scales with seven notes, but we call the space which they cover an octave! Inversion of intervals then follows the rule of nine: for instance the inversion of a third is a sixth (subtract three from nine). So three different numbers are at play for something so simple: seven (notes in a scale), eight (octave) and nine (subtract from to invert).

If seven notes made a septave or heptave, and intervals were zero based, then we would subtract from seven to invert. Everything based on seven.

Furthermore, intervals could be easily stacked. In the current system, if we leap by a fifth and then by a fourth again, and then by a third, we cannot just add these. The resulting interval is two less. It is not a twelvth, but actually a tenth! At each stage, we have to subtract a one. Going up by a fifth and then a fourth isn't a ninth, but only an octave.

In a sanely designed music system, we could just add intervals to determine the resulting leaps. A sequence of notes which begins and ends on the same note would then have a property similar to the voltage law around a circuit: all the intervals would add to zero.

Music theory and writing is badly outdated. Most of it has not changed since the days composing was done with quill pens by the light of a candle.

### One-based systems confuse the same people who can't handle zero-based arrays

When the year 2000 rolled around, many people were confused why the new millennium hasn't begun. Those pointing out that it won't begin until 2001 were regarded as party poopers and dweebs. After all, you're in your 20's when you turn 20, right? Not when you turn 21. If you thought that the millennium started on January 1, 2000, then you have no right to complain about zero based arrays in any programming language. They work how exactly how you like. (But, yes, proponents of one-based displacements and arrays are dweebs and party-poopers. Centuries should start on the XX00 years, and millennia on X000 years.)

### Calendars are dumb, but at least time of day is zero based

Each new minute on your watch starts with :00 seconds. Each new hour starts with 00:00 minutes and seconds. And, at least on a 24 hour clock, the day rolls around when midnight strikes and 11:59:59 increments to 00:00:00.

Thus if you want to calculate seconds from midnight for a time like 13:53:04, you just have to evaluate 13 * 3600 + 53 * 60 + 4. No insipid 1 additions or subtractions.

Okay, what is it with musicians, even supposedly technical ones?

MIDI! It uses zero-based numbering for programs and channels in the actual wire representation of messages, but gear displays it as 1 based! For instance programs 0 to 127 are called 1 to 128 on most gear, but some calls them 0 to 127 or even gives the user a choice.

Programs 71 through 80 are a considered a "bank" of ten. It says so right on my MIDI pedal, for example. The footswitches are labeled from 1 to 10 and if I'm in the seventh bank, they pick programs 71 through 80. However, some devices or computer software displays the 1-128 program numbers as 0 to 127, or even gives the user a choice! What is worse: one-based systems, or chaos created by using both one and zero based at the same time?

MIDI channel numbers are called 1 to 16, but are represented by 0 to 15 binary. As if out of spite for the one-based presentation, some gear uses a dispswitch for configuring a channel number and, often, thes switches just use the zero based binary code. So if you want channel 3, you must toggle it to 0010 (binary 2).

-

Starting at zero is practical when describing a distance from something. So in this array:

[4,9,25,49]

the distance from the start of the array to the 25 is 2 - you need to skip two steps to get there. The distance to the 4 is zero - you don't need to move from the start at all.

It's practical to think like this when adding up distances (or indexes) - I advance one step, then zero steps, then two steps, where am I? I am at index 1 + 0 + 2 = 3. Skipping three steps, I end up at 49 in the array above.

-

If I recall correctly from my Programming Language Concepts class... languages being 0-indexed and others being 1-index had to do with historical reasons. Algol-68, the grand-daddy of programming languages was actually 1-indexed, as well as Fortran and a few other "business" languages like COBOL. In some of these languages however, you could actually specify explicitly what your starting index would be. There's a interesting table of this here.

Basically back in the "Ye Olde Days" mathematicians, scientists, and other "academics" usually used 0-indexed languages, while users of languages like COBOL found it of no use to start counting at 0, so in those languages it made more sense to start at 1 (it seemed less confusing).

Now if your question refers to why as far as why a computer (not a language) naturally starts to count from zero... well it's I guess inherent in the binary really: ex: 0000 = zero 0001 = one ... so on and so forth...

-
Has nothing to do with binary representation. Both binary and decimal numbers begins from 0 (as you show in your example). – Matteo Apr 5 '13 at 6:15

Number 0 could denote various meaning: numeric value, ordinal, memory address, etc.

'Index zero' doesn't means programmers count from zero. It denote the first place of an allocated memory block and '0' is the address of it.

In C, looping through an array could be written as below:

int arr[N];
for (i=0; arr[N]; ++i) {
...
}

Same work can be done in C#:

Object[] arr;

for (Object o in arr) {
...
}

I think there is no counting in both examples.

-

Remember how numbers are represented in a computer. Let's take a byte variable. 0 is represented as 000000001 in binary. 1 is 00000001. 2 is 00000010. And so on.

Note that the lowest number that a byte can store is 0. If we started array indices with 1, then the system would be inefficient, since we now have an array of length 255 instead of 256. Since numbers in a C program compile to binary numbers (ints usually, unsigned ints in array indices), it seems natural to use 0 as a starting index as it is more efficient.

Besides, in C++, a[p] unfolds to *(a+p*n), where n is the size of the datatype. In other words, a[p] means "Give me the element at index a+n*p". If p started with 1, then We'd have a blank/unused portion at index a.

1. Of course, the obvious question "why" arises. Why not set 00000000 to1? Simple: binary addition (done by cascades of full adder units) is easy in the hardware when 00000000 is 0. Binary addition is an integral part of all arithmetic operations. If you make it represent 1, you'd either need to tell the compiler to subtract 1 from all numbers, or you'd need to hardwire the adder circuits to subtract one first from the addends and tack it back on to the sum. (note that you can't just subtract one later, since the carry bit may be involved)

-

## Modulo

One thing the existing good answers don't mention yet: zero-based indexing works well together with modulo operations, which can therefore be combined to form cyclic list. Think for example about something like

color = colors[i % colors.length]

which might give each object (indexed by i) a different color from the list colors, until all colors have been used, at which point it would start again from the beginning. Expressing the same in one-based indexing is pretty clumsy:

color = colors[(i - 1) % colors.length + 1]

The automatic modulo operations imposed by fixed size unsigned binary arithmetic with wrap-around are another example of why this makes sense.

## Caters for both

Another thing to consider is the fact that it is pretty easy to not use the first element of a zero-based array. (This does not hold for foreach-style iteration and similar language constructs which treat the array as a whole.) Many programmers, myself included, might feel a bit awkward about the wasted space, but in most situations the amount is so tiny that these worries are unfounded. On the other hand, if languages are using one-based indexing, then there is no way to simulate an element at index zero without a lot of code. So given that in some situations zero-based indexing is better than one-based, choosing zero as the basis everywhere is the more flexible approach, as opposed to one-based everywhere, and it is also more consistent than configurable starting positions.

-

Computer systems use both natural numbers (counting from 0) and whole numbers (counting from 1). People count things in whole numbers, which makes them intuitive for numbering lists, and many programming languages take advantage of that: BASIC, COBOL, Fortran, Lua, and Pascal all count from 1. Those languages target niches like data processing, numerical analysis, and teaching, where simple, intuitive lists are an advantage.

Whole numbers become awkward when you start analyzing and manipulating the structure of data, instead of just processing everything in order. When you need to refer to sequences in a formula or algorithm, it's easier and less error-prone to number them from 0, like mathematicians do: a0, a1, an, etc. Otherwise, you must often adjust by +1 and –1 to get at the right data, and it's easy to get wrong, creating bugs. Therefore, languages designed for computer scientists typically use natural numbers: C, Java, and Lisp all count from 0.

Beyond programming languages, lots of computer systems number things from 0 because that's what computer scientists are used to. Also, because numbering from 1 leads to so many insidious bugs, many of us avoid it outside of interface elements designed strictly for non-technical end users.

-

The simple answer is that the first numeral is not 1 it is 0.

Explanation: The formula for calculating a multi-digit number in any base is:

n = sum(i=0 to n, Di^i)

WHERE
n = numeric result
i = index (starting with 0)
Di = is the digit at index i

Let's take the decimal system, it's the one we're most used to.

Looking at number 1234, we can write it as:

4 x 10^0 = 4
3 x 10^1 = 30
2 x 10^2 = 200
1 x 10^3 = 1000

in other words, sum of digits raised to the power if their index.

So, it's not just the computers, we, the people, count from 0 too.

-

An array index is the offset from the base memory location to the memory location of the element. Element i is then Base + i. The first element is located at the Base location, so it is at location 0 (Base + 0).

-

Apart from computational efficiency, there is also another aspect to counting. There are two ways to give each element in a sequence a sequential number:

1. The number of preceding (whole) elements (cardinal numbers)
2. The position of the element (ordinal numbers)

People's ages are cardinal numbers: in the first year after a baby's birth it is 0 years old, because it has been alive for zero whole years.

Years in dates are ordinal numbers: in the first year Anno Domini (AD), the year is 1 AD. There is no year 0, just like there is no zeroth anything.

Programming languages (such as Matlab and Mathematica) where an element's index represents its position in the array start counting from 1: the first element. In other languages (such as all C-based languages) an element's index is the number of preceding elements, and therefore the first element is 0.

Of course, Matteo is only partially correct when stating that zero-based indexing is more efficient.

element(n) = address + n * element_size

One-based indexing can be just as efficient provided all array addresses already have one element_size subtracted from them. This can be done when the array is allocated, in which case this is just as fast: