Float сколько знаков после запятой

от admin

4.8 – Числовые типы с плавающей точкой

Целочисленные типы отлично подходят для подсчета целых чисел, но иногда нам нужно хранить очень большие числа или числа с дробной частью. Переменная типа с плавающей точкой (запятой) – это переменная, которая может содержать действительное число, например 4320,0, -3,33 или 0,01226. «Плавающая» в названии «с плавающей точкой» указывает на то, что десятичная точка может «плавать»; то есть она может поддерживать переменное количество цифр до и после себя.

Существует три разных типа данных с плавающей точкой: float , double и long double . Как и в случае с целыми числами, C++ не определяет фактические размеры этих типов (но гарантирует минимальные размеры). В современных архитектурах представление с плавающей точкой почти всегда соответствует двоичному формату IEEE 754. В этом формате float составляет 4 байта, double – 8, а long double может быть эквивалентно double (8 байтов), 80 битам (часто с дополнением до 12 байтов) или 16 байтам.

Типы данных с плавающей запятой всегда идут со знаком (могут содержать положительные и отрицательные значения).

Категория Тип Минимальный размер Типовой размер
С плавающей запятой float 4 байта 4 байта
double 8 байт 8 байт
long double 8 байт 8, 12 или 16 байт

Ниже показан пример определения чисел с плавающей запятой:

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

Обратите внимание, что по умолчанию литералы с плавающей точкой по умолчанию имеют тип double . Суффикс f используется для обозначения литерала типа float .

Лучшая практика

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

Предупреждение

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

Печать чисел с плавающей точкой

Теперь рассмотрим следующую простую программу:

Результаты работы этой, казалось бы, простой программы могут вас удивить:

В первом случае std::cout напечатал 5, хотя мы ввели 5.0. По умолчанию std::cout не будет печатать дробную часть числа, если она равна 0.

Во втором случае число печатается так, как мы и ожидали.

В третьем случае напечаталось число в экспоненциальном представлении (если вам нужно освежить в памяти экспоненциальное представление, смотрите урок «4.7 – Введение в экспоненциальную запись»).

Диапазоны значений типов с плавающей точкой

Предполагая, что используется представление IEEE 754:

Размер Диапазон значений Точность
4 байта от ±1,18 × 10 -38 до ±3,4 × 10 38 6-9 значащих цифр, обычно 7
8 байт от ±2,23 × 10 -308 до ±1,80 × 10 308 15-18 значащих цифр, обычно 16
80 бит (обычно используется 12 или 16 байт) от ±3,36 × 10 -4932 до ± 1,18 × 10 4932 18-21 значащая цифра
16 байт от ±3,36 × 10 -4932 до ± 1,18 × 10 4932 33-36 значащих цифр

80-битный тип с плавающей запятой – это своего рода историческая аномалия. На современных процессорах он обычно реализуется с использованием 12 или 16 байтов (что является более естественным размером для обработки процессорами).

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

Точность с типов плавающей запятой

Рассмотрим дробь 1/3. Десятичное представление этого числа – 0,33333333333333… с тройками, уходящими в бесконечность. Если бы вы писали это число на листе бумаги, ваша рука в какой-то момент устала бы, и вы, в конце концов, прекратили бы писать. И число, которое у вас осталось, будет близко к 0,3333333333…. (где 3-ки уходят в бесконечность), но не совсем.

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

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

При выводе чисел с плавающей точкой std::cout по умолчанию имеет точность 6, то есть предполагает, что все переменные с плавающей точкой имеют только до 6 значащих цифр (минимальная точность с плавающей точкой), и, следовательно, он будет отсекать всё, что идет дальше.

Следующая программа показывает усечение std::cout до 6 цифр:

Эта программа выводит:

Обратите внимание, что каждое из напечатанных значений имеет только 6 значащих цифр.

Также обратите внимание, что std::cout в некоторых случаях переключился на вывод чисел в экспоненциальном представлении. В зависимости от компилятора показатель степени обычно дополняется до минимального количества цифр. Не беспокойтесь, 9.87654e+006 – это то же самое, что 9.87654e6 , только с некоторым количеством дополнительных нулей. Минимальное количество отображаемых цифр показателя степени зависит от компилятора (Visual Studio использует 3, некоторые другие в соответствии со стандартом C99 используют 2).

Число цифр точности переменной с плавающей запятой зависит как от размера (у float точность меньше, чем у double ), так и от конкретного сохраняемого значения (некоторые значения имеют большую точность, чем другие). Значения float имеют точность от 6 до 9 цифр, при этом большинство значений float имеют не менее 7 значащих цифр. Значения double имеют от 15 до 18 цифр точности, при этом большинство значений double имеют не менее 16 значащих цифр. Значения long double имеет минимальную точность 15, 18 или 33 значащих цифр в зависимости от того, сколько байтов этот тип занимает.

Мы можем переопределить точность по умолчанию, которую показывает std::cout , используя функцию манипулятора вывода с именем std::setprecision() . Манипуляторы вывода изменяют способ вывода данных и определяются в заголовке iomanip .

Поскольку с помощью std::setprecision() мы устанавливаем точность в 16 цифр, каждое из приведенных выше чисел печатается с 16 цифрами. Но, как видите, числа определенно неточны до 16 цифр! А поскольку числа float менее точны, чем числа double , число ошибок у float больше.

Проблемы с точностью влияют не только на дробные числа, они влияют на любое число со слишком большим количеством значащих цифр. Рассмотрим большое число:

123456792 больше, чем 123456789 . Значение 123456789.0 имеет 10 значащих цифр, но значения float обычно имеют точность 7 цифр (и результат 123456792 точен только до 7 значащих цифр). Мы потеряли точность! Когда теряется точность из-за того, что число не может быть точно сохранено, это называется ошибкой округления.

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

Лучшая практика

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

Ошибки округления затрудняют сравнение чисел с плавающей запятой

