Страницы: 1 2 След.
RSS
Варианты округления в VBA, Поиск самого быстрого
 
Доброго времени суток, Планетяне!

Тестовый стенд делать не буду, но результаты скажу  :)

Итак, что я знаю на данный момент по тестам на 150 тыс элементов массива:
  • --Format$() шустрее WorksheetFunction.Round() ~ 1,5 раза (Format или  Format$ — на скорость не повлияло)
  • Round() шустрее --Format$() ~ 2 раза
  • CCur() где-то на уровне --Format$(), но всегда 4 знака - не подходит
Вопрос: кто какие ещё методы/способы округления знает? Протестирую на своих данных

Скрин (собственно, сам участок кода):
  • 47 тыс строк
  • 3 столбца с округлением до 6ти
  • 3 столбца с округлением до 4ёх
  • 12 столбцов с округлением до 2ух

P.S.: Я в курсе, что Round использует "банковское" округление — ради скорости пренебрёг
Скрин

UPD: Самый быстрый  стабильный вариант найден — от ZVI
Изменено: Jack Famous - 02.03.2021 10:42:43
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
А зачем? Спортивный интерес или практическая задача которая будет кому-то полезной? Скорость - понятие относительное :)  
 
Цитата
Jack Famous написал:
Тестовый стенд делать не буду
А я сделаю (на 1000000 обращений): :)
Код
Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function getFrequency Lib "kernel32" Alias _
        "QueryPerformanceFrequency" (cyFrequency As Currency) As Long
    Private Declare PtrSafe Function getTickCount Lib "kernel32" Alias _
         "QueryPerformanceCounter" (cyTickCount As Currency) As Long
#Else
    Private Declare Function getFrequency Lib "kernel32" Alias _
    "QueryPerformanceFrequency" (cyFrequency As Currency) As Long
    Private Declare Function getTickCount Lib "kernel32" Alias _
        "QueryPerformanceCounter" (cyTickCount As Currency) As Long
#End If

Function MicroTimer() As Double
' Returns seconds.
    Dim cyTicks1 As Currency
    Static cyFrequency As Currency
    '
    MicroTimer = 0
' Get frequency.
    If cyFrequency = 0 Then getFrequency cyFrequency
' Get ticks.
    getTickCount cyTicks1
' Seconds
    If cyFrequency Then MicroTimer = cyTicks1 / cyFrequency
End Function

Sub Test()
  Dim t As Double, nMax As Long, i As Long, d As Double
  nMax = 1000000
 
  t = MicroTimer
  With WorksheetFunction
    For i = 1 To nMax
      d = .Round(1 + i / nMax, 2)
    Next i
  End With
  
  Debug.Print "WorksheetFunction.Round", FormatNumber(MicroTimer - t, 3)
  
  t = MicroTimer
  For i = 1 To nMax
     d = Round(1 + i / nMax + 0.00000001, 2)
  Next i
  Debug.Print "VBA.Round" & Space(10), FormatNumber(MicroTimer - t, 3)
  
  t = MicroTimer
  For i = 1 To nMax
     d = CDbl(Format(1 + i / nMax, "0.00"))
  Next i
  Debug.Print "VBA.Format" & Space(10), FormatNumber(MicroTimer - t, 3)
  
End Sub

У меня разница в скорости в 20 раз.
Так что, если банковское округление не важно, то WorksheetFunction.Round (как и другие обращения к объектной модели Excel из VBA) сразу снимаем с пробега.

Функция (VBA.)Round специально написана для округления, нет оснований сомневаться в ее работоспособности и эффективности.
Изменено: sokol92 - 18.04.2020 17:14:13
Владимир
 
Цитата
_Igor_61: практическая задача которая будет кому-то полезной?
это практическая задача — я же даже код прикрепил (из своего проекта)  ;)
Цитата
sokol92: А я сделаю
вот видите - у вас аж в 20 раз разница  8-0
Тестовый стенд не делал, потому что реальный проект мс данными и тест — это бывает 2 разных результата, но для "спортивного интереса" ничего лучше нет - это правда)))
1. смущает, что у вас WorksheetFunction и в With сидит и просто так
2. вы сравнили 2 крайности, т.к. я в основном --Format$() использую, а он шустрее (у меня разница с функцией листа и форматом — 40 и 20 раз соответственно)
3.
Цитата
Jack Famous:Round использует "банковское округление"
Изменено: Jack Famous - 18.04.2020 16:14:49
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Цитата
Jack Famous написал:
кто какие ещё методы/способы округления знает? Протестирую на своих данных
Как вариант, ещё есть функции Int() и CInt(), которые можно использовать для округления.
 
