473,480 Members | 5,041 Online
Bytes | Software Development & Data Engineering Community
Create Post

Home Posts Topics Members FAQ

Does anyone have a favorite Units Conversion Function written for VBA?

jforbes
1,107 Recognized Expert Top Contributor
I have an application where I want to perform a conversion on a unit of measure during an Access Query. This is an example of some SQL of how I would like to use the function:
Expand|Select|Wrap|Line Numbers
  1. SELECT 
  2.   [ID]
  3. , [ExchangerType]
  4. , [Capacity]
  5. , [CapacityUnitOfMeasure]
  6. , convertToCurrentUnits([Capacity], [CapacityUnitOfMeasure]) AS CapacityInCurrentUnits
  7. FROM tblHeatExchangerSizingData
I've started building a function to perform this conversion, but I thought I would ask around since I haven't seen a lot on this subject either on the Net or on Bytes.

I'm primarily concerned with converting from one engineering unit to another. Units conversions like these, but not limited to these or by my lack of Imagination:
  • Gallons to Liters
  • Liters to Cubic Centimeters
  • Cubic Centimeters to Gallons
  • Kilowatts to Horsepower
  • Horsepower to BTUs
  • BTU to Kilowatts
  • Fahrenheit to Celsius

I will appreciate any input. Thanks -J
Apr 8 '16 #1
13 5555
ADezii
8,834 Recognized Expert Expert
I am not sure that there is a simple answer to your question but here is something that I came up with. I'm not saying that it is the most efficient solution, but it is a solution nonetheless. Here goes:
  1. Create a Table consisting of Unique Unit of Measure Types such as:
    UOM_ID Unit
    1 Yards
    2 Feet
    3 Inches
    4 Fahrenheit
    5 Celsius
  2. Create a Link {1 ==> MANY} between [UOM_ID] in tblUOMTypes and [ExchangerType] and [CapacityUnitOfMeasure] in tblHeatExchangeSizingData. The prior two Fields will store LONG Values indicating specific Unit of Measure Types.
  3. Create a Public Function (ConvertToCurrentUnits) that will accept three arguments: [Capacity], [CapacityUnitOfMeasure], and [ExchangerType].
  4. Create Logic within this Function that will analyze these Arguments and generate the appropriate Results. Again, this resolves the problem using a Bulk Code approach and probably not the most efficient approach. The Code will need to be expanded to allow for future conversions.
  5. Now that I have totally confused you, download the Attachment to get a first hand view on how this can be done.
Attached Files
File Type: zip UOM.zip (27.3 KB, 224 views)
Apr 10 '16 #2
jforbes
1,107 Recognized Expert Top Contributor
Thanks ADezii,

You came up with pretty much an identical approach as the one I was using, which makes me feel really a good about it. I know it's not going to be particularly fast and for this application that is fine.

I'm posting my test version incase you or anyone else would like to see what I came up with. This is just the prototype for what I hope to have, but it's neat seeing the similarities:
Expand|Select|Wrap|Line Numbers
  1.     ' convertToCurrentUnits
  2. Public Function convertToCurrentUnits(ByRef vValue As Variant, ByRef vFrom As Variant) As Variant
  3.  
  4.     Dim vReturn As Variant
  5.     vReturn = vValue
  6.  
  7.     Select Case vFrom
  8.         ' Power
  9.         Case "BTU"
  10.             Select Case gCurrentPowerUnits
  11.                 Case "Kilowatt", "KW"
  12.                     vReturn = vValue * 0.0002928104
  13.                 Case "HP"
  14.                     vReturn = vValue * 0.0003926652
  15.             End Select
  16.         Case "Kilowatt", "KW"
  17.             Select Case gCurrentPowerUnits
  18.                 Case "BTU"
  19.                     vReturn = vValue * 3415.179
  20.                 Case "HP"
  21.                     vReturn = vValue * 1.341022
  22.             End Select
  23.         Case "HP"
  24.             Select Case gCurrentPowerUnits
  25.                 Case "BTU"
  26.                     vReturn = vValue * 2546.699
  27.                 Case "Kilowatt", "KW"
  28.                     vReturn = vValue * 0.7456999
  29.             End Select
  30.  
  31.         ' Temperature
  32.         Case "Fahrenheit", "F"
  33.             Select Case gCurrentTempUnits
  34.                 Case "Celsius", "C"
  35.                     vReturn = (vValue - 32) * (5 / 9)
  36.             End Select
  37.         Case "Celsius", "C"
  38.             Select Case gCurrentTempUnits
  39.                 Case "Fahrenheit", "F"
  40.                     vReturn = (vValue * (9 / 5)) + 32
  41.             End Select
  42.  
  43.         ' Flow/Volume
  44.         Case "Gallon", "Gallons", "gpm", "g"
  45.             Select Case gCurrentFlowUnits
  46.                 Case "Liter", "Liters", "L"
  47.                     vReturn = vValue * 3.785412
  48.                 Case "CC"
  49.                     vReturn = vValue * 3785.412
  50.             End Select
  51.         Case "Liter", "Liters", "L"
  52.             Select Case gCurrentFlowUnits
  53.                 Case "gallon", "gallons", "gpm", "g"
  54.                     vReturn = vValue * 0.2641721
  55.                 Case "CC"
  56.                     vReturn = vValue * 1000
  57.             End Select
  58.         ' Pressure
  59.         ' Time
  60.         ' Area
  61.         ' Torque
  62.         ' Force
  63.         ' Mass
  64.         ' Acceleration
  65.         ' Distance
  66.     End Select
  67.  
  68.     convertToCurrentUnits = vReturn
  69.  
  70. End Function
