Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Как правильно выполнять округление до ближайшего?
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
sonycman
Заметил, что при приведении типа с плав. точкой (float) к целочисленному значению, дробная часть просто выкидывается, или производится округление к нулю: было 4.8, стало -> 4.
А надо бы юзать округление до ближайшего целого -> 5.

Пришлось подключить хидер rt_fp.h и пользоваться тамошней функцией UINT _ffixu_r(float).

Может быть, режим округления можно изменить каким-то другим, более правильным путём, не прибегая к вызову функций внешней библиотеки?

И почему такой грубый способ округления юзается по умолчанию? Разве это правильно - 4.8 округлять до 4? cranky.gif
rezident
Приведение типа переменной и округление чисел, вообще говоря не очень связанные между собой операции. Самый простой способ общепринятого округления чисел при преобразовании из float в int это прибавление к результату 0.5f непосредственно перед таким преобразованием.
aaarrr
Цитата(rezident @ Dec 14 2009, 02:26) *
Самый простой способ общепринятого округления чисел при преобразовании из float в int это прибавление к результату 0.5f непосредственно перед таким преобразованием.

Только числа бывают и отрицательными.

ИМХО, лучше вариант (int)floor(x + 0.5)
rezident
Цитата(aaarrr @ Dec 14 2009, 04:52) *
Только числа бывают и отрицательными.
Согласен. У меня предложено "округление к большему". Если нужно именно "округление к ближайшему целому", то нужно учитывать знак числа.
Microwatt
да, один клерк в банке наокруглял десятые доли цента за пять лет что-то на 20000 баксов.
И судить было вроде не за что, раньше машина их просто отбрасывала, а он заставил переводить на свой счет.
dch
Цитата(sonycman @ Dec 14 2009, 02:19) *
Разве это правильно - 4.8 округлять до 4?

это такое соглашение если в выражении языка C нет вещественных то арифметика целочисленная
1/3 дает в результате 0, а если есть вещественное, то каждое целое переводится в вещественное 1/3. дает около одной трети :-)
sonycman
Цитата(rezident @ Dec 14 2009, 03:26) *
Приведение типа переменной и округление чисел, вообще говоря не очень связанные между собой операции. Самый простой способ общепринятого округления чисел при преобразовании из float в int это прибавление к результату 0.5f непосредственно перед таким преобразованием.

Вот, спасибо, теперь буду знать smile.gif
Хотя и смахивает это на некий костыль...

Цитата(aaarrr @ Dec 14 2009, 03:52) *
Только числа бывают и отрицательными.
ИМХО, лучше вариант (int)floor(x + 0.5)

А вот здесь не понял, floor при округлении отрицательного числа (-4.8 + 0.5 = -4.3) даст результат -5?
demiurg_spb
Цитата(sonycman @ Dec 14 2009, 12:23) *
А вот здесь не понял, floor при округлении отрицательного числа (-4.8 + 0.5 = -4.3) даст результат -5?
Судя по всему да. ссылка
А я использую вот такие макросы
Код
#define FROUND(x)                 ((unsigned long)((x)+0.5f))
#define FROUNDS(x)                (((x)>=0)?(long)((x)+0.5f):(long)((x)-0.5f))

До такого не додумалсяsmile.gif
Цитата(aaarrr @ Dec 14 2009, 02:52) *
ИМХО, лучше вариант (int)floor(x + 0.5)
sonycman
demiurg_spb
По идее да - floor округляет до целого, не превышающего начальное значение.
Если округлить -4.8 до -4, то -4 будет больше -4.8.
Значит, правильно будет -5 smile.gif

В принципе, чтобы юзать floor, надо подключить math.h
А чтобы юзать _ffixu_r, которая округлит число безо всяких заморочек, надо подключить rt_fp.h.
Не знаю, есть ли между ними какая-то разница smile.gif

Имхо - самое простое - юзать предложенные вами макросы smile.gif
demiurg_spb
Цитата(sonycman @ Dec 14 2009, 12:53) *
В принципе, чтобы юзать floor, надо подключить math.h
Это не страшно.
Цитата
А чтобы юзать _ffixu_r, которая округлит число безо всяких заморочек, надо подключить rt_fp.h.
А это уже нестандартно (видимо от keil примочки).
Цитата
Имхо - самое простое - юзать предложенные вами макросы smile.gif
Это как сказать... У меня два макроса, а ведь можно обойтись одним. Вопрос что оптимальнее. Нужно экспериментировать.
tag
Цитата(sonycman @ Dec 14 2009, 02:19) *
Заметил, что при приведении типа с плав. точкой (float) к целочисленному значению, дробная часть просто выкидывается, или производится округление к нулю: было 4.8, стало -> 4.


В описании языка C четко сказано, что при приведении типа от вещественного к целочисленному дробная часть просто отсекается.
sonycman
Цитата(tag @ Dec 14 2009, 15:24) *
В описании языка C четко сказано, что при приведении типа от вещественного к целочисленному дробная часть просто отсекается.

