Каков наилучший способ округления в VBA Access?
В моем текущем методе используется метод Excel
Excel.WorksheetFunction.Round(...
Но я ищу средства, которые не полагаются на Excel.
Каков наилучший способ округления в VBA Access?
В моем текущем методе используется метод Excel
Excel.WorksheetFunction.Round(...
Но я ищу средства, которые не полагаются на Excel.
Будьте осторожны, функция VBA Round использует округление Banker, где она округляет 0,5 до четного числа, например:
Round (12.55, 1) would return 12.6 (rounds up)
Round (12.65, 1) would return 12.6 (rounds down)
Round (12.75, 1) would return 12.8 (rounds up)
В то время как функция рабочего листа Excel Round, всегда округляет .5 вверх.
Я сделал несколько тестов, и похоже, что округление округлой формы .5 (симметричное округление) также используется форматированием ячейки, а также для округления ширины столбца (при использовании формата общего номера). Флаг "Точность как отображаемый", похоже, не делает никакого округления, он просто использует округленный результат формата ячейки.
Я попытался реализовать функцию SymArith от Microsoft в VBA для моего округления, но обнаружил, что Fix имеет ошибку, когда вы пытаетесь дать ей число, подобное 58.55; функция, дающая результат 58,5 вместо 58,6. Затем я наконец обнаружил, что вы можете использовать функцию Round Worksheet Excel, например:
Application.Round(58.55, 1)
Это позволит вам выполнять нормальное округление в VBA, хотя это может быть не так быстро, как некоторые пользовательские функции. Я понимаю, что это вызвало полный круг вопроса, но хотелось включить его для полноты.
Чтобы немного расширить принятый ответ:
"Функция Round работает округленно до четного, которая отличается от округления к большему".
--Microsoft
Формат всегда округляется.
Debug.Print Round(19.955, 2)
'Answer: 19.95
Debug.Print Format(19.955, "#.00")
'Answer: 19.96
ACC2000: ошибки округления при использовании чисел с плавающей запятой: http://support.microsoft.com/kb/210423
ACC2000: как округлить число вверх или вниз желаемым приращением: http://support.microsoft.com/kb/209996
Круглые функции: http://msdn2.microsoft.com/en-us/library/se6f2zfx.aspx
Как реализовать пользовательские процедуры округления: http://support.microsoft.com/kb/196652
В Швейцарии и, в частности, в страховой отрасли, мы должны использовать несколько правил округления, в зависимости от того, как они изнашиваются, выгода и т.д.
В настоящее время я использую функцию
Function roundit(value As Double, precision As Double) As Double
roundit = Int(value / precision + 0.5) * precision
End Function
который работает нормально
Int и Fix являются полезными функциями округления, которые дают вам целую часть числа.
Int всегда округляется вниз - Int (3.5) = 3, Int (-3.5) = -4
Fix всегда округляется до нуля - Fix (3.5) = 3, Fix (-3.5) = -3
Также существуют функции принуждения, в частности CInt и CLng, которые пытаются принудить число к целочисленному типу или длинному типу (целые числа находятся между -32,768 и 32,767, longs - между 2,147,483,648 и 2,147,483,647). Они будут округлены к ближайшему целому числу, округляя от нуля от .5 - CInt (3.5) = 4, Cint (3.49) = 3, CInt (-3.5) = -4 и т.д.
1 place = INT(number x 10 + .5)/10
3 places = INT(number x 1000 + .5)/1000
и т.д. Вы часто обнаружите, что, по-видимому, подобные решения намного быстрее, чем использование функций Excel, потому что VBA работает в другом пространстве памяти.
eg If A > B Then MaxAB = A Else MaxAB = B
примерно на 40 x быстрее, чем при использовании ExcelWorksheetFunction.Max
Если вы говорите о округлении до целочисленного значения (а не округления до десяти знаков после запятой), всегда существует старая школа:
return int(var + 0.5)
(Вы можете сделать эту работу и для n десятичных знаков, но она начинает немного запутываться)
Лэнс уже упомянул о наследовании округления bug
в реализации VBA.
Поэтому мне нужна реальная функция округления в приложении VB6.
Вот что я использую. Он основан на том, что я нашел в Интернете, как указано в комментариях.
' -----------------------------------------------------------------------------
' RoundPenny
'
' Description:
' rounds currency amount to nearest penny
'
' Arguments:
' strCurrency - string representation of currency value
'
' Dependencies:
'
' Notes:
' based on RoundNear found here:
' http://advisor.com/doc/08884
'
' History:
' 04/14/2005 - WSR : created
'
Function RoundPenny(ByVal strCurrency As String) As Currency
Dim mnyDollars As Variant
Dim decCents As Variant
Dim decRight As Variant
Dim lngDecPos As Long
1 On Error GoTo RoundPenny_Error
' find decimal point
2 lngDecPos = InStr(1, strCurrency, ".")
' if there is a decimal point
3 If lngDecPos > 0 Then
' take everything before decimal as dollars
4 mnyDollars = CCur(Mid(strCurrency, 1, lngDecPos - 1))
' get amount after decimal point and multiply by 100 so cents is before decimal point
5 decRight = CDec(CDec(Mid(strCurrency, lngDecPos)) / 0.01)
' get cents by getting integer portion
6 decCents = Int(decRight)
' get leftover
7 decRight = CDec(decRight - decCents)
' if leftover is equal to or above round threshold
8 If decRight >= 0.5 Then
9 RoundPenny = mnyDollars + ((decCents + 1) * 0.01)
' if leftover is less than round threshold
10 Else
11 RoundPenny = mnyDollars + (decCents * 0.01)
12 End If
' if there is no decimal point
13 Else
' return it
14 RoundPenny = CCur(strCurrency)
15 End If
16 Exit Function
RoundPenny_Error:
17 Select Case Err.Number
Case 6
18 Err.Raise vbObjectError + 334, c_strComponent & ".RoundPenny", "Number '" & strCurrency & "' is too big to represent as a currency value."
19 Case Else
20 DisplayError c_strComponent, "RoundPenny"
21 End Select
End Function
' -----------------------------------------------------------------------------
Вот простой способ всегда округлить до следующего целого числа в Access 2003:
BillWt = IIf([Weight]-Int([Weight])=0,[Weight],Int([Weight])+1)
Например:
К сожалению, собственные функции VBA, которые могут выполнять округление, либо отсутствуют, либо ограничены, либо неточны, либо ошибочны, и каждый из них обращается только к одному методу округления. Поверхность заключается в том, что они быстрые, и это может в некоторых ситуациях быть важным.
Однако часто точность является обязательной, и со скоростью компьютеров сегодня небольшая медленная обработка вряд ли будет замечена, а не для обработки одиночных значений. Все функции на приведенных ниже ссылках работают примерно в 1 мкс.
Полный набор функций - для всех методов общего округления можно найти все типы данных VBA для любого значения и не возвращать неожиданные значения:
Округление значений вверх, вниз, на 4/5 или на значительные цифры (EE)
или здесь:
Округление значений вверх, вниз, на 4/5 или на значительные цифры (CodePlex)
Код только в GitHub:
Они покрывают обычные методы округления:
Раунд вниз, с возможностью округления отрицательных значений к нулю
Раунд вверх, с возможностью округления отрицательных значений от нуля
Round by 4/5, либо от нуля, либо до четного (округление Banker)
Раунд к количеству значимых цифр
Первые три функции принимают все числовые типы данных, а последние существуют в трех вариантах - для Currency, Decimal и Double соответственно.
Все они принимают заданное количество десятичных знаков - включая отрицательный счетчик, который будет округлен до десятков, сотен и т.д. Те, у кого Variant как возвращаемый тип, вернут Null для непонятного ввода
Также включен тестовый модуль для тестирования и проверки.
Пример здесь - для общего округления 4/5. Пожалуйста, изучите встроенные комментарии для тонких деталей и способ использования CDec для предотвращения ошибок в битах.
' Common constants.
'
Public Const Base10 As Double = 10
' Rounds Value by 4/5 with count of decimals as specified with parameter NumDigitsAfterDecimals.
'
' Rounds to integer if NumDigitsAfterDecimals is zero.
'
' Rounds correctly Value until max/min value limited by a Scaling of 10
' raised to the power of (the number of decimals).
'
' Uses CDec() for correcting bit errors of reals.
'
' Execution time is about 1µs.
'
Public Function RoundMid( _
ByVal Value As Variant, _
Optional ByVal NumDigitsAfterDecimals As Long, _
Optional ByVal MidwayRoundingToEven As Boolean) _
As Variant
Dim Scaling As Variant
Dim Half As Variant
Dim ScaledValue As Variant
Dim ReturnValue As Variant
' Only round if Value is numeric and ReturnValue can be different from zero.
If Not IsNumeric(Value) Then
' Nothing to do.
ReturnValue = Null
ElseIf Value = 0 Then
' Nothing to round.
' Return Value as is.
ReturnValue = Value
Else
Scaling = CDec(Base10 ^ NumDigitsAfterDecimals)
If Scaling = 0 Then
' A very large value for Digits has minimized scaling.
' Return Value as is.
ReturnValue = Value
ElseIf MidwayRoundingToEven Then
' Banker rounding.
If Scaling = 1 Then
ReturnValue = Round(Value)
Else
' First try with conversion to Decimal to avoid bit errors for some reals like 32.675.
' Very large values for NumDigitsAfterDecimals can cause an out-of-range error
' when dividing.
On Error Resume Next
ScaledValue = Round(CDec(Value) * Scaling)
ReturnValue = ScaledValue / Scaling
If Err.Number <> 0 Then
' Decimal overflow.
' Round Value without conversion to Decimal.
ReturnValue = Round(Value * Scaling) / Scaling
End If
End If
Else
' Standard 4/5 rounding.
' Very large values for NumDigitsAfterDecimals can cause an out-of-range error
' when dividing.
On Error Resume Next
Half = CDec(0.5)
If Value > 0 Then
ScaledValue = Int(CDec(Value) * Scaling + Half)
Else
ScaledValue = -Int(-CDec(Value) * Scaling + Half)
End If
ReturnValue = ScaledValue / Scaling
If Err.Number <> 0 Then
' Decimal overflow.
' Round Value without conversion to Decimal.
Half = CDbl(0.5)
If Value > 0 Then
ScaledValue = Int(Value * Scaling + Half)
Else
ScaledValue = -Int(-Value * Scaling + Half)
End If
ReturnValue = ScaledValue / Scaling
End If
End If
If Err.Number <> 0 Then
' Rounding failed because values are near one of the boundaries of type Double.
' Return value as is.
ReturnValue = Value
End If
End If
RoundMid = ReturnValue
End Function
VBA.Round(1.23342, 2) // will return 1.23
Чтобы решить проблему расщепления пенни, не добавляя к сумме, из которой они были первоначально разделены, я создал определенную пользователем функцию.
Function PennySplitR(amount As Double, Optional splitRange As Variant, Optional index As Integer = 0, Optional n As Integer = 0, Optional flip As Boolean = False) As Double
' This Excel function takes either a range or an index to calculate how to "evenly" split up dollar amounts
' when each split amount must be in pennies. The amounts might vary by a penny but the total of all the
' splits will add up to the input amount.
' Splits a dollar amount up either over a range or by index
' Example for passing a range: set range $I$18:$K$21 to =PennySplitR($E$15,$I$18:$K$21) where $E$15 is the amount and $I$18:$K$21 is the range
' it is intended that the element calling this function will be in the range
' or to use an index and total items instead of a range: =PennySplitR($E$15,,index,N)
' The flip argument is to swap rows and columns in calculating the index for the element in the range.
' Thanks to: http://stackoverflow.com/questions/5559279/excel-cell-from-which-a-function-is-called for the application.caller.row hint.
Dim evenSplit As Double, spCols As Integer, spRows As Integer
If (index = 0 Or n = 0) Then
spRows = splitRange.Rows.count
spCols = splitRange.Columns.count
n = spCols * spRows
If (flip = False) Then
index = (Application.Caller.Row - splitRange.Cells.Row) * spCols + Application.Caller.Column - splitRange.Cells.Column + 1
Else
index = (Application.Caller.Column - splitRange.Cells.Column) * spRows + Application.Caller.Row - splitRange.Cells.Row + 1
End If
End If
If (n < 1) Then
PennySplitR = 0
Return
Else
evenSplit = amount / n
If (index = 1) Then
PennySplitR = Round(evenSplit, 2)
Else
PennySplitR = Round(evenSplit * index, 2) - Round(evenSplit * (index - 1), 2)
End If
End If
End Function
Я использовал следующую простую функцию для округления моих валют, так как в нашей компании мы всегда округлены.
Function RoundUp(Number As Variant)
RoundUp = Int(-100 * Number) / -100
If Round(Number, 2) = Number Then RoundUp = Number
End Function
но это ВСЕГДА округляется до 2 десятичных знаков и может также быть ошибкой.
даже если он отрицательный, он будет округлен (-1.011 будет -1.01 и 1.011 будет 1.02)
чтобы предоставить дополнительные опции для округления (или вниз для отрицательного), вы могли использовать эту функцию:
Function RoundUp(Number As Variant, Optional RoundDownIfNegative As Boolean = False)
On Error GoTo err
If Number = 0 Then
err:
RoundUp = 0
ElseIf RoundDownIfNegative And Number < 0 Then
RoundUp = -1 * Int(-100 * (-1 * Number)) / -100
Else
RoundUp = Int(-100 * Number) / -100
End If
If Round(Number, 2) = Number Then RoundUp = Number
End Function
(используется в модуле, если это не очевидно)