С числами с плавающей запятой сложно работать из-за неочевидных различий между двоичными (как хранятся данные) и десятичными (как мы думаем) числами. Рассмотрим дробь 1/10. В десятичном формате ее легко представить как 0,1, и мы привыкли думать о 0,1 как о легко представимом числе с 1 значащей цифрой. Однако в двоичном формате 0,1 представлен бесконечной последовательностью: 0,00011001100110011… Из-за этого, когда мы присваиваем 0,1 числу с плавающей точкой, мы сталкиваемся с проблемами точности.

Эффект от этого можно увидеть в следующей программе:

Эта программ выводит следующее:

Как и ожидалось, в первой строке std::cout печатает 0,1 .

Во второй строке, где std::cout показывает нам 17-значную точность, мы видим, что d на самом деле не совсем равно 0,1 ! Это связано с тем, что из-за ограниченной памяти double пришлось усекать приближение. В результате получается число с точностью до 16 значащих цифр (что гарантирует тип double ), но это число не равно 0,1. Ошибки округления могут сделать число немного меньше или немного больше, в зависимости от того, где происходит усечение.

Ошибки округления также могут иметь неожиданные последствия:

Хотя можно было ожидать, что d1 и d2 должны быть равны, мы видим, что это не так. Если бы мы сравнивали d1 и d2 в программе, программа, вероятно, не работала бы так, как ожидалось. Поскольку числа с плавающей запятой имеют тенденцию быть неточными, их сравнение обычно проблематично – мы обсудим эту тему (и решения) подробнее в уроке «5.6 – Операторы отношения и сравнение значений с плавающей запятой».

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

Ключевые выводы

Ошибки округления возникают, когда число не может быть сохранено точно. Это может произойти даже с простыми числами, например, 0,1 . Таким образом, ошибки округления могут происходить постоянно. Ошибки округления – не исключение, а правило. Никогда не предполагайте, что ваши числа с плавающей запятой точны.

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

NaN и Inf

Существует две особые категории чисел с плавающей запятой. Первая – Inf , которая представляет бесконечность. Inf может быть положительной или отрицательной. Вторая – NaN , что означает «Not a Number» (не число). Существует несколько различных типов NaN (которые мы здесь обсуждать не будем). NaN и Inf доступны только в том случае, если компилятор для чисел с плавающей запятой использует определенный формат (IEEE 754). Если используется другой формат, следующий код приводит к неопределенному поведению.

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

И результаты работы этой программы при использовании Visual Studio 2008 в Windows:

INF означает бесконечность, а IND означает неопределенность. Обратите внимание, что результаты печати Inf и NaN зависят от платформы, поэтому ваши результаты могут отличаться.

Лучшая практика

Вообще избегайте деления на 0, даже если ваш компилятор поддерживает это.

Одинарная или двойная точность?

В научных вычислениях мы часто используем числа с плавающей запятой (плавающей точкой). Эта статья представляет собой руководство по выбору правильного представления числа с плавающей запятой. В большинстве языков программирования есть два встроенных вида точности: 32-битная (одинарная точность) и 64-битная (двойная точность). В семействе языков C они известны как float и double , и здесь мы будем использовать именно такие термины. Есть и другие виды точности: half , quad и т. д. Я не буду заострять на них внимание, хотя тоже много споров возникает относительно выбора half vs float или double vs quad . Так что сразу проясним: здесь идёт речь только о 32-битных и 64-битных числах IEEE 754.

Статья также написана для тех из вас, у кого много данных. Если вам требуется несколько чисел тут или там, просто используйте double и не забивайте себе голову!

Статья разбита на две отдельные (но связанные) дискуссии: что использовать для хранения ваших данных и что использовать при вычислениях. Иногда лучше хранить данные во float , а вычисления производить в double .

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

Точность данных

У 32-битных чисел с плавающей запятой точность примерно 24 бита, то есть около 7 десятичных знаков, а у чисел с двойной точностью — 53 бита, то есть примерно 16 десятичных знаков. Насколько это много? Вот некоторые грубые оценки того, какую точность вы получаете в худшем случае при использовании float и double для измерения объектов в разных диапазонах:

Масштаб Одинарная точность Двойная точность
Размер комнаты микрометр радиус протона
Окружность Земли 2,4 метра нанометр
Расстояние до Солнца 10 км толщина человеческого волоса
Продолжительность суток 5 миллисекунд пикосекунда
Продолительность столетия 3 минуты микросекунда
Время от Большого взрыва тысячелетие минута

(пример: используя double , мы можем представить время с момента Большого взрыва с точностью около минуты).

Итак, если вы измеряете размер квартиры, то достаточно float . Но если хотите представить координаты GPS с точностью менее метра, то понадобится double .

Почему всегда не хранить всё с двойной точностью?

Если у вас много оперативной памяти, а скорость выполнения и расход аккумулятора не являются проблемой — вы можете прямо сейчас прекратить чтение и использовать double . До свидания и хорошего вам дня!

Если же память ограничена, то причина выбора float вместо double проста: он занимает вдвое меньше места. Но даже если память не является проблемой, сохранение данных во float может оказаться значительно быстрее. Как я уже упоминал, double занимает в два раза больше места, чем float , то есть требуется в два раза больше времени для размещения, инициализации и копирования данных, если вы используете double . Более того, если вы считываете данные непредсказуемым образом (случайный доступ), то с double у вас увеличится количество промахов мимо кэша, что замедляет чтение примерно на 40% (судя по практическому правилу O(√N), что подтверждено бенчмарками).

Влияние на производительность вычислений с одинарной и двойной точностью

Если у вас хорошо подогнанный конвейер с использованием SIMD, то вы сможете удвоить производительность FLOPS, заменив double на float . Если нет, то разница может быть гораздо меньше, но сильно зависит от вашего CPU. На процессоре Intel Haswell разница между float и double маленькая, а на ARM Cortex-A9 разница большая. Исчерпывающие результаты тестов см. здесь.

Конечно, если данные хранятся в double , то мало смысла производить вычисления во float . В конце концов, зачем хранить такую точность, если вы не собираетесь её использовать? Однако обратное неправильно: может быть вполне оправдано хранить данные во float , но производить некоторые или все вычисления с двойной точностью.

Когда производить вычисления с увеличенной точностью

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

