我有一个问题,真的让我难过。最终,我认为这个问题很可能是因为我缺乏关于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)。
我发表了一些评论作为答案。虽然他们可能没什么帮助 - 对不起 – HarveyFrench
可能是一个四舍五入的问题。一般来说,总是使用一个epsilon来比较浮点值,例如,如果(f1 + f2 == f3)'变成'if(abs(f1 + f2-f3)
谢谢。哈维,我已经评论了你的答案和保罗,谢谢你的评论。它确实具有很大的意义,我会研究它! – mdv