2014-09-02 2 views
2

Это код, который округляет от 62 до 61 и показывает его на выходе. Почему он решает объединиться и как получить 62 результата?двойное округление

var d: double; 
i: integer; 
begin 
    d:=0.62; 
    i:= trunc(d*100); 
    Showmessage(inttostr(i)); 

end; 
+0

Возможный дубликат [Является ли математика с плавающей запятой?] (Http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Mark

ответ

13

Это сводится к тому, что 0.62 не является точно представимым в двоичном виде с плавающей точкой. closest representable double value to 0.62 является:

 
0.61999 99999 99999 99555 91079 01499 37383 83054 73327 63671 875 

Если умножить это значение на 100, полученное значение немного меньше, чем 62. Что произойдет дальше, зависит от того, как промежуточное значение d*100 лечится. В вашей программе под 32-битным компилятором Windows с настройками по умолчанию промежуточное значение сохраняется в 80-битном расширенном регистре. А ближе 80 бит расширенной точности значение:

 
61.99999 99999 99999 55591 07901 49937 38383 05473 32763 67187 5 

Поскольку значение меньше 62, Trunc возвращается 61 С Trunc раундов к нулю.

Если вы сохранили d*100 в двойном значении, вы увидите другой результат.

d := 0.62; 
d := d*100; 
i := Trunc(d); 
Writeln(i); 

Эта программа выводит 62, а не 61. Это потому, что хотя d*100 расширенного 80 битной точности меньше, чем 62, то closest double precision value to that 80 bit value фактически 62.

Точно так же, если вы скомпилировать оригинальную программу с 64-битный компилятор, то арифметика выполняется в блоке SSE, который не имеет 80-битных регистров. И поэтому нет промежуточного значения 80 бит и выходов вашей программы 62.

Или, вернувшись к 32-битовому компилятору, вы можете организовать промежуточное значение, которое хранится до 64-битной точности на FPU, а также получить выход 62. Для этого необходимо позвонить Set8087CW($1232).

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


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

Но, возможно, лучшим решением было бы использовать десятичный тип данных, а не двоичный тип данных. Если вы это сделаете, вы можете точно представить 0.62 и тем самым избежать всех таких проблем. Встроенный десятичный реальный тип данных Delphi - Currency.

+0

+1 за точный. –

4

Использование round вместо trunc.

round округляется в направлении ближайшего целого числа, а 62.00 очень близко к 62, поэтому проблем нет. trunc округляется до ближайшего целого числа по направлению к нулю и 62.00 очень близко к 61.9999999, поэтому числовой «пух» может очень сильно вызвать проблему, которую вы описываете.

Смежные вопросы