我有一个问题,真的让我感到困惑。最后,我认为问题很可能归结为我对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)。
答案 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代码,并在得知我没有正确执行它们之后更改了变量声明。然而,这些变化应该从上面非常明显,所以我不会在这里展示它们。