Каков простой пример ошибки с плавающей запятой / округления?

Я слышал об «ошибке» при использовании переменных с плавающей запятой. Теперь я пытаюсь решить эту головоломку и, кажется, получаю ошибку округления / с плавающей запятой. Итак, я, наконец, собираюсь разобраться в основах ошибки с плавающей запятой.

Каков простой пример ошибки с плавающей запятой / округления (желательно на C++)?

Обновлено: например, скажем, у меня есть событие с вероятностью p успеха. Я делаю это упражнение 10 раз (p не меняется и все попытки независимы). Какова вероятность ровно двух успешных испытаний? Я закодировал это как:

double p_2x_success = pow(1-p, (double)8) * pow(p, (double)2) * (double)choose(8, 2);

Это возможность ошибки с плавающей запятой?

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

Patrick 30.10.2008 10:17

Прочтите это: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1

Nicholas Wilson 03.05.2013 23:09

См. Простой пример Java, он должен быть таким же в C: stackoverflow.com/a/15790782/254109

xmedeko 05.11.2013 16:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
35
3
40 680
8

Ответы 8

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

Целые числа хранятся с крайним правым битом равным 1, а каждый левый бит вдвое больше (2,4,8, ...). Легко видеть, что здесь можно хранить любое целое число до 2 ^ n, где n - количество бит.

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

Таким образом, такие числа, как 0,5 (1/2), легко хранить, но не каждое число <1 может быть создано путем добавления фиксированного количества дробей вида 1/2, 1/4, 1/8, ...

Очень простой пример - 0,1 или 1/10. Это можно сделать с помощью бесконечного ряда (что я не особо беспокоюсь о разработке), но всякий раз, когда компьютер сохраняет 0,1, сохраняется не совсем это число.

Если у вас есть доступ к Unix-машине, это легко увидеть:

Python 2.5.1 (r251:54863, Apr 15 2008, 22:57:26) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1
0.10000000000000001
>>> 

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

(Что касается вашего примера, 0,2 - это еще одно из тех надоедливых чисел, которые нельзя сохранить в двоичном формате IEEE, но пока вы проверяете неравенства, а не равенства, например p <= 0,2, тогда все будет в порядке.)

Простой пример на C, который поймал меня некоторое время назад:

double d = 0;
sscanf("90.1000", "%lf", &d);
printf("%0.4f", d);

Это печатает 90.0999

Это было в функции, которая конвертировала углы в DMS в радианы.

Почему в приведенном выше случае не работает?

Как заметил анонимный пользователь, с sscanf для спецификатора преобразования «f» требуется аргумент float, а не double (однако «f» означает double в printf - да, это сбивает с толку). Чтобы заставить sscanf работать с double, следует использовать модифицированный спецификатор преобразования «lf».

Dan Moulding 07.10.2011 16:05
 for(double d = 0; d != 0.3; d += 0.1); // never terminates 

Картинка стоит тысячи слов - попробуйте нарисовать уравнение f(k):
enter image  description here
, и вы получите такой график XY (X и Y в логарифмической шкале) .

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

hth!

Могу ли я добавить это изображение (переделанное, чтобы оно было SVG) в Wikipedia Commons под лицензией CC0 (ссылка на это для идеи)?

Martin Thoma 13.08.2018 08:56

Вот один, который меня поймал:

 round(256.49999) == 256
roundf(256.49999) == 257

double и float имеют разную точность, поэтому первый будет представлен как 256.49999000000003, а второй как 256.5, и поэтому округление будет по-разному.

Мне нравится это из интерпретатора Python:

Python 2.7.10 (default, Oct  6 2017, 22:29:07) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1+0.2
0.30000000000000004
>>>

супер просто:

a = 10000000.1
b = 1/10
print(a - b == 10000000)
print ('a:{0:.20f}\nb:{1:.20f}'.format(a, b))

печатает (в зависимости от платформы) что-то вроде:

False                                                                                                                                 
a:10000000.09999999962747097015                                                                                                       
b:0.10000000000000000555 

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

0.2 + 0.1

Вот несколько примеров с REPL, которые приходят мне в голову, но должны возвращать этот результат на любом языке, совместимом с IEEE754.

Python

>>> 0.2 + 0.1
0.30000000000000004

Котлин

0.2 + 0.1
res0: kotlin.Double = 0.30000000000000004

Scala

scala> 0.2 + 0.1
val res0: Double = 0.30000000000000004

Ява

jshell> 0.2 + 0.1
$1 ==> 0.30000000000000004

Рубин

irb(main):001:0> 0.2 + 0.1
=> 0.30000000000000004

Другие вопросы по теме