простое число в VBA Excel 2003 - PullRequest
1 голос
/ 19 ноября 2009

Я анализирую код с веб-сайта и попробовал его на своей стороне, но, похоже, он не работает. Не могли бы вы сказать мне, почему? Буду очень признателен за вашу помощь.

Спасибо

Private Sub CommandButton1_Click()
    Dim N, D As Single
    Dim tag As String

    N = Cells(2, 2)
    Select Case N
        Case Is < 2
            MsgBox "It is not a prime number"
        Case Is = 2
            MsgBox "It is a prime number"
        Case Is > 2
            D = 2
            Do
                If N / D = Int(N / D) Then
                    MsgBox "It is not a prime number"
                    tag = "Not Prime"
                    Exit Do
                End If
                D = D + 1
            Loop While D <= N - 1
            If tag <> "Not Prime" Then
                MsgBox "It is a prime number"
            End If
    End Select
End Sub

Ответы [ 3 ]

7 голосов
/ 19 ноября 2009

Самая большая проблема, которую я вижу, это использование Single вместо Integer или Long. Простые числа являются положительными целыми числами и не рассматриваются в контексте десятичных значений (насколько я знаю). Таким образом, используя синглы и сравнивая их с разделенными целочисленными значениями, вы открываете себя для неприятных ошибок разброса по краям.

Строка If N / D = Int(N / D) Then использует плохой метод, чтобы увидеть, являются ли числа простыми. предполагает , что каждый раз, когда вы делите число с плавающей запятой (в данном случае, одиночное) на делитель, если у него есть десятичный остаток, то целочисленное преобразование этого остатка не будет равным. Однако я иногда сталкиваюсь с ошибками округления с числами с плавающей запятой при попытке сравнить ответы, и в целом я научился избегать использования преобразований с плавающей запятой в int для сравнения чисел.

Вот код, который вы можете попробовать вместо этого. Некоторые вещи на заметку:

  • Я изменил типы N и D, чтобы они были длинными, а не одиночными. Это означает, что они не являются числами с плавающей запятой и подвержены возможным ошибкам округления.
  • Я также явно преобразовал значение ячейки в long. Таким образом, вы узнаете в своем коде, что не работаете с типом с плавающей точкой.
  • Для сравнения я использовал Mod, который возвращает остаток от N, деленный на D. Если остаток равен 0, он возвращает true, и мы знаем, что у нас нет простого числа. (Примечание: Remainder часто используется с \, который возвращает только целочисленное значение результата деления. Mod и \ обычно используются при точном делении целочисленных типов, что в этом случае очень подходит
  • Наконец, я изменил ваше сообщение, чтобы показать фактическое сравниваемое число. Поскольку число в ячейке преобразуется, если пользователь вводит значение с плавающей запятой, ему будет полезно посмотреть, во что оно было преобразовано.

Вы, вероятно, также заметите, что этот код работает на намного быстрее, чем ваш код, когда вы добираетесь до больших чисел в сотни миллионов. «

Sub GetPrime()
Dim N As Long
Dim D As Long
Dim tag As String

N = CLng(Cells(2, 2))

Select Case N
    Case Is < 2
        MsgBox N & " is not a prime number"
    Case Is = 2
        MsgBox N & " is a prime number"
    Case Is > 2
        D = 2
        Do
            If N Mod D = 0 Then
                MsgBox N & " is not a prime number"
                tag = "Not Prime"
                Exit Do
            End If
            D = D + 1
        Loop While D <= N - 1
        If tag <> "Not Prime" Then
            MsgBox N & " is a prime number"
        End If
End Select
End Sub

ПРИМЕЧАНИЕ. Я изменил название процедуры на GetPrime. В вашем коде у вас было:

Private Sub CommandButton1_Click()

В строке выше вы определяете процедуру (также называемую метод или иногда просто называемую sub ). Слово Sub указывает, что вы определяете процедуру в коде, который не возвращает значения. (Иногда вы можете увидеть слово Function вместо Sub. Это означает, что процедура возвращает значение, например Private Function ReturnANumber() As Long.) Процедура (Sub) - это фрагмент кода, который будет выполняться при вызове. Также стоит отметить, что макрос Excel хранится в VBA как Sub процедура.

В вашей строке кода CommandButton1_Click() - это имя процедуры. Скорее всего, это было создано автоматически путем добавления кнопки в таблицу Excel. Если кнопка привязана к электронной таблице Excel, CommandButton1_Click() будет выполняться при каждом нажатии кнопки.

В вашем коде Private обозначает область действия процедуры. Private обычно означает, что процедура не может быть вызвана вне модуля или класса, в котором она находится. В моем коде я пропустил Private, потому что вы можете вызвать GetPrime из другого модуля кода.

Вы упомянули в своих комментариях, что вам пришлось изменить название моей процедуры с GetPrime() на CommandButton1_Click(). Это, безусловно, работает. Тем не менее, вы также можете просто позвонить GetPrime из в CommandButton1_Click(), как показано ниже:

Private Sub CommandButton1_Click()
    'The following line of code will execute GetPrime()  '
    'Since GetPrime does not have parameters and does not return a value, '
    'all you need to do is put the name of the procedure without the ()   '
    GetPrime
End Sub

'Below is the entire code for the Sub GetPrime()    '
Sub GetPrime()
    'The body of the code goes below: '
    ' ... '

End Sub

Надеюсь, это помогло немного объяснить VBA для вашего понимания!

2 голосов
/ 25 ноября 2009

Вы можете оптимизировать его (и, на мой взгляд, сделать его более читабельным), внеся следующие изменения. Первое исполнение:

  • Используйте длинные, а не поплавки. Это приведет к огромному увеличению скорости.
  • Вам не нужно проверять до n-1, только квадратный корень из n. Это связано с тем, что если существует коэффициент d больше sqrt(n), то его аналог n/d уже был бы найден в sqrt(n). Для этого мы используем специальную переменную, чтобы не допустить переполнения путем вычисления делителя 2 . Это также ускоряет его, вычисляя это один раз, а не вычисляя квадрат каждый раз через цикл (хотя получение квадратного корня, несомненно, медленнее, чем возведение в квадрат, это происходит только один раз).
  • Сначала сделайте специальную проверку для кратных двум, затем вам нужно только проверить, что ваш номер кратен нечетному числу, эффективно удваивая скорость (не проверяя, если вы кратны двум).
  • Используйте оператор по модулю, а не деление / умножение.

Теперь читаемость:

  • Использовать описательные имена переменных.
  • Используйте логическое значение для логических значений (не строка как tag).
  • Переместите логику окна сообщений вниз на основе логического значения isPrime, а не разбрасывайте сообщения по вашему коду.

Со всеми этими изменениями следующий код может обнаружить 9-значное простое число (795 028 841) намного быстрее, чем за секунду. Фактически, мы можем обнаружить самое большое 31-разрядное простое число (2 147 483 647) одновременно.

Исходя из результатов тестов (с циклом в 10 000 итераций for вокруг select), на моем устройстве требуется 35 секунд, чтобы обнаружить 31-разрядное простое число. Это примерно 285 раз в секунду - надеюсь, это будет достаточно быстро для вас: -)

