Skip to content

Commit

Permalink
Add perfect-numbers exercise (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
keiravillekode authored Jan 6, 2025
1 parent 669e99e commit eb096b2
Show file tree
Hide file tree
Showing 11 changed files with 3,663 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,14 @@
"strings"
]
},
{
"slug": "perfect-numbers",
"name": "Perfect Numbers",
"uuid": "9f3e26d6-a2c0-42fe-84b4-3c60e805471a",
"practices": [],
"prerequisites": [],
"difficulty": 5
},
{
"slug": "raindrops",
"name": "Raindrops",
Expand Down
39 changes: 39 additions & 0 deletions exercises/practice/perfect-numbers/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Instructions

Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.

The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of [perfect](#perfect), [abundant](#abundant), or [deficient](#deficient) based on their [aliquot sum][aliquot-sum].
The _aliquot sum_ is defined as the sum of the factors of a number not including the number itself.
For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`.

## Perfect

A number is perfect when it equals its aliquot sum.
For example:

- `6` is a perfect number because `1 + 2 + 3 = 6`
- `28` is a perfect number because `1 + 2 + 4 + 7 + 14 = 28`

## Abundant

A number is abundant when it is less than its aliquot sum.
For example:

- `12` is an abundant number because `1 + 2 + 3 + 4 + 6 = 16`
- `24` is an abundant number because `1 + 2 + 3 + 4 + 6 + 8 + 12 = 36`

## Deficient

A number is deficient when it is greater than its aliquot sum.
For example:

- `8` is a deficient number because `1 + 2 + 4 = 7`
- Prime numbers are deficient

## Task

Implement a way to determine whether a given number is [perfect](#perfect).
Depending on your language track, you may also need to implement a way to determine whether a given number is [abundant](#abundant) or [deficient](#deficient).

[nicomachus]: https://en.wikipedia.org/wiki/Nicomachus
[aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum
19 changes: 19 additions & 0 deletions exercises/practice/perfect-numbers/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"keiravillekode"
],
"files": {
"solution": [
"perfect_numbers.asm"
],
"test": [
"perfect_numbers_test.c"
],
"example": [
".meta/example.asm"
]
},
"blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.",
"source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.",
"source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/"
}
81 changes: 81 additions & 0 deletions exercises/practice/perfect-numbers/.meta/example.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
DEFICIENT equ 1
PERFECT equ 2
ABUNDANT equ 3
INVALID equ -1

section .text
global classify

; extern int classify(int64_t number);
classify:
cmp rdi, 1
jle .le_one

mov r8, 1 ; product of factors
mov r9, 1
mov r10, rdi
shl r10, 1 ; 2 * number

.next:
inc r9 ; candidate factor
mov r11, 1 ; sum of powers of factor
mov rax, r9
mul r9
cmp rax, rdi
jbe .divide

mov r9, rdi ; as r9*r9 exceeded rdi, then set r9 to rdi

.divide:
xor rdx, rdx
mov rax, rdi
div r9
test rdx, rdx
jnz .next

.repeat:
mov rdi, rax

mov rax, r11
mul r9
inc rax
mov r11, rax ; r11 * r9 + 1

xor rdx, rdx
mov rax, rdi
div r9
test rdx, rdx
jz .repeat

mov rax, r8
mul r11
mov r8, rax ; r11 * r8

cmp rdi, 1
jne .next

cmp r8, r10
je .perfect
jb .deficient

.abundant:
mov rax, ABUNDANT
ret

.perfect:
mov rax, PERFECT
ret

.deficient:
mov rax, DEFICIENT
ret

.le_one:
je .deficient

mov rax, INVALID
ret

%ifidn __OUTPUT_FORMAT__,elf64
section .note.GNU-stack noalloc noexec nowrite progbits
%endif
49 changes: 49 additions & 0 deletions exercises/practice/perfect-numbers/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[163e8e86-7bfd-4ee2-bd68-d083dc3381a3]
description = "Perfect numbers -> Smallest perfect number is classified correctly"

[169a7854-0431-4ae0-9815-c3b6d967436d]
description = "Perfect numbers -> Medium perfect number is classified correctly"

[ee3627c4-7b36-4245-ba7c-8727d585f402]
description = "Perfect numbers -> Large perfect number is classified correctly"

[80ef7cf8-9ea8-49b9-8b2d-d9cb3db3ed7e]
description = "Abundant numbers -> Smallest abundant number is classified correctly"

[3e300e0d-1a12-4f11-8c48-d1027165ab60]
description = "Abundant numbers -> Medium abundant number is classified correctly"

[ec7792e6-8786-449c-b005-ce6dd89a772b]
description = "Abundant numbers -> Large abundant number is classified correctly"

[e610fdc7-2b6e-43c3-a51c-b70fb37413ba]
description = "Deficient numbers -> Smallest prime deficient number is classified correctly"

[0beb7f66-753a-443f-8075-ad7fbd9018f3]
description = "Deficient numbers -> Smallest non-prime deficient number is classified correctly"

[1c802e45-b4c6-4962-93d7-1cad245821ef]
description = "Deficient numbers -> Medium deficient number is classified correctly"

[47dd569f-9e5a-4a11-9a47-a4e91c8c28aa]
description = "Deficient numbers -> Large deficient number is classified correctly"

[a696dec8-6147-4d68-afad-d38de5476a56]
description = "Deficient numbers -> Edge case (no factors other than itself) is classified correctly"

[72445cee-660c-4d75-8506-6c40089dc302]
description = "Invalid inputs -> Zero is rejected (as it is not a positive integer)"

[2d72ce2c-6802-49ac-8ece-c790ba3dae13]
description = "Invalid inputs -> Negative integer is rejected (as it is not a positive integer)"
46 changes: 46 additions & 0 deletions exercises/practice/perfect-numbers/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
AS = nasm

CFLAGS = -g -Wall -Wextra -pedantic -Werror
LDFLAGS =
ASFLAGS = -g -F dwarf -Werror

ifeq ($(shell uname -s),Darwin)
ifeq ($(shell sysctl -n hw.optional.arm64 2>/dev/null),1)
ALL_CFLAGS = -target x86_64-apple-darwin
endif
ALL_LDFLAGS = -Wl,-pie
ALL_ASFLAGS = -f macho64 --prefix _
else
ALL_LDFLAGS = -pie -Wl,--fatal-warnings
ALL_ASFLAGS = -f elf64
endif

ALL_CFLAGS += -std=c99 -fPIE -m64 $(CFLAGS)
ALL_LDFLAGS += $(LDFLAGS)
ALL_ASFLAGS += $(ASFLAGS)

C_OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
AS_OBJS = $(patsubst %.asm,%.o,$(wildcard *.asm))
ALL_OBJS = $(filter-out example.o,$(C_OBJS) $(AS_OBJS) vendor/unity.o)

CC_CMD = $(CC) $(ALL_CFLAGS) -c -o $@ $<

all: tests
@./$<

tests: $(ALL_OBJS)
@$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -o $@ $(ALL_OBJS)

%.o: %.asm
@$(AS) $(ALL_ASFLAGS) -o $@ $<

%.o: %.c
@$(CC_CMD)

vendor/unity.o: vendor/unity.c vendor/unity.h vendor/unity_internals.h
@$(CC_CMD)

clean:
@rm -f *.o vendor/*.o tests

.PHONY: all clean
14 changes: 14 additions & 0 deletions exercises/practice/perfect-numbers/perfect_numbers.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
DEFICIENT equ 1
PERFECT equ 2
ABUNDANT equ 3
INVALID equ -1

section .text
global classify
classify:
; Provide your implementation here
ret

%ifidn __OUTPUT_FORMAT__,elf64
section .note.GNU-stack noalloc noexec nowrite progbits
%endif
104 changes: 104 additions & 0 deletions exercises/practice/perfect-numbers/perfect_numbers_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "vendor/unity.h"

#include <stdint.h>

#define DEFICIENT 1
#define PERFECT 2
#define ABUNDANT 3
#define INVALID -1

extern int classify(int64_t number);

void setUp(void) {
}

void tearDown(void) {
}

void test_smallest_perfect_number_is_classified_correctly(void) {
TEST_ASSERT_EQUAL_INT(PERFECT, classify(6));
}

void test_medium_perfect_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(PERFECT, classify(28));
}

void test_large_perfect_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(PERFECT, classify(33550336));
}

void test_smallest_abundant_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(ABUNDANT, classify(12));
}

void test_medium_abundant_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(ABUNDANT, classify(30));
}

void test_large_abundant_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(ABUNDANT, classify(33550335));
}

void test_smallest_prime_deficient_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(DEFICIENT, classify(2));
}

void test_smallest_nonprime_deficient_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(DEFICIENT, classify(4));
}

void test_medium_deficient_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(DEFICIENT, classify(32));
}

void test_large_deficient_number_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(DEFICIENT, classify(33550337));
}

void test_edge_case_no_factors_other_than_itself_is_classified_correctly(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(DEFICIENT, classify(1));
}

void test_zero_is_rejected_as_it_is_not_a_positive_integer(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(INVALID, classify(0));
}

void test_negative_integer_is_rejected_as_it_is_not_a_positive_integer(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(INVALID, classify(-1));
}

void test_large_negative_is_rejected(void) {
TEST_IGNORE();
TEST_ASSERT_EQUAL_INT(INVALID, classify(-7001002003));
}

int main(void) {
UNITY_BEGIN();
RUN_TEST(test_smallest_perfect_number_is_classified_correctly);
RUN_TEST(test_medium_perfect_number_is_classified_correctly);
RUN_TEST(test_large_perfect_number_is_classified_correctly);
RUN_TEST(test_smallest_abundant_number_is_classified_correctly);
RUN_TEST(test_medium_abundant_number_is_classified_correctly);
RUN_TEST(test_large_abundant_number_is_classified_correctly);
RUN_TEST(test_smallest_prime_deficient_number_is_classified_correctly);
RUN_TEST(test_smallest_nonprime_deficient_number_is_classified_correctly);
RUN_TEST(test_medium_deficient_number_is_classified_correctly);
RUN_TEST(test_large_deficient_number_is_classified_correctly);
RUN_TEST(test_edge_case_no_factors_other_than_itself_is_classified_correctly);
RUN_TEST(test_zero_is_rejected_as_it_is_not_a_positive_integer);
RUN_TEST(test_negative_integer_is_rejected_as_it_is_not_a_positive_integer);
RUN_TEST(test_large_negative_is_rejected);
return UNITY_END();
}
Loading

0 comments on commit eb096b2

Please sign in to comment.