Что означает текст nan при выполнении арифметических операций: Что означает текст «NaN» при выполнении арифметических операций в языке JavaScript? – NaN — Википедия

Содержание

NaN — Википедия

Материал из Википедии — свободной энциклопедии

NaN (англ. Not-a-Number, «нечисло») — одно из особых состояний числа с плавающей запятой. Используется во многих математических библиотеках и математических сопроцессорах. Данное состояние может возникнуть в различных случаях, например, когда предыдущая математическая операция завершилась с неопределённым результатом или если в ячейку памяти попало не удовлетворяющее условиям число.

В соответствии с IEEE 754, такое состояние задаётся через установку показателя степени в зарезервированное значение 11…11, а мантиссы — во что угодно, кроме 0 (зарезервированное значение для машинной бесконечности). Знак и мантисса могут нести какую-то дополнительную информацию: многие библиотеки «отрицательный» NaN выводят как -NaN.

К операциям, приводящим к появлению NaN в качестве ответа, относятся:

  • все математические операции, содержащие NaN в качестве одного из операндов;
  • деление нуля на ноль;
  • деление бесконечности на бесконечность;
  • умножение нуля на бесконечность;
  • сложение бесконечности с бесконечностью противоположного знака;
  • вычисление квадратного корня отрицательного числа[1];
  • логарифмирование отрицательного числа.

В некоторых языках программирования есть «тихий» и «сигнальный» NaN: первый, попав в любую операцию, возвращает NaN, второй — вызывает исключительную ситуацию. Обычно «тихий» или «сигнальный» определяется старшим битом мантиссы.

  • NaN не равен ни одному другому значению (даже самому себе). Благодаря этому один из распространённых, однако не интуитивных, способов проверки результата на NaN — это сравнение полученной величины с самой собой. Более прозрачным способом является вызов функции isNaN().
  • Поведение других операций сравнения зависит от языка. Одни языки дают ложь[2] (так что a < b и b > a по-разному ведут себя с NaN), другие — формируют исключительную ситуацию даже для «тихого» NaN.
  • Любая нетривиальная операция, принимающая «тихий» NaN как аргумент, всегда возвращает NaN вне зависимости от значения других аргументов. Единственными исключениями из этого правила являются функции max и min, которые возвращают значение «второго» аргумента (отличного от NaN).
  • Тривиальные операции, являющиеся тождеством, обрабатываются особо: так, например, 1NaN равно 1.

Вопрос: Что означает текст «NaN» при выполнении арифметических операций в языке JavaScript?

Смотря что ты хочешь изучить и что имеешь ввиду под программированием.

Если html+css — да, вполне возможно. Но это верстка а не программирование. SQL — так же возможно, но и работу с БД я так же не могу назвать настоящим программированием.

Если же брать серьезное программирование — однозначное нет. Минимум год самообучения по 8-12 часов в день. Минимум — если у тебя уже есть некая база и неплохо поставленная логика. Значительно больше если нет.
У меня была очень сильная база (я несколько лет работал в IT конторе мирового масштаба(входит в первую десятку по размеру) международной тех.поддержкой высшего уровня[там было 5 таких] а так же QA[тестировщиком], а так же некую базу программирования уже имел), но у меня пошло пол года по 8-12 часов в день что бы достичь некоего более-менее неплохого уровня на C#. По факту недостаточного что бы работать полноценным программистом. Но все же достаточного для автоматизации.

Реальное программирование — это не просто синтаксис языка. Программирование — это умение решать задачи. Как математические так и логические. Логические — в первую очередь! А так же знание ряда алгоритмов. А так же знание инструментов которыми пользуешься(например нужно понимать как внутри устроен List/LinkedList и Array для того,что бы их правильно оптимально использовать, просто знать что длинна аррея не меняется — НЕ ДОСТАТОЧНО). Синтаксиса языка НЕ ДОСТАТОЧНО кто бы тебе не говорил обратного.

Что бы не быть баснословным, я наведу простой пример использования алгоритмов:

Алгоритм среднего между 2мя целыми числами нормального человека:
(a+b)/2
алгоритм среднего из 2х целых чисел программиста(умного, а не в кавычках):
a/2+b/2+(a%2+b%2)/2
где % — вычисление остачи от деления.

потому как первый алгоритм даст ровно в половине из возможных случаев неправильный ответет из-за переполнения памяти + еще в четверти случаях просто на одиницу меньше нужного. Заметьте! Не ошибку! А неправильный ответ в трех случаях из 4х! А «программист»(именно в кавычках) еще и не будет в курсе почему так 🙂

Для нецелочисленного типа данных(double, float) проще:
a/2+b/2

Как видишь, проблема не в синтаксисе как таковом 🙂 Выучить синтаксис — как раз наименьшая из проблем. Хотя, замечу, что, даже, это для многих будет непосильной задачей.

С другой стороны — я за то что бы не грузить человека «паттернами» и излишними алгоритмами. На новичков это подействует, скорее всего, негативно, чем позитивно. Типа…. «я слышал про паттерн ____________, вот задача на которую КАЖЕТСЯ подойдет он.» А потом решение задачи усложняется в разы. Ну или затягивается. Паттерны нужно не только знать, но и применять с умом. К алгоритмам так же нужны знания как и когда их лучше применять. Поэтому — БЕЗ ФАНАТИЗМА.

PS: человек снизу, который меня активно критикует(Jone Done), даже путает среду разработки и язык… Delphi — это не язык, а IDE, а язык там это Object Pascal.
Так же там в коментариях у нее я проверил ее «нормальные знания джавы»… Желающие посмотреть на «номально выучившую язык за 2,5 месяца» смотреть туда. И это при том что Я ДЖАВУ НЕ ЗНАЮ, а, так, посмотрел инфу не более чем 3 дня где-то пол года назад просто для общего развития и что бы лично сложить свое собственное мнение по языку.