Apr 11 '16 #3
NeoPa
32,556 Recognized Expert Moderator MVP
How about storing :
  1. The name of the UoM.
  2. The number of units of X^1 this UoM relates to.
  3. Type of UoM^2 (Optional).
^1 X is a UoM of the same type, assigned arbitrarily by the designer, which allows correlations between UoMs of the same type. It will typically be one of the UoMs covered by the table but this need not necessarily be the case.
^2 If used, this is only necessary to allow the routine to return a calling error if the types of the two UoMs passed are not the same.
Multiple names for the same UoM can be handled by using multiple, otherwise duplicate, records.

The routine would have a call format of :
Expand|Select|Wrap|Line Numbers
  1. ConvertUnit(strFromUoM As String _
  2.           , strToUnit As String _
  3.           , dblUnits As Double) As Double
The logic is simply :
  1. Find the records of the two UoMs passed.
  2. Check that the [Type] of both records match (Only if third field included).
  3. Take the Units figure and multiply it by the number of units of the To unit record.
  4. Divide that result by the number of units of the From unit record.
  5. Return that result.
Apr 11 '16 #4
ADezii
8,834 Recognized Expert Expert
NeoPa:
Hope things are well with you. See you are still coming up with interesting and innovative approaches to these Threads. I would expect nothing less from you. One question I would like to ask you for my own benefit. How would you logic handle conversions that would involve an Expression as opposed to an Operator and Operands? Case in point would be:
Expand|Select|Wrap|Line Numbers
  1. Celsius = (5/9 (Fahrenheit - 32))
Apr 11 '16 #5
Rabbit
12,516 Recognized Expert Moderator MVP
How about a conversion table with the fields FromUOM, ToUOM, ShiftBefore, Proportion, and ShiftAfter.

So some of the data in the table could look like:
Expand|Select|Wrap|Line Numbers
  1. FromUOM   ToUOM   ShiftBefore   Proportion   ShiftAfter
  2. F         C       -32           .5555        0    
  3. C         F       0             1.8          32
Apr 11 '16 #6
NeoPa
32,556 Recognized Expert Moderator MVP
Very well thank you my friend :-)

That's a very good question, and finds a hole in my overly simplistic suggestion.

Nevertheless, I suspect expanding this table by a single [Offset] field, a Double value that is a measurement of the offset between the zero points of the UoM and the arbitrary UoM used, would get around it quite straightforwardly.

So, the approach would be :
  1. Find the records of the two UoMs passed.
  2. Check that the [Type] of both records match (Only if third field included).
  3. Divide the starting figure by the factor of the From unit record.
  4. Take that result and add to it the [Offset] of the From unit record.
  5. Subtract the [Offset] of the To unit record from the previous result.
  6. Multiply that result by the factor of the To unit record.
  7. Return that result.

Assuming these records :
Expand|Select|Wrap|Line Numbers
  1. UoM       Factor Offset   Type
  2. Celcius       1  273.15   Temperature
  3. Fahrenheit  1.8  255.372  Temperature
  4. Kelvin        1    0      Temperature
  5. ºC            1  273.15   Temperature
  6. ºF          1.8  241.15   Temperature
  7. K             1    0      Temperature