Если вы запустите этот код на десяти числах одинарной точности, то не заметите каких-либо проблем с точностью. Но если запустите на миллионе чисел, то определённо заметите. Причина в том, что точность теряется при сложении больших и маленьких чисел, а после сложения миллиона чисел, вероятно, такая ситуация встретится. Практическое правило такое: если вы складываете 10^N значений, то теряете N десятичных знаков точности. Так что при сложении тысячи (10^3) чисел теряются три десятичных знака точности. Если складывать миллион (10^6) чисел, то теряются шесть десятичных знаков (а у float их всего семь!). Решение простое: вместо этого выполнять вычисления в формате double :

Скорее всего, этот код будет работать так же быстро, как и первый, но при этом не будет теряться точность. Обратите внимание, что вовсе не нужно хранить числа в double , чтобы получить преимущества увеличенной точности вычислений!

Пример

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

Чтобы увеличить точность, вы решаете сложить результаты измерений и вычислить среднее значение. В этом примере используется число с плавающей запятой в base-10, у которого точность составляет точно семь десятичных знаков (похоже на 32-битный float ). С тремя значимыми разрядами это даёт нам четыре дополнительных десятичных знака точности:

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

Всё ещё остались два неиспользованных разряда. Если суммировать тысячу чисел?

Пока что всё хорошо, но теперь мы используем все десятичные знаки для точности. Продолжим складывать числа:

Заметьте, как мы сдвигаем меньшее число, чтобы выровнять десятичный разделитель. У нас больше нет запасных разрядов, и мы опасно приблизились к потере точности. Что если сложить сто тысяч значений? Тогда добавление новых значений будет выглядеть так:

Обратите внимание, что последний значимый разряд данных (2 в 3.12) теряется. Вот теперь потеря точности действительно происходит, поскольку мы непрерывно будем игнорировать последний разряд точности наших данных. Мы видим, что проблема возникает после сложения десяти тысяч чисел, но до ста тысяч. У нас есть семь десятичных знаков точности, а в измерениях имеются три значимых разряда. Оставшиеся четыре разряда — это четыре порядка величины, которые выполняют роль своеобразного «числового буфера». Поэтому мы можем безопасно складывать четыре порядка величины = 10000 значений без потери точности, но дальше возникнут проблемы. Поэтому правило следующее:

Если в вашем числе с плавающей запятой P разрядов (7 для float , 16 для double ) точности, а в ваших данных S разрядов значимости, то у вас остаётся P-S разрядов для манёвра и можно сложить 10^(P-S) значений без проблем с точностью. Так, если бы мы использовали 16 разрядов точности вместо 7, то могли бы сложить 10^(16-3) = 10 000 000 000 000 значений без проблем с точностью.

(Существуют численно стабильные способы сложения большого количества значений. Однако простое переключение с float на double гораздо проще и, вероятно, быстрее).

Выводы

  • Не используйте лишнюю точность при хранении данных.
  • Если складываете большое количество данных, переключайтесь на двойную точность.

Приложение: Что такое число с плавающей запятой?

Я обнаружил, что многие на самом деле не вникают, что такое числа с плавающей запятой, поэтому есть смысл вкратце объяснить. Я пропущу здесь мельчайшие детали о битах, INF, NaN и поднормалях, а вместо этого покажу несколько примеров чисел с плавающей запятой в base-10. Всё то же самое применимо к двоичным числам.

Вот несколько примеров чисел с плавающей запятой, все с семью десятичными разрядами (это близко к 32-битному float ).

1.875545 · 10^-18 = 0.000 000 000 000 000 001 875 545
3.141593 · 10^0 = 3.141593
2.997925 · 10^8 = 299 792 500
6.022141 · 10^23 = 602 214 100 000 000 000 000 000

Выделенная жирным часть называется мантиссой, а выделенная курсивом — экспонентой. Вкратце, точность хранится в мантиссе, а величина в экспоненте. Так как с ними работать? Ну, умножение производится просто: перемножаем мантисссы и складываем экспоненты:

1.111111 · 10^42 · 2.000000 · 10^7
= (1.111111 · 2.000000) · 10^(42 + 7)
= 2.222222 · 10^49

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

3.141593 · 10^0 + 1.111111 · 10^-3 =
3.141593 + 0.0001111111 =
3.141593 + 0.000111 =
3.141704

Заметьте, как мы сдвинули некоторые из значимых десятичных знаков, чтобы запятые совпадали. Другими словами, мы теряем точность, когда складываем числа разных величин.

IEEE-754

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

  • дроби вида m/n: пара целых чисел (m, n);
  • числа с фиксированной запятой fixed point : целое число m представляет дробь вида m·rp , где r и p — заранее заданные константы;
  • числа с плавающей запятой floating point : пара целых чисел (m, p) задает дробь вида m·rp , где только основание r задано заранее. Таким образом, позиция запятой (порядок) pr-ичной системе счисления) указана в самом представлении числа.

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

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

Наиболее распространенным стандартом представления и правил работы с числами с плавающей запятой является стандарт IEEE-754 (в последней редакции известный также под обозначением ISO/IEC/IEEE 60559:2011). Его поддерживают большинство современных процессоров, умеющих работать с числами с плавающей запятой “непосредственно”.

Формат

IEEE-754 задаёт следующее двоичное представление числа.

Двоичное представление числа с плавающей запятой

Знак s Экспонента E Мантисса M
1 разряд e разрядов m разрядов
старший бит биты (m + e – 1) … m биты (m – 1) … 0

Экспонента exponent — обычно то же, что и порядок, однако в конкретных форматах порядок (как целое число) закодирован специальным образом (не равен численно беззнаковому представлению E), см. ниже.

Мантисса mantissa — специальным образом закодированное значение множителя степени основания, называемое также показателем significand или коэффициентом coefficient .

В таблице ниже приведены сведения о четырёх основных двоичных (т.е. r = 2) форматах стандарта IEEE-754. Число ε — наименьшее положительное представимое число такое, что 1 + ε ≠ 1 в заданном формате. Смысл понятий “денорм.” (денормализованные числа) и “норм.” (нормализованные числа) дан ниже. В качестве числа d в таблице указано минимальное число десятичных знаков после запятой, которого достаточно для сохранения двоичного представления числа при преобразовании в десятичную форму и обратно. Необходимо помнить, что многие десятичные дроби являются бесконечными в двоичной системе: например, 0.2 не представимо точно в указанных форматах. Двоичные же дроби хотя и представимы точно конечным числом знаков, могут потребовать сотни десятичных цифр для точной записи.

