Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Правильное оформление умножения больших целых чисел  (Прочитано 2239 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Aether
Молодой специалист

ru
Offline Offline
Пол: Мужской

« : 11-02-2017 14:05 » 

Добрый день.

Дано два числа типа long long и их необходимо перемножить. Но для простоты заменю их на char.
Код: (C)
...
char x;
char y;
char z;
...
z = x * y;
...

Естественно, при перемножении двух восьмибитных чисел получается в общем случае 16-битное число, но приёмник только восьмибитный. Значит ситуацию, когда произведение не поместится в z необходимо отловить.
На ассемблере это бы лечилось проверкой старшего слова после умножения на равенство нулю. А как это правильно описать в "С"? Первое, что приходит на ум - сравнить аргументы до умножения:

Код: (C)
...
char size_of_x;
...
/* Определяю истинный размер аргументов в битах */
...
if (x >> 4) size_of_x = 5;
if (x >> 5) size_of_x = 6;
if (x >> 6) size_of_x = 7;
if (x >> 7) size_of_x = 8;
...
if ((size_of_x + size_of_y) > 8) { /* Исключение */ }
Может есть способ проще?
« Последнее редактирование: 11-02-2017 14:39 от Aether » Записан
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #1 : 11-02-2017 15:30 » 

Целочисленное умножение всегда даст результат разрядности, равной сумме разрядности множителей. Далее ты можешь: использовать полную разрядность, отбросить младшую часть (округление, но мы помним, что реальная разрядность выше), игнорировать старшую часть (если произведение гарантированно не переполнит младшей части) или нормализовать до исходного формата (используется для fixed point вычислений).
Любые преобразования, ведущие к потери точности лучше выполнять один раз. Например, типичная свертка sum(x[i] * k[i]): аккумулятор большой разрядности (разрядность x + разрядность к + log2(N)), округление и потеря точности только один раз, в конце вычисления.
« Последнее редактирование: 11-02-2017 15:40 от RXL » Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.

Хз, я не очень просто не очень во всё это верю, во всякие там сатурны и прочую поебень.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #2 : 11-02-2017 16:14 » 

Но для простоты заменю их на char.

Эта простота на самом деле не так проста.

Во-первых, для типа char по умолчанию не определено, является ли он знаковым. Зависит от реализации. А знаковая и беззнаковая арифметики в C существенно отличаются. Поэтому использовать переменную типа char без риска побочных эффектов можно только в том случае, если переменная будет хранить литеру текста.

Определение вроде

Код: (C)
...
char x;
char y;
char z;
...
z = x * y;
...
- очень хороший способ заработать себе геморрой, причем на одних компиляторах эта конструкция будет работать в точности так, как ожидал автор, на других будут неожиданные сюрпризы.

Решение вроде signed char (либо unsigned char) несколько получше. Действительно грамотное решение - использовать типы из <stdint.h>.

Во-вторых, при вычислении выражения, поскольку операнды приводимы к int, сначала будет произведено "integer promotion" операндов (расширение значений до int), затем выполнено умножение. Результатом char*char всегда будет int, и переполнения не может быть в принципе.

Подробнее можно посмотреть, например, ISO/IEC 9899:1999, 5.1.2.3 Program execution (если у Вас другой диалект C, тогда, соответственно, в другом стандарте).
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Aether
Молодой специалист

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 12-02-2017 09:32 » 

Хорошо, возьмём 64 битную машину:
Код: (C)
...
uint64_t x;
uint64_t y;
uint64_t z;
...
z = x * y;
...
По идее z должна быть uint128_t, но такого типа там нет. Суть не в переполнении, а в том, что после вычисления выражения его будут приводить к формату меньшей разрядности, и тут может быть срез старшей части.

Мне также интересно, как работать с такими типами, как size_t? Они переменной ширины, поэтому проблема среза при вычислении, например, требуемого объёма актуальна.

Кстати, умножение в ARM, если я правильно помню, несколько отличается от x86: стандартный набор инструкций умножает сразу без сохранения старшего слова.
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #4 : 12-02-2017 13:02 » 

По идее z должна быть uint128_t, но такого типа там нет.

Верно, и быть не может. Не забываем: C - это не язык высокого уровня, это машинно-независимый ассемблер с возможностью генерации максимально эффективного кода. Как и в любом ассемблере, в нем нет никаких абстракций, которые напрямую не отображаются на архитектуру процессора. Неспроста в учебниках Кернигана и Ритчи Вы не найдете даже упоминаний о всяких uint64_t: в эпоху PDP-11 такое слово не влезло бы ни в одно арифметико-логическое устройство.

Вообще C - весьма своеобразный язык: его нельзя просто сесть и выучить по талмуду. Получается какая-то уродливая куча нелепостей, со всех сторон подпертых костылями. Нужно понять его многолетнюю историю, проследить эволюцию возможностей, чтобы понять логику того или иного решения.

Например, как человек со здоровой головой воспримет вот такой закидон: чтобы вычислить выражение над числами с плавающей точкой одинарной точности, нужно сначала преобразовать все операнды к двойной точности, произвести вычисления, а затем результат преобразовать обратно к одинарной точности. Выглядит полным бредом, если не знать истинную причину такого странного поведения.

может быть срез старшей части.

И при этом весьма вероятно, что Вы даже не узнаете об этом, поскольку поведение программы C при арифметических переполнениях не регулируется стандартом. Равно как не узнаете о выходе за границы массива и других неприятностях, которые в случае, скажем, того же Pascal'я привели бы к аварийному завершению программы. Из всех языков программирования C - один из наименее подходящих для арифметических вычислений, ибо задумывался совсем для другой цели.

Мне также интересно, как работать с такими типами, как size_t? Они переменной ширины, поэтому проблема среза при вычислении, например, требуемого объёма актуальна.

Если произошел такой срез, значит, требуемый Вами объем памяти на данной архитектуре принципиально не может быть выделен. Задача решения не имеет, и усечение переменной - не самая большая проблема.

Кстати, умножение в ARM, если я правильно помню, несколько отличается от x86: стандартный набор инструкций умножает сразу без сохранения старшего слова.

Верно, и тем самым сразу же избавляет нас от дурацкой проблемы: куда впихнуть невпихуемое что делать со старшей половиной произведения, обработать которую в рамках данной разрядности мы не можем.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #5 : 13-02-2017 06:33 » 

Ну, x86, последние лет 30-35, тоже поддерживает целочисленное умножение без сохранения старшей части.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.

Хз, я не очень просто не очень во всё это верю, во всякие там сатурны и прочую поебень.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #6 : 13-02-2017 08:32 » 

Я небольшой знаток ассемблера x86, но по той литературе, что у меня есть, старшая часть произведения вроде бы всегда вычисляется.

Цитата
MUL Умножение целых чисел без знака

Команда MUL выполняет умножение целого числа без знака, находящегося в регистре AL (в случае умножения на байт) или АХ (в случае умножения на слово), на операнд-источник (целое число без знака). Размер произведения в два раза больше размера сомножителей.

Для однобайтовых операций один из сомножителей помещается в регистр AL; после выполнения операции произведение записывается в регистр АХ.

Для двухбайтовых операций один из сомножителей помещается в регистр АХ; после выполнения операции произведение записывается в регистры DX:AX (в DX - старшая часть, в АХ - младшая). Предыдущее содержимое регистра DX затирается.

386+: Допустимо использование 32-битовых операндов и дополнительных режимов адресации 32-разрядных процессоров. При этом, если указанный операнд представляет собой 32-байтовую величину, то результат размещается в регистрах EDX:EAX.

Знаковый вариант команды (IMUL) делает примерно то же.

У x86 есть другие целочисленные команды умножения, где старшая часть игнорируется?
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #7 : 13-02-2017 09:22 » 

У x86 есть другие целочисленные команды умножения, где старшая часть игнорируется?
В принципе, FPU позволяет загружать целочисленные значения и перемножать их (FILD/FIMUL/FIST). Другой вариант - использование SSE 4.1 PMULLD - правда там перемножается сразу несколько пар значений.
« Последнее редактирование: 13-02-2017 10:17 от darkelf » Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #8 : 13-02-2017 11:41 » 

Насчет усечения старшей части произведения на регистрах FPU я тоже не нашел упоминаний. Не исключено, конечно, что усекутся впоследствии при вытаскивании результата из стека FPU.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #9 : 13-02-2017 12:16 » 

Насчет усечения старшей части произведения на регистрах FPU я тоже не нашел упоминаний. Не исключено, конечно, что усекутся впоследствии при вытаскивании результата из стека FPU.
Похоже я был не прав, сорри. Почитал документацию - если полученное значение слишком велико для отведённой переменной, то должно произойти исключение.

Тогда остаётся только PMULLD. Ну и ещё SHL, если надо умножать на степени двойки.
Записан
Aether
Молодой специалист

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 13-02-2017 12:44 » 

Приведу небольшой бета-пример, иллюстрирующий, когда проблема с умножением может быть, а памяти ещё как-бы хватает. Оформление постарался учесть, и прилагаю мини-тест для лучшей иллюстрации. Возможно, заодно будут замечания касательно общей корректности, и касательно того, как грамотно строить тесты.

У x86 есть другие целочисленные команды умножения, где старшая часть игнорируется?
В принципе, FPU позволяет загружать целочисленные значения и перемножать их (FILD/FIMUL/FIST). Другой вариант - использование SSE 4.1 PMULLD - правда там перемножается сразу несколько пар значений.
Насколько я понимаю, FPU тут же приводит целочисленное значение к своему максимальному формату с плавающей запятой в х86. Вообще с FPU есть другие краевые проблемы, например, приближаясь к очень малым числам может начать стремительно падать точность вычислений, а приближаясь к внешним границам его пространства тоже может случиться переполнение. Как пример: вычисление центра дуги - он может оказаться вне пространства FPU, хотя сама дуга может быть вполне всецело в нём. Такие вещи не сразу видны, и могут за всю эксплуатацию ни разу не вылезти по причине оперирования много меньшими размерностями.

* AE_bfield.h (2.11 Кб - загружено 28 раз.)
* AE_bfield.c (15.55 Кб - загружено 29 раз.)
* AE_bfield_test.c (4.04 Кб - загружено 41 раз.)
* test.txt (55.69 Кб - загружено 40 раз.)
* info_1_3.txt (0.19 Кб - загружено 31 раз.)
* info_1_5.txt (0.19 Кб - загружено 29 раз.)
* info_1_7.txt (0.19 Кб - загружено 29 раз.)
* info_1_9.txt (0.19 Кб - загружено 28 раз.)
* info_1_14.txt (0.3 Кб - загружено 27 раз.)
* info_1_16.txt (0.3 Кб - загружено 27 раз.)
* info_1_19.txt (0.3 Кб - загружено 27 раз.)
* info_1_21.txt (0.23 Кб - загружено 29 раз.)
* bfield_1_17.bin (0.02 Кб - загружено 29 раз.)
Записан
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #11 : 13-02-2017 14:42 » 

По поводу "отловить переполнение при умножении" - насколько я понимаю стандартного способа нет, но есть "почти стандартный", если считать gcc стандартом де-факто.
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #12 : 13-02-2017 15:01 » 

как грамотно строить тесты.

IMHO самый грамотный способ строить тесты - это воспользоваться стандартными инструментами для тестирования, благо развиваются они не один десяток лет, и даже для экзотических языков есть реализации. Какие именно инструменты? Это зависит от того, что тестируем.

Вообще с FPU есть другие краевые проблемы, например, приближаясь к очень малым числам может начать стремительно падать точность вычислений, а приближаясь к внешним границам его пространства тоже может случиться переполнение. Как пример: вычисление центра дуги - он может оказаться вне пространства FPU, хотя сама дуга может быть вполне всецело в нём.

Можно практический пример? Абстрактно затрудняюсь представить.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Aether
Молодой специалист

ru
Offline Offline
Пол: Мужской

« Ответ #13 : 13-02-2017 17:21 » 

Можно практический пример? Абстрактно затрудняюсь представить.
Вот сходу пример привести в коде будет сложно, так как дело не в коде, а в математике.

Ситуация с выходом за верхние пределы выглядит так: берём лист бумаги формата А4, представим, что максимальные числа в формате с плавающей запятой это границы этого листа. Чертим дугу на нём так, чтобы её центр лежал вне листа - это легко. При вычислении центра FPU заменит максимальное значение на код #INF - бесконечность, в разных форматах это выглядит, как знак = 0 (+) или 1 (-), экспонента = все единицы, а мантисса заполнена нулям.

В случае очень малых чисел экспонента достигает своего нижнего предела, но можно туда впихнуть числа ещё меньшие, если их денормализовать - заполнить верх мантиссы необходимым числом нулей, что FPU и делает, таким образом, диапазон расширяется на ещё с десяток порядков, но точность при этом начинает улетучиваться при приближении к нулю - мы расширяем диапазон ценою потери битов мантиссы. В итоге, FPU, приравнивает число к +0. или -0. в зависимости от того с какой стороны шло приближение к нулю.

Я вот думал, как-нибудь оформить это в коде или в графике, чтобы было видно, как устроен IEEE 754. Кстати, long double (80 бит), вроде, устроен похоже, но является стандартом Intel, а не общим.
Записан
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #14 : 13-02-2017 19:24 » 

http://x86.renejeschke.de/html/file_module_x86_id_138.html
Первые три операции, доставшиеся с древнейших времен, сохраняют старшую часть. Все остальные - нет.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.

Хз, я не очень просто не очень во всё это верю, во всякие там сатурны и прочую поебень.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #15 : 13-02-2017 21:46 » 

Ситуация с выходом за верхние пределы выглядит так: берём лист бумаги формата А4, представим, что максимальные числа в формате с плавающей запятой это границы этого листа.

Так мы ведь сами загоняем себя в угол при такой постановке.

Поступим по-другому. Поставим посередине листа точку и объявим, что она имеет координаты (0, 0). Соответственно абсцисса меняется в диапазоне +/-0.105, ордината +/-0.1485. Это как бы наше окно на плоскости, в которое попадают не все предметы из-за отсечения по границам. При этом координаты на плоскости представляются числами double от 10−308 до 10308.

Теперь немного физики. Сначала в микромир. Шаг кристаллической решетки оценивается в 1 ангстрем, или 10-10 м. Примерно так же оцениваются размеры молекул. Если это для Вашей задачи слишком грубо, считаем с точностью до адрона: 10-15. Если и это чересчур грубо, крайняя мера: планковская длина = 10-35.

Цитата
Планковская длина является пределом расстояния, меньше которого сами понятия пространства и длины перестают существовать.

Если радиус дуги меньше планковской длины, смело заменяем ее точкой. Никакое реальное приложение не заметит подмены.

Теперь в макромир. Радиус наблюдаемой Вселенной оценивается порядком 1026 метров. Самая большая координата, которую мы можем задать на нашей плоскости, в 10282 раз дальше. Допустим, дуга проходит через 2 точки на нашем листе, расстояние между которыми 0.3 м. Ее радиус равен 10308 метров. Длина стрелки такой дуги получается порядка 10-308 м. Если Вы замените эту дугу отрезком, соединяющим точки, опять ни один инструмент не сможет найти подвох.

Существуют реальные инженерные задачи, для которых этого все еще недостаточно?
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Aether
Молодой специалист

ru
Offline Offline
Пол: Мужской

« Ответ #16 : 13-02-2017 22:03 » 

Вот, собственно, для понимания набрал, хотя не тестировал:
Код: (C)
  // Windows - 1251 (ANSI)

#include <locale.h>
#include <stdint.h>
#include <stdio.h>

char hex(uint8_t a) {

    switch (a) {
        case 0: return '0';
        case 1: return '1';
        case 2: return '2';
        case 3: return '3';
        case 4: return '4';
        case 5: return '5';
        case 6: return '6';
        case 7: return '7';
        case 8: return '8';
        case 9: return '9';
        case 10: return 'A';
        case 11: return 'B';
        case 12: return 'C';
        case 13: return 'D';
        case 14: return 'E';
        case 15: return 'F';
        default: return '?';
    }
}



uint8_t getbit(void* addr, uint8_t count) {
        uint8_t* t = (uint8_t*)addr;
        t += (count >> 3);
        return (((*t) >> (count%8)) & 0x01) ? 1 : 0;
}



void dprint(double* a) {

    printf("\nHex: ");

    for (uint8_t* i = (uint8_t*)a + 7; i >= (uint8_t*)a; i--) {
        putchar(hex((*i) >> 4));
        putchar(hex((*i) & 0x0F));
        putchar(' ');
    }

    printf("\nBin: \n  Sign: ");

    uint8_t i = 64;
    do {
        i--; uint8_t res = getbit((void*)a, i);
        (res) ? putchar('1') : putchar('0');
        if (i == 63) (res) ? printf(" (-)\n  Exp: ") : printf(" (+)\n  Exp: ");
        if (i == 52) printf("\n  Man:");
        if (!(i%4)) putchar(' ');
    } while (i != 0);

    printf("\nMath: %e\n", *a);
}



int main(int argc, char* argv) {

    setlocale(LC_ALL, "");

  // double IEEE 754:
  // sign(1) + exp(11) + man(52)

  // Начало пространства начинается с чисел NAN, собственно, это и не числа.
  // Маска для NAN 1-111 1111 1111-xxxx ...

  // Конец отрицательного пространства NAN
    static uint8_t MinusEndNAN[8] = {0xFF, 0xFF, 0xFF, 0xFF,
                                     0xFF, 0xFF, 0xFF, 0xFF};

  // Начало отрицательного пространства NAN
    static uint8_t MinusStartNAN[8] = {0x01, 0x00, 0x00, 0x00,
                                       0x00, 0x00, 0xF0, 0xFF};

  // Минус бесконечность
    static uint8_t MinusInf[8] = {0x00, 0x00, 0x00, 0x00,
                                  0x00, 0x00, 0xF0, 0xFF};

  // Конец пространства отрицательных нормализованных чисел
    static uint8_t MinusMaxNorm[8] = {0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xEF, 0xFF};

  // Начало пространства отрицательных нормализованных чисел
    static uint8_t MinusMinNorm[8] = {0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0x0F, 0x80};

  // Конец пространства отрицательных денормализованных чисел
    static uint8_t MinusMaxDenorm[8] = {0xFE, 0xFF, 0xFF, 0xFF,
                                        0xFF, 0xFF, 0x0F, 0x80};

  // Начало пространства отрицательных денормализованных чисел
    static uint8_t MinusMinDenorm[8] = {0x01, 0x00, 0x00, 0x00,
                                        0x00, 0x00, 0x00, 0x80};

  // Минус ноль
    static uint8_t MinusZero[8] = {0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x80};

  // Плюс ноль
    static uint8_t PlusZero[8] = {0x00, 0x00, 0x00, 0x00,
                                  0x00, 0x00, 0x00, 0x00};

  // Начало пространства положительных денормализованных чисел
    static uint8_t PlusMinDenorm[8] = {0x01, 0x00, 0x00, 0x00,
                                       0x00, 0x00, 0x00, 0x00};

  // Конец пространства положительных денормализованных чисел
    static uint8_t PlusMaxDenorm[8] = {0xFE, 0xFF, 0xFF, 0xFF,
                                       0xFF, 0xFF, 0x0F, 0x00};

  // Начало пространства положительных нормализованных чисел
    static uint8_t PlusMinNorm[8] = {0xFF, 0xFF, 0xFF, 0xFF,
                                     0xFF, 0xFF, 0x0F, 0x00};

  // Конец пространства положительных нормализованных чисел
    static uint8_t PlusMaxNorm[8] = {0xFF, 0xFF, 0xFF, 0xFF,
                                     0xFF, 0xFF, 0xEF, 0x7F};

  // Плюс бесконечность
    static uint8_t PlusInf[8] = {0x00, 0x00, 0x00, 0x00,
                                 0x00, 0x00, 0xF0, 0x7F};

  // Начало положительного пространства NAN
    static uint8_t PlusStartNAN[8] = {0x01, 0x00, 0x00, 0x00,
                                      0x00, 0x00, 0xF0, 0x7F};

  // Конец положительного пространства NAN
    static uint8_t PlusEndNAN[8] = {0xFF, 0xFF, 0xFF, 0xFF,
                                    0xFF, 0xFF, 0xFF, 0x7F};

    double* a;

    printf("\nКонец отрицательного пространства NAN.");
    a = (double*)(MinusEndNAN);
    dprint(a);

    printf("\nНачало отрицательного пространства NAN.");
    a = (double*)(MinusStartNAN);
    dprint(a);

    printf("\nМинус бесконечность.");
    a = (double*)(MinusInf);
    dprint(a);

    printf("\nКонец пространства отрицательных нормализованных чисел.");
    a = (double*)(MinusMaxNorm);
    dprint(a);

    printf("\nНачало пространства отрицательных нормализованных чисел.");
    a = (double*)(MinusMinNorm);
    dprint(a);

    printf("\nКонец пространства отрицательных денормализованных чисел.");
    a = (double*)(MinusMaxDenorm);
    dprint(a);

    printf("\nНачало пространства отрицательных денормализованных чисел.");
    a = (double*)(MinusMinDenorm);
    dprint(a);

    printf("\nМинус Ноль.");
    a = (double*)(MinusZero);
    dprint(a);

    printf("\nПлюс Ноль.");
    a = (double*)(PlusZero);
    dprint(a);

    printf("\nНачало пространства положительных денормализованных чисел.");
    a = (double*)(PlusMinDenorm);
    dprint(a);

    printf("\nКонец пространства положительных денормализованных чисел.");
    a = (double*)(PlusMaxDenorm);
    dprint(a);

    printf("\nНачало пространства положительных нормализованных чисел.");
    a = (double*)(PlusMinNorm);
    dprint(a);

    printf("\nКонец пространства положительных нормализованных чисел.");
    a = (double*)(PlusMaxNorm);
    dprint(a);

    printf("\nПлюс бесконечность.");
    a = (double*)(PlusInf);
    dprint(a);

    printf("\nНачало положительного пространства NAN.");
    a = (double*)(PlusStartNAN);
    dprint(a);

    printf("\nКонец положительного пространства NAN.");
    a = (double*)(PlusEndNAN);
    dprint(a);

    return 0;
}

Результат:
Конец отрицательного пространства NAN.
Hex: FF FF FF FF FF FF FF FF
Bin:
  Sign: 1 (-)
  Exp: 111 1111 1111
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Math: -1,#QNAN0e+000

Начало отрицательного пространства NAN.
Hex: FF F0 00 00 00 00 00 01
Bin:
  Sign: 1 (-)
  Exp: 111 1111 1111
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
Math: -1,#QNAN0e+000

Минус бесконечность.
Hex: FF F0 00 00 00 00 00 00
Bin:
  Sign: 1 (-)
  Exp: 111 1111 1111
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Math: -1,#INF00e+000

Конец пространства отрицательных нормализованных чисел.
Hex: FF EF FF FF FF FF FF FF
Bin:
  Sign: 1 (-)
  Exp: 111 1111 1110
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Math: -1,797693e+308

Начало пространства отрицательных нормализованных чисел.
Hex: 80 0F FF FF FF FF FF FF
Bin:
  Sign: 1 (-)
  Exp: 000 0000 0000
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Math: -2,225074e-308

Конец пространства отрицательных денормализованных чисел.
Hex: 80 0F FF FF FF FF FF FE
Bin:
  Sign: 1 (-)
  Exp: 000 0000 0000
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110
Math: -2,225074e-308

Начало пространства отрицательных денормализованных чисел.
Hex: 80 00 00 00 00 00 00 01
Bin:
  Sign: 1 (-)
  Exp: 000 0000 0000
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
Math: -4,940656e-324

Минус Ноль.
Hex: 80 00 00 00 00 00 00 00
Bin:
  Sign: 1 (-)
  Exp: 000 0000 0000
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Math: -0,000000e+000

Плюс Ноль.
Hex: 00 00 00 00 00 00 00 00
Bin:
  Sign: 0 (+)
  Exp: 000 0000 0000
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Math: 0,000000e+000

Начало пространства положительных денормализованных чисел.
Hex: 00 00 00 00 00 00 00 01
Bin:
  Sign: 0 (+)
  Exp: 000 0000 0000
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
Math: 4,940656e-324

Конец пространства положительных денормализованных чисел.
Hex: 00 0F FF FF FF FF FF FE
Bin:
  Sign: 0 (+)
  Exp: 000 0000 0000
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110
Math: 2,225074e-308

Начало пространства положительных нормализованных чисел.
Hex: 00 0F FF FF FF FF FF FF
Bin:
  Sign: 0 (+)
  Exp: 000 0000 0000
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Math: 2,225074e-308

Конец пространства положительных нормализованных чисел.
Hex: 7F EF FF FF FF FF FF FF
Bin:
  Sign: 0 (+)
  Exp: 111 1111 1110
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Math: 1,797693e+308

Плюс бесконечность.
Hex: 7F F0 00 00 00 00 00 00
Bin:
  Sign: 0 (+)
  Exp: 111 1111 1111
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Math: 1,#INF00e+000

Начало положительного пространства NAN.
Hex: 7F F0 00 00 00 00 00 01
Bin:
  Sign: 0 (+)
  Exp: 111 1111 1111
  Man: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
Math: 1,#QNAN0e+000

Конец положительного пространства NAN.
Hex: 7F FF FF FF FF FF FF FF
Bin:
  Sign: 0 (+)
  Exp: 111 1111 1111
  Man: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Math: 1,#QNAN0e+000

В инженерных задачах, да, даже при умножении и возведении в степень, даже вложенном, выходов за пространство не наблюдается, обычно. Из математических задач на первом месте ряды требующие вычисления факториала, итерационные и прочие вычисления, когда имеет место быть аккумуляторный эффект накопления погрешности.
Здесь я всё же больше говорю об академической стороне вопроса, то есть программа по хорошему должна быть написана для идеального мира с реальной машиной, где возможна ситуация вблизи предела машины, и она должна быть отработана, а не оставлена на авось.

Кстати, в числах float выход за пределы в инженерных задачах встречался.
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #17 : 13-02-2017 23:17 » 

Факториал в принципе не может накопить погрешность, он целочисленный. Итерации - могут при большом количесиве, но в большинстве случаев эти проблемы решаемы (специалисты по численным методам знают, что достаточно поменять порядок операций, и скорость схождения/ результат вычислений могут кардинально поменяться).

Для инженерных приложений существующего диапазона значений более чем достаточно. Возьмем, скажем планковскую длину за единицу (меньше расстояний все равно нет). Тогда размер Вселенной в этих единицах "всего-навсего" 1060, дальше тоже идти некуда. У нас же огромный запас от 10-308 до 10308.

Проблемы накопления погрешности вызваны не тем, что диапазон представления чисел мал (он реально много больше разумных границ), а тем, что разрядность мантиссы недостаточна (порядка 15 значащих десятичных цифр). Тут я вижу только три выхода: правильные численные методы, которые быстро сходятся, не успев накопить большую погрешность; представление чисел с произвольным количеством знаков (во многих языках есть децимальные типы произвольной длины, правда, они не слишком эффективны в расчетах, поскольку эта арифметика слабо поддержана аппаратно); наконец, аппаратные спецвычислители для конкретных применений с повышенной разрядностью (например, можно сделать на базе FPGA).

Кстати, недавно в блоге одного умного человека нашел высказывание о том, что настоящий инженер - тот, у кого есть логарифмическая линейка и кто умеет ей пользоваться. С ностальгией вспомнил, что свою выбросил давным-давно в порыве юношеского максимализма после приобретения программируемого калькулятора. Сейчас с удовольствием положил бы на стол как символ нескольких инженерных поколений... У них никогда не было проблемы недостатка разрядов. Ну и от "Феликса" не отказался бы, конечно, но не попадались в должной кондиции.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #18 : 14-02-2017 08:41 » 

http://x86.renejeschke.de/html/file_module_x86_id_138.html
Первые три операции, доставшиеся с древнейших времен, сохраняют старшую часть. Все остальные - нет.
Совсем забыл про эту форму IMUL. Но она может применяться только для знакового умножения. К сожалению аналогичной беззнаковой MUL не существует.
Записан
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #19 : 14-02-2017 21:12 » 

И не надо. У каждой команды свое назначение.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.

Хз, я не очень просто не очень во всё это верю, во всякие там сатурны и прочую поебень.
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines