【问题标题】:Function Returns #Value Error sometimes函数有时会返回 #Value 错误
【发布时间】:2016-06-10 12:08:54
【问题描述】:

此方法基于以下 3 步算法:

1 - 在 [-1,1] 区间上生成两个统一的数字,您将称之为 U1 和 U2

2 - 计算 S = U1 ^2 + U2^2

3 - 如果 S

用 VB 编写这个函数并将其命名为 BoxMuller。

这是我根据上述步骤写的函数我不确定它是否正确,因为有时它会返回#Value错误

我将以下值传递给函数=BoxMuller(Rand(),Rand())

Function BoxMuller(U1 As Double, U2 As Double) As Double
Dim S As Double

Do
    U1 = WorksheetFunction.NormInv(U1, 0, 1)
    U2 = WorksheetFunction.NormInv(U2, 0, 1)
    S = U1 * U1 + U2 * U2
    
    If S < 1 Then
        BoxMuller = U1 * Sqr(-2 * Log(S) / S)
        Exit Function
    End If

Loop Until S < 1
End Function

Loop Until S &lt; 1 条件正确,因为我认为这可能是错误的真正原因。

还尝试了以下方法:

Function BoxMuller() As Double
Dim S As Double
Dim U1 As Double
Dim U2 As Double
Do

U1 = WorksheetFunction.RandBetween(-1, 1)
U2 = WorksheetFunction.RandBetween(-1, 1)

    S = U1 * U1 + U2 * U2
    
    If S < 1 Then
        BoxMuller = U1 * Sqr(-2 * Log(S) / S)
        Exit Function
    End If

Loop
End Function

并调用=BoxMuller() 仍然#Value 错误

【问题讨论】:

  • 您是否曾经将负值传递给 Log(S)?
  • 我认为 S 不会是负数,因为 S 是 U1 和 U2 的平方和,所以平方总是返回正数
  • 对对对。尝试一段时间而不是像你建议的那样循环
  • hmm 也许.... 如果 S 0 那么 ....
  • 我的意思是......你也不能通过 log(0) 如果 Rand 给你 .0001 * .0001 你开始得到比 double 可以容纳的更大的数字大小?我会编写一个调试 if 语句来查看 s 是否小于 .0001

标签: vba function excel excel-formula


【解决方案1】:

KS Sheon 的工作流程是正确的

但是

  • WorksheetFunction.RandBetween(-1, 1) 返回一个介于 -1 和 1 之间的整数

    而 VBA Rnd() 函数返回一个介于 0 和 1 之间的随机双精度数

  • VBA Log() 函数实际上返回自然对数

我发布了两个解决方案(BoxMuller1 和 BoxMuller2),与上面的一样,它们只是编码风格不同,并且都使用递归调用

Function BoxMuller1(mu As Double, sigma As Double) As Double
    Application.Volatile
    Dim U1 As Double, U2 As Double, S As Double

    Do While GetS(Rnd, Rnd, U1, U2, S) >=1
        Randomize
    Loop
    BoxMuller1 = U1 * Sqr(-2 * Log(S) / S) * sigma + mu

End Function

Function GetS(Rnd1 As Double, Rnd2 As Double, U1 As Double, U2 As Double, S As Double) As Double
    U1 = 2*Rnd1 - 1
    U2 = 2*Rnd2 - 1
    S = U1 * U1 + U2 * U2
    GetS = S
End Function




Function BoxMuller2(mu As Double, sigma As Double) As Double
    Application.Volatile
    Dim U1 As Double, U2 As Double, S As Double

    Randomize
    U1 = 2*Rnd -1
    U2 = 2*Rnd -1
    S = U1 * U1 + U2 * U2

    If S >= 1 Then
        BoxMuller2 = BoxMuller2(mu, sigma)
    Else
        BoxMuller2 = U1 * Sqr(-2 * Log(S) / S) * sigma + mu
    End If

End Function

【讨论】:

  • 哈哈是的,你说得对,我没想到。顺便说一句,U1 应该被允许为负数 i。 e. (-1,1)。 Rnd 仅在 (0,1) 范围内。
  • 是的,递归函数更优雅。但只是不想让他感到困惑。
  • 更正 U1 和 U2 设置:添加“-1”以返回 -1 和 1 之间的双精度数
【解决方案2】:

我对最终输出做了一些调整,输出不是标准分布而是样本分布,所以乘sigma然后加上mu。否则该函数不需要任何输入。

Rnd 是原生 VBA 生成随机数,总是在 (0, 1) 范围内。

您可以使用GoTo 而不是do...loop,这样您就不必调用exit function 来结束循环。

application.volatile 将确保函数在您每次按下 F9 时重新计算。如果你不这样做,请删除它。

Function BoxMuller(mu As Double, sigma As Double) As Double
    Application.Volatile
    Dim U1 As Double, U2 As Double, S As Double

ReCalc:

    Randomize
    'U1 = Rnd 'this is not correct for the function, leaving it here for reference.
    'U2 = Rnd
    'U1 = WorksheetFunction.RandBetween(-1, 1) 'this is wrong too, RandBetween only returns interger
    'U2 = WorksheetFunction.RandBetween(-1, 1)
    U1 = Rnd * 2 - 1
    U2 = Rnd 'the BoxMuller formula don't require U2 to be negative.
    S = U1 * U1 + U2 * U2

    If S < 1 Then
        BoxMuller = U1 * Sqr(-2 * (Log(S) / S) * sigma + mu
    Else
        GoTo ReCalc
    End If

End Function

【讨论】:

  • 啊,我看到你的第二次尝试了,也许U1 = WorksheetFunction.RandBetween(-1, 1) 更合适
  • 感谢您的帮助,但它仍然返回 #Value 错误。
猜你喜欢
  • 1970-01-01
  • 2018-10-08
  • 1970-01-01
  • 1970-01-01
  • 2018-11-27
  • 1970-01-01
  • 1970-01-01
  • 2015-08-17
  • 1970-01-01
相关资源
最近更新 更多