Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Approach/leap #206

Merged
merged 18 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 50 additions & 0 deletions exercises/practice/leap/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"introduction": {
"authors": [
"clechasseur",
"keiravillekode"
]
},
"approaches": [
{
"uuid": "816aabe8-4b78-40b2-934e-053bf103dc3c",
"slug": "using-div",
"title": "Using the div instruction",
"blurb": "Use the div instruction to get a division's remainder.",
"authors": [
"clechasseur",
"keiravillekode"
]
},
{
"uuid": "11fe5497-f748-4b9e-912e-38ae0e93b9da",
"slug": "using-rem",
"title": "Using the rem instruction",
"blurb": "Use the rem instruction to get a division's remainder.",
"authors": [
"clechasseur",
"keiravillekode"
]
},
{
"uuid": "39f6b092-e400-4ba1-a0d1-4cd2ca2a78ba",
"slug": "using-macro",
"title": "Using a \"divisible by\" macro",
"blurb": "Define a macro to avoid repeating code too often.",
"authors": [
"clechasseur",
"keiravillekode"
]
},
{
"uuid": "92f9310c-041d-46ed-b06f-0496edb5cabc",
"slug": "using-andi",
"title": "Using the andi instruction",
"blurb": "Use the andi instruction to determine if a number is divisible by 4.",
"authors": [
"clechasseur",
"keiravillekode"
]
}
]
}
173 changes: 173 additions & 0 deletions exercises/practice/leap/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Introduction

Although MIPS assembly is more limited because it does not provide access to actual date/time functions,
there are still multiple approaches to solve Leap that involve different MIPS instructions.

## General guidance

The key to solving Leap is to know if the year is evenly divisible by `4`, `100` and `400`.
There are several instructions that can be used to help us determine this, like `div` (divide),
`rem` (modulo) and `andi` (bitwise AND immediate). Once a check has been made, we can use
instructions like `bnez` (branch if not equal zero) to short circuit the other checks:

| year | year divisible by 4 | year divisible by 100 | year divisible by 400 | is leap year |
| ---- | ------------------- | --------------------- | --------------------- | ------------ |
| 2020 | true | false | not evaluated | true |
| 2019 | false | not evaluated | not evaluated | false |
| 2000 | true | true | true | true |
| 1900 | true | true | false | false |

For more information on the MIPS instructions used, you can refer to this [MIPS instructions reference][mips-instructions-ref]
or to this [MIPS green sheet][mips-green-sheet].

## Approach: Using the `div` instruction

```asm
## Registers

# | Register | Usage | Type | Description |
# | -------- | --------- | ------- | ------------------------------------------------ |
# | `$a0` | input | integer | year to check |
# | `$v0` | output | boolean | input is leap year (`0` = `false`, `1` = `true`) |
# | `$t0-9` | temporary | any | used for temporary storage |

.globl is_leap_year

is_leap_year:
li $v0, 0

li $t0, 4
div $a0, $t0
mfhi $t0
bnez $t0, end

li $t0, 100
div $a0, $t0
mfhi $t0
bnez $t0, set_leap

li $t0, 400
div $a0, $t0
mfhi $t0
bnez $t0, end

set_leap:
li $v0, 1
end:
jr $ra
```

For more information, check the [approach using `div`][approach-using-div].

## Approach: Using the `rem` instruction

```asm
## Registers

# | Register | Usage | Type | Description |
# | -------- | --------- | ------- | ------------------------------------------------ |
# | `$a0` | input | integer | year to check |
# | `$v0` | output | boolean | input is leap year (`0` = `false`, `1` = `true`) |
# | `$t0-9` | temporary | any | used for temporary storage |

.globl is_leap_year

is_leap_year:
li $v0, 0

rem $t0, $a0, 4
bnez $t0, end

rem $t0, $a0, 100
bnez $t0, set_leap

rem $t0, $a0, 400
bnez $t0, end

set_leap:
li $v0, 1
end:
jr $ra
```

For more information, check the [approach using `rem`][approach-using-rem].

## Approach: Using a "divisible by" macro

```asm
## Registers

# | Register | Usage | Type | Description |
# | -------- | --------- | ------- | ------------------------------------------------ |
# | `$a0` | input | integer | year to check |
# | `$v0` | output | boolean | input is leap year (`0` = `false`, `1` = `true`) |
# | `$t0-9` | temporary | any | used for temporary storage |

.globl is_leap_year

.macro if_divisible_by($divisor, $then_jump)
li $t0, $divisor
div $a0, $t0
mfhi $t0
beqz $t0, $then_jump
.end_macro

is_leap_year:
if_divisible_by(400, set_leap)
if_divisible_by(100, not_leap)
if_divisible_by(4, set_leap)

not_leap:
li $v0, 0
jr $ra

set_leap:
li $v0, 1
jr $ra
```

