Почему расчет с использованием real дает результат, отличный от расчета с использованием int?

У меня есть этот код, например:

(a) writeln ('real => ', exp(3*Ln(3)):0:0);  // return 27
(b) writeln ('int => ', int(exp(3*Ln(3))):0:0); // return 26

Ошибка? Функция calc 3^3 (показатель степени с использованием функций ln и exp), но преобразование из вещественного в целое не выполняется; в случае (а) вернуть 27, в случае (б) вернуть (26), когда должно быть 27 оба. Как я могу это решить? Большое спасибо за помощь.

Ps: тоже присвойте результат целочисленной переменной, используя trunc, результат не изменится.


person Marcello Impastato    schedule 08.09.2011    source источник
comment
Отрицательный. Тема уже обсуждалась тысячу раз.   -  person Premature Optimization    schedule 08.09.2011
comment
@Downvoter, в этом случае вы должны проголосовать за закрытие со ссылкой на повторяющийся вопрос.   -  person Johan    schedule 09.09.2011
comment
возможный дубликат Почему результат RoundTo(87.285, -2) =› 87,28   -  person Johan    schedule 09.09.2011
comment
@Johan Йохан, сужая его, Delphi не показывает всю картину. Эта ошибка даже была упомянута в СМИ: /08/12/приближение_с плавающей_точкой   -  person Premature Optimization    schedule 09.09.2011
comment
FWIW, Int(X) не возвращает целое число, а просто возвращает целочисленное значение X, то есть значение X с удаленной дробной частью. Он эквивалентен Trunc(X), за исключением того, что фактически возвращает целое число.   -  person Rudy Velthuis    schedule 09.09.2011


Ответы (5)


Нет, это не ошибка. У компьютеров просто нет бесконечной точности, поэтому результат не точно 27, а, возможно, 26,999999999 или что-то в этом роде. И поэтому, когда вы int или trunc это заканчивается как 26. Вместо этого используйте Round.

person Andreas Rejbrand    schedule 08.09.2011
comment
Это тоже когда реально как: 27.000? Какой смысл округлять 27.000 до 26.000? Я не понимаю. - person Marcello Impastato; 08.09.2011
comment
int возвращает целую часть своего аргумента, то есть аргумент, округленный до нуля. trunc делает то же самое, но возвращает целое число (как тип данных). Округление округляет до ближайшего целого числа. Это то, что ты хочешь. - person Andreas Rejbrand; 08.09.2011
comment
но функция: exp(3*ln(3)) возвращает 27.000, а не 26.9999999 - person Marcello Impastato; 08.09.2011
comment
@Марчелло: Нет, это не так. Попробуйте exp(3*ln(3)) = 27.000 сами, это false. 27.000 - это то, что печатается на вашем мониторе, и это после того, как некоторая процедура взяла биты числа, хранящиеся в памяти, и создала из него текстовое представление. - person Andreas Rejbrand; 08.09.2011

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

В комментариях вы утверждаете, что exp(3*ln(3)) = 27.000, но вы не показали никаких программных доказательств этого утверждения. Ваш код говорит, что exp(3*ln(3)) = 27, что менее точно. Он печатает это, потому что вы явно сказали WriteLn использовать меньшую точность. Часть :0:0 — это не просто украшение. Это означает, что вы хотите распечатать результат без десятичных знаков. Когда вы указываете WriteLn сделать это, оно округляет до указанного количества знаков после запятой. В этом случае оно округляется. Но когда вы вводите вызов Int, вы усекаете значение почти-27 ровно до 26, а затем WriteLn тривиально округляет его до 26 перед печатью.

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

person Rob Kennedy    schedule 08.09.2011

Работа с плавающей запятой не всегда дает 100% точный результат. Причина в том, что двоичная переменная с плавающей запятой не всегда может точно представлять значения. То же самое верно и для десятичных чисел. Если вы возьмете 1/3 с точностью до 6 цифр, это будет 0,333333. Тогда если взять 0,333333 * 3 = 0,999999. Целое (0,999999) = 0

Вот литература по этому поводу...

Что должен знать каждый программист об арифметике с плавающей запятой

person Ken Bourassa    schedule 08.09.2011
comment
Я думаю, что название очень вводит в заблуждение. Те, кто проигнорировал руководство пользователя, будут игнорировать и публикации, адресованные ученым. Между тем эта статья содержит абсолютно обязательную информацию, с которой нужно ознакомиться перед подходом к машине. - person Premature Optimization; 08.09.2011

Вы также должны взглянуть на статью Руди Вельтуса:

http://rvelthuis.de/articles/articles-floats.html

person Jørn E. Angeltveit    schedule 08.09.2011
comment
Спасибо за упоминание! - person Rudy Velthuis; 09.09.2011

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

person David Hammen    schedule 08.09.2011
comment
Понятно, но как было сказано ранее, результат функции 27.000 в первом случае и 26.000 во втором случае. И функция calc 3*3*3 (в реальном формате) примерно так: 3.000 * 3.000 * 3.000 = 27.000; где это приближение от десятичной части? Но вообще, если я хочу вычислить точно x^n (n = 0), как это сделать, без использования математических функций? нужно использовать сложную функцию, основанную на for, if и т. д.? ничего проще? Я знаю, как это решить, но я пытаюсь найти элегантное решение. - person Marcello Impastato; 08.09.2011