почему получается странный ответ при простых вычислениях?

решали простой математический пример (-0.5/(-0.25)-(-1.2)*(-1.5)+0.00235/(-0.047))/100*30

ответ должен получиться 0,045

однако

writeln((-0.5/(-0.25)-(-1.2)*(-1.5)+0.00235/(-0.047))/100*30);

выдает 0.0450000000000001

откуда берутся нули и единица?

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

Для расчётов где такое недопустимо - надо использовать decimal. Оно тоже огругляет, но так чтоб в десятичной системе красиво выглядело. Но, конечно, это будет медленнее.

Надо все же иногда хотя бы и книги/руководства читать… Я понимаю, что нынешнее молодое поколение считает чтение книг уделом ботанов, но либо Вы читаете книги, либо еще не раз будете получать “по носу” в самых, казалось бы, тривиальных случаях.
Читайте тут 2.4 - все же Вам выложено…

1 симпатия

какие бесконечные числа? какие книги? вы о чем?

тип real должен считать без погрешностей в пределах чисел с 15 значащими цифрами. В данном примере и половины знаков нету. Давайте пересчитаем пример еще раз:

(-0.5/(-0.25))-(-1.2*(-1.5))+(0.00235/(-0.047))=0,15

0,15*0,3=0,045

но если записать так

((-0.5/(-0.25))-(-1.2*(-1.5))+(0.00235/(-0.047)))*0,3=0.0450000000000001

если верить правилам вычисления то сперва должно быть посчитано то что в скобках (оно равно 0,15) затем умножено на 0.3 в какой момент и почему происходит потеря точности? И даже если бы я вышел за рамки значащих цифр я бы потерял точность то есть число стало бы меньше но ни как не больше.

немного повычислял любопытства ради и пришел к странному выводу что

0.15 <> 0.1+0.05

объясните, я этого не понимаю.

интереса ради попробовал посчитать все это в Delphy

не поверите там все считается правильно!!! И там 0.15 = 0.1+0.05 !!!

выводит он числа в консоль правда в экспоненциальной форме но это легко решаемо заданием формата. Главное что там в отличии от ABC числа считаются верно.

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

Формат хранения чисел с плавающей запятой всемирный. Дельфи может только производить дополнительное округление перед выводом.

И попробуйте всё же прочитать на этот раз:

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

Т.е. в данном случае мы имеем дело с элементарной упертостью. Ему уже и объяснили, и ссылку дали где можно за пять минут все прочитать и понять - но нет, зачем что-то читать, если можно тупо продолжать вопрошать? Пытаться и дальше отвечать такому сапиенсу - только время терять. Двоичная система счисления - это не для него.

Неча на зеркало пенять, коли рожа крива
(И.А.Крылов, "Мартышка и очки")
1 симпатия

С интересом прочитал всю вот эту дискуссию. Какая она замечательная :grinning: Ощущение, что все повторяется. Вот, например, древняя книжка:

Дж. Форсайт, М. Малькольм, К. Моулер - Машинные методы математических вычислений (1980, Prentice-Hall перевод на русский язык Мир)

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

3 симпатии

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

А вот и подтверждение: фрагмент кода из моей библиотеки NumLibABC, вошедшей в дистрибутив PascalABC.NЕТ

А мне тоже интересно, почему PascalABC.NET не округлил вывод. Вот тут же он округляет?:

begin
  var a := 0.1;
  var b := 0.2;
  var c := 0.3;
  Writeln(a + b); // 0.3
  Writeln(c);     // 0.3
  Writeln(a + b = c); // False
end.

Хотя и выводит что 0.3 не равно 0.3. Как бы может возникнуть вопрос, а почему не равно? Если тот же пример посмотреть в FreePascal, то там видно откуда ноги растут.

%D0%A1%D0%BD%D0%B8%D0%BC%D0%BE%D0%BA

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

Delphy - аналогично. То, что у них оно каким-то образом не валится - не повод этим свойством пользоваться. Ни разу.

Есть такое понятие в современных языках - undefined behavior. Неопределенное поведение, да. Это когда результат исполнения не зависит ни от стандарта, ни от документации, ни от компилятора.

Во всем мире более-менее грамотные программисты пользуют несколько иной способ проверки на равенство двух чисел с плавающей точкой: abs(x1 - x2) < eps, где eps - какое-то маленькое число, обеспечивающее необходимую точность сравнения (например, 0.000001).

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

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

Подчеркиваю - даже если какая-то среда (хоть Delphi, хоть Brainfuck) в каких-то случаях считает верно, на этот результат рассчитывать НЕЛЬЗЯ. За сравнение real на равенство вообще надо ошибку компиляции кидать, чтоб неповадно было. Сколько ж багов мир создал подобным образом…

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

1 симпатия

…если хотя бы одно из действительных чисел было получено в результате арифметических вычислений.

Вывод “округляет” (или не округляет) вовсе не Паскаль. Как в любом .NET-языке, это делают соответствующие майкрософтовские библиотеки. Free Pascal - он использует совсем другие библиотеки, нет смысла сравнивать.

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

Ну, да, спору нет.

Полагаю, вот это высказывание в каком-то смысле тождественно первой цитате сообщения. Да, без отсылок к теории, но эти отсылки - по ссылке в сообщении выше.

Да нинадо с ним мириться (ну то есть, не всегда). Есть decimal. Конечно, 1/3 он всё ещё не сможет представить без погрешности. Но любое десятичное число с не_бесконечным кол-вом знаков после зяпятой - запросто.

Я на детские вопросы не отвечаю. И другим не советую.

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

1 симпатия