2016-02-02 102 views
1

我有一个问题,真的让我难过。最终,我认为这个问题很可能是因为我缺乏关于Windows C编程的知识或者如何在C和VBA之间正常工作。Excel VBA不能很好地与小数的C DLL - 与整数工作正常

我一直在涉足VBA和C一段时间,我想我会结合他们的Excel项目,因为VBA执行速度不是很快。

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

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

为了尝试找到问题出在哪里,我让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子仅仅是将结果呈现给用户的方式,不应该是相关的。不过,请让我知道,如果你想看到它。

在DLL用于与VBA交互的功能我的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,而是在转移我正在寻找的价值。

不过,我想硬编码在C中搜索值,在FindValTwo功能,用线:

sVal = 18.9; 

...但是这并没有帮助,也没有被发现。文本文件看起来完全如上。

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

+0

我发表了一些评论作为答案。虽然他们可能没什么帮助 - 对不起 – HarveyFrench

+2

可能是一个四舍五入的问题。一般来说,总是使用一个epsilon来比较浮点值,例如,如果(f1 + f2 == f3)'变成'if(abs(f1 + f2-f3)

+0

谢谢。哈维,我已经评论了你的答案和保罗,谢谢你的评论。它确实具有很大的意义,我会研究它! – mdv

回答

0

我已经通过代码读取

在VBA

Dim i, j As Integer 

将使我成为一个变种

同理: 昏暗arrSz,RETVAL只要

而且, from you code,

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的多个列,因为性能原因

我希望这可以有所帮助,对不起,我没有发现的bug,这听起来像一个数据类型的问题,所以

哈维

+0

感谢哈维。你可能没有找到这个bug,但是你已经给了我一些非常有用的VBA提示。我将haystack数组中的值分配给valArr的原因是 - 据我所知,当将一个范围读入一个数组时它变成了一个变体数组,但我需要将一个双精度数组传递给dll。 – mdv

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代码消失了,当得知我没有正确做他们改变后的变量声明。然而这些变化应该从上面显而易见,所以我不会在这里展示它们。

相关问题