Если убрать With, то будет чуть-чуть медленнее.
Format делает кучу лишней работы. Если известен диапазон чисел, с которыми Вы работаете, то положительные числа можно округлять в таком стиле:
Код
     d = Round(1 + i / nMax + 0.00000001, 2)
Чаще всего, особой практической разницы между всеми указанными методами нет, так как на миллионе операций речь идет о различиях в доли секунды.
Изменено: sokol92 - 18.04.2020 16:17:58
Владимир
 
Цитата
МатросНаЗебре: Int() и CInt(),
Fix пошустрее вроде, но все - для целочисленных операций, а приведение к целому для их использования "съест" весь возможный выигрыш в скорости (не тестил)
За вариант спасибо - для целых точно быстрее будет  ;)

Цитата
sokol92: практической разницы нет, так как на миллионе операций речь идет о различиях в доли секунды
на моих данных прирост скорости с Format$() на Round сократил время в 2(!) раза — с 4 секунд до 2ух (только цикл со скрина)
Или вы про ваш вариант округления положительных?  :)
Изменено: Jack Famous - 18.04.2020 16:20:09
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Цитата
sokol92 написал:
разница в скорости в 20 раз
Больше :) правда год назад ноут апгрейдил - SSD поставил
 
Round быстрее Format'а с последующим обратным преобразованием в число раз в 10 на современных машинах (раньше разрыв был больше). Значит, в Вашем тесте собственно Format занимал примерно половину от общего времени.
Дополнил тест обращением к Format.
Изменено: sokol92 - 18.04.2020 16:27:38
Владимир
 
Цитата
МатросНаЗебре написал:
Как вариант, ещё есть функции Int()
Цитата
Jack Famous написал:
Fix пошустрее вроде,
Код
Public Function MyRoundFix(X As Double, R As Long) As Double
  Dim D As Double
  Dim i As Long
  D = 1
  For i = 1 To R
    D = D * 10#
  Next i
  MyRoundFix = (Fix(X * D + 0.5)) / D
End Function

Результаты по тесту из #3:
Код
WorksheetFunction.Round      5,765625 
VBA.Round                    0,203125 
MyRoundInt                   0,375 
MyRoundFix                   0,359375 

 
Alec Perle, фигасе - даже на целочисленных Round быстрее - вот это интересно  8-0 (опровергнуто при сравнении "в лоб" далее)
Спасибо!  :idea:

Цитата
sokol92: положительные числа можно округлять в таком стиле:
у вас получается округление Round, но до следующего знака и по-моему это не тоже самое , что банковское округление…ща потестирую

Цитата
sokol92:CDbl(Format
--Format$() стабильно быстрее в сотых долях
Изменено: Jack Famous - 18.04.2020 17:57:16
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Можно еще чуть ускорить (своеобразный "дизельгейт"), если каждый раз не вычислять множитель:
Код
Public Function MyRoundFixStatic(X As Double, R As Long) As Double
  Static OldR As Long
  Static D As Double
  Dim i As Long
  If R <> OldR Then
    D = 1
    For i = 1 To R
      D = D * 10#
    Next i
    OldR = R
  End If
  MyRoundFixStatic = Fix(X * D + 0.5) / D
End Function

Результаты:
Код
WorksheetFunction.Round      5,78125 
VBA.Round                    0,203125 
MyRoundFix                   0,4375 
MyRoundFixStatic             0,296875 
И да, округление математическое, проверял с ОКРУГЛ() сравнением результатов на листе))
Изменено: Alec Perle - 18.04.2020 16:48:42
 
Цитата
Jack Famous: даже на целочисленных Round быстрее - вот это интересно
ничего подобного (Fix в 2,5 раза быстрее) на тесте "в лоб"  - вот и верь тестам  :D
Изменено: Jack Famous - 18.04.2020 16:50:53
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Кстати, давно хотел написать о точности измерений. Сейчас (в момент написания заметки) функция Timer выдает свыше 61000 (сек). Учитывая, что тип результата функции - Single, а это 7 (?) значащих цифр, долям секунды вполне доверять нельзя. Пора менять измерительный инструмент на более точный, рекомендованный разработчиком. Еще раз изменил #3 - теперь точности измерений времени можно доверять.
Изменено: sokol92 - 18.04.2020 17:25:40
Владимир
 