Полезный NaN / Habr

О NaN больше всего известно то, что он не равен самому себе.
NaN === NaN // false

И что операции, невозможные арифметически, вернут NaN.
'BlaBlaBla'/0 // NaN

Но у NaN есть одно мало известное(?), и, как мне кажется, весьма полезное применение.

TL;DR Все дело в Date


В двух словах:
 new Date(NaN) // Invalid Date 

Чем полезно? Invalid Date все равно Date. И все операции с Date все ещё на месте.
Любые операции с Date, кроме прямой установки timestamp вернут NaN, оставив Date как Invalid Date.
const x = new Date(NaN) // Invalid Date x.setTime(12345) // 12345 x.toString() // "Thu Jan 01 1970 00:00:12 GMT+0000 (+00)"

При этом, проверка на валидность даты становится проще некуда
 
const x = new Date(NaN) // Invalid Date 
isNaN(x) // true
x.setTime(0)
isNaN(x) // false

Заметьте, преобразование в timestamp здесь не требуется, valueOf() делает это под капотом.

Все операции с Date — мутабельные. Тем не менее, клонирование через конструктор прекрасно работает и с Invalid Date.

 
const x =  new Date(NaN) // Invalid Date 
const y = new Date(x) // Invalid Date

Сравнение двух дат напрямую в Date не реализовано и сравнивать даты можно только через timestamp. NaN гарантирует что Invalid Date точно не будет равно никакой другой дате. Думаю, это весьма полезное свойство.
 
const x =  new Date(NaN) // Invalid Date 
const y = new Date(NaN) // Invalid Date
x.getTime() === y.getTime() // false

К моему сожалению, конструктор Date ведёт себя несколько странно по отношению к входному параметру.
 new Date(null) // Thu Jan 01 1970 00:00:00 GMT+0000 (+00) 

Было бы намного логичнее конструировать Invalid Date, ведь null — это не совсем ноль. Оставим это на совести Javascript-а.

Однако, если насильственно передать в конструктор undefined, то результат выглядит ожидаемым. Так что будьте осторожны.

 new Date(undefined) // Invalid Date 

Статья получилась больше о Date чем о NaN, но, в целом, именно об этой связке я хотел рассказать.

Арифметические операции над числами с плавающей точкой / Habr

Все читатели Хабра так или иначе связаны с ИТ направлением. Будь ты программист или работаешь с железом, сетями и так далее, все мы знаем общие концепции.

Когда-то на втором курсе университета я познакомился как раз с одной из вещей, которую, по-моему, должен знать каждый из нас, ну или хотя бы услышать о ней вот в такой статье. Это стандарт представления чисел в формате с плавающей точкой(в других источниках с плавающей запятой). Как же въелось мне это название: стандарт IEEE-754.

Уверен, что каждый из айтишников хоть раз но слышал с числами в формате плавающей точкой, но для меня впервые это показалось полной чушью. И не с проста: ведь предмет, на котором мы изучали стандарт, назывался «Архитектура ЭВМ» да и преподаватель был, да и сейчас есть живой легендой. Ну, это оффтоп.

Итак, что же такое этот стандарт IEEE-754? Скажу сразу, что нам в университете дали его в электронном виде на русском, но в интернете я не смог его найти, даже дойдя до 30-й страницы гугла. Был пример на английском, в котором автор писал его в 4:36 АМ. Я даже нашёл сайт, в котором говориться, что если бы Сатана решил захватить Землю медленно, он бы создал этот стандарт. Но его создали люди, такие же, как и мы с вами.

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

По этому предмету в университете у нас рассчитывалась РГР (Расчётно-Графическая работа) и почему то тогда я понял, что стоит ей уделить больше времени, чем чему-либо и оказался прав. Это, наверное, был переломный момент моей учёбы. Я сидел ночами над этим стандартом и над конкретно поставленной передо мной задачей: «Деление двух чисел в формате с плавающей точкой двойной точности с заменой цепочек непрерывных единиц нулями и с округлением до ближайшего чётного». Тогда это нельзя было понять. И стандарт IEEE-754 всегда следовал рядом с этим заданием. На самом деле там было всё, абсолютно всё, что мне нужно было.

Ну, а теперь подробнее собственно, о стандарте IEEE-754. Он представляет собой несколько глав, которые я хотел бы описать поподробнее.
Всё как всегда начинается с введения. О том, что существуют программы, намного сложнее чем то, что я видел. Рассказывается об истории создания стандарта. Ведь программы становятся всё сложнее и сложнее, а ЦВМ стареет и следует заменить её новой архитектурой. Это послужило причиной тому, что IEEE (Институт инженеров по электротехнике и электронике США) в конце 70-х годов создал комиссию, рассмотревшую множество предложений. Результатом работы комиссии явился стандарт IEEE 754 ≪Двоичная арифметика с плавающей точкой≫ (1985г.), ставший международным. Его основы разработал профессор математики университета Беркли William Kahan.
В последующие годы на базе IEEE 754 – 1985 были разработаны стандарты:

— IEEE 854 – 1987, покрывающий десятичную арифметику также как и двоичную;

— IEC 60559 — 1989 IEC ≪Двоичная арифметика с плавающей точкой для

микропроцессорных систем≫ (IEC — International Electrotechnical Commission).

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

Стандарт поддерживает несколько форматов: одинарная точность(32 разряда), двойная(64 разряда) и двойная расширенная точность. Так же предусмотрены другие форматы для предотвращения ошибок округления и т.д. В стандарте описаны случаи возникновения исключительных ситуаций: Nan, бесконечность, деление на ноль и т.д. Ничего не напоминает? Очень важную роль играет округление чисел в формате с плавающей точкой. Это тоже описано в стандарте.

