A Retro Adventure: Implementing an Aspect Ratio Calculator for the Commodore 64 in Multiple Programming Languages
Tags: commodore64, retrocomputing, math, assemblylanguage
Published: 2023-08-07 09:38:58

---
title: A Retro Adventure: Implementing an Aspect Ratio Calculator for the Commodore 64 in Multiple Programming Languages 
published: true
description: In this article, I explore the possibility of using C64 to solve mathematical problems. Using the example of solving the proportion, I will show how BASIC, C, and MOS 6510 (6502) Assembly can be used for these purposes using the BASIC functions for the floating point operation.
tags: #commodore64, #retrocomputing, #math, #assemblylanguage
# cover_image: https://cdn-images-1.medium.com/max/4372/1*Y4MnZOySXUe2G8wqx1Y0TQ.jpeg
# Use a ratio of 100:42 for best results.
# published_at: 2023-08-07 09:32 +0000
---
![](https://cdn-images-1.medium.com/max/4372/1*Y4MnZOySXUe2G8wqx1Y0TQ.jpeg)

In this article, I explore the possibility of using C64 to solve mathematical problems. Using the example of solving the proportion, I will show how BASIC, C, and MOS 6510 (6502) Assembly can be used for these purposes using the BASIC functions for the floating point operation.

## What do I want to do?

![](https://cdn-images-1.medium.com/max/2048/1*-MTuEAQaERdVUMMFl_ng2g.png)

And so I continue my journey through time in the 80s. I’m trying to settle down here and use C64 as a tool for study and work.

In past articles:

[Time-Travelling to Commodore 64: Hello World Program on C.](https://dev.to/medvecky/time-travelling-to-commodore-64-hello-world-program-on-c-2kj3)

[Time-Travelling to Commodore 64: Hello World Program on Assembly.](https://dev.to/medvecky/time-travelling-to-commodore-64-hello-world-program-on-assembly-296b)

I learned a little about the tools I can use: BASIC, Assembly, C. Contrary to my expectations, C could have been better here than BASIC and Assembly. It’s time to do something more exciting and valuable than just “Hello, world”.

Problems from the school mathematics course for solving proportions are best suited for this. The formula for equivalent ratios looks like this.

![](https://cdn-images-1.medium.com/max/2000/1*bylbQPOFf_TvEY3nLgrPuA.png)

Given three knowns and one unknown, the unknown can continuously be computed. For example, in the formula n1 / d1 = n2 / d2, if d1 is unknown, it can be calculated as follows d1 = n1 * d2 / n2.

The proportions are simple enough to solve without mastering programming tools. At the same time, the proportion calculator is quite a helpful thing.

It can be used for various calculations:

 1. The ratio in proportions in the preparation of recipes

 2. Conversion from one unit of measurement to another

 3. Percent-related problems.

 4. etc

## First Approach

I made the prototype in BASIC.

![](https://cdn-images-1.medium.com/max/2000/1*MivunO5DHN4wbEu3WCuyAQ.png)

![](https://cdn-images-1.medium.com/max/2000/1*zRe-KqrYILeyYi7LMM7qCw.png)

Works well enough.

![](https://cdn-images-1.medium.com/max/2000/1*FLVMb5zog_3vuajONKx8iA.gif)

There is implemented following algorithm. nj

 1. I read the parameters and used 0 to sign that the parameter is unknown.

 2. Using branching, I select the desired formula for the calculation.

 3. I bring out the result.

 4. I offer the choice to continue the work or exit the program.

Now I want to make a more compact program version with my design.

Since I still feel unsure about the MOS 6510(6502) Assembly, I decide to try C again on C64.

Of course, I can also do the screen design in BASIC, but I need to be more active to remember all this again. Still, I want to use more advanced tools.

## Big fail with C.

And so the implementation in C.

    void setUpScreen( void )
    {
       clrscr();
       defaultBGColor = bgcolor( COLOR_BLACK );
       defaultBorderColor = bordercolor( COLOR_BLACK );
       defaultTextColor = textcolor( COLOR_GREEN );
    }

I started by setting screen parameters using library functions. Functions have an exemplary implementation. When selecting new parameters, they return the original ones. It is very convenient to save them to restore the settings when I exit the program.

Usage hint output function

    void usage( void )
    {
       cputsxy( 12, 0, "RATIO CALCULATOR" );
       cputsxy( 12, 2, "N1/D1 = N2/D2" );
       gotoxy( 0, 4 );
       puts( "Input ratio pramams by prompt" );
       puts( "Press any key to continue" );
       puts( "or q to exit" );
    }

Here I use both library functions for C64 and standard C functions for text output and positioning.

Argument input function.

    void getArgs( int * n1, int * d1, int * n2, int * d2 )
    {
       cursor( 1 );
       printf( "%s? ", "n1 (zero if unknown)" );
       scanf( "%d", n1 );
       printf( "%s? ", "d1 (zero if unknown)" );
       scanf( "%d", d1 );
       printf( "%s? ", "n2 (zero if unknown)" );
       scanf( "%d", n2 );
       printf( "%s? ", "d2 (zero if unknown)" );
       scanf( "%d", d2 );
       cursor( 0 );
    }

I use a library function for C64 to turn the cursor blinking on and off.

Response Calculation Function

    char getAnswer( int n1, int d1, int n2, int d2 )
    {
       int answer = 0;
    
       if ( n2 == 0 )
       {
          answer = d2 * n1 / d1;
          printf( "n2 = %d\n", answer );    
       }
       else if ( d2 == 0 )
       {
          answer = n2 * d1 / n1;
          printf( "d2 = %d\n", answer );    
       }
       else if ( n1 == 0 )
       {
          answer = d1 * n2 / d2;
          printf( "n1 = %d\n", answer );    
       }
       else if ( d1 == 0 )
       {
          answer = n1 * d2 / n2;
          printf( "d1 = %d\n", answer );    
       }
       else
       {
          puts( "Wrong params" );
       }
       
       puts( "Press any key to continue" );
       puts( "or q to exit" );
       
       return cgetc();
    }

An unpleasant surprise awaited me, the C compiler available does not support floating point numbers.
This is one more point in favour of using Assembly for development under C64.

In this function, I violated the principle of sole responsibility
Because in addition to calculating the answer, the operator’s choice to continue the calculations or exit is returned here and also displays a hint.
I’ll probably fix this in the future.

The last function I wrote is to restore the screen state before exiting the program.

    void resetDefaultScreen( void )
    {
       clrscr();
       bgcolor( defaultBGColor );
       bordercolor( defaultBorderColor );
       textcolor( defaultTextColor );
       * ( char *  ) 0xD018 = 0x15;
    }

Here I use the same functions as in the screen settings.
I also use the system variable setting.
This command is equivalent to POKE 53272.21, which sets the output mode of letters to uppercase.

The main function looks like this.

    int main ( void )
    {
       int n1 = 0;
       int d1 = 0;
       int n2 = 0;
       int d2 = 0;
       unsigned character = 0;
       int answer = 0;
       
       setUpScreen();
    
       usage();
       character = cgetc();
       
       while ( character != 'q' )
       {
          getArgs( &n1, &d1, &n2, &d2 );
          character = getAnswer( n1, d1, n2, d2 );
          
          clrscr();
       }
    
       resetDefaultScreen();
       
       return EXIT_SUCCESS;
    }

It uses the C64 library function to clear the screen.

Here is a working example

![](https://cdn-images-1.medium.com/max/2000/1*ehTXUXjd1TEIwRGZfWakyQ.gif)

[The complete source code of the program is available here.](https://gist.github.com/medvecky/020dca571c25b844de123f1b5af55f70)

The executable file turned out to be 7167 bytes.

Calculating the proportions of the possibility of a floating point operation could be more helpful. So I take up the implementation in Assembly.

## Assembly Saves the World.

![](https://cdn-images-1.medium.com/max/2048/1*0TCFMKSbPgA6BeIxg_z5gA.png)

I’ll start as before with the preparation of the screen.

    prepare_screen:
    // set gren text color
            lda     #5
            sta     text_color
            jsr     clearscreen
    
    // set border and background black
            lda     #$00
            sta     border
            sta     background
            rts

Here, the same thing happens as in the analogue in C. I write to the system variables at the desired addresses and values through the accumulator.

Here is the main hint.

    main_usage:
            ldx     #$00
            ldy     #$0a
            jsr     set_cursor
            
            lda     #<usage_1
            ldy     #>usage_1
            jsr     print_str
    
            lda     #new_line
            jsr     print_char
            jsr     print_char
            
            lda     #<usage_1_1
            ldy     #>usage_1_1
            jsr     print_str
    
            lda     #new_line
            jsr     print_char
            jsr     print_char
    
            lda     #<usage_2
            ldy     #>usage_2
            jsr     print_str
    
            lda     #new_line
            jsr     print_char
    
            lda     #<usage_3
            ldy     #>usage_3
            jsr     print_str
            lda     #new_line
            jsr     print_char
            rts

Here I use three kernal functions

 1. set_cursor (0xE50C) Arguments are passed through X Y registers

 2. print_char (0xFFD2) character code for output must be in the accumulator

 3. print_str(0xAB1E) in register A low byte of row address in Y high byte

The string must end with ‘\0’ as in this example.

    usage_1:
            .text   "RATIOS CALCULATOR"
            .byte   $00

Next comes a piece of code responsible for waiting and processing the user’s reaction.

    wait_for_continue:
            jsr     getin
            beq     wait_for_continue
            cmp     #q_sym
            beq     restore_and_exit_jmp
            jmp     get_args
    
    restore_and_exit_jmp:
            jmp     restore_and_exit

To read a character into the accumulator, I use the getin(0xFFE4) function

Three checks follow.

 1. If the accumulator is 0, the user did not enter anything. Repeat the entry procedure.

 2. If a user selects “Q “ as an exit, I proceed to the appropriate procedure.

 3. If any other character, I jump to calculations.

Calculations begin with the collection of arguments

The following function performs them.

    get_arguments:
            jsr     input_n1_proc
            jsr     input_d1_proc
            jsr     input_n2_proc
            jsr     input_d2_proc
            jsr     cursor_blink_off
            rts

Functions run similar functions for receiving each argument and, in the end, call the function to turn off the cursor blinking.

    cursor_blink_off:
            lda     $00cf
            beq     cursor_blink_off
            lda     #$01
            sta     $cc
            rts

According to the state of the system variable 0x00CF, this function waits for the moment when the cursor will go out and then writing one to the system variable 0x00CC disables the cursor blinking.

The function for getting an argument is already more complicated.

    input_n1_proc:
            jsr     input_n1_prompt
            jsr     input_string_proc
            jsr     string_to_fp
            ldx     #<n1
            ldy     #>n1
            jsr     fp_store_fac_to_ram
            lda     #space_sym
            jsr     print_char
            lda     #new_line
            jsr     print_char
            rts

The input_prompt function prints an input prompt to the screen as described.
Next, the operation of reading the string representation of the argument from the keyboard starts.

    input_string_proc:
            ldy #$00
            ldx     #$00
            lda     #$00
            sta     counter
    input_get:
            jsr     getin
            beq     input_get
            cmp     #$0d
            beq     input_string_end
            cmp     #$14
            bne     increase_counter
            jsr     print_char
            ldx     counter
            dex
            stx     counter
            lda     #$00
            sta     input_string,x
            jmp     input_get
    increase_counter:
            ldx     counter
            sta     input_string,x
            jsr     print_char
            inx
            stx     counter
            cpx     #string_length
            bne input_get
    input_string_end:
            rts

In this function, I first reset the accumulator, all index registers, and the counter.

I call the get_in function

Next, I check for the following cases

 1. If the user presses return, the input is complete.

 2. If the user has entered backspace, I display the backspace character. Showing this character causes the cursor to jump back one character visually. Next, I decrease the counter of entered characters and delete the previously entered character in the buffer by writing zero in its place. After that, I return to the beginning of the procedure.

When entering any other character, I perform the following actions.

 1. I store the character in the buffer with an offset equal to the counter of the entered characters.

 2. I display the character on the screen.

 3. I increment the counter.

 4. I check the counter. If the maximum is reached, then an entry is terminated.

 5. Otherwise, I proceed to the processing of the next entered character.

Further, the function input_n1_proc continues to be executed.
After the input of the string is completed, I use the following function
to convert the string to float.

    string_to_fp:
            lda     #<input_string
            sta     $22
            lda     #>input_string
            sta     $23
            lda     #string_length
            jsr     fp_string_to_fac 
            jsr     clear_input_string
            rts

To do this, I use the existing BASIC function that can be called through the address (0xB7B5). As arguments at addresses 0x22 0x23, I need to place the low and high byte of the address of the buffer with the string, in register A, the length of the entered string. Next, I call the conversion function. The function puts the received number in the FAC, which is located at the following addresses in the RAM:

* Address 97/$61 is the exponent byte.

* Addresses 98–101/$62–$65 hold the four-byte (32 bit) mantissa.

* Address 102/$66 stores the sign in its most significant bit; 0 for positive, $FF (-1) for negative.

* Address 112/$70 contains rounding bits for intermediate calculations.

Then I clear the buffer by writing 0 to it.

After that, I write the number from the FAC to a variable using the function (0xBBD4) . Funcion needs address of 5 bytes variable as argument where will store value from FAC.

One important point some of operations with FAC corupt data in FAC. Fro example function for print FAC to scree. That’s why need in first store data from FAC before any operation with FAC.

Next, I output space to overwrite the courses and translate the line.

Similarly, I get and save all the necessary variables.

Next block of code

    //check n1 
            lda     #<n1
            ldy     #>n1
            jsr     fp_load_ram_to_fac
            lda     #<fp_zero
            ldy     #>fp_zero
            jsr     fp_cmp
            cmp     #$00
            beq     solve_for_n1
       
    ..................................
            
            lda     #<wrong_params
            ldy     #>wrong_params
            jsr     print_str
            lda     #new_line
            jsr     print_char
            
            jmp     wait_to_exit
    
    solve_for_n1:
            jsr     solve_for_n1_proc
            jmp     wait_to_exit

The entered variables will be checked for 0 one by one using the floating point comparison function (0xBC55B), which compares the FAC with the number in memory at A Y.
After the unknown variable is found, control is transferred to the corresponding calculation function.
If the operator has not entered an unknown value, an error message and a suggestion to exit the program or continue calculations are displayed.

The calculation function looks like this.

    solve_for_n1_proc:
            lda     #<d1
            ldy     #>d1
            jsr     fp_load_ram_to_fac
    
            lda     #<n2
            ldy     #>n2
            jsr     fp_mult
    
            ldx     #<n2
            ldy     #>n2
            jsr     fp_store_fac_to_ram
    
            lda     #<d2
            ldy     #>d2
            jsr     fp_load_ram_to_fac
    
            lda     #<n2
            ldy     #>n2
            jsr     fp_div
    
            ldx     #<n1
            ldy     #>n1
            jsr     fp_store_fac_to_ram
    
            jsr     result_n1_prompt
            
            lda     #<n1
            ldy     #>n1
            jsr     fp_load_ram_to_fac
    
            jsr     fp_fac_print
            lda     #new_line
            jsr     print_char
            rts

The function performs multiplication and division of floating point numbers using the functions provided by BASIC. Further, using the previously described function, the result is output.

The final block of the program.

    wait_to_exit:
            jsr     usage_at_exit
    wait_for_input:    
            jsr     getin
     beq     wait_for_input
            cmp     #q_sym
            bne     continue
            jmp     restore_and_exit
    continue:
            jsr     clearscreen
            jmp     get_args
    restore_and_exit:
            jsr     restore_screen
    
            rts

Displays a hint prompting a user to continue computing or exit.
Depending on the user’s choice, respectively returns to the parameter input stage or restores the initial state of the screen and exits to BASIC.

Screen recovery.

    estore_screen:
    // restore border and background colors
            lda     #$0f6
            sta     background
            lda     #$fe
            sta     border
    
    // restore text color
            lda     #$e
            sta     text_color
            jsr     clearscreen
    
    // restore text mode
            lda     #$015
            sta     $d018
            rts

The program works like this.

![](https://cdn-images-1.medium.com/max/2000/1*Imda523BAbkO6GnOdA2bBg.gif)

[The full text of the program is available here](https://gist.github.com/medvecky/72ef713b03d53d8973d2fb26fc5719d5)
The executable file size is 3137 bytes. 
Approximately two times less than the option on C.

## Final conclusion of today’s journey.

![](https://cdn-images-1.medium.com/max/2400/1*oFlUuS_hGn2A34l3C36HVA.jpeg)

What conclusions can be drawn from the work done?

 1. C in the realities of C64 in the 80s is not as good as in our time.

 2. BASIC is quite good and has extensive features, but it needs to be faster, and programs come out large.

 3. Assembly, on the one hand, looks like a leader. Learning and using it is easier than it seemed, mainly if you use the ready-made functions that kernal and BASIC provide. But the development speed still needs to be improved, especially if you write something more voluminous.

If you look closely at Assembly, learning BASIC seems more valid now. BASIC is just fine for writing program prototypes before they are implemented in Assembly.

It was a good day in the 80s. I am more and more immersed in the realities of programming at this time. When you try something with your hands, many mental distortions dissipate.

I like it here more and more, and I’m still in no hurry to return. Thank you for being a part of this journey.

I hope it was at least fun.

Warm regards.

Stay tuned for my next posts.