mailto: blog -at- heyrick -dot- eu


I got up late this morning. <cough>the small hand to the right side of 12</cough>
It was cold and grey and I just didn't feel like it. Instead, I sat in bed and read loads of websites, the content of most of which I've forgotten.

But one thing stayed with me.

The "FizzBuzz" test, and the number of comp.sci graduates and senior programmers that don't either have a clue how to answer it, or take quarter of an hour.
To the point where it's a nerdy interview question.

Write a program that prints the numbers 1 to 100.
But, for multiples of three print "Fizz" instead of the number,
and for multiples of five print "Buzz".
For numbers that are multiples of both three and five, print "FizzBuzz".


I'll admit that I suck at maths, so I'm still flummoxed by "Becky is on a train travelling south at 65mph" style questions, but this is a dead simple logic program.

ARM BBC BASIC V (C) Acorn 1989

Starting with 4190460 bytes free

   10FOR n% = 1 TO 100
   20  IF ( (n% MOD 3) = 0 ) THEN PRINT "Fizz";
   30  IF ( (n% MOD 5) = 0 ) THEN PRINT "Buzz";
   40  IF ( ((n% MOD 3) <> 0) OR ((n% MOD 5) <> 0) ) THEN PRINT STR$(n%);
   50  PRINT

The system ticker started at 23634331 and ended at 23642474, which is a difference of 8143 centiseconds. Or 81 seconds to type that into a TaskWindow.

I don't think I'm a great coder. I wouldn't even call myself a good coder. But anybody that takes more than twice as long as me to create a functional program should not be given employment in any situation that would depend upon their "programming" abilities.

Because this isn't testing programming, it's testing problem solving. And a pretty simple one at that.


For those who don't think BASIC is a real language, here's the equivalent in C:

#include <stdio.h>

int main(void)
   for (int num = 1; num <= 100; num++)
      if ( ( num % 3 ) == 0 )

      if ( ( num % 5 ) == 0 )

      if ( (( num % 3) != 0 ) && (( num % 5 ) != 0 ) )
         printf("%d", num);


   return 0;

Can we improve upon that? Sure. Use variables to save repeating the modulus calculation (which could be 'expensive') and use the ternary operator to shift the conditional into the printf itself.

#include <stdio.h>

int main(void)
   for (int num = 1; num <= 100; num++)
      int  isthree = num % 3;
      int  isfive  = num % 5;

      printf("%s", (isthree == 0) ? "Fizz" : "");
      printf("%s", (isfive  == 0) ? "Buzz" : "");

      if ( isthree && isfive )
         printf("%d", num);


   return 0;

Now, you might wonder if there's a penalty in having printf() evaluate the expression. The answer would be yes...if that's what happened. Luckily it isn't. The only penalty is in calling printf() to output nothing.

But the result might surprise you.

Here is what happens in the BASIC-like incarnation of the program:

; set the initial loop value to 1
000080A0 : .... : E3A04001 : MOV     R4,#1
; call through to _kernel_sdiv(divisor, dividend),
; returns division in R0 and remainder in R1
000080A4 : .... : E1A01004 : MOV     R1,R4
000080A8 : .... : E3A00003 : MOV     R0,#3
000080AC : .... : EB00018C : BL      &000086E4          ; call: x$divide
; place the remainder in R5, and set the flags...
000080B0 : .... : E1B05001 : MOVS    R5,R1
; order to call _printf for "Fizz" if the result was non-zero
000080B4 : .... : 028F0F12 : ADREQ   R0,&00008104       ; -> string: "Fizz"
000080B8 : .... : 0B00019B : BLEQ    &0000872C          ; call: _printf
; do the division again, this time for modulus 5
000080BC : .... : E1A01004 : MOV     R1,R4
000080C0 : .... : E3A00005 : MOV     R0,#5
000080C4 : .... : EB000186 : BL      &000086E4          ; call: x$divide
; as before, place the remainder in R7 and set the flags...
000080C8 : .... : E1B07001 : MOVS    R7,R1
; order to print "Buzz" if the result was non-zero
000080CC : .... : 028F0F0E : ADREQ   R0,&0000810C       ; -> string: "Buzz"
000080D0 : .... : 0B000195 : BLEQ    &0000872C          ; call: _printf
; pick up the previously calculated remainders and compare
000080D4 : .... : E3550000 : CMP     R5,#0
000080D8 : .... : 13570000 : CMPNE   R7,#0
; if they were both zero, invoke _printf to output the number
000080DC : .... : 11A01004 : MOVNE   R1,R4
000080E0 : .... : 128F0F0B : ADRNE   R0,&00008114       ; -> string "%d"
000080E4 : .... : 1B000190 : BLNE    &0000872C          ; call: _printf
; now output the newline
000080E8 : .... : E28F0F0A : ADR     R0,&00008118       ; -gt; # 10 (&A)
000080EC : .... : EB00018E : BL      &0000872C          ; call: _printf
; increment the loop counter and repeat until it's 100
000080F0 : .... : E2844001 : ADD     R4,R4,#1
000080F4 : .... : E3540064 : CMP     R4,#&64            ; ="d" (100)
000080F8 : .... : DAFFFFE9 : BLE     &000080A4
; exit
000080FC : .... : E3A00000 : MOV     R0,#0
00008100 : .... : E91BA8B0 : LDMDB   R11,{R4,R5,R7,R11,R13,PC}
; data follows:
00008104 : Fizz : 7A7A6946 : BVC     &01EA2624
00008108 : .... : 00000000 : ANDEQ   R0,R0,R0
0000810C : Buzz : 7A7A7542 : BVC     &01EA561C
00008110 : .... : 00000000 : ANDEQ   R0,R0,R0
00008114 : %d.. : 00006425 : ANDEQ   R6,R0,R5,LSR #8
00008118 : .... : 0000000A : ANDEQ   R0,R0,R10


And for comparison, here's a breakdown of the "optimised" version:

; set the initial loop value to 1
000080A0 : .... : E3A04001 : MOV     R4,#1
; call through to _kernel_sdiv(divisor, dividend),
; for modulus 3 and place the remainder into R6
000080A4 : .... : E1A01004 : MOV     R1,R4
000080A8 : .... : E3A00003 : MOV     R0,#3
000080AC : .... : EB000194 : BL      &00008704          ; call: x$divide
000080B0 : .... : E1A06001 : MOV     R6,R1
; call through to _kernel_sdiv(divisor, dividend),
; for modulus 5 and place the remainder into R5
000080B4 : .... : E1A01004 : MOV     R1,R4
000080B8 : .... : E3A00005 : MOV     R0,#5
000080BC : .... : EB000190 : BL      &00008704          ; call: x$divide
000080C0 : .... : E1A05001 : MOV     R5,R1
; compare the MOD 3 remainder
000080C4 : .... : E3560000 : CMP     R6,#0
; if not-equal, set up to print a blank string
000080C8 : .... : 128F1F13 : ADRNE   R1,&0000811C       ; -> string: ""
; otherwise set up to print "Fizz"
000080CC : .... : 028F1F13 : ADREQ   R1,&00008120       ; -> string: "Fizz"
; do the output
000080D0 : .... : E28F0F14 : ADR     R0,&00008128       ; -> string: "%s"
000080D4 : .... : EB00019C : BL      &0000874C          ; call: _printf
; same as the above, for MOD 5 to output "Buzz"
000080D8 : .... : E3550000 : CMP     R5,#0
000080DC : .... : 128F1F0E : ADRNE   R1,&0000811C       ; -> string: ""
000080E0 : .... : 028F1F11 : ADREQ   R1,&0000812C       ; -> string: "Buzz"
000080E4 : .... : E28F0F0F : ADR     R0,&00008128       ; -> string: "%s"
000080E8 : .... : EB000197 : BL      &0000874C          ; call: _printf
; if both remainders are non-zero, output the number
000080EC : .... : E3560000 : CMP     R6,#0
000080F0 : .... : 13550000 : CMPNE   R5,#0
000080F4 : .... : 11A01004 : MOVNE   R1,R4
000080F8 : .... : 128F0F0D : ADRNE   R0,&00008134       ; -> string: "%d"
000080FC : .... : 1B000192 : BLNE    &0000874C          ; call: _printf
; finally the newline
00008100 : .... : E28F0F0C : ADR     R0,&00008138       ; -> # 10 (&A)
00008104 : .... : EB000190 : BL      &0000874C          ; call: _printf
; increment the loop counter, repeat until it's 100
00008108 : .... : E2844001 : ADD     R4,R4,#1
0000810C : .... : E3540064 : CMP     R4,#&64            ; ="d"
00008110 : .... : DAFFFFE3 : BLE     &000080A4
; done
00008114 : .... : E3A00000 : MOV     R0,#0
00008118 : .... : E91BA870 : LDMDB   R11,{R4-R6,R11,R13,PC}
; data follows:
0000811C : .... : 00000000 : ANDEQ   R0,R0,R0
00008120 : Fizz : 7A7A6946 : BVC     &01EA2640
00008124 : .... : 00000000 : ANDEQ   R0,R0,R0
00008128 : %s.. : 00007325 : ANDEQ   R7,R0,R5,LSR #6
0000812C : Buzz : 7A7A7542 : BVC     &01EA563C
00008130 : .... : 00000000 : ANDEQ   R0,R0,R0
00008134 : %d.. : 00006425 : ANDEQ   R6,R0,R5,LSR #8
00008138 : .... : 0000000A : ANDEQ   R0,R0,R10


