Excel VBA与C dll的小数不兼容 - 适用于整数

时间:2016-02-02 11:32:40

标签: c vba excel-vba excel

我有一个问题,真的让我感到困惑。最后,我认为问题很可能归结为我对Windows的C编程缺乏了解,以及如何在C和VBA之间正常工作。

我一直在涉及VBA和C一段时间,我认为我会将它们组合成一个Excel项目,因为VBA执行速度不是很快。

有时在Excel中工作时,我需要在一长串值中找到一个值。对于一个值,它很容易(Ctrl + F),但有时我想要的值只能通过组合列表中的两个或多个值来找到。

由于这个原因,我编写了一个宏,将值读入数组,然后循环遍历数组,尝试将每个值与另一个值结合使用,看看它们是否合并成为我寻求的值。我现在已经将循环部分移动到用C编写的dll中并且当然加快了速度,但是存在一个问题:大部分时间 - 但并非总是如此 - 它无法找到实际上合并到我的值中的值寻找我寻求的值是十进制值。

为了尝试找出问题所在,我已经将所有经过测试的组合及其结果打印到一个文本文件中,我可以看到有匹配,但由于某种原因,我的if语句没有触发它。

可能是什么问题?

这是我的VBA代码:

Private Declare Function FindVal Lib "mdvlib.dll" (ByRef dIn As Double, ByRef dOut As Double, ByVal iSizeIn As Long, ByVal sVal As Double, ByVal lvl As Long) As Long

Sub Match_Amounts(needle As Double, startcell As Range, level As Integer)

Dim haystack() As Variant
Dim i, j As Integer
Dim num As String
' dim variables going to the dll
Dim valArr() As Double
Dim valArr2() As Double
Dim arrSz, retval As Long

' read values from sheet into the array and find out its size
haystack() = Range(startcell, startcell.End(xlDown))
arrSz = UBound(haystack, 1)

're-dimension arrays that will be passed to the dll
ReDim valArr(1 To arrSz)
‘ using 100 here just to be on the safe side, will optimize later…
    ReDim valArr2(1 To arrSz * 100)
    ' assign values
    For i = 1 To arrSz
        valArr(i) = haystack(i, 1)
    Next
    ' change directory so that the macro finds the dll
    ChDir Application.UserLibraryPath
    ' use the FindVal function in the dll
    retval = FindVal(valArr(1), valArr2(1), arrSz, needle, level)

' present results
If retval > 0 Then
    j = PresRes(valArr2, level, retval)
Else
    num = Format(needle, "#,##0.00")
    'Then show a message to the user
    MsgBox "The value " & num & " could not be obtained by combining " & level & " values in the given range." _
    & vbCr & vbLf & vbCr & vbLf _
    & "This sometimes happens when searching for numbers with decimals. If this was the case, there could be values " _
    & "that combine to make up the sought number.", vbInformation, "Match Amounts"
End If

Erase haystack
Erase valArr
Erase valArr2

End Sub

PresRes子只是向用户展示结果的一种方式,不应该是相关的。但是,如果您想看到它,请告诉我。

与VBA交互的函数的dll中的我的C代码是:

int __stdcall FindVal(double* dIn, double* dOut, int iSizeIn, double sVal, int lvl)
{
    if(lvl == 2) return FindValTwo(dIn, dOut, iSizeIn, sVal);
    if(lvl == 3) return FindValThree(dIn, dOut, iSizeIn, sVal);
    if(lvl == 4) return FindValFour(dIn, dOut, iSizeIn, sVal);

    return -1;
}

从上面可以看出,我已经为三种不同的场景编写了C函数,用于查找两个,三个或四个加数,但是在这里我将仅显示用于查找两个值的代码,因为该代码更紧凑且不太复杂。其他人,我在所有这些功能中遇到了问题。

以下是FindValTwo函数的代码:

int FindValTwo(double* dIn, double* dOut, int iSizeIn, double sVal)
{
    int i, j, k = 0;
    FILE *dumpfile = NULL;

    dumpfile = fopen("arraydump.txt", "a");

    for(i = 0; i < iSizeIn; i++){
        for(j = 0; j < iSizeIn; j++){
            fprintf(dumpfile, "%f + %f = %f (%f) [%d][%d]\n", dIn[i], dIn[j], dIn[i] + dIn[j], sVal, i, j);
            if(dIn[i] + dIn[j] == sVal && i != j){
                fprintf(dumpfile, "\t^ found and added!\n");
                if(ExistAlreadyTwo(dIn[i], dIn[j], dOut, k / 2) == 0){
                    dOut[k + 0] = dIn[i];
                    dOut[k + 1] = dIn[j];
                    k += 2;
                }
            }
        }
    }
    fclose(dumpfile);
    return k;
}