Converting 50ºC to Fahrenheit (ºF) (and back again) thus goes :
50ºC -> 323.15 -> 323.15 -> 67.778 -> 122ºF.
122ºF -> 67.778 -> 323.15 -> 323.15 -> 50ºC.
Apr 11 '16 #7
jforbes
1,107 Recognized Expert Top Contributor
Reading the suggestions and feedback really helped me think this through. So thank you all.

Because of all the thinking, I decided to try out storing off the Formulas in a conversion definitions table and then using the Eval() function along with the Replace() function to see how well it would work. And it seems to be working quite well.

It's getting late, so I'll give it a stress test tomorrow, but I wanted to post back the code for more critique and review, plus I thought is was some interesting code:
Expand|Select|Wrap|Line Numbers
  1.     ' convertToCurrentUnits
  2. Public Function convertToCurrentUnits(ByRef vValue As Variant, ByRef vFromUnit As Variant) As Variant
  3.     On Error GoTo ErrorOut
  4.  
  5.     Dim vReturn As Variant
  6.     vReturn = vValue
  7.  
  8.     Static gCurrentFromUnit As String
  9.     Static gCurrentFormula As String
  10.  
  11.     Dim sToUnit As String
  12.     Dim sFormula As String
  13.  
  14.     ' Look up new Formula if From Units have changed, or Formula is empty.
  15.     ' Example of F to C formula: ({From}-32) * (5/9)
  16.     If vFromUnit <> gCurrentFromUnit Or gCurrentFormula = "" Then
  17.         Select Case DFirst("UofMType", "tblUofMConversion", "FromUofM='" & vFromUnit & "'")
  18.             Case "Power"
  19.                 sToUnit = gCurrentPowerUnits
  20.             Case "Flow"
  21.                 sToUnit = gCurrentFlowUnits
  22.             Case "Temp"
  23.                 sToUnit = gCurrentTempUnits
  24.             Case "Pressure"
  25.                 sToUnit = gCurrentPressureUnits
  26.             Case "Area"
  27.                 sToUnit = gCurrentAreaUnits
  28.             Case "Torque"
  29.                 sToUnit = gCurrentTorqueUnits
  30.             Case "Force"
  31.                 sToUnit = gCurrentForceUnits
  32.             Case "Mass"
  33.                 sToUnit = gCurrentMassUnits
  34.             Case "Acceleration"
  35.                 sToUnit = gCurrentAccelerationUnits
  36.             Case "Distance"
  37.                 sToUnit = gCurrentDistanceUnits
  38.             Case Else
  39.                 sToUnit = ""
  40.         End Select
  41.  
  42.         gCurrentFormula = DFirst("Formula", "tblUofMConversion", "FromUofM='" & vFromUnit & "' AND ToUofM='" & sToUnit & "'")
  43.         gCurrentFromUnit = vFromUnit
  44.     End If
  45.  
  46.     ' place From Value into the formula and Evaluate it.
  47.     sFormula = Replace(gCurrentFormula, "{From}", vValue)
  48.     vReturn = Eval(sFormula)
  49.  
  50. ExitOut:
  51.     convertToCurrentUnits = vReturn
  52.    Exit Function
  53.  
  54. ErrorOut:
  55.     gErrorMessage = ""
  56.     Call customErrorHandler(mProcedureName, True, Err, Erl, gErrorMessage)
  57.     Resume ExitOut
  58. End Function
Thanks again, I really appreciate all the help.
Apr 12 '16 #8
MikeTheBike
639 Recognized Expert Contributor
Hi All

If you don't want to write a VBA function there is the possibility of using a calculated field in a query (faster that a VBA function for more than a few records!). To achieve this you would need a table like this
Expand|Select|Wrap|Line Numbers
  1.     Units       Factor           a           b
  2.     F -> C      0.5556           459.67      273.15
  3.     C -> F      1.8              273.15      459.67
  4.     HP -> kW    0.746              0           0
The calculated field would take the form
([UniteToConvert] + [a])*[Factor] - [b]

In the case of Horse power to kW etc this effectively
[UniteToConvert]*[Factor] as [a] and [b] are zero.

This only required a reference to one record for the conversion.

All you need to do is define a relationship between the units be be converted and the convertion record !?

Not sure if this too relevant to the actual requirements, but it is interesting!

MTB
Apr 12 '16 #9
ADezii
8,834 Recognized Expert Expert
I accidentally came across this Thread again and a light bulb went on in my head, a scary thought to say the least! Why not let Excel do all the hard work via Automation Code in Access with its Convert() Function which converts a Number from one Measurement System to another. You don't have to worry about Formulas, Conversion Tables, Offsets, Conversion Factors, etc. You simply need a small amount of Code along with the knowledge of what Arguments to pass to the Function. I'll throw it out there and graciously welcome any comments either Pro or Con. Here it goes:
  1. Create a Global Variable to represent the Excel Application Object in a Standard Code Module.
    Expand|Select|Wrap|Line Numbers
    1. Public appExcel As Excel.Application
  2. Create a simple Function that performs the conversion encapsulating Excel's Convert() Function within.
    Expand|Select|Wrap|Line Numbers
    1. Public Function fConvertUnits(sngValue As Single, strFrom As String, strTo As String) As Variant
    2. On Error GoTo Err_fConvertUnits
    3.  
    4. fConvertUnits = appExcel.WorksheetFunction.Convert(sngValue, strFrom, strTo)
    5.  
    6. Exit_fConvertUnits:
    7.   Exit Function
    8.  
    9. Err_fConvertUnits:
    10.   fConvertUnits = "N/A"
    11.     Resume Exit_fConvertUnits
    12. End Function
  3. Create an Instance of Excel, NOT within the Function, then Call this Function with various Arguments passed to it.
    Expand|Select|Wrap|Line Numbers
    1. 'Create a Single Instance of the Excel Application and use it
    2. 'for all subsequent Function Calls
    3. Set appExcel = New Excel.Application
    4.  
    5. 'Convert 100 Degrees Celsius to Fahrenheit ==> produces 212
    6. Debug.Print fConvertUnits(100, "C", "F")
    7.  
    8. 'Convert 212 Degrees Fahrenheit to Celsius ==> produces 100
    9. Debug.Print fConvertUnits(212, "F", "C")
    10.  
    11. 'Convert 250 Degrees Fahrenheit to Kelvin ==> produces 394.261111111111
    12. Debug.Print fConvertUnits(250, "F", "K")
    13.  
    14. 'Convert 400 Degrees Kelvin to a fictitious Unit of Measurement ==> produces N/A
    15. Debug.Print fConvertUnits(250, "K", "HELP")
    16.  
    17. MsgBox "Demo finished"
  4. Display the OUTPUT.
    Expand|Select|Wrap|Line Numbers
    1. 212 
    2. 100 
    3. 394.261111111111 
    4. N/A
  5. At some point Quit the Instance of Excel and deallocate any Resources assigned to this Instance.
    Expand|Select|Wrap|Line Numbers
    1. appExcel.Quit
    2. set appExcel = Nothing
Apr 14 '16 #10
jforbes
1,107 Recognized Expert Top Contributor
I haven't spelled this question out fully as I was trying to keep things simple, but it seems like it is no longer simple. =)

The utility that I'm creating is partially for Hydraulics Systems design and sizing, with some electronics and pneumatics design thrown in for good measure (measure, haha). The idea behind all this is that the user would specify on the fly what units they want to work in and the Worksheets, Selector Forms, Configurators and Calculators would adjust and display everything they are working on in their units of choice. For the Calculator and Worksheet style Forms, this wasn't much of a problem and it was addressed primarily in code.

What started this Bytes Question was the first of many Selection Forms to be created. Where the User enters in some ballpark parameters which would Filter a Datagrid of Vendor Data, which the user could reorder based on what was most important for the day, like Price, Lead Time or Type.

The Vendor Data is from quite a few global Vendors. So the units of measure vary considerably. In the previous example of Heat Exchangers, there are about 6000 rows and there are three points of data that we are concerned with (right now) for Selection.
  • Capacity - The amount of heat transferred (currently three different units of measure)
  • Temperature Delta - between the two fluids exchanging heat (two units of measure)
  • Flow - rates of the two different fluids in and out of the Heat Exchanger (four different units of measure times two measurements)

I could have just added multiple columns for all the different units and used a query to blown out all the data so there is a measurement for every unit and then display the whole mess to the user (thirteen columns) and be done with it, or even hide and show columns based on what the user has selected as their preferred units. It would probably run faster.

But my hope is to use the same mechanism where the User can select which units of measures they want to work in and just those units are displayed. And more importantly are the units that are used in the Filter/Selection process. Providing this will be transparent to them (except in the case of a long conversion time) and will allow me to address all these conversions through Queries which I will be creating regardless, instead of a surprisingly lot of code.


MikeTheBike,
I appreciate the input and that approach will certainly work for a lot of situations. And it's possible I will need to utilize something of this nature if the response times of the previous test code runs too slow. I'm betting it would speed things up at the cost of flexibility. I've been working on a different project for the last couple days, so I haven't had a chance to work on this to see if speed is an issue. I hope to get back to it in a day or two. So we will see.


ADezii,
You gotta love those Eureka moments. The ones that wake you up out of sleep are the best.

Excel's Convert() was what I looked at right before posting this Question. It seems to work quite well, and it's pretty peppy. I ruled it out though because a few conversions that I know I will eventually want aren't supported. A big one is converting Pressure in Bar to PSI and back again. But, I think if anyone else looking to do something like this they should definitely give this approach some serious thought.



Again, thanks for all the input. It is greatly appreciated.


On a side note, I had my own Eureka moment of loading the Conversion table into an Array during the Database Startup or first use to help speed things up. I just haven't tried it yet.
Apr 14 '16 #11
ADezii
8,834 Recognized Expert Expert
loading the Conversion table into an Array during the Database Startup or first use to help speed things up
You may wish to look into the GetRows() Method should you take this approach. The GetRows method copies Records from a Recordset into a two-dimensional Array. The first subscript identifies the field and the second identifies the row number.
Apr 14 '16 #12
NeoPa
32,556 Recognized Expert Moderator MVP
I doubt the performance would be very noticeable for any of these suggestions, assuming you implement them remotely well. I can't imagine you'd do anything but that so I wouldn't worry about response times on that, not unless you're dealing with many thousands of rows at a time. That wasn't my understanding of your situation at least.
Apr 15 '16 #13
jforbes
1,107 Recognized Expert Top Contributor
Thanks to everyone for all the help on this. It turned out well.
  • I thought I would share the results for anyone that is interested, so I've attached an Example Database.
  • I ended up using an Array to cache the table with all the Unit of Measure conversion info as it did speed things up a little and it was a fun exercise. I attempted to use GetRows() Method as suggested but had a bit of trouble implementing it, so I switched to opening a recordset and then loading it into an Array. During that process I realized what I was doing wrong with the GetRows() method, but I just continued on as the end result was pretty much the same.
  • In the Example Database I created a stripped down Selector Form to show what the ultimate goal was to be. It can be Sorted and Filtered by the Converted values. When it is all said an done, it doesn't look like much, but it will save us a lot of time with the added benefit of allowing us to make some better decisions.
Attached Files
File Type: zip UnitofMeasureExample.zip (43.0 KB, 138 views)
Apr 27 '16 #14

Sign in to post your reply or Sign up for a free account.

Similar topics

14
2087
by: Dave | last post by:
Hello all, Can anybody help with the problem below? I'm trying to define a conversion function that converts objects to function pointers and am getting a compile error on the line indicated...
40
4188
by: Matt | last post by:
Please skip to the last paragraph if you are in a hurry. Some of the integer variables in my application will need to hold values bigger than 2^32-1. Others won't need to be that big. Time...
2
1907
by: Edward Hua | last post by:
Hi, I'm wondering if there is a function written in C that computes the inverse hypergeometric distribution (i.e., not the density function, but the cumulative distribution) that's been written...
0
944
by: Chris | last post by:
I have a template class called Image and I want to make a conversion function to that I can easily convert one template type to another. I tried making functions for a couple specific cases, but...
1
1289
by: Shapper | last post by:
Hello, Does anyone knows a component to convert an Word DOC File to Adobe Acrobat PDF file? I am creating a web site where people upload word Word DOC files to the server. I need to convert...
4
3893
by: Ernie Otero | last post by:
Does anyone know where I can find a function to convert numeric data to overpunch format? I've looked around but I can't seem to find any. Ernie
15
2550
by: Pucca | last post by:
I'm getting an error when I tried to use this BerConverter class in my C# code. Even though the Interent doc says that it runs on Win2000 sp4, I just thgouth I'll double check. Does anyone know...
6
2474
by: bimal.eranda | last post by:
Hi! I have some problems in the following code. #include <windows.h> #include<iostream.h> void main(){ LPSTR x=""; long y=125;
1
2001
by: zaeminkr | last post by:
I have a question about type conversion function for user defined type. I have two classes class DRect { private : double x0; double y0;
0
7041
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
7080
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
6908
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...
0
5331
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing,...
1
4772
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new...
0
4478
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and...
0
1299
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated ...
1
561
muto222
php
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
0
178
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.