Четыре стандартных формата

Формат “Точность” precision всего бит e m d ε min денорм. min норм. max норм.
binary16 половинная half 16 5 10 5 9.77·10 –4 5.96·10 –8 6.1·10 –5 65504
binary32 одинарная single 32 8 23 9 1.19·10 –7 1.4·10 –45 1.18·10 –38 3.4·10 38
binary64 двойная double 64 11 52 17 2.22·10 –16 4.94·10 –324 2.2·10 –308 1.8·10 308
binary128 четверная quadruple 128 15 112 36 1.93·10 –34 6.48·10 –4966 3.4·10 –4932 1.2·10 4932

Порядок представлен в сдвинутом коде со сдвигом b = 2 e–1 – 1. Максимальное и минимальное значение E, взятого как беззнаковое число, интерпретируются особым образом.

Интерпретация представления числа с плавающей точкой, М — биты мантиссы (двоичная дробь)

Тип значения E M Значение числа с плав. точкой
нуль 0 0 (–1) s ·0
денормализованное 0 ≠ 0 (–1) s ·0.(M)·2 1–b
нормализованное 1 … 2 b любое (–1) s ·1.(M)·2 Eb
бесконечность 2 b + 1 0 (–1) s ·∞
нечисло (“NaN”) 2 b + 1 ≠ 0 код ошибки

Запись 0.(M) или 1.(M) означает, что m бит мантиссы выписываются после точки в двоичной записи числа. Под нормализацией числа понимается его домножение на такую степень двойки, что слева от точки получается единица. Денормализованные числа требуют недоступных значений экспоненты для нормализации.

Замечания

  • Денормализованные числа позволяют постепенно приблизиться к нулю (с постоянным шагом).
  • Наличие двух нулей (отрицательного и положительного) иногда полезно: так, при получении очень малых величин, округлившихся к нулю, мы по крайней мере сохраним знак (с какой стороны от нуля).
  • Тем не менее оба нуля считаются равными друг другу. Кроме того, полагается sqrt(–0) = –0, log(–0) = –∞, ±0 ±0 = 1.
  • Бесконечность можно получить, например, поделив ненулевое число на нуль, или любым другим способом, при котором результат вычислений пусть даже и конечен, но слишком велик по абсолютной величине, чтобы быть представленным в используемом формате.
  • Так как нулей два, то, например, (–1)/(–0) = +∞.
  • Нечисло можно получить, если, например, вычесть бесконечность из бесконечности или попробовать вычислить квадратный корень отрицательного числа (т.е. когда результат операции не определен).
  • Арифметические операции и большинство математических функций возвращают нечисла, если хотя бы один аргумент или операнд является нечислом. Поэтому появившееся в одном месте нечисло может породить лавину нечисел в последующих вычислениях. Эта ситуация называется “заражение нечислами” NaN plagueing .
  • Для нечисел операторы сравнения всегда возвращают false (нечисло не равно даже само себе).
  • Как правило, процессоры гораздо медленнее выполняют арифметические операции с денормализованными числами, бесконечностями и нечислами, чем с нормализованными числами, что может вызывать резкое падение производительности в некоторых случаях.
  • Сумма и произведение чисел с плавающей запятой, строго говоря, не являются ассоциативными операциями.

Режимы округления

Стандарт IEEE-754 предусматривает наличие пяти режимов округления, которые можно проиллюстрировать с помощью следующей таблицы.

Режимы округления IEEE-754

Режим (округление к целому) –2.7 –2.5 –2.2 –1.5 1.5 2.2 2.5 2.7
к ближайшему, половинки к чётному to nearest, ties to even –3.0 –2.0 –2.0 –2.0 2.0 2.0 2.0 3.0
к ближайшему, половинки от нуля to nearest, away from zero –3.0 –3.0 –2.0 –2.0 2.0 2.0 3.0 3.0
к нулю toward zero, truncation –2.0 –2.0 –2.0 –1.0 1.0 2.0 2.0 2.0
к плюс-бесконечности to positive infinity, upwards, ceiling –2.0 –2.0 –2.0 –1.0 2.0 3.0 3.0 3.0
к минус-бесконечности to negative infinity, downwards, floor –3.0 –3.0 –3.0 –2.0 1.0 2.0 2.0 2.0

При округлении к ближайшему представимому выбор в пользу округления половинок именно к чётному (как правило, используется именно этот вариант, например, на x86) или нечётному вместо “школьного” округления “от нуля” был сделан с целью балансировки накапливающихся округлений и уменьшению случайной погрешности.

Режим округления применяется не только при приведении к целым, но и при округлении результата для “вписывания” в мантиссу, т.е. вычислении результирующего младшего бита мантиссы.

Числа с плавающей запятой в C и C++

Литералы констант с плавающей точкой обязаны содержать точку (целая или дробная часть числа при этом могут быть опущены, если они нулевые) или символ, отделяющий порядок от коэффициента ( e , E или p , P ), в противном случае они трактуются как целые числа. Например, 10 10 как число в плавающей точке будет 1e10 , 10’000’000’000. или 10.0e+9 — все эти представления равнозначны.

Для использования точного двоичного представления константы следует воспользоваться префиксом 0x , 0X (C99, C++11). В этом случае коэффициент записывается в виде шестнадцатеричной дроби и позволяет задать мантиссу итогого представления с точностью до бита. Показатель записывается после буквы p или P в десятичной форме, но задаёт степень r (как правило, двойки), например 0xA.BCDp+4 равно (10 + 11·16 –1 + 12·16 –2 + 13·16 –3 )·2 4 = 171.80078125.

Одна из типичных ошибок новичков — использование целочисленных констант там, где необходимы константы в плавающей точке.

В примере выше 1 и 3 — целые числа, поэтому к ним применяется целочисленное деление (с отбрасыванием остатка), т.е. 1/3 = 0. Поэтому функция bad_cubic_root не вычисляет кубический корень. Правильным вариантом будет следующий код (также с учётом знака, так как стандартная функция возведения в степень не принимает отрицательное основание и нецелый показатель)

В стандарте C++11, впрочем, определена функция для вычисления кубического корня (cbrt). При её наличии следует использовать её вместо варианта на основе pow.

Языки C и C++ предоставляют три встроенных типа для работы с числами с плавающей точкой:

  • float обычно соответствует IEEE-754 binary32. Литералы этого типа задаются с помощью суффиксов f , F , например 3.14f ;
  • double обычно соответствует IEEE-754 binary64. Данный тип используется “по умолчанию” для литералов без суффиксов;
  • long double на x86 нередко отождествляется с 80-битным форматом сопроцессора Intel 8087 (использование которого на платформе x86-64 объявлено устаревшим), в то время как Visual C++ использует представление, идентичное типу double, а некоторые другие компиляторы могут использовать IEEE-754 binary128. Литералы данного типа задаются с помощью суффиксов l , L , например 1e-1000L .

Стандарт гарантирует лишь отношение float ⊆ double ⊆ long double.

Часть характеристик форматов с плавающей запятой, используемых конкретным компилятором, можно получить, подключив заголовочный файл cfloat, где определен ряд макросов. Ниже * соответствует FLT для типа float, DBL для double и LDBL для long double.

  • FLT_RADIX база системы счисления r (обычно 2);
  • FLT_ROUNDS режим округления по умолчанию;
  • *_MANT_DIG разряды полной мантиссы (m + 1);
  • *_DIG достаточное число десятичных знаков после запятой d;
  • *_MIN_EXP минимальная смещенная экспонента для нормализованных;
  • *_MIN_10_EXP минимальная десятичная экспонента для нормализованных;
  • *_MAX_EXP максимальная смещенная экспонента для нормализованных;
  • *_MAX_10_EXP максимальная десятичная экспонента;
  • *_MIN наименьшее положительное нормализованное число;
  • *_TRUE_MIN наименьшее положительное (денормализованное) число;
  • *_MAX наибольшее конечное представимое число;
  • *_EPSILON наименьшее положительное представимое число ε такое, что 1 + ε ≠ 1 в заданном формате.

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

Заголовочный файл cmath

Заголовочный файл cmath предоставляет ряд определений математических функций (вычисляемых приближённо в плавающей точке, тем не менее, для простоты в обозначениях используются действительные числа вплоть до всей числовой оси). Здесь приводятся сведения по определениям для C++, принимающим разнотипные значения (float, double и long double). Если предел той или иной функции при стремлении к нулю (с той или иной стороны) или бесконечностям определён (включая значения ±∞), то он берётся в качестве значения этой функции в соответствующих предельных точках (поэтому многие интервалы и полуинтервалы в колонке “область значений” могут считаться отрезками).

Тригонометрические и гиперболические функции

Функция Область определения Область значений Замечание
sin(x) [–1, 1] x в радианах
cos(x) [–1, 1] x в радианах
tan(x) x ∉ πℤ + π/2 неограниченный рост погрешности около точек разрыва
asin(x) [–1, 1] [–π/2, π/2]
acos(x) [–1, 1] [0, π]
atan(x) [–π/2, π/2]
atan2(y, x) ℝ 2 [–π, π] угол против часовой стрелки между векторами (1, 0) и (x, y)
sinh(x)
cosh(x) [1, +∞)
tanh(x) (–1, 1)
asinh(x) C++11
acosh(x) [1, +∞) C++11
atanh(x) (–1, 1) C++11

Степени и логарифмы

Функция Область определения Область значений Замечание
exp(x) (0, +∞) константа Эйлера e (основание натурального логарифма) в степени x
expm1(x) (–1, +∞) C++11, формально то же, что exp(x) – 1, но сохраняет высокую точность при x ≈ 0
exp2(x) (0, +∞) C++11, 2 x
log(x) (0, +∞) натуральный логарифм
log10(x) (0, +∞) десятичный логарифм
log1p(x) (–1, +∞) C++11, формально то же, что log(x + 1), но сохраняет высокую точность при x ≈ 0
log2(x) (0, +∞) C++11, двоичный логарифм
sqrt(x) [0, +∞) [0, +∞) квадратный корень
cbrt(x) C++11, кубический корень
hypot(x, y) ℝ 2 [0, +∞) C++11, sqrt(x 2 + y 2 ), без промежуточного переполнения
pow(x, i) ℝ × ℤ возведение x в целочисленную степень i
pow(x, y) (0, +∞) × ℝ (0, +∞) возведение x в степень с показателем y, заданным в плавающей точке

Округление

Функция Замечание
ceil(x) округление наверх: ⌈x⌉, результат в формате плавающей точки
floor(x) округление вниз: ⌊x⌋, результат в формате плавающей точки
trunc(x) C++11, округление к нулю, результат в формате плавающей точки
round(x) C++11, округление к ближайшему целому (режим “от нуля” для половинок), результат в формате плавающей точки
lround(x) C++11, округление к ближайшему целому, результат в формате long int
llround(x) C++11, округление к ближайшему целому, результат в формате long long int
nearbyint(x) C++11, округляет до целых, используя текущий режим округления (который можно выставить с помощью fesetround)

Утилиты

Функция Область определения Область значений Замечание
abs(x), fabs(x) [0, +∞) модуль x
fmod(x, y) ℝ × (ℝ \ <0>) (–|y|, |y|) обобщение остатка от деления: xy·trunc(x/y), имеет знак x
fmax(x, y) ℝ 2 ℝ 2 C++11, максимум, если один аргумент — нечисло, возвращает другой аргумент
fmin(x, y) ℝ 2 ℝ 2 C++11, минимум, если один аргумент — нечисло, возвращает другой аргумент
fdim(x, y) ℝ 2 [0, +∞) C++11, максимум из нуля и (xy)
fma(a, b, c) ℝ 3 C++11, вычисляет a·b + c без промежуточных округлений
ldexp(x, n) ℝ × ℤ вычисляет x·2 n , n — целое число (т.е. добавляет n к показателю)
frexp(x, p) ±[0.5, 1) × ℤ сохраняет целочисленный показатель по указателю p, возвращает норм. мантиссу
modf(x, p) (-1, 1) × ℤ “разбирает” число на дробную часть (возвращает) и целую часть (через указатель p)
ilogb(x) int C++11, извлекает значение показателя
nextafter(x, y) ℝ 2 C++11, возвращает следующее после x в направлении y представимое число
copysign(x, y) ℝ 2 C++11, возвращает |x|·sgn y (подменяет знак x на знак y)
signbit(x) bool C++11, извлекает бит знака
nanf(s), nan(s) строки нечисла C++11, создаёт нечисло, отвечающее ошибке s (определяется реализацией библиотеки)

При использовании C или для явного указания того, что аргумент и результат функции “абсолютное значение” имеют тип double, следует использовать функцию fabs вместо abs. См. также о целочисленном варианте abs.

Классификация (все из C++11)

Функция Замечание
fpclassify(x) возвращает описание x: одну из предопределённых констант FP_INFINITE , FP_NAN , FP_NORMAL , FP_SUBNORMAL , FP_ZERO
isfinite(x) возвращает fpclassify(x) ∈
isinf(x) возвращает fpclassify(x) = FP_INFINITE
isnan(x) возвращает fpclassify(x) = FP_NAN
isnormal(x) возвращает fpclassify(x) = FP_NORMAL
isunordered(x, y) возвращает истину, если x или y — нечисло, ложь в противном случае
islessgreater(x, y) возвращает истину, если x и y не являются нечислами и не равны
isgreater(x, y) возвращает истину, если x > y, иначе ложь, нечисла не вызывают исключительную ситуацию
isgreaterequal(x, y) аналогично isgreater для x >= y
isless(x, y) аналогично isgreater для x < y
islessequal(x, y) аналогично isgreater для x <= y

Некоторые константы, определённые в cmath.

Константа Смысл
INFINITY бесконечность, тип float
NAN “тихое” нечисло, тип float
HUGE_VALF число типа float, вызывающее переполнение (в случае IEEE-754 — бесконечность)
HUGE_VAL то же, что HUGE_VALF , но типа double

Управление режимом округления cfenv

Подключив заголовочный файл cfenv можно управлять текущим режимом округления. Возможен выбор из четырёх стандартных (по стандарту C) режимов:

  • FE_DOWNWARD — округление к минус-бесконечности;
  • FE_TONEAREST — округление к ближайшему (стандарт не указывает, какой вариант округления “половинок” будет использоваться при использовании данного режима);
  • FE_TOWARDZERO — округление к нулю;
  • FE_UPWARD — округление к плюс-бесконечности.

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

  • int fegetround() — функция возвращает текущий режим округления.
  • int fesetround(int mode) — функция устанавливает новый режим округления. Возвращает 0 в случае успеха и не 0 в случае ошибки.

Точность операций

Пусть x — приближённое значение некоторой величины x * .

Абсолютная погрешность x равна |xx * |.

Относительная погрешность x при условии x * ≠ 0 равна |xx * |/|x * |.

Точность выполнения операций часто измеряется в единицах, равных весу самого младшего разряда мантиссы результата, называемых ULP units in the last place . Например, расстояние между 1.0 и (1.0 + ε) составляет как раз 1 ULP. При удвоении числа ULP также удваивается (это относительная, а не абсолютная единица).

При выполнении арифметических операций, вычислении функции fma (слитое умножение-сложение fused multiply–add ), а также вычислении квадратного корня результат округляется к ближайшему представимому в текущем формате числу, т.е. имеет точность 0.5 ULP (при использовании соответствующего режима округления). Одним из следствий такого округления является точный результат sqrt от квадрата целого числа (если он представим). Вычисление тригонометрических функций, степеней и логарифмов обычно выполняется не столь точно, давая погрешность в 1–5 ULP и более (зависит от реализации). В результате вычисление формул, содержащих такие функции, с одними и теми же значениями переменных может давать разные результаты на разных платформах.

Благодаря двоичному формату чисел IEEE-754, при интерпретации их как целых (на некоторых платформах может потребоваться обращение порядка байт), модуль их разности в случае совпадения знака и порядка равен расстоянию между ними в ULP.

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

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

В качестве eps можно брать кратное DBL_EPSILON — оценивать разность в “эпсилонах”.

Наконец, можно оценивать погрешность непосредственно в ULP.

В отличие от предыдущих вариантов, ulp_equal вернет true для бесконечностей одного знака. В то же время расстояние в ULP от DBL_MAX до +∞ равно всего лишь единице. Нечисла также могут оказаться “почти равными”. Напротив, расстояние между –0 и +0 получается очень большим (числа разного знака таким способом сравнивать в общем-то бессмысленно, и определённая ниже функция возвращает ложь для неравных чисел разного знака).

Функция memcpy копирует кусок памяти.

Продолжая тему оперирования непосредственно двоичными представлениями чисел с плавающей запятой формата IEEE-754, можно обратить внимание на следующее. Предположим, что a ≥ +0 (поведение отрицательных чисел аналогично), тогда при увеличении a, интерпретируемого как целое, на единицу получаем:

  • следующее представимое число b = (1 + ε) a;
  • либо +∞, если a — уже наибольшее нормализованное число;
  • либо нечисло, если a = +∞.

Простым циклом можно перечислить все представимые неотрицательные числа с плавающей точкой формата binary32:

Стандартным средством получения следующего представимого числа является функция nextafter (см. в таблице, приведённой в разделе, описывающем стандартный заголовочный файл cmath):

Примеры

Параметр линейной интерполяции

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

Lerp — распространённая аббревиатура английского linear interpolation — “линейная интерполяция”.

Однако округление при вычитании существенно различных по величине t1 и h может приводить к выходу конечного результата за пределы отрезка [0, 1]. Исправленный вариант может выглядеть так:

Вычисление значений многочленов

Метод Горнера