Можно и WorksheetFunction.Round ускорить, правда на доли процента. Нужно второй параметр передавать сразу как Double:
Код
d = .Round(1 + i / nMax, 2#)
 
Цитата
sokol92: Пора менять измерительный инструмент на более точный,  рекомендованный  разработчиком
себе занёс (спасибо за науку), но не знаю, где нужна такая точность - таймера пока вполне хватает  :)

Цитата
Jack Famous: у вас получается округление Round, но до следующего знака и по-моему это не тоже самое , что банковское округление…ща потестирую
на скрине примеры несоответствия обычного округления и "банковского", а вычисление d - вообще ни в какие ворота (ни 0,00000001 ни 0,01  :sceptic:
Тестовый стенд


Цитата
Alec Perle: Можно и WorksheetFunction.Round ускорить, правда на доли процента
по-спортивному, это хорошо, но не на практике  :)
Изменено: Jack Famous - 18.04.2020 18:35:04
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Цитата
_Igor_61 написал:
А зачем? Спортивный интерес или практическая задача
Это пагубно сказываются результаты самоизоляции.
Jack Famous, Алексей прям скучаю, уж сколько времени не встречаемся по утрам  :D
По вопросам из тем форума, личку не читаю.
 
Цитата
Jack Famous написал:
вычисление d - вообще ни в какие ворота
А что с ним не так? Ведь закомментировано же)) А скрин не раскомментируется никак, чтоб самому проверить...
 
Цитата
БМВ: Это пагубно сказываются результаты самоизоляции
повторюсь, что задача именно практическая. Ускоряю свои таблицы. Вот успешно применил Round и в 2 раза сократил время (на бОльших объёмах ещё больше получится)
Сюда написал в надежде, что хоть кто-то поделится своими "фишками" и получится ещё быстрее - помнится ZVI как-то успешно писал математические аналоги
Цитата
БМВ: прям скучаю, уж сколько времени не встречаемся по утрам
и не будем больше (к сожалению), т.к. я переехал в другую часть города - там снимал. Может теперь вы захотите посидеть где-нибудь?  ;)

Цитата
Alec Perle: А что с ним не так? Ведь закомментировано же))
привёл код - пробуйте. Убрал, чтобы не забивать окно Innediate
Изменено: Jack Famous - 18.04.2020 17:59:14
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
del
Изменено: Jack Famous - 18.04.2020 17:59:04
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
У меня в тесте #3 все три прогона выдают одинаковые результаты на каждом шаге цикла.
Изменено: sokol92 - 18.04.2020 18:01:13
Владимир
 
Цитата
sokol92: У меня в тесте #3 все три прогона выдают одинаковые результаты
вы про скорость (вряд ли) или про результаты округлений (где там сравнение)?
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Off
Цитата
Jack Famous написал:
Может теперь вы захотите посидеть где-нибудь?
Уже дома насиделся :-) и где?  :D  Вроде Гренландию не зацепило :-)
По вопросам из тем форума, личку не читаю.
 
Цитата
БМВ написал:
Гренландию не зацепило
Зацепило (куда она денется). Сейчас всё хорошо.
Владимир
 
БМВ, после пандемии вернёмся к обсуждению посиделок  :D
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Цитата
Jack Famous написал:
про результаты округлений (где там сравнение)?
У себя сравнил. В тесте не стал, так как это исказит точность измерений времени.
Добавьте в первый цикл #3 после присвоения d:
Код
      If Abs(d - Round(1 + i / nMax + 0.00000001, 2)) > 0.00000000000001 Then Stop
Изменено: sokol92 - 18.04.2020 18:11:03
Владимир
 
Цитата
sokol92: У себя сравнил
а если в моём (из #16)?
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Добавлять нужно не 0.01, a что-нибудь "маленькое", например 1E-8 (как у меня в #3). И сравниваются дробные числа таким образом:

Код
If Abs(a-d)>1E-14 Then  ' караул!
Изменено: sokol92 - 18.04.2020 18:16:01
Владимир
 
Цитата
sokol92: Добавлять нужно не 0.01, a что-нибудь "маленькое", например 1E-8
я попробовал d = Round(x + 0.00000000001, 2) и у меня не получилось (сравнивал как обычно)
Цитата
sokol92: сравниваются дробные числа таким образом: If Abs(a-d)>1E-14 Then
это с каких пор "хвост" у нас в сравнении не учитывается???
Изменено: Jack Famous - 18.04.2020 18:21:23
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
С момента изобретения формата Double. Читаем:  Floating-Point Expressions Do Not Compare as Equal
Владимир
Страницы: 1 2 След.
Наверх