Option Explicit

Public Sub Go()
    Dim number As Long
    Dim divisor As Long
    Dim maxdivisor As Long
    Dim isPrime As Boolean

    number = CLng(Cells(2, 2))
    Select Case number
        Case Is < 2
            isPrime = False
        Case Is = 2
            isPrime = True
        Case Is > 2
            isPrime = True
            If number mod 2 = 0 Then
                isPrime = False
            Else
                maxdivisor = CLng(Sqr(number)) + 1
                divisor = 3
                Do
                    If number mod divisor = 0 Then
                        isPrime = False
                        Exit Do
                    End If
                    divisor = divisor + 2
                Loop While divisor <= maxdivisor
            End If
    End Select
    If isPrime Then
        MsgBox "Number (" & number & ") is prime"
    Else
        MsgBox "Number (" & number & ") is not prime"
    End If
End Sub
2 голосов
/ 20 ноября 2009

Я не уверен, откуда вы скопировали этот код, но он ужасно неэффективен. Если я могу:

  1. Dim N, D As Long приведет к тому, что D будет длинным, а N - вариантом. Как вы, возможно, знаете, варианты являются одним из самых медленных типов данных. Эта строка должна быть: Dim N As Long, D As Long
  2. Вам нужно проверить только каждое другое число, так как четное число всегда делится на два. (Следовательно, не может быть простым).
  3. Вам не нужно тестировать вплоть до N. Вам нужно только протестировать до квадратного корня из N . Это потому, что после квадратного корня факторы просто меняются сторонами, поэтому вы просто повторно тестируете значения.
  4. Для циклов оценивают For-Line только один раз в течение срока службы цикла, но циклы Do и While оценивают свои условия на каждый цикл , поэтому N-1 оценивается много, много раз. Сохраните это значение в переменной, если вы хотите использовать Do Loop.

Хорошо, теперь, когда мы отказались от бла, бла, бла, вот код. Я структурировал его, чтобы вы могли использовать его как UDF из Excel (например: = ISPRIME (A2)):

Option Explicit

Sub GetPrime()
    Dim varValue As Variant
    varValue = Excel.ActiveSheet.Cells(2&, 2&).Value
    If IsNumeric(varValue) Then
        If CLng(varValue) = varValue Then
            If IsPrime(varValue) Then
                MsgBox varValue & " is prime", vbInformation, "Prime Test"
            Else
                MsgBox varValue & " is not prime", vbExclamation, "Prime Test"
            End If
            Exit Sub
        End If
    End If
    MsgBox "This operation may only be performed on an integer value.", vbCritical, "Tip"
End Sub

Public Function IsPrime(ByVal num As Long) As Boolean
    Dim lngNumDiv As Long
    Dim lngNumSqr As Long
    Dim blnRtnVal As Boolean
    ''//If structure is to optimize logical evaluation as AND/OR operators do not
    ''//use short-circuit evaluation in VB.'
    If num = 2& Then
        blnRtnVal = True
    ElseIf num < 2& Then 'Do nothing, false by default.
    ElseIf num Mod 2& = 0& Then 'Do nothing, false by default.
    Else
        lngNumSqr = Sqr(num)
        For lngNumDiv = 3& To lngNumSqr Step 2&
            If num Mod lngNumDiv = 0& Then Exit For
        Next
        blnRtnVal = lngNumDiv > lngNumSqr
    End If
    IsPrime = blnRtnVal
End Function
...