Пусть дан многочлен вида Σai x i , где ai — коэффициенты, а x — свободная переменная. Широко известен метод эффективного вычисления значения многочлена для заданного x, называемый методом Горнера. Метод заключается в циклическом выполнении добавления очередного коэффициента и домножении суммы на x.

Воспользовавшись стандартной функцией fma можно попробовать увеличить точность функции poly_horner (сократив половину промежуточных округлений).

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

Метод Эстрина

Впрочем, можно попробовать ещё ускорить данную операцию, если у нас в распоряжении имеется процессор, способный исполнять одновременно более одной команды (в данном случае имеется в виду не “многоядерность”, а наличие таких аппаратных особенностей как глубокий конвейер и/или суперскалярность и/или SIMD-команды). Для этого воспользуемся ассоциативностью суммы (впрочем, в плавающей точке ассоциативность теряется, поэтому результат может не совпадать с результатом функции poly_horner_fma). Кроме того, чтобы игра стоила свеч, нужен многочлен высокой степени (с большим количеством членов).

Один шаг: разбить по квадратам x.

Второй шаг: разбить по квадратам квадратов x.

Обобщение метода Горнера — метод Эстрина: дерево суммирования по квадратам

Вычисление произведения последовательности без промежуточного переполнения или исчезновения

Иногда случается ситуация, когда требуется вычислить произведение элементов некоторой конечной последовательности чисел в плавающей точке, хранящихся в массиве, читаемых с потока или вычисляемых по формуле. Так или иначе, в процессе такого вычисления может возникнуть неприятная ситуация: нехватка диапазона экспоненты для представления величины промежуточного результата, в то время как правильно вычисленных конечный результат представим. Например, для простоты представим себе последовательность, состоящую из двух тысяч двоек и двух тысяч “половинок” (0.5). Произведение равно единице. Однако, даже при использовании формата binary64 ещё на первой половине произойдёт переполнение, так как 2 2000 слишком велико, и общий результат наивного последовательного перемножения получится равным бесконечности. Если перемножать с конца к началу, то произойдёт исчезновение, так как 2 –2000 слишком мало, и общий результат получится равным нулю.

Рассмотрим простой алгоритм, который позволяет избежать этой неприятной ситуации. Заметим, что нормальные числа в формате binary32 (например), находятся в полуинтервале [2 –126 , 2 128 ), поэтому перемножение любой пары чисел из отрезка [2 –63 , 2 63 ] даст нормальное число. Это означает, что если текущее произведение и сомножители “загонять” в отрезок [2 –63 , 2 63 ] (домножая на 2 –63 или на 2 63 в случае нормальных чисел), то переполнения или исчезновение не случится. При этом необходимо запоминать множитель — соответствующую степень двойки.

Суммирование с компенсацией погрешности округлений

Предположим, требуется вычислить арифметическое среднее большого набора чисел. Простое последовательное суммирование может приводить к очень большой погрешности. Например, последовательное суммирование миллиарда единиц в формате binary32 даст 2 24 , что примерно 17 миллионов, далеко не миллиард (почему?).