Now, isn't that interesting? The "optimised" code is six words longer, requires two more words of data, calls printf all the time to output nothing, and stashing the result of the modulus calculations in variables...actually made no difference at all as the compiler already spotted that.

There's a lesson to be learned here about unnecessary optimisation, and maybe not trusting your compiler. ☺


Bad English

At work we have "medical" face masks. By the brand "Mutexil", they are made in China (like everything else) and shipped in sachets of 50.

The box has the following instructions:

Mask instructions
Mask instructions.

The first obvious error is the English translation of step #4:

A well-worn mask covers the mouth and nose.

The second obvious error is the recommendations:

Use it only for one care, do not reuse it.

It is rather obvious that the instructions were originally written in French, as you would expect for a box made for a French company, and then they had somebody "who had a piece of paper from some university" provide the translations into English.

Because, sadly, these days it seems that a piece of paper attests to so much more than being a native speaker of the language. I mean, was it really so hard to find an actual British person?

Pour les français, "well-worn" est pareil que "bien usé"! ☺

And I know it was a half-arsed human translation, because Google Translate correctly says "A properly worn mask...".

Likewise, the peculiar (to my ears) French phrase is painfully literally translated into English to arrive at the gibberish "Use it for only one care - do not reuse it". The word "care" just doesn't mean the same thing.
Again, Google Translate gets a better result with "To be used for one treatment only, do not reuse."
However, personally, I'd go with "Single use only, do not reuse." (the "do not reuse" is superfluous but some people are thicker than the average brick).

Seriously, though. Exactly how hard is it to find a native English speaker in France? Don't rely on some random person with a diplôme. They appear to suck at programming, and clearly they also appear to suck at foreign languages...
Me? I don't have fancy pieces of paper, but I have something no newbie university graduate has - experience. I've been blathering bollocks in my native language for forty six years, so consider me suitably experienced to point out crappy translations.


Wait, forty six years? Yup. Not forty nine (my age) because I didn't speak for the first two years 'cos, you know, "waaaah". Also, I spent about a year communicating in sign language because I don't really like speaking. Writing is much more my speed as I can think carefully about what I want to say, and then write crap.



Your comments:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

Anon, 5th March 2023, 18:48
We used to do something similar when I was in primary school, going around the class. Shout out the number, unless it ended in a 5 (fizz) or a 0 (buzz). Get it wrong and you're out. Last one standing gets the tube of blackcurrant Chewits on the teacher's desk. Etc etc. 
That's even easier to code: 
for (n=1;n<100;n++) 

if (n/5=int(n/5)) write("Fizz\n"); 
elseif (n/10=int(n/10)) write("Buzz\n"); 
else write(n."\n"); 

(Let's see if the comment code preserves the formatting!)
Anon, 5th March 2023, 18:52
Of course that should be a double-equals sign in the example above, (n/5==int(n/5)), but there's no way of editing the comments. Rick - you were writing in BASIC to start with then switched to C, it's your fault!
Rick, 5th March 2023, 19:23
What language is that? 
It looks like C, but you're using write rather than printf, writeln, or puts (depending on system) and what looks like a function to make a number an int instead of just casting. 
Write could be JavaScript, but then you'd be using Math.floor (or something like that) to convert to integer? 
No, the comment code won't preserve formatting as it's a proportionally spaced font.
Rick, 5th March 2023, 19:25
BASIC was the quickest way to bash out some code in a TaskWindow. 
C, for "real programmers". I was about to delete the C code and just go with the BASIC until I noticed that the improved version was worse. ;) 
That's worthy of discussion at least, no?
Rick, 5th March 2023, 19:35
Anyway, you and me. We grew up in a time when opening the editor and bashing out some code would be the quickest way to get a job done. 
We had simple machines (think of the likes of the Beeb or the Archimedes range) so had to think about what we wanted and write it, and we learned through the course of debugging what didn't work. Like your accidental omission of the second equals sign. That would make it an assignment in C (though any half competent compiler should warn you) so the program will act wrong in a specific way to guide you to spotting the problem. 
Do graduates these days just not write software? Or are they so busy with weird niche things ("design an emulator for a four bit CPU") that they can't handle the simple stuff? 🤷🏻‍♀️
S, 5th March 2023, 23:56
"think carefully about what I want to say, and then write crap" 
Heyrick: mind blowing amounts of snark sneaked in to serious topics 
J.G.Harston, 6th March 2023, 12:23
PRINT STR$(n%) ? That's just PRINT ;n% PRINT already calls STR$ to convert a numeric into a printable string. 
I've seen this so many times it really puzzles me. Do people really think that PRINT can't print numbers?
J.G.Harston, 6th March 2023, 12:37
"Do graduates these days just not write software?" 
Undergradates in *my* day 35 years ago didn't write software. I spent three years at university on my Computing Science course thinking "when are we going to do some, y'know, *actual* computing?" 
The mistake I made was the prevailing culture/knowledge/system never told me that "computing" wasn't, well, what we called "computing". I'd been programming computers, designing and building software and hardware for at least six years before uni. I'd spent one summer building a blood particle counter interface and monitoring thingy at the local hospital. To me, and my peers, that was "computing". So, naturally, when going to university picked a "computing" course. And then wondered why I wasn't doing any "computing". 
When I met my who-was-to-be wife at uni she was doing a one-year "IT" course - yet she was doing what I expected a "computing" course to do - writing an assembler/ disassembler/ interactive emulator package. WTF????? "IT" is *USING* computers. WTH haven't I been doing what the mis-named "IT" course is doing and been usefully using the skills and knowledge I've built up over the last eight years? 
Nobody told me that what I should have gone for was courses called something like "electrical engineering". And if you *had* told me, I'd have said "I don't want to be an electrician, I want to do *computing*!". 
It is still going on. University courses called anything "computing" are actually IT courses, or worse, Office Admin. YOU DO NOT NEED A DEGREE TO HAVE A JOB RESETTING PASSWORDS AND CHANGING PRINTER CARTRIDGES!!!! Yet that is exactly what the degree mill/job market/industrial complex actually feeds people through. 
David Pilling, 6th March 2023, 12:52
I once read a long discussion about fizz buzz and similar on the register. Another example was how to swop the contents of two variables. Conclusion people who are going to be programmers today don't need this kind of skill. 
I look at the fb problem, and think I'm not going to use mod - ooh too expensive. Alas the world has moved on from counting cycles and bytes. 
By now there are programmers who don't like writing code, just getting their foot in the door for management, programmers who have not ever written much code, just read books. 
The code keeps coming though - no shortage - a lot of it is rubbish - who cares, your phone/tv is a throwaway item along with its software. 
I reckon in 1987 I was one in 10,000 in the world for programming skills and knowledge. Now one in 100 million. And most of them have more saleable skills than I do. 
My favourite programming problem is "write a program, the effect of which when run is to print out a copy of itself". 
I can't recall how to solve it. Although I once had the solution printed in Personal Computer News. 
copy filename.txt serial:  
is not allowed. 
Yup it is a mystery why companies don't get native speakers to write their documentation. Bit like why they don't get competent programmers to write the drivers. 
Rick, 6th March 2023, 15:02
JGH - it's pretty obvious that PRINT can print numbers, but left to its own devices it'll put them over there, not over here on the left. Using STR$ defeats this. 
I wasn't aware of the semicolon prefix. Nice trick, thanks. 
Yeah, at school I started Computer Studies (CS) and part of our assignment was to write a database program in BASIC that could index records to load them off disc (and thus not be restricted by memory). 
He left, a new teacher arrived. He was only qualified to teach IT, which meant he explained in simple words what a database *was*, and was quite lost when I handed him a wodge of fanfold and said I didn't need to know any of this, here's a database I wrote last term... I spent far too many lessons playing Chuckie Egg with the speaker disconnected, rather than, you know, learning anything. 
Thank god for Acorn User's Yellow Pages. 
You need a degree to change a printer cartridge these days? Damn. I've repaired and rebuilt laser printers, fixed inkjets, and the odd mechanical photocopier. 
Is there a Uni out there willing to give me a pointless piece of paper, or do I need to help the President or the Provost buy their next yacht first? 
David: I think the primary skill of a successful programmer these days is to cope with management bullshit. You need to be able to spew endless buzzwords with a straight face, and deal with office politics. 
The self reproducing program is a Quine. Part of the definition is that it takes no input, so it can't cheat and read it's own source code. 
Gavin Wraith, 6th March 2023, 16:47
bq. The self reproducing program is a Quine.  
Here is one in BASIC. 
a$=CHR$(34):d$=CHR$(58)+CHR$(99)+CHR$(36)+CHR$(61):b$="a$= CHR$(34):d$=CHR$(58)+CHR$(99)+CHR$(36)+CHR$(61):b$=":c$=":P.b$+a $+b$+a$+d$+a$+c$+a$+c$":P.b$+a$+b$+a$+d$+a$+c$+a$+c$ 
and one in Lua: 
#! lua 
for line in io.lines (arg[0]) do 
print (line) 
end -- for 
Just run them in a taskwindow.
David Pilling, 6th March 2023, 18:06
Thanks, I have never heard of the term "Quine", I encountered the idea as a problem in a programming book. Like a lot else, judging from wikipedia, it has been subject to much attention over the 40 years since.
Gavin Wraith, 6th March 2023, 19:27
The Lua example is cheating according to Rick's criterion because arg[0] inputs the pathname of the program itself. A proper Quine has to treat itself in two ways: as a piece of text and as an executable command. They are read-once jokes, really, like the contents of a fortune cookie.
Rick, 6th March 2023, 21:01
I'll point you at the master:
Anon, 7th March 2023, 01:51
The example I posted earlier was a sort of weird pseudo-code that I made up on the spot, just to demonstrate a concept. BBC BASIC doesn't have 'elseif', which sucks once you've got used to using it. 
Basically a weird amalgamation of C, Javascript and PHP. (Prefix all the variables with $ and that would probably run in a PHP interpreter.) 
I like writing pseudo-code when I'm trying to figure out how to tackle a problem. It annoys the hell out of people when they can't figure out what language I'm using. 
If you're using a macro assembler it's also fun to alias 'MOVS PC,LR' (in ARM) to 'RTS'. That really messes with people's heads.
Anon, 7th March 2023, 01:56
So if you wanted to run that code as PHP: 
for ($n=1;$n<100;$n++)  

if ($n/5==intval($n/5)) echo "Fizz\n";  
elseif ($n/10==intval($n/10)) echo "Buzz\n";  
else echo $n."\n"; 

The 'intval()' function in PHP does basically the same thing as INT() and VAL() combined in BBC BASIC. Very useful.
J.G.Harston, 7th March 2023, 21:44
"there are programmers who ... just getting their foot in the door for management" 
This is another thing that infuriates me. If you want to be a manager, WTF are you going into a doing job? If you want to be a manager, go into a ****g *MANAGEMENT* job. 
J.G.Harston, 7th March 2023, 21:49
IF A=1 PRINT "ONE" ELSE IF A=2 PRINT "TWO" ELSE PRINT "something else" 
Anon, 7th March 2023, 22:48
The 'elseif' construct (available in most languages with C-like syntax) works slightly differently - JGH, what you've done there is nested IFs. 
Basically works like this: 
if (first condition) { /* do this */ } 
elseif (second condition) { /* do this instead */ } 
elseif (third condition) { /* nope, maybe this */ } 
else { /* fall through and do this */ } 
The 'else' in that construct works kinda like the "OTHERWISE" statement in BBC BASIC V (when used with CASE - WHEN). 
As for the previous post about getting a foot in the door... 20 years back I worked for a small company doing IT stuff. Some of which involved writing back-end code for web sites. In VBscript. (I did eventually 'persuade' them to migrate everything to PHP/MySQL but that's another story.) 
So the IT manager complained bitterly because I always put "option explicit" at the top of all my code. If you're not familiar with VBscript, by default variables are 'implicit' and will be assigned a value of zero (for numeric) or null (for strings) when first used. Similar to Commodore BASIC back in the days of the C64. 
By putting "option explicit" at the top of the VB source file it forces you to define all variables before using them, and throws an error if you attempt to use an undefined variable (similar to BBC BASIC). Much more sensible as it stops numerous bugs creeping into the code, particularly when the IT manager has the spelling ability of one of the infinite number of monkeys that are currently typing out Shakespeare. 
His response was to call me out for using explicit variable declarations because 'the code keeps giving errors with it turned on'. 
Eventually I won that argument, but it was a hollow victory.
Rick, 7th March 2023, 23:22
When I used to program in VB, it was always OPTION EXPLICIT. 
It's only ancient or toy languages that don't require you to predeclare (not because declaring is a special thing, but mostly because declaring stops a variety of odd little bugs because the language now won't quietly assign values to typos). 
Anon, 8th March 2023, 10:18
Yup. Commodore BASIC (as on the C64 and VIC-20) didn't require variables to be declared before they were used. 
BBC BASIC did. And it was much better. Far better for your program to throw a "No such variable at line XXX" error during testing then go figure out what the typo was, than for the code to run but give garbage output because it was referencing an unknown variable. 
Or worse... where the typo was a variable that pointed to a block of memory. For example (screen memory on the VIC-20 started at 7680 - why do I still remember this): 
FOR I=0 TO 506 
The typo'd variable 'DC' (D is next to S on the keyboard, remember?) will be assigned a value of 0 the first time it's referenced. 
So instead of writing space characters (32) to the screen memory, the code above will write 32 to every byte in zero page as well as obliterating most of the 6502 CPU stack. 
Bearing in mind the VIC (and C64) didn't have a reset button, this would result in an 'IT Crowd' moment - ie 'turn it off and on again'. 
This of course was on a single-tasking 8-bit system. Now imagine the chaos if a user-mode application on a multitasking system was somehow able to overwrite zero page memory? Hmmm...
J.G.Harston, 9th March 2023, 02:19
What would your code do with: 
for (a=0, a++, a=5) { /* guessing your syntax */ 
if (a == 1) { writeln "one"; }  
elseif (a == 2) { writeln "two"; }  
elseif (a == 3) { writeln "three"; }  
else { writeln "something else"; }  

I expect it would write 
something else 
something else 
something else 
which is exactly what 
FOR A=0 TO 5 
IF A=1 PRINT "one" ELSE IF A=2 PRINT "two" ELSE IF A=3 PRINT "three" ELSE PRINT "something else" 

Add a comment (v0.11) [help?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 47102 backwards.
Your comment
French flagSpanish flagJapanese flag
«   March 2023   »

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index



Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.


Last read at 15:51 on 2024/06/19.

QR code

Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0


© 2023 Rick Murray
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.


Have you noticed the watermarks on pictures?
Next entry - 2023/03/06
Return to top of page