Как рассчитать точность типа float и имеет ли это смысл? - PullRequest
0 голосов
/ 05 мая 2020

У меня проблемы с пониманием точности типа float. Msdn записывает с точностью от 6 до 9 цифр. Но я отмечаю, что точность зависит от размера числа:

  float smallNumber = 1.0000001f;
  Console.WriteLine(smallNumber); // 1.0000001

  bigNumber = 100000001f;
  Console.WriteLine(bigNumber); // 100000000

smallNumber точнее, чем big, я понимаю IEEE754, но я не понимаю, как MSDN вычисляет точность и делает ли это смысл?

Также вы можете поиграть с представлением чисел в формате с плавающей запятой здесь . Введите значение 100000000 в поле «Вы ввели» и нажмите «+1» справа. Затем измените значение ввода на 1 и снова нажмите «+1». Вы можете увидеть разницу в точности.

Ответы [ 4 ]

5 голосов
/ 05 мая 2020

Документация MSDN бессмысленна и неверна.

Плохая концепция. Двоичный формат с плавающей запятой не имеет точности в десятичных числах, потому что в нем нет десятичных цифр. Он представляет числа со знаком, фиксированным числом двоичных цифр (битов) и показателем степени двойки.

Неправильно на верхнем конце. Формат с плавающей запятой представляет ровно много чисел с бесконечной точностью. Например, цифра «3» представлена ​​точно. Вы можете записать его в десятичной системе сколь угодно долго, 3.0000000000…, и все десятичные цифры будут правильными. Другой пример: 1.40129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125e-45. Это число имеет 105 значащих цифр в десятичной системе, но формат float представляет его в точности (это 2 −149 ).

Неправильно на нижнем конце. * Когда «999999,97» преобразовывается из десятичного числа в float, результат составляет 1 000 000. Таким образом, даже одно десятичное число di git не является правильным.

Не является мерой точности. Поскольку значение float имеет 24 бита, разрешение его младшего бита составляет около 2 * В 1022 * 23 раз лучше, чем разрешение самого высокого бита. Это около 6,9 цифр в том смысле, что log 10 2 23 составляет около 6,9. Но это лишь говорит нам о разрешении - о грубости - изображения. Когда мы конвертируем число в формат float, мы получаем результат, который отличается от числа не более чем на ½ этого разрешения, потому что мы округляем до ближайшего представимого значения. Таким образом, преобразование в float имеет относительную ошибку не более 1 части из 2 24 , что соответствует примерно 7,2 цифрам в указанном выше смысле.

Где эти числа откуда?

Итак, если «~ 6-9 цифр» не является правильным понятием, не исходит из фактических границ цифр и не измеряет точность, откуда это взялось? Мы не можем быть уверены, но 6 и 9 присутствуют в двух описаниях формата float.

6 - наибольшее число x , для которого это гарантировано:

  • Если какое-либо десятичное число, содержащее не более x значащих цифр, находится в пределах конечных границ формата float и преобразуется в ближайшее значение, представленное в формате, то при преобразовании результата до ближайшего десятичного числа, содержащего не более x значащих цифр, результат этого преобразования равен исходному числу.

Таким образом, разумно сказать, что float может сохранить как минимум шесть десятичных цифр. Однако, как мы увидим, здесь нет границы, состоящей из девяти цифр.

9 - наименьшее число x , которое гарантирует это:

  • Если любое конечное * Число 1062 * преобразуется в ближайшее десятичное число с x цифрами, а затем, когда результат конвертируется в ближайшее значение, представленное в float, результат этого преобразования равен исходному числу.

По аналогии, если float является контейнером, то самый большой «десятичный контейнер», который гарантированно помещается в него, состоит из шести цифр, а наименьший «десятичный контейнер», который гарантированно хранит его, - девять цифр. 6 и 9 аналогичны внутренним и внешним размерам контейнера float.

Предположим, у вас есть блок длиной 7,2 единицы, и вы смотрите на его размещение на ряду кирпичей длиной 1 единицу. Если вы поместите начало блока в начало кирпича, он расширится на 7,2 кирпича. Однако кто-то другой выбирает, где он начинается, он может запустить его в середине кирпича. Затем он покроет часть этого кирпича, все следующие 6 кирпичей и часть последнего кирпича (например, 0,5 + 6 + 0,7 = 7,2). Таким образом, блок из 7,2 единиц гарантированно покрывает только 6 кирпичей. И наоборот, 8 кирпичей могут скрыть блок из 7,2 единиц, если вы выберете место их размещения. Но если кто-то другой выберет, где они начнутся, первый может скрыть всего 0,1 единицы блока. Затем вам понадобится еще 7 и еще одна дробь, значит понадобится 9 кирпичей.

Причина, по которой эта аналогия сохраняется, состоит в том, что степени двойки и степени 10 неравномерно расположены друг относительно друга. 2 10 (1024) около 10 3 (1000). 10 - это показатель степени, используемый в формате float для чисел от 1024 (включительно) до 2048 (исключая). Таким образом, этот интервал от 1024 до 2048 похож на блок, который был размещен сразу после окончания 100-1000 и начала блока 1000-10 000.

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