И наконец-то, главный раздел – Выполнение операций над числами в формате с плавающей точкой. В этом разделе описаны все арифметические операции от сравнения до деления, а так же все нюансы при выполнении таких операций. Про этот раздел нельзя сказать вот так, «в двух словах». Скажу лишь, что это настоящая морока и передо мной встала задача понять, как это происходит.
Опишу вкратце свой алгоритм работы «Деление в формате с плавающей точкой». После того, как мы получили операнды А и В, нужно было проверить их на все возможные случаи возникновения исключительных ситуаций. Это и деление на ноль и Nan и бесконечность. Немного ниже, на таблице, изображены типы представления чисел, которые поддерживает формат:

Если операнды действительно являлись числами в формате IEEE-754, начинался второй этап выполнения операции: приведение порядков. Ни для кого не секрет, что числа в формате с плавающей точкой выглядят примерно так:

Это представление числа в формате с одинарной точностью.
Порядок числа в ЦВМ – это, в моём понимании, порядковый номер числа в ЦВМ, то есть его порядок. Наверняка есть научное определение, но оно лишь запутает ещё больше. Так вот, раз числа имеют разные порядки, их нельзя делить. Следует сначала привести порядки к одному виду смещением порядков. Но для этого требовалось проанализировать порядки на min и маx значение. А когда происходит смещение порядков, мантиссы тоже сдвигаются. Если порядки уравнены, нужно проверить мантиссы, не вылетели ли они за границы и не заполнились ли они нулями и т.д. Закончив ряд проверок, можно приступать к самому главному: наконец-то делить мантиссы. Ну, тут всё просто, как и вся двоичная арифметика. Я делил делитель на делимое, а остаток записывал в регистр и складывал. Там ещё есть несколько способов деления: с восстановление и без восстановления остатка. Да и это ещё не всё! В конце следовало округлить полученный результат по нужному условию и определить знак частного.

Это всего лишь на словах, хоть и звучит страшно, на деле выглядит куда лучше. Тогда я откровенно запал на этот стандарт, что принесло мне не только более глубокие знания в ЦВМ и двоичной арифметике, но и удовольствие что я смог это сделать, удовольствие осознавать то, что я знаю что-то очень важное.
У меня всё, на самом деле тема очень интересная и увлекательная. Кто заинтересовался, с удовольствием скину стандарт IEEE-754 и отвечу на ваши вопросы.

Спасибо.

Математический сопроцессор — Википедия

Математический сопроцессор — сопроцессор для расширения командного множества центрального процессора и обеспечивающий его функциональностью модуля операций с плавающей запятой, для процессоров, не имеющих интегрированного модуля.