Я в курсе.
Имхо - очередная бяка стандарта sad.gif
Почему то, в тех редких случаях, когда приходится юзать плавучку, ни разу не приходила в голову мысль портить вычисления использованием столь грубого округления.
Всегда приходится обходить "чётко оговоренный" стандарт sad.gif
baralgin
sonycman Операция отбрасывания дробной части лёгкая для процессора(наличие fpu роли не играет), в отличии от полноценного округления. Округление нужно далеко не всегда, поэтому можно избежать использования лишних операций. Так наверное во всех вменяемых языках устроено...
Цитата(sonycman @ Dec 14 2009, 13:44) *
Почему то, в тех редких случаях, когда приходится юзать плавучку, ни разу не приходила в голову мысль портить вычисления использованием столь грубого округления.

Ммм.. а например при целочисленном делении ( a / b, a >> x) вам никогда не приходило в голову, что значения напрасно портяться? smile.gif (хотя могут быть относительно легко "округлены").
sonycman
Цитата(baralgin @ Dec 14 2009, 17:16) *
Ммм.. а например при целочисленном делении ( a / b, a >> x) вам никогда не приходило в голову, что значения напрасно портяться? smile.gif (хотя могут быть относительно легко "округлены").

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

А вот при отбрасывании дробной части числа 4.99 мы получаем чувствительную ошибку.
Я не затем проводил рассчёт в плавучке, чтобы затем при банальной передаче параметров всё угробить smile.gif
Sneg_87
предлагаю такой вариант:

long a=5.6;
//перед преобразованием умножать на 10, чтоб был остаток от деления в дальнейшем
a=a*10;
//теперь а=56
a=((int)(a+a%10))/10;
//поясню (int) (56+6) и деленое на 10 будет 6, что и необходимо

для отрицательного числа тоже пойдет


ЗЫ при проверке в досовском Borland: задавал а=5.1 далее умножал на 10 и получал а=50.999. кто может объяснить в чем дело?
aaarrr
Цитата(Sneg_87 @ Dec 14 2009, 19:22) *
long a=5.6;

Ну, дальше можно уже не читать.

Цитата(Sneg_87 @ Dec 14 2009, 19:22) *
//перед преобразованием умножать на 10, чтоб был остаток от деления в дальнейшем
a=a*10;
//теперь а=56
a=((int)(a+a%10))/10;
//поясню (int) (56+6) и деленое на 10 будет 6, что и необходимо

Умножение + 2 деления + сложение = хороший пример того, как делать не надо.
demiurg_spb
Цитата(Sneg_87 @ Dec 14 2009, 19:22) *
ЗЫ при проверке в досовском Borland: задавал а=5.1 далее умножал на 10 и получал а=50.999. кто может объяснить в чем дело?
Точность представления чисел конечная. Чего вы хотите?
Для float:
старший бит - знак,
8 бит - порядок со смещением 127,
23 бита - мантисса.

2^23= 8388608. Итог: имеем лишь ~7 десятичных значащих разрядов...

погуглите на тему: формат IEEE754
Sneg_87
Цитата(aaarrr @ Dec 14 2009, 22:25) *
Умножение + 2 деления + сложение = хороший пример того, как делать не надо.

Чем плох вариант без подключения внешних библиотек? Единственным пожалуй минусом является время, что думаю не критично для ситуации. Ну а вторымб что он не пойдет, сейчас исправим smile.gif

Вот работающий на 100% (сам проверял) листинг программки, выдающий требуемый результат:
Код
#include "stdio.h"
#include "conio.h"
#include <iostream.h>

main () {
clrscr();
float a=6.9;
int b=0;
a=a*10;
b=a;
b=((int)(b+b%10))/10;
printf ("%i",буква "b" не вводилась на форуме);
getch();
}
aaarrr
Цитата(Sneg_87 @ Dec 15 2009, 21:12) *
Чем плох вариант без подключения внешних библиотек?

Он бесконечно хуже такого макроса:
Код
#define FROUNDS(x)                (((x)>=0)?(long)((x)+0.5f):(long)((x)-0.5f))

А уж это (выделено):
Цитата
b=((int)(b+b%10))/10;

- вообще ни в какие ворота.

А то, что он "работает" - так это, извините, не аргумент.
Sneg_87
Цитата(aaarrr @ Dec 15 2009, 23:35) *
А то, что он "работает" - так это, извините, не аргумент.

не люблю разводить темы насчет демогогии выбора. каждый выбирает то, что хочет.
ЗЫ автору предложено 2 способа, думаю сам выберет, что подходит.
sigmaN
Sneg_87, такие "конструкции" в реальных проектах никто использовать не будет.
И никто Вам даже не будет объяснять почему! Это очевидно. smile.gif


P.S. дизассемблер, кстати, клёвая штука. Предлагаю им воспользоваться wink.gif
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.