Дополнительная литература

Для лучшего понимания арифметики с плавающей запятой c рассмотрите изучение стандарта IEEE-754 для арифметики с плавающей запятой c или хорошего учебника, например Handbook of Floating-Point Arithmeti c by Jean-Michel Muller et al .

2 голосов
/ 05 мая 2020

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

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

32-битные числа с плавающей запятой IEEE754 хранятся как:

bool(1bit sign) * integer(24bit mantisa) << integer(8bit exponent)

Да, мантисса составляет 24 бита вместо 23, поскольку это MSB неявно установлено в 1.

Как видите, есть только целые числа и битовый сдвиг. Итак, если вы представляете натуральное число до 2 ^ 24, вы не округляете полностью. Для больших чисел заполнение двоичным нулем происходит справа, что вызывает разницу.

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

0.3 dec = 0.100110011001100110011001100110011001100... bin
0.25 dec = 0.01 bin

Как видите, последовательность 0.3 dec в двоичном формате бесконечна (например, мы не можем записать 1/3 в декади c), поэтому, если обрезать его до 24 бит, вы потеряете остальное, и число больше не то, что вам нужно.

Если вы сравните 0.3 и 0.125, 0,125 будет точным, а 0,3 - нет, но 0,125 намного меньше, чем 0.3. Таким образом, ваша мера неверна, если не исследованы более очень близкие значения, которые будут охватывать шаги округления и вычисление максимальной разницы из такого набора. Например, вы можете сравнить

1.0000001f
1.0000002f
1.0000003f
1.0000004f
1.0000005f
1.0000006f
1.0000007f
1.0000008f
1.0000009f

и запомнить максимальную разницу fabs(x-round(x)), а затем сделать то же самое для

100000001
100000002
100000003
100000004
100000005
100000006
100000007
100000008
100000009

А затем сравнить две разницы.

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

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

Теперь вернемся к числу "точных" цифр, представленных float. Для целой части числа это очень просто:

dec_digits = floor(log10(2^24)) = floor(7.22) = 7

Однако для цифр после десятичной точки это не так точно (для первых нескольких десятков c цифр), так как происходит много округлений. Для получения дополнительной информации см .:

1 голос
/ 05 мая 2020

smallNumber точнее big

Неверное сравнение. Другой номер имеет более значимые цифры.

1.0000001f пытается N цифр десятичной точности.
100000001f пытается N + 1.

У меня проблемы с пониманием точность типа с плавающей запятой.

Чтобы лучше понять float точность, подумайте о двоичном. Используйте "%a" для печати с компилятором C99 или более поздней версии.

float хранится с основанием 2. Мантисса Dyadi c рациональное , некоторое целое число / степень двойки .

float обычно имеет 24 бита двоичной точности. (23-битное явно закодировано, подразумевается 1)

Между [1.0 ... 2.0) есть 2 23 различных float значений.
Между [2.0 ... 4.0) имеется 2 23 различных float значений.
Между [4.0 ... 8.0) существует 2 23 различных float значений.
...

Возможные значения a float неравномерно распределяются между степенями 10. Группировка float значений в степени 10 (десятичная точность) приводит к колебанию точности от 6 до 9 десятичных цифр.


Как рассчитать точность типа с плавающей запятой?

Чтобы найти разницу между последующими float значениями, начиная с C99, используйте nextafterf()

Иллюстративный код:

#include<math.h>
#include<stdio.h>

void foooo(float b) {
  float a = nextafterf(b, 0);
  float c = nextafterf(b, b * 2.0f);
  printf("%-15a %.9e\n", a, a);
  printf("%-15a %.9e\n", b, b);
  printf("%-15a %.9e\n", c, c);
  printf("Local decimal precision %.2f digits\n", 1.0 - log10((c - b) / b));
}

int main(void) {
  foooo(1.0000001f);
  foooo(100000001.0f);
  return 0;
}

Вывод

0x1p+0          1.000000000e+00
0x1.000002p+0   1.000000119e+00
0x1.000004p+0   1.000000238e+00
Local decimal precision 7.92 digits
0x1.7d783ep+26  9.999999200e+07
0x1.7d784p+26   1.000000000e+08
0x1.7d7842p+26  1.000000080e+08
Local decimal precision 8.10 digits
1 голос
/ 05 мая 2020

Я думаю, что в своей документации они имеют в виду, что в зависимости от числа точность варьируется от 6 до 9 знаков после запятой. Go по стандарту, который объясняется на странице, на которую вы ссылаетесь, иногда Microsoft, как и все мы, немного ленивы, когда доходит до документации. Проблема с плавающей запятой в том, что она неточна. Если вы поместите число 1.05 на сайт в своей ссылке, вы заметите, что оно не может быть точно сохранено с плавающей запятой. Фактически он хранится как 1.0499999523162841796875. Он хранится таким образом, чтобы быстрее выполнять вычисления. Это не очень хорошо для денег, например, что, если ваш товар стоит 1,05 доллара, а вы продаете миллиард таких товаров.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...