Алгоритм, называемый суммированием Кэхэна или компенсационным суммированием (У.Кэхэн), позволяет получить гораздо более точный результат, суммируя отброшенные при округлении основной суммы части в отдельной переменной — компенсации (в примере реализации — переменная comp ). Относительная погрешность этого алгоритма ограничена сверху значением (цитир. по: Higham N. The accuracy of floating point summation // SIAM J. Sci. Comput. 1993. Vol. 14, No. 4. P.783–799)

Погрешность компенсационного суммирования

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

Улучшенный вариант компенсационного суммирования Ноймайера имеет следующий вид:

Численное решение уравнений

Пусть дана непрерывная функция f : ℝ → ℝ и два числа a < b такие, что f(a) f(b) < 0, тогда по теореме о промежуточном значении найдётся c ∈ [a, b] такое, что f(c) = 0. Иными словами, уравнение f(x) = 0 имеет решение на отрезке [a, b]. Естественно поставить вопрос о том, как отыскать такое решение.

Метод деления отрезка пополам

Рассмотрим следующий алгоритм (метод бисекции или деления отрезка пополам):

  1. Положим m = (a + b) / 2.
  2. Если f(m) = 0, то c = m, закончить.
  3. Если f(a) f(m) < 0, то положить b = m.
  4. Иначе положить a = m.
  5. Продолжить с п.1.

На каждом шаге алгоритма отрезок [a, b] “уполовинивается”, но продолжает содержать некоторое решение уравнения. Впрочем, записанный выше алгоритм завершится только при чрезвычайно удачном стечении обстоятельств: если на каком-то шаге m совпадёт с решением. Легко привести пример, когда этого не произойдёт (пока рассуждаем в рамках действительных чисел):

В указанном примере на каждом шаге, очевидно, истинно m ∈ ℚ, в то же время, решения уравнения f(x) = 0 не принадлежат ℚ. Таким образом, наш алгоритм за n шагов может гарантировать лишь сужение исходного интервала, содержащего корень, в 2 n раз, но не обнаружение точного значения корня уравнения. Если достаточно знать корень с погрешностью ε > 0, то достаточно выполнить n = ⌈log2((ba)/ε)⌉ шагов.

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

Однако, при использовании физических компьютеров, абсолютно точная арифметика недоступна. При использовании чисел с плавающей запятой через конечное число шагов алгоритма станет истинно (a = m)∨(b = m) (почему?). Поэтому бисекцию можно записать следующим образом:

Вилочный метод секущих

Рассмотрим ещё другой алгоритм — вилочный метод секущих (другое название: метод ложного положения regula falsi method ), напоминающий метод бисекции в том плане, что по мере приближения корень всегда остаётся в некотором интервале. Всё отличие от предыдущего алгоритма будет состоять в способе вычисления очередной точки m. Вместо того, чтобы брать середину текущего отрезка, возьмём точку пересечения оси абсцисс секущей — прямой, проведённой через координаты (a, f(a)) и (b, f(b)). Делая так, мы “надеемся”, что на интервале [a, b] функция достаточно похожа на прямую. Это действительно так, если интервал достаточно мал, а функция f дифференцируема на нём.

Шаг 1: m = 30/13 ≈ 2.3077 Шаг 2: m = 1028/399 ≈ 2.57644 Шаг 3: m ≈ 2.61485 Шаг 4: m ≈ 2.61996

первые шаги метода секущих (значения с индексом b — результат бисекции для сравнения)

Шаг m bm f(m) mb f(mb)
1 2.30769 0.692308 –0.317251 2 –0.(5)
2 2.57644 0.423559 –0.0498588 2.5 –0.131944
3 2.61485 0.385152 –0.00673158 2.75 0.155382
4 2.61996 0.380036 –0.000889564 2.625 0.00488281
5 2.620639 0.379361 –0.000117219 2.5625 –0.0651991
6 2.620728 0.379272 –0.0000154404 2.59375 –0.0305803

Легко заметить, что, с одной стороны, метод секущих уже не гарантирует нам сколь угодно малый интервал — в нашем примере бесконечное число шагов даст a = m = корень ≈ 2.62074, в то время как b так и останется в точке 3. С другой стороны, m намного быстрее подходит к корню, чем в методе бисекции.

Реализации этих двух методов, а также некоторых других методов, см. в примере.

Поиск минимума функции

Функция унимодальна на отрезке [a, b], если существует m ∈ [a, b] такая, что функция строго убывает на [a, m] и строго возрастает на [m, b]. Таким образом, функция достигает единственного на [a, b] минимума в точке m.

Рассмотрим задачу поиска минимума унимодальной на заданном отрезке [a, b] функции f : ℝ → ℝ.

Троичный поиск

Троичный поиск ternary search — простейший метод, решающий с заданной точностью ε поставленную задачу. Принцип его работы можно описать следующим алгоритмом:

  1. Если (ba) ≤ ε, то закончить.
  2. Положить m1 = a + (ba)/3.
  3. Положить m2 = b – (ba)/3.
  4. Если (a = m1)∨(b = m2)∨(m2m1), то закончить (исчерпана точность представления чисел).
  5. Если f(m1) < f(m2), то положить b = m2.
  6. Иначе если f(m2) < f(m1), то положить a = m1.
  7. Иначе положить a = m1, b = m2.
  8. Повторить с п.1.

Примечание. Если снять условие строгой монотонности функции на участках “до минимума” и/или “после минимума”, то троичный поиск может потерять минимум (случай f(m1) = f(m2)).

Пример троичного поиска

Шаг 1: min = –0.223108 Шаг 2: min = –0.0200079 Шаг 3: min = –0.222849 Шаг 4: min = –0.210824 Шаг 5: min = –0.230368

Легко заметить, что приближение минимума (для данного примера min = –0.230445) происходит немонотонно.

Метод золотого сечения

Существует метод, основанный на том же принципе, что и троичный поиск, но более экономный — позволяющий вычислять одно новое значение функции на каждом шаге, а не два, как троичный поиск. Это метод носит название метод золотого сечения. Сократить вычисления можно в том случае, если одна из предыдущих позиций, выбранных в [a, b] будет сохраняться на каждом шаге. Этого можно добиться, если делить отрезок не на трети, а в отношении 1:φ, где φ — “золотое сечение”. Для любых x > y > 0 таких, что φ = x/y, выполняется также (x + y)/x = x/y = φ. Отсюда легко получить тождество φ = 1 + 1/φ, позволяющее вычислить φ (положительный корень квадратного уравнения).

Теперь передвинем точку b в точку d и пересчитаем новые c‘и d’:

Итак, алгоритм можно записать следующим образом:

  1. Положить c = b – (ba)/φ.
  2. Положить d = a + (ba)/φ.
  3. Если ((dc) ≤ ε)∨(ca)∨(bd), то закончить.
  4. Если f(c) < f(d), то положить b = d, d = c, c = b – (ba)/φ.
  5. Иначе если f(d) < f(c), то положить a = c, c = d, d = a + (ba)/φ.
  6. Иначе положить a = c, b = d, c = b – (ba)/φ, d = a + (ba)/φ.
  7. Повторить с п.3.

Пример работы метода золотого сечения

Используется та же функция и тот же начальный интервал, что и в примере работы троичного поиска. Обратим внимание, что теперь минимум приближается монотонно.

Шаг 1: min = –0.147106 Шаг 2: min = –0.147106 Шаг 3: min = –0.147106 Шаг 4: min = –0.227573 Шаг 5: min = –0.227573

Типы данных в C#

Типы данных в C#

C# — это статически типизированный язык программирования. Поэтому перед тем как использовать переменную в C# Вы должны определить ее тип. Тип переменной задает ограничение на значение переменной, а также виды операций, которые можно выполнять с ней.

Итак, таблица с типами переменных в C# представлена ниже:

Тип данных Размер Описание
int 4 байта Хранит целые числа в диапазоне от -2,147,483,648 до 2,147,483,647
long 8 байт Хранит целые числа в диапазоне от -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807
float 4 байта Хранит дробные числа с точностью до 7 знаков после запятой
double 8 байт Хранит дробные числа с точностью до 15 знаков после запятой
bool 1 бит Хранит логические значения true или false
char 2 байта Хранит один символ/букву в одинанрных кавычках
string 2 байта на символ Хранит последовательность символов, обрамленных в двойные кавычки

Числа

Числа в C# делятся на две большие группы:

  • Целочисленные типы — к ним относятся int и double
  • Числа с плавающей запятой — float и double

Как правило в C# наиболее часто используются тип int и double.

int number1 = 100 // целое число
long number2 = 230000000000L // целое число типа long

float number3 = 10.12F
double number4 = 1999399.23737D

Как говорилось ранее, типы float и double отличаются друг от друга диапазоном значений и количеством знаков после запятой. Так какой же из типов использовать?

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

Логические переменные

Логический тип данных в C# объявляется с ключевым словом bool и может принимать только значения true или false. Логические значения в основном используются в условных выражениях:

bool isTrue = true;
bool isFalse = false;

Символы и строки

char symbol = 'C' // один символ в одинарных кавычках!

string str = "как так строка"

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

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

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

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

Она выглядит вот так:

Комментарии ( 0 ):

Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.

Читать:
Как зайти на свой сайт

Похожие статьи