Creating a Fraction Converter for the Commodore 64 in Assembly Language (6510/6502)

Alexey Medvecky
6 min readSep 5, 2023

--

In this article, we’ll delve into the process of creating a fraction to decimal, percentage, and back converter for the Commodore 64 using Assembly language (6510/6502). This project not only provides a practical application of assembly programming skills but also allows us to explore the nostalgic charm of 80s computing.

Problem Statement

Embracing the 80s Nostalgia

As we traverse through the time machine, we find ourselves in the 80s, where writing mathematical programs for the Commodore 64 is not just a hobby but part of a school course. In this article, we embark on our journey to create a fraction converter, a seemingly simple yet insightful project to sharpen our Assembly language skills.

This article continues the topic that I began in previous articles:

Solution

Building a BASIC Prototype

Traditionally, we commence our implementation with a prototype in BASIC. Prototype serves as a foundation, allowing us to visualize the program’s structure and overall design.

Two noteworthy aspects of the prototype include:

  • Menu Implementation: We determine menu options.
  • Exponent Calculation: Utilizing the length of textual number representation to compute the exponent.

Transitioning to Assembly Language

Having covered numerous details in previous articles, we will focus on the high-level logic of our Assembly solution, along with the innovative approaches we’ve adopted.

Menu Handling

After the screen setup and tooltip display, the menu display function takes centre stage as the program’s initial function. This function efficiently displays menu items and handles user input. If the user enters an invalid option, the program gracefully ignores it. Subsequently, it invokes the relevant procedure based on the user’s input.

main:
jsr prepare_screen
jsr main_usage
menu:
jsr show_menu
wait_for_continue:
jsr getin
beq wait_for_continue
cmp #q_sym
beq go_to_exit
cmp #one_sym
beq handle_decimal
cmp #two_sym
beq handle_fraction
cmp #three_sym
beq handle_percent
jsr clearscreen
jmp menu
handle_decimal:
jsr decimal_handler
jmp continue_or_exit
handle_fraction:
jsr fraction_handler
jmp continue_or_exit
handle_percent:
jsr percent_handler
jmp continue_or_exit
go_to_exit:
jmp restore_and_exit

continue_or_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 menu

restore_and_exit:
jsr restore_screen

rts

Decimal Conversion

decimal_handler:
jsr input_decimal
jsr cursor_blink_off
jsr calculate_den
lda #<n
ldy #>n
jsr fp_load_ram_to_fac
lda #<den
ldy #>den
jsr fp_mult
ldx #<num
ldy #>num
jsr fp_store_fac_to_ram
lda #<hundred
ldy #>hundred
jsr fp_load_ram_to_fac
lda #<n
ldy #>n
jsr fp_mult
ldx #<per
ldy #>per
jsr fp_store_fac_to_ram
jsr show_results
rts

input_decimal:
jsr input_decimal_prompt
jsr input_string_proc
jsr string_to_fp
ldx #<n
ldy #>n
jsr fp_store_fac_to_ram
jsr get_exponent_by_counter
lda #space_sym
jsr print_char
lda #new_line
jsr print_char
rts

Our decimal handler routine takes a decimal as an argument and converts it into a fraction and a percentage. This routine initiates with an input procedure similar to our earlier discussion. The key difference lies in calculating the exponent based on the entered character count.

get_exponent_by_counter:
ldx counter
dex
txa
sta e_counter
lda number_strings
clc
adc counter
sta address
dec address
lda #<(address)
sta $22
lda #>(address)
sta $23
lda #string_length
jsr fp_string_to_fac
ldx #<e
ldy #>e
jsr fp_store_fac_to_ram
rts

This procedure does the following:

  • Decrements the counter value by one and stores the resulting value in the exponent counters.
  • Loads into the accumulator the address of the beginning of the array of string representation of numbers.
  • Adds to the result the counter of entered characters saved in a variable.
  • Since the offset counts from 0 reduces the value by one.
  • Next, the full address of the array element is loaded with an offset.
  • The string at the specified address is converted to a floating point number and stored in the variable e (exponent).

The number string array looks like the following:

number_strings:
.text "0"
.byte $0
.text "1"
.byte $0
.text "2"
.byte $0
.text "3"
.byte $0
.text "4"
.byte $0
.text "5"
.byte $0
.text "6"
.byte $0
.text "7"
.byte $0
.text "8"
.byte $0
.text "9"
.byte $0
.text "10"
.byte $0
.text "11"
.byte $0
.text "12"
.byte $0
.text "13"
.byte $0
.text "14"
.byte $0
.text "15"
.byte $0

Next, in the decimal handler, a procedure is called to calculate the denominator.

calculate_den:
ldx e_counter
dex
beq handle_tenth
txa
sta counter
lda #<ten
ldy #>ten
jsr fp_load_ram_to_fac
mult_loop:
lda #<ten
ldy #>ten
jsr fp_mult
ldx counter
dex
beq end_loop
txa
sta counter
jmp mult_loop
end_loop:
ldx #<den
ldy #>den
jsr fp_store_fac_to_ram
jmp end_mult
handle_tenth:
lda #<ten
ldy #>ten
jsr fp_load_ram_to_fac
ldx #<den
ldy #>den
jsr fp_store_fac_to_ram

end_mult:
rts

This routine calculates ten powered by exponent using multiplication and the e_counter value obtained in the previous routine.

Further, I get the numerator by multiplying the entered number by the denominator.
And by multiplying the entered number by 100, I get the percentage.

Next, I display the result by a separate procedure.

show_results:
lda #space_sym
jsr print_char
lda #new_line
jsr print_char
lda #<result_1
ldy #>result_1
jsr print_str
lda #<n
ldy #>n
jsr fp_load_ram_to_fac
jsr fp_to_str
jsr print_str
lda #new_line
jsr print_char
lda #<result_2
ldy #>result_2
jsr print_str
lda #<num
ldy #>num
jsr fp_load_ram_to_fac
jsr fp_to_str
jsr print_str
lda #<result_3
ldy #>result_3
jsr print_str
lda #<den
ldy #>den
jsr fp_load_ram_to_fac
jsr fp_to_str
jsr print_str
lda #new_line
jsr print_char
lda #<result_4
ldy #>result_4
jsr print_str
lda #<per
ldy #>per
jsr fp_load_ram_to_fac
jsr fp_to_str
jsr print_str
lda #new_line
jsr print_char
lda #new_line
jsr print_char
rts

Limitations of this implementation:

  • The function only works with fractions < 1.
  • Input format “.123” without leading zero.

Fraction Processing

The fraction processor takes numerator and denominator arguments, converting them into decimals and percentages.

fraction_handler:
jsr input_num_den
jsr cursor_blink_off
lda #<den
ldy #>den
jsr fp_load_ram_to_fac
lda #<num
ldy #>num
jsr fp_div
ldx #<n
ldy #>n
jsr fp_store_fac_to_ram
lda #<hundred
ldy #>hundred
jsr fp_load_ram_to_fac
lda #<n
ldy #>n
jsr fp_mult
ldx #<per
ldy #>per
jsr fp_store_fac_to_ram
jsr show_results
rts

A decimal is obtained by dividing the numerator by the denominator.
Percentages are obtained by multiplying a decimal by 100.

Percentage Conversion

The percentage handler takes percentage values as input and converts them into fractions and decimals.

percent_handler:
jsr input_percents
lda #<hundred
ldy #>hundred
jsr fp_load_ram_to_fac
lda #<per
ldy #>per
jsr fp_div
ldx #<n
ldy #>n
jsr fp_store_fac_to_ram
lda #<per
ldy #>per
jsr fp_load_ram_to_fac
ldx #<num
ldy #>num
jsr fp_store_fac_to_ram
lda #<hundred
ldy #>hundred
jsr fp_load_ram_to_fac
ldx #<den
ldy #>den
jsr fp_store_fac_to_ram
jsr show_results
rts

A decimal is obtained by dividing a percentage by 100.
Normal: numerator percentage entered denominator 100.

This function has a limitation: it can correct handle only whole percent.

Conclusion

In this journey through the Commodore 64’s mathematical realm, we’ve developed a fraction converter in assembly language. You can find the complete source code for this program [here].

Here is an example of execution

The binary file has a size of 3220 bytes.

While school mathematics may seem basic, it provides an excellent playground for honing assembly programming skills. This project has showcased the creative possibilities that the 80s era and Commodore 64 offer.

As we continue this exciting journey, I invite you to stay with me for more mathematical adventures in the world of Commodore 64 programming.

--

--