VBA Excel 2003 中的素数

发布于 2024-08-12 00:12:13 字数 782 浏览 1 评论 0原文

我正在分析网站上的代码,我也在我这边尝试过,但似乎不起作用。你能告诉我为什么吗?将非常感谢您的帮助。

谢谢

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

I'm analyzing a code from the website and I tried it on my side as well but seems it doesn't work. Could you please tell me why? would greatly appreciate your help.

Thanks

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浮生未歇 2024-08-19 00:12:13

我发现的最大问题是使用 Single 而不是 IntegerLong。素数是正整数,不考虑十进制值(据我所知)。因此,通过使用单精度并将它们与除法整数进行比较,您将面临令人讨厌的边缘情况路由错误。

If N / D = Int(N / D) Then 行使用了一种糟糕的方法来查看数字是否为素数。 假设每次将浮点数(在本例中为单数)除以除数时,如果它有小数余数,则该余数的整数转换将不相等。然而,在尝试比较答案时,有时我会遇到浮点数的舍入错误,一般来说,我学会了避免使用浮点到 int 转换作为比较数字的方式。

您可以尝试以下一些代码。需要注意的一些事项:

  • 我更改了 N 和 D 的类型,以便它们是 Longs 而不是 Singles。这意味着它们不是浮点并且可能存在舍入误差。
  • 我还明确地将单元格值转换为长整型。这样您就知道您的代码中没有使用浮点类型。
  • 为了进行比较,我使用了 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 宏作为 Sub 过程存储在 VBA 中。

在您的代码行中,CommandButton1_Click() 是该过程的名称。最有可能的是,这是通过向 Excel 电子表格添加按钮自动创建的。如果该按钮与 Excel 电子表格绑定,则每次按下该按钮时都会执行 CommandButton1_Click()

在您的代码中,Private 表示过程的范围Private 通常意味着该过程不能在其所在的模块或类之外调用。在我的代码中,我省略了 Private,因为您可能想从不同的代码模块调用 GetPrime

您在评论中提到必须将我的过程名称从 GetPrime() 更改为 CommandButton1_Click()。这当然有效。但是,您也可以简单地从 CommandButton1_Click() 内调用 GetPrime,如下所示:

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 的内容加深你的理解!

The single biggest problem I see is using Single instead of Integer or Long. Primes are positive integers and are not thought of in the context of decimal values (as far as I know). Thus by using a singles and comparing them to divided ints, you're opening yourself up to nasty edge-case rouding errors.

The line If N / D = Int(N / D) Then is using a poor method to see whether or not the numbers are prime. It's assuming that every time you divide a floating point number (in this case, the single) by the divisor, if it has a decimal remainder, then the integer conversion of that remainder will not be equal. However, I've run into rounding errors sometimes with floating point numbers when trying to compare answers, and in general, I've learned to avoid using floating point to int conversions as a way of comparing numbers.