Модуль операций с плавающей запятой (или с плавающей точкой; англ. floating point unit (FPU) — часть процессора для выполнения широкого спектра математических операций над вещественными числами.

Простым «целочисленным» процессорам для работы с вещественными числами и математическими операциями требуются соответствующие процедуры поддержки и время для их выполнения. Модуль операций с плавающей запятой поддерживает работу с ними на уровне примитивов — загрузка, выгрузка вещественного числа (в/из специализированных регистров) или математическая операция над ними выполняется одной командой, за счёт этого достигается значительное ускорение таких операций.

Основная статья: X87

x87 — это специальный набор инструкций для работы с математическими вычислениями, являющийся подмножеством архитектуры процессоров x86. Такое название он получил, потому что первоначальные отдельные математические сопроцессорные чипы имели названия, заканчивающиеся на 87. Как и другие расширения базового набора инструкций процессора, эти инструкции не являются строго необходимыми для построения рабочей программы, но будучи аппаратно реализованными, общие математические задачи они позволяют выполнять гораздо быстрее. Например, в наборе инструкций x87 присутствуют команды для расчёта значений синуса или косинуса.

Сопроцессоры Intel семейства x86[править | править код]

Для процессоров семейства x86 с 8086/8088 по 386 модуль операций с плавающей запятой был выделен в отдельную микросхему, называемую математическим сопроцессором. Для установки сопроцессора на плате компьютера предусматривался отдельный разъём.

Сопроцессор не является полноценным процессором, так как не умеет делать многих необходимых для этого операций (например, не умеет работать с программой и вычислять адреса памяти), являясь всего лишь придатком центрального процессора.

Одна из схем взаимодействия центрального процессора и сопроцессора, применяемая, в частности, в x86-сопроцессорах, реализуется следующим образом:

  • Сопроцессор подключен к шинам центрального процессора, а также имеет несколько специальных сигналов для синхронизации процессоров между собой.
  • Часть командных кодов центрального процессора зарезервирована для сопроцессора, он следит за потоком команд, игнорируя другие команды. Центральный процессор, наоборот, игнорирует команды сопроцессора, занимаясь только вычислением адреса в памяти, если команда предполагает к ней обращение. Центральный процессор делает цикл фиктивного считывания, позволяя сопроцессору считать адрес с адресной шины. Если сопроцессору необходимо дополнительное обращение к памяти (для чтения или записи результатов), он выполняет его через захват шины.
  • После получения команды и необходимых данных сопроцессор начинает её выполнение. Пока сопроцессор выполняет команду, центральный процессор выполняет программу дальше, параллельно с вычислениями сопроцессора. Если следующая команда также является командой сопроцессора, процессор останавливается и ожидает завершения выполнения сопроцессором предыдущей команды.
  • Также существует специальная команда ожидания (FWAIT), принудительно останавливающая процессор до завершения вычислений (если для продолжения программы необходимы их результаты). В настоящее время команда используется лишь для обработки исключений при работе с плавающей точкой, работа процессора и сопроцессора синхронизируется автоматически[1].

Начиная с процессора Intel486DX модуль операций с плавающей запятой был интегрирован в центральный процессор и назван FPU. В линейке Intel486SX модуль FPU отключался (поначалу в эту линейку попадали процессоры с бракованным FPU). Для процессоров Intel486SX также выпускался «сопроцессор» Intel487SX, но фактически он являлся процессором Intel486DX, и при его установке процессор Intel486SX отключался.

Несмотря на интеграцию, FPU в процессорах i486 представляет собой неизменный сопроцессор, выполненный на том же кристалле, более того, схема FPU i486 полностью идентична сопроцессору предыдущего поколения 387DX вплоть до тактовой частоты (в два раза меньшей, чем частота центрального процессора). Настоящая интеграция FPU c центральным процессором началась только в процессорах Pentium модели MMX.

Сопроцессоры x86 от сторонних производителей[править | править код]

Широкое распространение в соответствующий период получили сопроцессоры для платформы x86, выпускавшиеся компанией Weitek — ею были выпущены 1167, 2167 в виде набора микросхем и микросхемы 3167, 4167, для процессоров 8086, 80286, 80386, 80486, соответственно. По сравнению с сопроцессорами от Intel они обеспечивали в 2—3 раза большую производительность, но обладали несовместимым программным интерфейсом, реализованным через технологию memory-mapping. Она сводилась к тому, что основной процессор должен был записывать информацию в те или иные области памяти, контролируемые Weitek-овским сопроцессором (собственной оперативной памяти там не было). Конкретный адрес, куда производилась запись, интерпретировался в качестве той или иной команды. Несмотря на несовместимость, сопроцессоры от Weitek были широко поддержаны как разработчиками ПО, так и производителями материнских плат, предусматривавших на них гнёзда для установки такой микросхемы.

Ряд других компаний также выпускал различные несовместимые математические сопроцессоры, реализуя интерфейс к ним через порты ввода-вывода или прерывания BIOS, но они не получили такого широкого распространения.

Компании-производители клонов выпускали совместимые с 80287 80387 сопроцессоры, работавшие быстрее аналогичных интеловских. Среди этих компаний можно упомянуть Cyrix, AMD, Chips & Technologies (C&T). Иногда система команд этих сопроцессоров расширялась несколькими несовместимыми, например, аналог 80287 от C&T содержал команды для работы с вектором из четырёх значений с плавающей точкой. Серьёзной поддержки от производителей ПО эти расширенные команды не получили.

Процессоры EMC87 от фирмы Cyrix могли работать как в режиме программной совместимости с Intel 80387, так и в собственном несовместимом режиме программирования. Для них обеспечивалась аппаратная совместимость с разъёмом 80387-го сопроцессора.

В СССР выпускалась микросхема (КМ)1810ВМ87, которая являлась аналогом 8087.

Другие платформы[править | править код]

Аналогично, материнские платы ПК, построенных на процессорах Motorola, до разработки этой фирмой процессора MC68040 (в который сопроцессор был встроен) содержали математический сопроцессор. Как правило, в качестве FPU использовался сопроцессор 68881 16 МГц или 68882 25 МГц. Практически любой современный процессор имеет встроенный сопроцессор.

Компания Weitek также выпускала математические сопроцессоры для платформ 68000 и MIPS.

Регистры FPU организованы не в виде массива, как в некоторых других архитектурах, а как регистровый стек. Таким образом, FPU представляет собой стековый калькулятор, работающий по принципу обратной польской записи[2][3]. Это означает, что команды всегда используют верхнее значение в стеке для проведения операций, а доступ к другим хранящимся значениям обычно обеспечивается в результате манипуляций со стеком. Однако при работе с вершиной стека одновременно могут использоваться и другие элементы стека, для доступа к которым применяется прямая адресация относительно вершины стека. Также в операциях могут использоваться значения, хранящиеся в оперативной памяти. Обычная последовательность действий выглядит следующим образом. Перед операцией аргументы помещаются в LIFO-стек; при выполнении операции необходимое количество аргументов снимается со стека. Результат операции помещается в стек, где может быть использован в дальнейших вычислениях или снят со стека для записи в память. Хотя стековая организация регистров FPU получается и удобной для программистов, она усложняет задачу построения эффективного кода компиляторами.

Особенности использования[править | править код]

Все процессоры Intel и AMD, начиная с 486DX, имеют встроенный математический сопроцессор, и в отдельном сопроцессоре не нуждаются (за исключением Intel486SX). Тем не менее, термин x87 всё ещё применяется для выделения той части инструкций процессора, которая служит для работы с вещественными числами в стеке FPU. Отличительный признак этих инструкций: их мнемоники начинаются с буквы f (от англ. float). Компиляторы могут использовать эти инструкции для производства кода, который в ряде случаев работает быстрее, нежели тот, что использует вызовы к библиотекам для выполнения операций с плавающей запятой.

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

После появления расширения 3DNow! от AMD и затем SSE, начиная с процессоров Pentium III компании Intel, вычисления с одинарной точностью стало возможным проводить без помощи инструкций FPU, причём с возросшей производительностью. Расширение SSE2 и более поздние расширения системы команд обеспечили также быстрое выполнение расчётов с двойной точностью (см. стандарт IEEE-754). В связи с этим в современных компьютерах потребность в командах классического математического сопроцессора значительно уменьшилась. Тем не менее, во всех выпускаемых x86-процессорах они по-прежнему поддерживаются для совместимости со старыми приложениями, а также для нужд тех приложений, где требуются двоично-десятичные преобразования или вычисления с расширенной точностью (когда двойной точности недостаточно). В настоящее время использование команд x87 остаётся наиболее эффективным способом ведения таких расчётов.

Форматы данных[править | править код]

Внутри FPU числа хранятся в 80-битном формате с плавающей запятой (расширенная точность), для записи же или чтения из памяти могут использоваться:

  • Вещественные числа в трёх форматах: коротком (32 бита), длинном (64 бита) и расширенном (80 бит).
  • Двоичные целые числа в трёх форматах: 16, 32 и 64 бита.
  • Упакованные целые десятичные числа (BCD) числа — длина максимального числа составляет 18 упакованных десятичных цифр (72 бита).

FPU также поддерживает специальные численные значения:

  • Денормализованные вещественные числа — числа, которые по абсолютной величине меньше минимального нормализованного числа. При формировании такого значения в некотором регистре стека в соответствующем этому регистру теге регистра TWR формируется специальное значение 10. Признаком денормализованного числа в его двоичном представлении служит нулевое поле порядка.
  • Бесконечность (положительная и отрицательная), возникает при делении на нуль ненулевого значения, а также при переполнениях. При формировании такого значения в некотором регистре стека в соответствующем этому регистру теге регистра TWR формируется специальное значение 10.
  • нечисло (англ. not-a-number (NaN)). Различают два вида нечисел:
    • SNaN (Signaling Non a Number) — сигнальные нечисла. Сопроцессор реагирует на появление этого числа в регистре стека возбуждением исключения недействительной операции. Сопроцессор не формирует сигнальных чисел. Программисты формируют такие числа преднамеренно, чтобы возбудить в нужной ситуации исключение.
    • QNaN (Quiet Non a Number) — спокойные (тихие) нечисла. Сопроцессор может формировать спокойные нечисла в качестве реакции на определённые исключения, например, число вещественной неопределённости.
  • Нуль (положительный и отрицательный). Хотя с точки зрения формата с плавающей запятой нуль может считаться специальным значением, он в то же время является частным случаем денормализованного числа.
  • Неопределённости и неподдерживаемые форматы. Существует много битовых наборов, которые можно представить в расширенном формате вещественного числа. Для большинства их значений формируется исключение недействительной операции.

Регистры[править | править код]

В FPU можно выделить три группы регистров:

  • Стек процессора: регистры R0..R7. Размерность каждого регистра: 80 бит.
  • Служебные регистры
    • Регистр состояния процессора SWR (Status Word Register) — информация о текущем состоянии сопроцессора. Размерность: 16 бит.
    • Управляющий регистр сопроцессора CWR (Control Word Register) — управление режимами работы сопроцессора. Размерность: 16 бит.
    • Регистр слова тегов TWR (Tags Word Register) — контроль над регистрами R0..R7 (например, для определения возможности записи). Размерность: 16 бит.
  • Регистры указателей
    • Указатель данных DPR (Data Point Register). Размерность: 48 бит.
    • Указатель команд IPR (Instruction Point Register). Размерность: 48 бит.

Система команд сопроцессора[править | править код]

Система включает около 80 команд. Их классификация:

  • Команды передачи данных
    • Вещественные данные
    • Целочисленные данные
    • Десятичные данные
    • Загрузка констант (0, 1, число Пи, log2(10), log2(e), lg(2), ln(2))
    • Обмен
    • Условная пересылка (Pentium II/III)
  • Команды сравнения данных
    • Вещественные данные
    • Целочисленные данные
    • Анализ
    • С нулём
    • Условное сравнение (Pentium II/III)
  • Арифметические команды
    • Вещественные данные: сложение, вычитание, умножение, деление
    • Целочисленные данные: сложение, вычитание, умножение, деление
    • Вспомогательные арифметические команды (квадратный корень, модуль, изменение знака, выделение порядка и мантиссы)
  • Трансцендентные команды
    • Тригонометрия: синус, косинус, тангенс, арктангенс
    • Вычисление логарифмов и степеней
  • Команды управления
    • Инициализация сопроцессора
    • Работа со средой
    • Работа со стеком
    • Переключение режимов
  1. ↑ Intel 64 and IA-32 Architectures Software Developer’s Manual. Volume 2A and 2B: Instruction Set Reference. Order numbers #253666, #253667
  2. ↑ Intel 64 and IA-32 Architectures Software Developer’s Manual. Volume 1: Basic Architecture. Order number #253665
  3. ↑ AMD64 Architecture Programmer’s Manual. Volume 1: Application Programming. Publication number #24592

Фатальные ошибки двоичной арифметики при работе с числами с плавающей точкой / Habr

Среди всего разнообразия форматов представления действительных чисел в компьютерной технике особое место отведено формату чисел с плавающей точкой (ЧПТ), который запротоколирован в стандарте IEEE754. Главные достоинства чисел с плавающей точкой, как известно, заключаются в том, что они позволяют производить вычисления в большом диапазоне значений и при этом вычисления организуются инструментарием двоичной арифметики, легко реализуемой на вычислительном устройстве. Однако последнее обстоятельство таит в себе подводные камни, которые являются причиной того, что расчеты, сделанные с использованием этого формата, могут приводить к совершенно непредвиденным результатам.

Ниже мы приводим примеры арифметических операций над некоторыми числами с плавающей точкой, которые приводят к неверным результатам. Эти результаты не зависят от платформы, на которой реализованы вычисления.

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

Отметим, что причина ошибочных вычислений с ЧПТ, главным образом, обусловлена ни ошибками округления, устранению которых в стандарте уделяется большое внимание, а самой природой конвертации десятичных и двоичных чисел.

Будем рассматривать два основных формата для ЧПТ — float и double. Напомним, что формат float позволяет представлять до 7 верных десятичных цифр в двоичной мантиссе, содержащей 24 разряда. Формат double представляетдо 15 верных десятичных цифр в мантиссе, содержащей 53 двоичных разряда.

Непредставимые в двоичном машинном слове десятичные числа, после приведения их к десятичному виду, содержат как верные цифры так и «хвосты» из неверных цифр. Эти «хвосты» и являются источником ошибочных вычислений десятичных действительных ЧПТ с помощью двоичной арифметики. Покажем это на примерах.

СУММА

Итак, сначала рассмотрим сумму двух следующих действительных чисел, представленных в формате float, каждое из которых имеет по 7 верных значащих цифр:
0,6000006 + 0,03339874=0,6333993 4≈0,6333993

Все вычисления будем проводить в формате float. Для наглядности будем использовать числа в распакованном виде. Представим наши десятичные числа в нормализованном двоичном виде:
0.6000006 ≈ 1.001100110011001101001*2^(-1)
0.03339874≈1.00010001100110100011110*2^(-5)

Если полученные двоичные коды наших чисел опять представить в десятичном виде, то получим следующие значения:
0.6000006 ≈ 0.6000006 198883056640625≈0.6000006 2
0.03339874≈0.03339873 9993572235107421875≈0.0333987 4

Здесь каждое число, округленное до 7 верных цифр мы отделили от «хвоста» пробелом. Эти «хвосты» получились в результате обратной конвертации чисел из двоичного кода, записанного в машинной мантиссе, в десятичный код.
Сумма наших чисел в двоичном 24-х разрядном виде даст следующий результат:
1.001100110011001101001*2^(-1) + 1.00010001100110100011110*2^(-5)≈ 1.0100010001001100111011*2^(-1) ≈0,6333994

Тот же результат будет получен, если просуммировать десятичные числа с «хвостами»:
0,6000006 2+ 0,0333987 4= 0,6333993 6≈ 0,6333994

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

Приведем еще один пример сложения. Просуммируем на калькуляторе два следующих действительных числа, каждый из которых содержит по 7 верных десятичных значащих цифр:

6543.455+12.34548=6555.80048≈6555.800

Приведем наши десятичные слагаемые к двоичному нормализованному виду:
6543.455=1.10011000111101110100100*2^12=6543.455 078
12.3454810 = 1.10001011000011100010110*2^3 ≈ 12.345 48

Сумма этих слагаемых в двоичном виде даст следующее двоичное число:
1.10011001101111001101*2^12≈6555.80078125≈6555.801

Здесь мы снова получили результат, отличающийся от того, который вычислен на калькуляторе для неконвертированных десятичных чисел.
УМНОЖЕНИЕ

Такая же проблема неточных вычислений возникает при нахождении произведения некоторых ЧПТ, представленных в двоичном коде в формате float. Для примера рассмотрим произведение следующих действительных чисел:
0.06543455*139=9.095402 45≈9.095402

Представим в этом выражении сомножители в нормализованном двоичном виде:
0.06543455=1.00001100000001010001101*2^(-4)
139=1.0001011*2^10

Результатом перемножения наших чисел в двоичном виде будет число:
1.00001100000001010001101*2^(-4) × 1.0001011*2^10 = 1001.00011000011011000101 ≈9.095403

Мы получили ошибку в младшем разряде произведения, которую невозможно исправить путем округления числа, представленного в двоичном коде. Такие ошибки принято называть фатальными.
ДЕЛЕНИЕ

Аналогично умножению, операция деления в формате float для некоторых ЧПТ также приводит к фатальным ошибкам. Рассмотрим следующий пример:
131/0.066≈1984.848

Представим делимое и делитель в двоичном формате, в нормализованном виде:
13110 = 1.0000011*2^7
0.066= 1.00001110010101100000010*2^(-4)

Частное от деления наших чисел будет следующим:
1.0000011*2^7/1.00001110010101100000010*2^(-4)=
= 1.11110000001101100100111*2^10 = 1984.848 5107421875≈1984.849

Мы видим, что полученный здесь результат не соответствует правильному значению, вычисленному на калькуляторе или вручную.
ВЫЧИТАНИЕ

Вычитание — это та операция, которая полностью дискредитирует идею использования двоичной арифметики для вычисления десятичных значений ЧПТ. В результате этой операции, в некоторых случаях, можно получить результат даже близко не соответствующий действительности. Продемонстрируем это на примере.

Пусть уменьшаемое у нас будет равно 105.3256. Вычтем из него число 105.32. Разность этих чисел, вычисленная вручную, будет равна:

105.3256-105.32=0.0056

Представим десятичное уменьшаемое и десятичное вычитаемое в нормализованном двоичном виде:
105.3256 = 1.10100101010011010110101*2^6≈105.3255 997 041015625
105.32= 1.10100101010001111010111*2^6≈105.32

Найдем разность этих чисел в двоичном виде:
1.10100101010011010110101*2^6-1.10100101010001111010111*2^6= 1.01101111*2^(-8)

После преобразования этого числа в десятичный вид получим:
1.01101111*2^(-8)= 0.005599976

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

ОШИБКИ В ФОРМАТЕ ДАБЛ

Ситуацию с фатальными ошибками не спасает и формат более высокой точности, например double. Как мы выше уже отмечали, это объясняется самой природой конвертации чисел из одной системы счисления в другую. Покажем это на примерах.

В качестве инструмента для проверки правильности наших рассуждений будем использовать Excel 2009, в котором реализованы вычисления в строгом соответствии со спецификацией стандарта IEEE754.

Проведем следующие вычисления, используя средства Excel 2009. Формат ячеек выберем числовой, с 18 знаками после запятой. Для нахождения суммы запишем в ячейки таблицы Excel следующие числа:

A1= 0,6236
A2= 0,00661666666070646

В ячейке А3Excel получим сумму этих чисел:
А3=А1+А2=0,6236+0,00661666666070646≈0,630216666660707

Если посчитать эту сумму вручную или на калькуляторе, то получится число:
0,6236+0,00661666666070646≈0,630216666660706

Которое в младшем разряде не совпадает с тем, что получено в Excel.

Посмотрим, к чему приводит операция вычитания в Excel. Запишем в ячейки следующие числа:

А1= 123456,789012345
А2= 123456

В ячейке А3 найдем разность этих чисел. Она будет равна:
А3=А1-А2=0,789012345005176

А мы ожидали получить число:
123456,789012345-123456=0, 789012345

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

А1= 0,500000000660006
А2 = 0,0000213456548763
А3 = 0,00002334565487363
А4 = 0,000013345654873263

В ячейке А6 запишем формулу =A1/5+A2. После чего в ней будет получен результат.
A6= A1/5+A2= 0,100021345786878

В ячейке A7 произведем следующие вычисления:
A7=A6/3+A3=0,0333637942504995

А теперь вычислим
A8=A7/4+A4=0,00835429421749813

Проведем те же вычисления на калькуляторе. Вычисления будем производить с точностью до 15 десятичных значащих цифр. Цифры, которые стоят правее младшего значащего разряда, согласно правилам арифметики, будем округлять до ближайшего целого. В результате будем иметь:
A1/5=0,500000000660006/5=0,100000000132001 2≈0,100000000132001
A1/5+A2=0,100000000132001+0,0000213456548763≈ 0,100021345786877
( A1/5+A2)/3=0,100021345786877/3≈0,0333404485956257
( A1/5+A2)/3+A3=0,0333404485956257+0,00002334565487363≈ 0,0333637942504993
[( A1/5+A2)/3+A3]/4=0,0333637942504993/4≈0,00834094856262483
[( A1/5+A2)/3+A3]/4+A4=0,00834094856262483+0,000013345654873263=0,00835429421749809

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

Арифметические операторы — Visual Basic

  • Время чтения: 9 мин

В этой статье

Арифметические операторы используются для выполнения многих знакомых арифметических операций, использующих вычисление числовых значений, представленных литералами, переменными, другими выражениями, вызовами функций и свойств и констант.Arithmetic operators are used to perform many of the familiar arithmetic operations that involve the calculation of numeric values represented by literals, variables, other expressions, function and property calls, and constants. Кроме того, классификация с помощью арифметических операторов — это операторы сдвига в битах, которые действуют на уровне отдельных битов операндов и сдвигаются их битовые шаблоны влево или вправо.Also classified with arithmetic operators are the bit-shift operators, which act at the level of the individual bits of the operands and shift their bit patterns to the left or right.

Арифметические операцииArithmetic Operations

Можно добавить два значения в выражение вместе с оператором +или вычесть одно из другого с помощью оператора-operator (Visual Basic), как показано в следующем примере.You can add two values in an expression together with the + Operator, or subtract one from another with the — Operator (Visual Basic), as the following example demonstrates.

Dim x As Integer
x = 67 + 34
x = 32 - 12

Отрицание также использует оператор-operator (Visual Basic), но только с одним операндом, как показано в следующем примере.Negation also uses the — Operator (Visual Basic), but with only one operand, as the following example demonstrates.

Dim x As Integer = 65
Dim y As Integer
y = -x

При умножении и разделении используются оператор * и оператор (Visual Basic)соответственно, как показано в следующем примере.Multiplication and division use the * Operator and / Operator (Visual Basic), respectively, as the following example demonstrates.

Dim y As Double
y = 45 * 55.23
y = 32 / 23

В возведение в степень используется оператор ^, как показано в следующем примере.Exponentiation uses the ^ Operator, as the following example demonstrates.

Dim z As Double
z = 23 ^ 3
' The preceding statement sets z to 12167 (the cube of 23).

Целочисленное деление выполняется с помощью оператора \ (Visual Basic).Integer division is carried out using the \ Operator (Visual Basic). Целочисленное деление Возвращает частное, то есть целое число, представляющее количество, которое делитель может разделить на делимое, без учета остатка.Integer division returns the quotient, that is, the integer that represents the number of times the divisor can divide into the dividend without consideration of any remainder. Делитель и делимое должны быть целочисленными типами (SByte, Byte, Short, UShort, Integer, UInteger, Longи ULong) для этого оператора.Both the divisor and the dividend must be integral types (SByte, Byte, Short, UShort, Integer, UInteger, Long, and ULong) for this operator. Сначала необходимо преобразовать все остальные типы в целочисленный тип.All other types must be converted to an integral type first. В следующем примере показано целочисленное деление.The following example demonstrates integer division.

Dim k As Integer
k = 23 \ 5
' The preceding statement sets k to 4.

Арифметическая операция деления выполняется с помощью оператора MOD.Modulus arithmetic is performed using the Mod Operator. Этот оператор возвращает остаток от деления делителя на делимое на целое число раз.This operator returns the remainder after dividing the divisor into the dividend an integral number of times. Если и делитель, и делим являются целочисленными типами, возвращаемое значение является целочисленным.If both divisor and dividend are integral types, the returned value is integral. Если делитель и делимые являются типами с плавающей запятой, возвращаемое значение также будет плавающей запятой.If divisor and dividend are floating-point types, the returned value is also floating-point. Следующий пример демонстрирует эту ситуацию.The following example demonstrates this behavior.

Dim x As Integer = 100
Dim y As Integer = 6
Dim z As Integer
z = x Mod y
' The preceding statement sets z to 4.
Dim a As Double = 100.3
Dim b As Double = 4.13
Dim c As Double
c = a Mod b
' The preceding statement sets c to 1.18.

Попыток деления на нольAttempted Division by Zero

Деление на ноль приводит к различным результатам в зависимости от используемых типов данных.Division by zero has different results depending on the data types involved. В целочисленных подразделениях (SByte, Byte, Short, UShort, Integer, UInteger, Long, ULong) .NET Framework выдает исключение DivideByZeroException.In integral divisions (SByte, Byte, Short, UShort, Integer, UInteger, Long, ULong), the .NET Framework throws a DivideByZeroException exception. В операциях деления с типом данных Decimal или Single .NET Framework также вызывает исключение DivideByZeroException.In division operations on the Decimal or Single data type, the .NET Framework also throws a DivideByZeroException exception.

В подразделениях с плавающей запятой, включающих тип данных Double, исключение не создается, а результатом является член класса, представляющий NaN, PositiveInfinityили NegativeInfinityв зависимости от делимого.In floating-point divisions involving the Double data type, no exception is thrown, and the result is the class member representing NaN, PositiveInfinity, or NegativeInfinity, depending on the dividend. В следующей таблице перечислены различные результаты попытки деления Doubleного значения на ноль.The following table summarizes the various results of attempting to divide a Double value by zero.

Тип данных делимогоDividend data typeТип данных делителяDivisor data typeДелимое значениеDividend valueРезультатResult
DoubleDouble00NaN (не является математически определенным числом)NaN (not a mathematically defined number)
DoubleDouble> 0> 0PositiveInfinity
DoubleDouble< 0< 0NegativeInfinity

При перехвате исключения DivideByZeroException можно использовать его члены, чтобы помочь в его обработке.When you catch a DivideByZeroException exception, you can use its members to help you handle it. Например, свойство Message содержит текст сообщения для исключения.For example, the Message property holds the message text for the exception. Дополнительные сведения см. в разделе Оператор Try…Catch…Finally.For more information, see Try…Catch…Finally Statement.

Операции сдвига битовBit-Shift Operations

Операция сдвига в битах выполняет арифметический сдвиг для битового шаблона.A bit-shift operation performs an arithmetic shift on a bit pattern. Шаблон содержится в операнде слева, а операнд справа указывает количество позиций для сдвига шаблона.The pattern is contained in the operand on the left, while the operand on the right specifies the number of positions to shift the pattern. Можно сдвинуть шаблон вправо с помощью оператора > > или слева с помощью оператора < <.You can shift the pattern to the right with the >> Operator or to the left with the << Operator.

Тип данных операнда шаблона должен быть SByte, Byte, Short, UShort, Integer, UInteger, Longили ULong.The data type of the pattern operand must be SByte, Byte, Short, UShort, Integer, UInteger, Long, or ULong. Тип данных операнда суммы сдвига должен быть Integer или должен расширяться для Integer.The data type of the shift amount operand must be Integer or must widen to Integer.

Арифметические сдвиги не являются циклическими, то есть биты, сдвинутые за пределы результата, не переносятся на другой конец.Arithmetic shifts are not circular, which means the bits shifted off one end of the result are not reintroduced at the other end. Позиции битов, освобожденные сдвигом, устанавливаются следующим образом:The bit positions vacated by a shift are set as follows:

  • 0 для арифметического сдвига влево0 for an arithmetic left shift

  • 0 для арифметического сдвига вправо положительного числа0 for an arithmetic right shift of a positive number

  • 0 для арифметического сдвига вправо неподписанного типа данных (Byte, UShort, UInteger, ULong)0 for an arithmetic right shift of an unsigned data type (Byte, UShort, UInteger, ULong)

  • 1 для арифметического сдвига вправо отрицательного числа (SByte, Short, Integerили Long)1 for an arithmetic right shift of a negative number (SByte, Short, Integer, or Long)

В следующем примере значение Integer сдвигается влево и вправо.The following example shifts an Integer value both left and right.

Dim lResult, rResult As Integer
Dim pattern As Integer = 12
' The low-order bits of pattern are 0000 1100.
lResult = pattern << 3
' A left shift of 3 bits produces a value of 96.
rResult = pattern >> 2
' A right shift of 2 bits produces value of 3.

Арифметические сдвиги никогда не создают исключений переполнения.Arithmetic shifts never generate overflow exceptions.

Битовые операцииBitwise Operations

Помимо логических операторов, Not, Or, Andи Xor также выполняют побитовую арифметическую операцию при использовании числовых значений.In addition to being logical operators, Not, Or, And, and Xor also perform bitwise arithmetic when used on numeric values. Дополнительные сведения см. в разделе «битовые операции» в логических и побитовых операторах в Visual Basic.For more information, see «Bitwise Operations» in Logical and Bitwise Operators in Visual Basic.

Безопасность типовType Safety

Обычно операнды должны иметь один и тот же тип.Operands should normally be of the same type. Например, если вы делаете сложение с переменной Integer, то следует добавить ее в другую переменную Integer, а также следует присвоить результат переменной типа Integer.For example, if you are doing addition with an Integer variable, you should add it to another Integer variable, and you should assign the result to a variable of type Integer as well.

Одним из способов обеспечения хорошей строгой типизации кода является использование оператора Option строго.One way to ensure good type-safe coding practice is to use the Option Strict Statement. Если задано Option Strict On, Visual Basic автоматически выполняет преобразования, строго типизированные .If you set Option Strict On, Visual Basic automatically performs type-safe conversions. Например, при попытке добавить переменную Integer в переменную Double и присвоить значение переменной Double, операция будет выполняться обычным образом, так как значение Integer может быть преобразовано в Double без потери данных.For example, if you try to add an Integer variable to a Double variable and assign the value to a Double variable, the operation proceeds normally, because an Integer value can be converted to Double without loss of data. Типы-ненадежные преобразования, с другой стороны, приводят к ошибке компилятора с Option Strict On.Type-unsafe conversions, on the other hand, cause a compiler error with Option Strict On. Например, при попытке добавить переменную Integer в переменную Double и присвоить значение переменной Integer, возникает ошибка компилятора, так как переменная Double не может быть неявно преобразована в Integerтипа.For example, if you try to add an Integer variable to a Double variable and assign the value to an Integer variable, a compiler error results, because a Double variable cannot be implicitly converted to type Integer.

Однако если задать Option Strict Off, Visual Basic допускает неявные сужающие преобразования, хотя они могут привести к непредвиденной утрате данных или точности.If you set Option Strict Off, however, Visual Basic allows implicit narrowing conversions to take place, although they can result in the unexpected loss of data or precision. По этой причине рекомендуется использовать Option Strict On при написании рабочего кода.For this reason, we recommend that you use Option Strict On when writing production code. Для получения дополнительной информации см. Widening and Narrowing Conversions.For more information, see Widening and Narrowing Conversions.

См. такжеSee also

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *