2016-02-02 34 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代碼消失了,當得知我沒有正確做他們改變後的變量聲明。然而這些變化應該從上面顯而易見,所以我不會在這裏展示它們。

相關問題