DEV Community

Alexey Medvecky
Alexey Medvecky

Posted on

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


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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)