Возникла проблема какая-то странная на мой взгляд... Ф-ция округления в бейсике работает криво. Решил написать на VC dll-ку с таковой функцией и ничего не вышло... Собственно — не могу придумать корректную ф-цию округления более-менее высокого уровня. Не хотелось лезть во внутреннее представление числа и самому его разбирать...
Поскольку проблема достаточно очевидная, надеюсь кто-то сможет подсказать. Я уже отчаялся сделать ф-цию округления до произвольного количества знаков, нужно хотя бы до двух (имеет место бухг. система).
Есть надёжный вариант — взять число в виде строки и так его округлить (строкой же и вернуть), но хотелось сделать побыстрее.
Здравствуйте Smetanin, Вы писали:
S>Здравствуйте Snax, Вы писали:
S>Private Sub Command1_Click() S> Dim i As Double S> i = 0.005 S> MsgBox "Round(i,2) = " & CStr(Round(i, 2)) S>End Sub
S>Должно получаться, IMHO 0.01, а получается 0.
На С просто:
double Round(double val, int precision)
{
double dFr ;
// увеличиваем (уменьшаем на точность).
val = val * pow(10, precision) ;
// получаем значение до дес. точки.
dFr = modf( val, &val ) ;
// округляем по общему принципу.
if( fabs(dFr) >= 0.5 )
val = val + (dFr > 0.0 ? 1.0 : -1.0 ) ;
// возвращаем значение обратно.
val = val * pow(10,-precision) ;
return val ;
}
Вариант не самый оптимальный по скорости но для бухгалтерии сойдет...
S>>Должно получаться, IMHO 0.01, а получается 0. Не знаю где как, а наших бухгалтеров это не устраивает :-)
al>Зачем мучить C++? al>В MSDN есть статейка: HOWTO: Implement Custom Rounding Procedures (Q196652) — почитайте.
al>Вот Ваша функция:
al>Option Explicit
al>Function SymArith(ByVal X As Double, _ al> Optional ByVal Factor As Double = 1) As Double al> SymArith = Fix(X * Factor + 0.5 * Sgn(X)) / Factor al> ' Alternately: al> ' SymArith = Abs(AsymArith(X, Factor)) * Sgn(X) al>End Function
al>Private Sub Form_Load() al> MsgBox SymArith(0.005, 100) al>End Sub
А теперь попробуйте значние 25.435. Или мне поверьте — получается 25.43 и хоть ты тресни. Могу ещё глюки найти — это так, наугад... Конечно такой очевидный вариант я пробовал.
Тем не менее — спасибо.
Здравствуйте Smetanin, Вы писали:
S>А теперь попробуйте значние 25.435. Или мне поверьте — получается 25.43 и хоть ты тресни. Могу ещё глюки найти — это так, наугад... Конечно такой очевидный вариант я пробовал. S>Тем не менее — спасибо.
А такой вариант: MsgBox CDbl(Format(25.434, ".##"))
Здравствуйте al, Вы писали:
al>А такой вариант: MsgBox CDbl(Format(25.434, ".##"))
Сейчас некогда искать конкретную константу, но поверте — формат работает так же криво, как и раунд, только на других значениях. К тому же при использовании строк появляется отдельный геморрой с региональными настройками (с десятичным разделителем кокретно)...
Здравствуйте migel, Вы писали:
M>На С просто: M>double Round(double val, int precision) M>{ M> double dFr ; M> // увеличиваем (уменьшаем на точность). M> val = val * pow(10, precision) ; M> // получаем значение до дес. точки. M> dFr = modf( val, &val ) ; M> // округляем по общему принципу. M> if( fabs(dFr) >= 0.5 ) M> val = val + (dFr > 0.0 ? 1.0 : -1.0 ) ; M> // возвращаем значение обратно. M> val = val * pow(10,-precision) ; M> return val ; M>} M>Вариант не самый оптимальный по скорости но для бухгалтерии сойдет...
Сейчас С под рукой нет, но я делал подобный вариант на VC6 — тоже глючит (хотя и значительно реже бейсиковых). Берусь за выходные предоставить тест, на котором эта функция глюкнет.
S> К тому же при использовании строк появляется отдельный геморрой с региональными настройками (с десятичным разделителем кокретно)...
В случае CDbl(Format(...)) проблем с десятичным разделителем нет, т.к. и CDbl и Format используют один и
тотже.
Интересно, на каких значениях глючит Format?
Function vRound2(ByVal fVar As Double) As Double
vRound2 = Fix(CVar((fVar + 0.0055555555555) * 100)) / 100
End Function
Sub Test()
Debug.Print vRound2(0.005)
Debug.Print vRound2(25.4344444444444)
Debug.Print vRound2(25.4344444444445)
End Sub
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте Smetanin, Вы писали:
S>Возникла проблема какая-то странная на мой взгляд... Ф-ция округления в бейсике работает криво. Решил написать на VC dll-ку с таковой функцией и ничего не вышло... Собственно — не могу придумать корректную ф-цию округления более-менее высокого уровня. Не хотелось лезть во внутреннее представление числа и самому его разбирать... S>Поскольку проблема достаточно очевидная, надеюсь кто-то сможет подсказать. Я уже отчаялся сделать ф-цию округления до произвольного количества знаков, нужно хотя бы до двух (имеет место бухг. система). S>Есть надёжный вариант — взять число в виде строки и так его округлить (строкой же и вернуть), но хотелось сделать побыстрее.
Тут, конечно, много ответов, но все же не упущу случая предложить и свой способ, наиболее дубовый из всех имеющихся:
для округления к n-му разряду
к n+1-му добавляется 5,
все разряды после n отбрасываются.
Тот, кто желает, но не делает, распространяет чуму.
Здравствуйте Smetanin, Вы писали:
S>Возникла проблема какая-то странная на мой взгляд... Ф-ция округления в бейсике работает криво. Решил написать на VC dll-ку с таковой функцией и ничего не вышло... Собственно — не могу придумать корректную ф-цию округления более-менее высокого уровня. Не хотелось лезть во внутреннее представление числа и самому его разбирать... S>Поскольку проблема достаточно очевидная, надеюсь кто-то сможет подсказать. Я уже отчаялся сделать ф-цию округления до произвольного количества знаков, нужно хотя бы до двух (имеет место бухг. система). S>Есть надёжный вариант — взять число в виде строки и так его округлить (строкой же и вернуть), но хотелось сделать побыстрее.
Вот вам еще два варианта — округление по математике и округление по "бухгалтерии" (всегда вверх):
#define EEps 0.000000001
double round_number(double number, int pos) // pos — число знаков после запятой, если pos<0, то число знаков до запятой.
{ double base=10., coeff=1.;
int i;
double round_upper(double number, int pos) // все просто round_upper(2.123,2)=2.13
{ double base=10., coeff=1.;
int i;
for( i=0; i<pos; i++ ) coeff*=base;
number*=coeff;
// если стоит круглое число то округлять не надо
if( fabs(number-floor(number))>EEps )
number=floor(number+1.0); // instead `ceil`
number/=coeff;
Проблема округления стояла всегда, и будет стоять, поскольку так работает со-процессор. Разрядность-то ограничена, а представление такое:
1.[мантиса]*e^[степень].
Никогда не бойся браться делать то, что делать не умеешь. Помни, ковчег был построен любителем. Профессионалы построили Титаник...
Здравствуйте Smetanin, Вы писали:
V>Здравствуйте Smetanin, Вы писали:
S>>Возникла проблема какая-то странная на мой взгляд... Ф-ция округления в бейсике работает криво.
Скорее всего проверять надо не на точные cовпадения а на отклонения т.е.
if (fabs(dValue — 1.75) < 0.005)
// Ok
Здравствуйте Smetanin, Вы писали:
S>Возникла проблема какая-то странная на мой взгляд... Ф-ция округления в бейсике работает криво. Решил написать на VC dll-ку с таковой функцией и ничего не вышло... Собственно — не могу придумать корректную ф-цию округления более-менее высокого уровня. Не хотелось лезть во внутреннее представление числа и самому его разбирать... S>Поскольку проблема достаточно очевидная, надеюсь кто-то сможет подсказать. Я уже отчаялся сделать ф-цию округления до произвольного количества знаков, нужно хотя бы до двух (имеет место бухг. система). S>Есть надёжный вариант — взять число в виде строки и так его округлить (строкой же и вернуть), но хотелось сделать побыстрее.
Подобная проблема возникает при установке параметров оптимизации на "скорость работы".
Для решения проблемы надо изменить тип оптимизации и проверить, что не помечен пункт относящийся к округлению. К сожалению под рукой сейчас нет компилятора и точное название этого пункта сказать не могу, но тем кто знаком с english проблемы не будет.