простое число в vba excel 2003

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

Спасибо

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

person tintincutes    schedule 19.11.2009    source источник
comment
Для каких чисел это не работает? В чем проблема?   -  person Adriaan Stander    schedule 19.11.2009
comment
Кажется, у меня все хорошо. Возможно, вы захотите изменить это Loop While D <= N - 1, возможно, на Loop While D <= N/2 для начала.   -  person Russ Cam    schedule 19.11.2009
comment
Работает и для меня. Единственное, о чем я могу думать, это то, что, возможно, он / она не ставит фактическое число в нужное место на листе XLS (хе-хе).   -  person ajdams    schedule 19.11.2009
comment
@ajadams: да, может быть, но я написал число в Excel B1. например 53 говорит, что это не простое число.   -  person tintincutes    schedule 19.11.2009
comment
@astander: если я наберу число, которое является простым #, оно скажет, что это не простое число, и если это не простое число, оно также скажет то же самое.   -  person tintincutes    schedule 19.11.2009
comment
Вы пытались выполнить код, наблюдая за местными жителями, чтобы увидеть, что происходит?   -  person rjzii    schedule 19.11.2009
comment
@Rob: я еще не знаю, как отлаживать. я был бы признателен за любое руководство, как это сделать. спасибо   -  person tintincutes    schedule 19.11.2009
comment
@tintincute: это может звучать глупо, но убедитесь, что вы вводите значение в правильную ячейку. Несколькими комментариями выше вы сказали, что поместили число в ячейку B1. Однако в вашем коде вы ищете значение ячейки в строке 2, столбце 2, то есть B2. Хотя я все еще думаю, что есть проблема с использованием чисел с плавающей запятой для вычисления простых чисел (см. мой ответ ниже), возможно, ваша проблема заключается в том, чтобы просто поместить свое значение в правильную ячейку.   -  person Ben McCormack    schedule 24.11.2009
comment
@all: извините за это, с вами все в порядке, я поместил его не в ту ячейку. Я помещал его в (1,1) вместо (2,2). так что извини мой плохой. Теперь он работает нормально. Спасибо за помощь:-)   -  person tintincutes    schedule 25.11.2009
comment
Ирония в этом вопросе и ответах на Stack Overflow настолько велика, что вы можете ее прожевать. Есть несколько подробных ответов, которые делают все, от предоставления информации о целочисленном делении в VBA до оптимизации средства поиска простых чисел. Однако, если бы те из нас, кто давал ответы, прочитали комментарий автора от 19 ноября 15:16, мы бы увидели очевидную проблему. Может быть, мы действительно видели очевидную проблему, но все равно решили ответить на вопрос, научить, дать представление, помочь другим писать лучший код. В этом есть что-то очень освежающее.   -  person Ben McCormack    schedule 02.12.2009


Ответы (3)


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

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

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

  • Я изменил типы N и D, чтобы они были длинными, а не одиночными. Это означает, что они не являются числами с плавающей запятой и подвержены возможным ошибкам округления.
  • Я также явно преобразовал значение ячейки в длинное. Таким образом, вы знаете в своем коде, что не работаете с типом с плавающей запятой.
  • Для сравнения я использовал Mod, который возвращает остаток от деления N на D. Если остаток равен 0, он возвращает true, и мы знаем, что у нас нет простого числа. (Примечание: остаток часто используется с \, который возвращает только целочисленное значение результата деления. 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 указывает на то, что вы определяете процедуру в коде, который не возвращает никакого значения. (Иногда вы можете увидеть слово 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 и углубить ваше понимание!

person Ben McCormack    schedule 19.11.2009
comment
@bmccormack: спасибо за объяснение. позвольте мне прочитать это еще раз, так как я сейчас ухожу на тренировку. я проверю это снова, как только вернусь домой - person tintincutes; 19.11.2009
comment
Также стоит отметить, что я протестировал простое число больше 100 000 000, и мой код вернул результат примерно через 15 секунд. Код, который вы предоставили, даже не закончился после нескольких минут работы. - person Ben McCormack; 19.11.2009
comment
@bmccormack: я также попробовал ваш пример, и это не сработает, если я не изменил Sub GetPrime () на Private Sub CommandButton1_Click () я только начинаю изучать VBA, я не понимаю это начало Sub GetPrime. Что такое разница? Если вы делаете свой тест, вы пишете его в редакторе VBA, как тот, который вы опубликовали? спасибо - person tintincutes; 25.11.2009
comment
@tintincute Очень хороший способ заставить ваш код работать, изменив имя процедуры! Я отредактирую нижнюю часть своего поста, чтобы немного объяснить, как работают процедуры. - person Ben McCormack; 25.11.2009

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

  1. Dim N, D As Long приведет к тому, что D будет Long, а N будет вариантом. Как вы, возможно, знаете, варианты являются одним из самых медленных доступных типов данных. Эта строка должна быть: Dim N As Long, D As Long
  2. Вам нужно только проверить каждое другое число, так как четное число всегда будет делиться на два. (Поэтому не может быть простым).
  3. Вам не нужно проверять все до N. Вам нужно только проверить квадратный корень из N. Это связано с тем, что после извлечения квадратного корня факторы просто меняются местами, так что вы просто повторно проверяете значения.
  4. Циклы for оценивают 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
person Oorang    schedule 20.11.2009

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

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

Теперь читабельность:

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

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

Основываясь на контрольных показателях (помещая цикл for из 10 000 итераций вокруг 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
person paxdiablo    schedule 25.11.2009
comment
@paxdiablo: строка, в которой говорится: если число rem divisor = 0, то помечается красным. Не могли бы вы проверить на вашей стороне? У вас тоже такая же проблема? Код не запустится. - person tintincutes; 25.11.2009
comment
Извините, это должно быть «mod», как в «modulo», а не «rem», как в «остатке», или, как правильно видит VBA, «rem», как в «remark» :-) - person paxdiablo; 25.11.2009
comment
нет проблем, я изменил это, но это не работает. Нет окна сообщения дисплея, чтобы сказать, является ли его простым или не простым числом. Ничего не произошло:-( - person tintincutes; 25.11.2009
comment
Здесь отлично работает :-) Я бы посоветовал поставить msgbox в начале, чтобы убедиться, что он вызывается. Кроме того, вы получаете значок песочных часов, когда запускаете его? - person paxdiablo; 25.11.2009
comment
нет, я ничего не получаю. Я просто поставил поле, где я могу щелкнуть его, чтобы показать окно сообщения после, но этого не происходит. хм ... - person tintincutes; 25.11.2009
comment
я вижу проблему сейчас. В вашем коде вы используете: Public Sub Go(), и я изменил его на: Private Sub CommandButton1_Click(), и теперь он работает. Не могли бы вы объяснить, что означает Public Sub Go()? спасибо - person tintincutes; 25.11.2009
comment
Я сделал это просто по привычке, так как я обычно запускаю макросы, назначая их на CTRL-SHIFT-A и тому подобное - по памяти, вы должны сделать его общедоступным, чтобы в диалоговом окне макроса можно было назначить клавишу к нему (а Go — это просто случайное имя). Извини за это. - person paxdiablo; 25.11.2009