上面提到文件写入的行是用于调试目的,否则不包括在内。 ExistAlreadyTwo函数的代码是:

int ExistAlreadyTwo(double needle1, double needle2, double* haystack, int l)
{
    // checks if the found values already exist in the return array
    int i, existalready = 0;

    for(i = 0; i < l; i++){
        if((needle1 == haystack[i * 2] && needle2 == haystack[i * 2 + 1]) || (needle1 == haystack[i * 2 + 1] && needle2 == haystack[i * 2])){

            existalready = 1;
            break;
        }
    }
    return existalready;
}

为了测试,我在Excel中创建了一个简单的数组:

2.1
4.2
6.3
8.4
10.5
12.6

如果我搜索21,我会得到一个报告,它是8.4和12.6合并为21.文本文件也验证了这一点:

 8.400000 + 12.600000 = 21.000000 (21.000000) [3][5]
        ^ found and added!

再向下一点:

12.600000 + 8.400000 = 21.000000 (21.000000) [5][3]
    ^ found and added!

然而,当搜索小数值时,例如18.9,我没有得到任何命中,即使文件表明值确实存在并且确实组合成所寻求的值。文本文件的输出:

8.400000 + 10.500000 = 18.900000 (18.900000) [3][4]

并且

10.500000 + 8.400000 = 18.900000 (18.900000) [4][3]

因为如果它们组合成一个整数,就会拾取两个十进制值,起初我并不认为问题在于将数组传输到dll而是传输我正在寻找的值。

但是,我尝试在FindValTwo函数中使用以下行对C中的搜索值进行硬编码:

sVal = 18.9;

......但这也没有帮助,但没有找到。该文本文件看起来与上面完全相同。

我已经尝试了ByVal和ByRef(但只有ByRef用于数组),但我得到了相同的结果。我使用的是32位Excel 2010(版本14.0.7163.5000)。

2 个答案:

答案 0 :(得分:0)

我已经阅读了代码

在VBA中

 Dim i, j As Integer

将导致我成为变种

类似地: Dim arrSz,retval As Long

另外,从你的代码,

ReDim valArr2(1 To arrSz * 100)
' assign values
For i = 1 To arrSz
    valArr(i) = haystack(i, 1)
Next

我怀疑您可能不知道范围可以直接复制到Excel中的数组 See here FYI:

我很惊讶您必须使用C来提高速度,我的直觉告诉我,如果编码良好,这应该在VBA中运行得非常快。

出于性能原因,我还假设您决定不在工作簿中使用多列VLOOKUPS

我希望这可能有所帮助,抱歉我没有找到错误,这听起来像数据类型问题,所以

哈维

答案 1 :(得分:0)

所以,我会让每个人都知道我做了什么让它发挥作用。

正如Paul Ogilvie所指出的那样,问题在于浮点数的比较,所以问题一直存在于我的C代码中。

我确实计算了epsilon,但出于某种原因,我无法在这种情况下完成这项工作。我会在以后再追求这个,但是现在我刚刚接受了0.000000001的可接受误差。所以,根据我在问题中提供的代码,我更改了FindValTwo函数。新功能现在看起来像这样:

int FindValTwo(double* dIn, double* dOut, int iSizeIn, double sVal)
{
    int i, j, k = 0;
    double aErr = 0.000000001; // acceptable error (for still calling it a match)

    for(i = 0; i < iSizeIn; i++){
        for(j = 0; j < iSizeIn; j++){
            if(fabs(dIn[i] + dIn[j] - sVal) < aErr && i != j){
                if(ExistAlreadyTwo(dIn[i], dIn[j], dOut, k / 2) == 0){
                    dOut[k + 0] = dIn[i];
                    dOut[k + 1] = dIn[j];
                    k += 2;
                }
            }
        }
    }
    return k;
}

我还查看了所有的VBA代码,并在得知我没有正确执行它们之后更改了变量声明。然而,这些变化应该从上面非常明显,所以我不会在这里展示它们。