Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions docs/tasks/5245-remainder-and-division-over-the-exact-integer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
номер: 5245
заголовок: Над точным целым нет остатка и деления, а без остатка не пишется конгруэнтный генератор
статус: свободна
приоритет: P2
исполнитель: —
ветка: —
команда: первая
карта: Чего в языке нет вовсе
рядом: 1412, 1413, 1908
нужность: без остатка точное целое не закрывает тот самый случай, ради которого заведено — счёт за 2⁵³ с переносом результата обратно в узкий ряд
---

# 5245. Над точным целым нет остатка и деления

## Шаги воспроизведения

1. Остаток над точным целым:

```flang
модуль «A remainder over the exact integer is refused»

тотальная функция «Остаток»
принимает а: точное целое, б: точное целое
возвращает точное целое
а остаток от б
```

2. Деление — та же программа с `а делить на б`.
3. Команда: `bootstrap/flang check <файл>`.

## Что происходит

```
$ bootstrap/flang check <остаток>
FLANG_TYPE … строка 6, столбец 5: над точным целым «mod» не определено:
точное целое знает «плюс», «минус» и «умножить на»
<файл>: не проверено — замечаний 1 код 1
$ bootstrap/flang check <деление>
FLANG_TYPE … строка 6, столбец 5: над точным целым «div» не определено:
точное целое знает «плюс», «минус» и «умножить на»
<файл>: не проверено — замечаний 1 код 1
```

Отказ сознательный, не недосмотр: задача 1908 ввела над точным целым `плюс`,
`минус`, `умножить на` и пять слов порядка, а деление с остатком оставила
названным отказом. Довод был в объёме: разрядное деление в
`flang/stdlib/bignum.flang` — **7 функций и 86 непустых строк** (строки
608–699) против **1 функции и 23 непустых строк** у вычитания с займом
(строки 222–245).

## Что должно быть

Остаток и деление над точным целым определены, и слово отказа уходит.

**Случай, который без остатка не пишется — линейный конгруэнтный генератор.**
Его шаг — `(зерно · множитель + добавка) остаток от модуль`, и без остатка
последнего действия взять негде. Померено на двоичном задачи 1908 против
точных целых Python:

| шаг | `число` (flang) | точно | расхождение |
|---|---|---|---|
| Лемер, зерно 2 000 000 000, множитель 16807 | 33614000000000 | 33614000000000 | 0 |
| glibc, зерно 2 000 000 000, множитель 1103515245, добавка 12345 | 2207030490000012300 | 2207030490000012345 | 45 |
| glibc, зерно 1 234 567 891, тот же множитель и добавка | 1362364488706010600 | 1362364488706010640 | 40 |

Что именно врёт, названо точно, чтобы довод не рассыпался при проверке:
**произведение само по себе здесь точно** — `2 000 000 000 · 1103515245`
даёт 2 207 030 490 000 000 000, это выше 2⁵³, но значащих бит в нём 51, и
double держит его знак в знак. Ломает **добавка**: она сносит нижние нули, и
числу нужно уже больше 53 бит. На полном ряду зёрен хуже:
`2147483647 · 1103515245 + 12345` требует **60 значащих бит**.

Отсюда порог, который и загоняет автора в угол: множитель glibc выводит
произведение за 2⁵³ при зерне **8 162 279** и выше. Дальше точность не
гарантирована — какое зерно уцелеет, зависит от того, сколько у произведения
нижних нулей, и полагаться на это нельзя. Ряд, на котором точность
гарантирована, — **0,38 %** от ряда по модулю 2³¹. Множитель Лемера 16807
точен всюду: наибольшее произведение по всему ряду, `2147483646 · 16807`,
равно 36 092 757 638 322 и ниже 2⁵³. Поэтому сегодня у автора генератора
выбора нет — либо Лемер, либо числа, неверные молча.

## Обходной путь

Множитель, укладывающийся в 2⁵³ вместе с добавкой и модулем, — так и сделано
в примере генетического алгоритма (`flang/experimental/`): взят Лемер
`16807 mod 2147483647`. Обход закрывает только этот множитель; `число` остаётся
IEEE-754, и законы кольца над ним ядро не доказывает.

## Цена, насколько она померена

- **Объём** померен: 7 функций и 86 непустых строк в `bignum.flang`.
- **Своя мера завершения НЕ нужна**, и это проверено чтением, а не памятью:
в строках 608–699 нет ни одного `убывает`. `«Биты разряда»` убывает по
счётчику `осталось: неотрицательное`, а `«Бит в деление»` и
`«Разряд в деление»` рекурсии не несут вовсе — второе сворачивает список
ровно из 22 бит. Явная мера `«Запас алгоритма Евклида»` относится к `НОД`
(строки 701–780), а не к делению; не перепутать.
- **Что уже готово и переиспользуется**: `«Умножить разряды на малое»`,
`«Разность разрядов»` и `«Меньше разрядов»` из `flang/self/interpret.flang`
задачи 1908 — ровно те три слова, на которых стоит `«Бит в деление»`.
Осторожно с одним: `«Разность разрядов»` ниже нуля даёт завёрнутые разряды,
а не отказ (отказ живёт выше, в `«Точное вычитание»`), поэтому стеречь
`«Меньше разрядов»` перед вычитанием — обязанность вызывающего.
- **Число новых функций НЕ мерено**: оценку по образцу `bignum.flang` в 7
функций брать нельзя, пока не решено, нужен ли быстрый путь для делителя в
один разряд.

## Чего эта задача НЕ закрывает

`равен` и `не равен` над точным целым сравнивают структурно и приведения не
несут — так решено в 1908 намеренно, потому что при вычислении тип неизвестен,
а `список числа` и `точное целое` — одно значение. Остаётся один путь к
неканоническому значению: `список числа`, собранный списочными словами,
поданный в позицию точного целого и сравнённый `равен` до любого действия над
ним. Закрывается сужением втекания `список числа` в `точное целое` — это
задача 1412, не эта.

## Когда задача сделана

Программа из шагов воспроизведения проходит `check` с кодом 0. Полный шаг
генератора на точном целом даёт тот же ответ, что точные целые Python, на всех
трёх строках таблицы выше, включая `2147483647 · 1103515245 + 12345` с его 60
значащими битами. Есть проба с числами за 2⁵³ и подделкой, на которой ответ
обязан измениться. Деление на ноль отвергнуто названным словом.

## Где живёт правка

`flang/self/interpret.flang` (разрядное деление и остаток),
`flang/self/types.flang` (снять отказ `mod` и `div` над точным целым),
`flang/proof/probes/exact-integer/**` (пробы). Всё под `flang/self`, до
двоичного доезжает только перепечаткой семени.
Loading