Почему арифметические результаты различаются, если в Tcl вложено выражение "expr"? - PullRequest
4 голосов
/ 28 марта 2012

Зная о неточностях из-за внутреннего представления чисел с плавающей запятой (см. Википедия , поиск до: «Использование теста на равенство» ...), при выполнении этого действия все равно остается сюрприз:

% expr int(0.57 * 10000)
5699
% expr int([expr 0.57 * 10000])
5700

Вложены ли expr запрещены? Почему это изменяет значение с плавающей запятой, которое передается через? Или это меняет порядок, в котором выполняется арифметика с плавающей запятой, влияющая на результат?

Обновление: хорошее чтение по теме сравнения чисел с плавающей запятой со скоростью (и вторичными ссылками), некоторыми основами и не очень основами здесь , а в Описание стандарта IEEE 754-2008 .

1 Ответ

7 голосов
/ 28 марта 2012

Это интересный вопрос, поскольку он затрагивает несколько тонких вещей.

% expr int(0.57 * 10000)
5699

Этот код (без подстановок, поэтому он работает «неудивительно») показывает, что числа с плавающей запятой сами по себе удивительны. В частности, 0.57 не имеет точного представления в качестве числа с плавающей запятой IEEE с двойной точностью (то есть base-2); на самом деле его представление немного ниже, чем точно 0,57, и поэтому при округлении в меньшую сторону (что и делает int(...); точное значение - 10000) вы переходите к 5699. Такое же поведение вы увидите и с другими языками. .

% expr int([expr 0.57 * 10000])
5700

Теперь это особенно интересно. Сначала вы видите, как выполняются внутренние вычисления, и результирующее число с плавающей запятой преобразуется в строку (потому что другого способа сделать это не существует). Теперь, вы должны использовать Tcl 8.4 (или раньше), где правила рендеринга номеров по умолчанию были (в действительности) тем, что вы получите, напечатав первые 15 значащих цифр номера; в этом случае это дает вам 5700.00000000000 (ну, с некоторым усечением нулей справа), а затем интерпретируется с нуля как первое двойное (ровно 5700.0), а затем преобразуется в 5700.

Правила преобразования чисел изменены в Tcl 8.5. В настоящее время, когда Tcl преобразует числа с плавающей запятой в строки, он генерирует самую короткую строку, которая преобразует обратно в одно и то же число с плавающей запятой (т. Е. Боковое перемещение по земле строк даст одинаковый битовый шаблон в полученном двойном значении). Это, в свою очередь, означает, что теперь вы никогда не увидите различий между двумя вещами выше (Если вы действительно хотите ввести фиксированное количество десятичных знаков, используйте format %.15g [expr 0.57 * 10000].)

Вы также не заметите наблюдаемое поведение с 8.0 до 8.4, если правильно подготовите свои выражения, как сообщество рекомендует людям делать это уже более десяти лет:

$ tclsh8.4
% expr {int([expr 0.57 * 10000])}
5699

Это потому, что это не заставляет интерпретировать результат внутреннего вызова expr как строку. (Это также быстрее и безопаснее.)

...