Я столкнулся с проблемой округления в Delphi, когда значения, очень близкие к средней точке (например, 21,499991254), иногда округляются в меньшую сторону, а не в большую, что может привести к несогласованности в моих расчетах.
Например:
var
value1, value2: Double;
begin
value1 := 21.5;
value2 := 21.499991254;
ShowMessage(IntToStr(Round(value1))); // Returns 22, as expected
ShowMessage(IntToStr(Round(value2))); // Returns 21, which is not ideal for my case
end;
Я понимаю, что добавление небольшого значения эпсилон перед округлением может помочь, например:
Round(Value + Epsilon)
Но мне интересно, есть ли в Delphi более надежный или стандартный подход для решения этой ситуации? Существуют ли какие-либо встроенные функции или рекомендации, обеспечивающие единообразное округление фактически одинаковых значений (например, 21,499991254 и 21,5)?
Любые советы или идеи будут очень признательны!
прямо сейчас я использую его для графики, чтобы выровнять изображение по позиции пикселя. когда я прошу выровнять по позиции пикселя квадрат 21,5*21,5, он выравнивается по 22x22 на экране, но если это какой-то эпсилон, например 21,49999x21,49999, который визуально точно такой же, как 21,5x21,5, он округляется до 21x21, что не то же, что 22х22 :(
Обычным соглашением для округления является округление менее 0,5 в меньшую сторону и более 0,5 в большую сторону. Ровно 0,5 округления до ближайшего четного целого числа. Ваши примеры демонстрируют именно такое поведение. Всегда должна быть точка отсечения, где раунд переключается снизу вверх, поэтому я не знаю точно, чего вы надеетесь достичь. Такова природа Раунда.
Почему 21,5*21,5? Что означают эти цифры? Если бы оно было 21х21, оно тоже будет округлено до 21х21, а не до 22х22.
Как отмечали другие, результаты, которые вы получаете, верны. В зависимости от того, сколько цифр вы хотите принять во внимание, вы можете сделать что-то вроде:
Round(Round(Value2 * 10) / 10);
{ What this is doing: }
{ Move the decimal place to the right: }
{ 21.499991254 * 10 = 214.99991254 }
{ Round the decimal places: }
{ Round(214.99991254) = 215.0 }
{ Move the decimal place back: }
{ 215.0 / 10 = 21.5 }
{ Round the decimal places: }
{ Round(21.5) = 22.0 }
Если вы хотите, чтобы округление было более точным, увеличьте количество десятичных знаков, используемых в разделах *10 и /10.
Обратите внимание, что округление в сторону ближайшего четного числа по-прежнему действует для младшей значащей цифры в расчете. Итак, 21.4999 вернет 22, а 20.4999 вернет 20.
Если вы хотите всегда округлять числа, оканчивающиеся на .5 и выше, я думаю, вам понадобится оператор if, например:
if Frac(Value) >= 0.5 then
begin
Result := Ceil(Value);
end
else
begin
Result := Floor(Value);
end;
При этом я новичок в Delphi, поэтому у кого-то может быть лучшее решение.
Я также хотел бы добавить, что если эти значения являются результатом точности с плавающей запятой, возможно, измените тип данных вашей переменной на что-то более надежное:
Decimal1: Single; // 7 significant digits, exponent -38 to +38
Decimal2: Currency; // 50+ significant digits, fixed 4 decimal places
Decimal3: Double; // 15 significant digits, exponent -308 to +308
Decimal4: Extended; // 19 significant digits, exponent -4932 to +4932
Подробности о типах данных из Delphi Basics.
В 64-битных приложениях (то есть в большинстве современных приложений) Extended
— это просто синоним Double
.
Это зависит от характера ваших расчетов, для чего они используются?