Here's some code you might try instead. Some things to note:

  • I've changed the types of N and D so that they are Longs and not Singles. This means they are not floating point and subject to possible rounding errors.
  • I've also explicitly converted the cell value to a long. This way you know in your code that you are not working with a floating point type.
  • For the comparison, I've used Mod, which returns the remainder of the N divided by D. If the remainder is 0, it returns true and we know we don't have a prime. (Note: Remainder is often used with \, which only returns the integer value of the result of the division. Mod and \ are commonly used in precise division of integer types, which in this case is very appropriate.
  • Lastly, I changed your message box to show the actual number being compared. Since the number in the cell is converted, if the user enters a floating point value, it will be good for them to see what it was converted to.

You'll probably also note that this code runs a lot faster than your code when you get to high numbers in the hundreds of millions.
'

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

NOTE: I changed the name of the procedure to be GetPrime. In your code, you had:

Private Sub CommandButton1_Click()

In the line above, you are defining a procedure (also called a method or sometimes just referred to as a sub). The word Sub indicates you are defining a procedure in code that returns no value. (Sometimes you might see the word Function instead of Sub. This means the procedure returns a value, such as Private Function ReturnANumber() As Long.) A procedure (Sub) is a body of code that will execute when called. Also worth noting, an excel macro is stored in VBA as a Sub procedure.

In your line of code, CommandButton1_Click() is the name of the procedure. Most likely, this was created automatically by adding a button to an Excel spreadsheet. If the button is tied to the Excel spreadsheet, CommandButton1_Click() will execute each time the button is pressed.

In your code, Private indicates the scope of the procedure. Private generally means that the procedure cannot be called outside of the module or class in which it resides. In my code, I left out Private because you may want to call GetPrime from a different module of code.

You mentioned in your comments that you had to change the name of my procedure from GetPrime() to CommandButton1_Click(). That certainly works. However, you could also have simply called GetPrime from within CommandButton1_Click(), like below:

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

Hopefully this helped to explain a little bit about VBA to further your understanding!

停滞 2024-08-19 00:12:13

我不确定你从哪里复制这段代码,但它的效率非常低。如果我可以:

  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 循环,请将此值存储在变量中。

好的,现在我们已经省去了废话,废话,废话,这是代码。我对其进行了结构化,以便您也可以将其用作 Excel 中的 UDF(例如:=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

I'm not sure where you copied this code from, but it's terribly inefficient. If I may:

  1. Dim N, D As Long will cause D to be a Long, and N to be a variant. As you may know, variants are one of the slowest data types available. This line should be: Dim N As Long, D As Long
  2. You only need to test every other number as an even number will always be divisible by two. (Therefore can not possibly be prime).
  3. You don't need to test all the way up to N. You only need to test up to the Square Root of N. This is because after the square root the factors just switch sides, so you are just retesting values.
  4. For Loops only evaluate the For-Line once for the life of the loop, but Do and While loops evaluate their conditional on every loop, so N-1 is being evaluated many, many times. Store this value in a variable if you want to use a Do Loop.

Ok, so now that we have dispensed with the blah, blah, blah, here is the code. I structured it so you can use it as a UDF from Excel as well (Ex: =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
不弃不离 2024-08-19 00:12:13

您可以通过进行以下更改来进一步优化它(在我看来,使其更具可读性)。第一个表现:

  • 使用长整型,而不是浮点数。这将带来巨大的速度提升。
  • 您不需要检查 n-1,只需检查 n 的平方根。这是因为,如果存在大于 sqrt(n) 的因子 d,则其对应的 n/d 已经在下找到< /em> sqrt(n).我们为此使用一个特殊变量,这样我们就不会因计算除数2而溢出。它还通过计算一次而不是每次通过循环计算平方来加速它(尽管获得平方根无疑比平方慢,但它只发生一次)。
  • 首先对二的倍数进行特殊检查,然后您只需要检查您的数字是否是奇数的倍数,从而有效地使速度加倍(而不检查您是否是二的倍数的因子)。
  • 使用模运算符而不是除法/乘法。

现在可读性:

  • 使用描述性变量名称。
  • 使用布尔值作为布尔值(而不是像 tag 这样的字符串)。
  • 根据 isPrime 布尔值,将消息框逻辑移至底部,而不是将消息分散在代码中。

经过所有这些更改,以下代码可以在不到一秒的时间内检测到 9 位素数 (795,028,841)。事实上,我们可以同时检测到最大的 31 位素数(2,147,483,647)。

根据基准测试(在 select 周围放置 10,000 次迭代 for 循环),我的机器需要 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

You can optimise it further (and make it more readable, in my opinion) by making the following changes. First performance:

  • Use longs, not floats. This will result in a huge speed increase.
  • You don't need to check up to n-1, only the square root of n. That's because if a factor d greater than sqrt(n) exists, its counterpart n/d would have already been found under sqrt(n). We use a special variable for this so that we don't get overflow by calculating divisor2. It also speeds it up by calculating that once rather than calculating the square every time through the loop (even though getting the square root is undoubtedly slower than squaring, it only happens once).
  • Do a special check first for multiples of two then you need only check that your number is a multiple of an odd number, effectively doubling the speed (not checking if you're a factor of a multiple of two).
  • Use the modulo operator rather than division/multiplication.

Now readability:

  • Use descriptive variable names.
  • Use a boolean for boolean values (not a string like tag).
  • Move the message box logic down to the bottom, based on the isPrime boolean, rather than scattering the messages amongst your code.

With all those changes, the following code can detect a 9-digit prime number (795,028,841) in well under a second. In fact, we can detect the largest 31-bit prime (2,147,483,647) in the same time.

Based on benchmarks (putting a 10,000-iteration for loop around the select), it takes 35 seconds on my box to detect that 31-bit prime. That's about 285 times per second - hopefully that'll be fast enough for you :-)

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文