format PE64 console
entry start
include 'INCLUDE\win64axp.inc'
section '.data' data readable writable
msg_intro db 'Triplets with exactly 4 divisors (up to 1000):', 13, 10, 0
len_intro = $ - msg_intro
buffer rb 64 ; буфер для чисел и строк
buf_len dq 0
comma_space db ', ', 0
newline db 13, 10, 0
total_msg db 'Total found: ', 0
len_total = $ - total_msg
stdout_handle dq ?
triplet_count dq 0 ; счётчик найденных троек
section '.code' code readable executable
start:
sub rsp, 40
mov ecx, -11 ; STD_OUTPUT_HANDLE
call [GetStdHandle]
mov [stdout_handle], rax
; Вывод заголовка
mov rcx, [stdout_handle]
mov rdx, msg_intro
mov r8, len_intro
mov r9, buf_len
mov qword [rsp + 32], 0
call [WriteConsoleA]
xor r15, r15 ; n = 0 — текущее число
xor r14, r14 ; счётчик подряд идущих с 4 делителями
xor [triplet_count], rax ; обнуляем счётчик троек
main_loop:
inc r15 ; n++
; Проверяем, если n > 1000 — выходим
cmp r15, 1000
jg print_total
mov rdi, r15
call count_divisors_optimized ; в rax — количество делителей
cmp rax, 4
je has_four
; Не 4 делителя — сбрасываем счётчик подряд
xor r14, r14
jmp main_loop
has_four:
inc r14 ; увеличиваем счётчик подряд
cmp r14, 3
jl main_loop ; если меньше 3 — продолжаем
; НАШЛИ ТРОЙКУ: n-2, n-1, n
inc qword [triplet_count]
; Выводим: (n-2), (n-1), (n)
mov rax, r15
sub rax, 2
call print_number
mov rdx, comma_space
call print_string
mov rax, r15
sub rax, 1
call print_number
mov rdx, comma_space
call print_string
mov rax, r15
call print_number
mov rdx, newline
call print_string
; ВАЖНО: не сбрасываем r14 полностью!
; Пример: 33,34,35,36 — если 36 тоже имеет 4 делителя,
; то 34,35,36 — тоже тройка!
; Поэтому сдвигаем "окно": считаем, что последние 2 числа всё ещё валидны.
; Но для простоты — просто сбросим и продолжим с n (текущего).
; Это безопасно, потому что если n+1 и n+2 тоже будут с 4 делителями —
; мы снова наберём 3 подряд.
; Альтернатива: r14 = 2 (если текущее число — третье в цепочке, то следующее может быть четвёртым — тогда тройка 2,3,4)
; Но для простоты и надёжности — сбросим и продолжим.
; → Сбрасываем, но оставляем текущее число как начало новой цепочки:
mov r14, 1 ; текущее число — уже одно в цепочке
jmp main_loop
print_total:
; Вывод "Total found: N"
mov rcx, [stdout_handle]
mov rdx, total_msg
mov r8, len_total
mov r9, buf_len
mov qword [rsp + 32], 0
call [WriteConsoleA]
mov rax, [triplet_count]
call print_number
mov rdx, newline
call print_string
jmp exit_program
exit_program:
xor ecx, ecx
call [ExitProcess]
; === ОПТИМИЗИРОВАННЫЙ ПОДСЧЁТ ДЕЛИТЕЛЕЙ ===
; Вход: rdi = n (n > 0)
; Выход: rax = количество делителей
count_divisors_optimized:
push rbx
push rcx
push rdx
push rsi
mov r9, 0 ; счётчик делителей
mov rbx, 1 ; d = 1
mov r8, rdi ; сохраняем n
.loop:
; Проверка: если d * d > n — выходим
mov rax, rbx
mul rbx ; rax = d * d
cmp rax, r8
ja .done ; ← ЗДЕСЬ БЫЛО end_loop — ЗАМЕНИТЕ НА .done
mov rax, r8 ; n
xor rdx, rdx
div rbx ; делим n на d → частное в rax, остаток в rdx
test rdx, rdx ; остаток == 0?
jnz .next ; если не делится — переходим к следующему d
inc r9 ; d — делитель, +1
; Проверяем, не является ли d квадратным корнем (d² == n)
mov rax, rbx
mul rbx
cmp rax, r8
je .next ; если d² == n — не добавляем парный делитель
inc r9 ; иначе — добавляем парный делитель (n/d)
.next:
inc rbx
jmp .loop
.done:
mov rax, r9 ; возвращаем количество делителей
pop rsi
pop rdx
pop rcx
pop rbx
ret
; Вычислим √n приблизительно — перебираем до rbx*rbx <= n
sqrt_loop:
mov rax, rbx
mul rbx ; rax = rbx * rbx
cmp rax, r8
jg end_loop ; если rbx² > n — выходим
mov rax, r8 ; n
xor rdx, rdx
div rbx ; n / d
test rdx, rdx ; остаток == 0?
jnz not_divisor
; rbx — делитель
inc rax ; +1 делитель
; Если rbx * rbx != n, то есть и парный делитель: n / rbx
mov rax, rbx
mul rbx
cmp rax, r8
je not_double ; если квадрат — не добавляем второй
inc rax ; +1 за парный делитель
jmp not_double
not_divisor:
xor rax, rax ; сброс, чтобы не мешало
not_double:
; Восстановим счётчик (rax был испорчен — сохраним отдельно)
; → лучше хранить счётчик в другом регистре
; Переделываем: храним счётчик в r9
; Исправленная версия:
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
; → Перепишем правильно:
; === Исправленная реализация ===
push rbx
push rcx
push rdx
push rsi
push rdi
mov r9, 0 ; счётчик делителей
mov rbx, 1 ; d = 1
mov r8, rdi ; n
cd_loop:
; Проверяем: если d*d > n — выход
mov rax, rbx
mul rbx
cmp rax, r8
ja cd_done
mov rax, r8
xor rdx, rdx
div rbx ; rax = n/d, rdx = остаток
test rdx, rdx
jnz cd_next
; rbx — делитель
inc r9
; Проверяем, не квадрат ли это
mov rax, rbx
mul rbx
cmp rax, r8
je cd_next ; если d² == n — не добавляем парный
inc r9 ; иначе — добавляем парный делитель
cd_next:
inc rbx
jmp cd_loop
cd_done:
mov rax, r9 ; возвращаем результат
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
ret
; === Вывод числа из rax ===
print_number:
push rax
push rbx
push rcx
push rdi
push rsi
mov rdi, buffer + 63
mov byte [rdi], 0
dec rdi
mov rbx, 10
pn_convert:
xor rdx, rdx
div rbx
add dl, '0'
mov [rdi], dl
dec rdi
test rax, rax
jnz pn_convert
inc rdi
mov rsi, rdi
; Считаем длину строки
xor rcx, rcx
mov rdx, rsi
pn_len_loop:
cmp byte [rdx], 0
je pn_print
inc rdx
inc rcx
jmp pn_len_loop
pn_print:
mov [buf_len], rcx
mov rcx, [stdout_handle]
mov rdx, rsi
mov r8, rcx
mov r9, buf_len
mov qword [rsp + 32], 0
call [WriteConsoleA]
pop rsi
pop rdi
pop rcx
pop rbx
pop rax
ret
; === Вывод строки, на которую указывает rdx (с нулевым завершением) ===
print_string:
push rax
push rcx
push r8
push r9
push rdx
; Считаем длину строки
xor rcx, rcx
mov rax, rdx
str_len_loop:
cmp byte [rax], 0
je str_print
inc rax
inc rcx
jmp str_len_loop
str_print:
mov r8, rcx
mov rcx, [stdout_handle]
pop rdx
mov r9, buf_len
mov qword [rsp + 32], 0
call [WriteConsoleA]
pop r9
pop r8
pop rcx
pop rax
ret
section '.idata' import data readable
library kernel32, 'kernel32.dll'
import kernel32, \
GetStdHandle, 'GetStdHandle', \
WriteConsoleA, 'WriteConsoleA', \
ExitProcess, 'ExitProcess'