For more information, check the [approach using a macro][approach-using-macro].

## Approach: Using the `andi` instruction

```asm
## Registers

# | Register | Usage | Type | Description |
# | -------- | --------- | ------- | ------------------------------------------------ |
# | `$a0` | input | integer | year to check |
# | `$v0` | output | boolean | input is leap year (`0` = `false`, `1` = `true`) |
# | `$t0-9` | temporary | any | used for temporary storage |

.globl is_leap_year

is_leap_year:
li $v0, 0

andi $t0, $a0, 3
bnez $t0, end

li $t0, 100
div $a0, $t0
mfhi $t0
bnez $t0, set_leap

mflo $t0
andi $t0, $t0, 3
bnez $t0, end

set_leap:
li $v0, 1
end:
jr $ra
```

For more information, check the [approach using `andi`][approach-using-andi].


[mips-instructions-ref]: https://pages.cs.wisc.edu/~markhill/cs354/Fall2008/notes/MAL.instructions.html
[mips-green-sheet]: https://inst.eecs.berkeley.edu/~cs61c/resources/MIPS_Green_Sheet.pdf
[approach-using-div]: https://exercism.org/tracks/mips/exercises/leap/approaches/using-div
[approach-using-rem]: https://exercism.org/tracks/mips/exercises/leap/approaches/using-rem
[approach-using-macro]: https://exercism.org/tracks/mips/exercises/leap/approaches/using-macro
[approach-using-andi]: https://exercism.org/tracks/mips/exercises/leap/approaches/using-andi
58 changes: 58 additions & 0 deletions exercises/practice/leap/.approaches/using-andi/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Using the `andi` instruction

```asm
## Registers

# | Register | Usage | Type | Description |
# | -------- | --------- | ------- | ------------------------------------------------ |
# | `$a0` | input | integer | year to check |
# | `$v0` | output | boolean | input is leap year (`0` = `false`, `1` = `true`) |
# | `$t0-9` | temporary | any | used for temporary storage |

.globl is_leap_year

is_leap_year:
li $v0, 0

andi $t0, $a0, 3
bnez $t0, end

li $t0, 100
div $a0, $t0
mfhi $t0
bnez $t0, set_leap

mflo $t0
andi $t0, $t0, 3
bnez $t0, end

set_leap:
li $v0, 1
end:
jr $ra
```

Equivalent C code:

```c
#include <stdlib.h>

int is_leap_year(int year) {
if ((year & 3) != 0) {
return 0;
}

div_t by_100 = div(year, 100);
return by_100.rem != 0 || (by_100.quot & 3) == 0;
}
```

This approach uses the `andi` instruction to determine if the year is divisible by `4`
by relying on the fact that if a number is divisible by `4`, its two least significant bits
will be zero.

To determine if the year is divisible by `100`, we then use `div` combined with `mfhi` to get
the remainder. Finally, if we need to check if the year is divisible by `400`, we use another
trick: calling `div` left the quotient of the division by `100` in the `lo` register. Thus,
we can fetch that quotient using `mflo` and then re-use the same trick as before to determine
if that quotient is divisible by `4` (making the year divisible by `400`).
5 changes: 5 additions & 0 deletions exercises/practice/leap/.approaches/using-andi/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
is_leap_year:
li $v0, 0

andi $t0, $a0, 3
bnez $t0, end
52 changes: 52 additions & 0 deletions exercises/practice/leap/.approaches/using-div/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Using the `div` instruction

```asm
## Registers

# | Register | Usage | Type | Description |
# | -------- | --------- | ------- | ------------------------------------------------ |
# | `$a0` | input | integer | year to check |
# | `$v0` | output | boolean | input is leap year (`0` = `false`, `1` = `true`) |
# | `$t0-9` | temporary | any | used for temporary storage |

.globl is_leap_year

is_leap_year:
li $v0, 0

li $t0, 4
div $a0, $t0
mfhi $t0
bnez $t0, end

li $t0, 100
div $a0, $t0
mfhi $t0
bnez $t0, set_leap

li $t0, 400
div $a0, $t0
mfhi $t0
bnez $t0, end

set_leap:
li $v0, 1
end:
jr $ra
```

Equivalent C code:

```c
int is_leap_year(int year) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
```

This approach uses the `div` instruction to compute the remainder of divisions.
This instruction actually computes both the quotient _and_ the remainder of the division and
places them in the `lo` and `hi` registers, respectively.

However, those registers cannot be used as arguments to most instructions. Instead, in order
to use their values, we need to _move_ them from the `lo` or `hi` registers into another
register using the `mfhi` (move from hi) or `mflo` (move from lo) instructions.
7 changes: 7 additions & 0 deletions exercises/practice/leap/.approaches/using-div/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
is_leap_year:
li $v0, 0

li $t0, 4
div $a0, $t0
mfhi $t0
bnez $t0, end