The Salty Economist

Things I Should Have Learned in High School
posts - 56, comments - 0, trackbacks - 0

Tuesday, December 4, 2018

Just Check It

So, I spent half a day trying to figure out why my checkboxes on a web page were not working, meaning not 'Checkable'

The quick and dirty answer is that, I had added an onclick event handler to the checkox like:

checkBox.setAttribute("onclick", "updateMe(this);return false;");

My mistake is in adding the 'return false' statement to the handler.

Why did I add it anyway? Well, I had copied the handler code from when I added a handler to a button. When added to an onclick event on a button, 'return false;' stops allows my onclick code to run, but then stops the button from posting back to the web page. So, I use it to stop unwanted postbacks.

But, on a checkbox, it stops the checkbox from changing from checked to un-checked and vice versa.

After strugging all afternoon on this, I finally found the answer here:

Here goes:

Let's take a look at the normal order of events when a user triggers the click event on an input element of type="checkbox" (this could happen through a mouse click, a keyboard command, or a touch event; they all trigger the click event):

change (see Special Note for Firefox below)

At the end of the process, these four events have fired and the checkbox has toggled its state (from checked to unchecked or vice versa). But what if you want to conditionally prevent the state change? For instance, clicking the checkbox may trigger some kind of validation; if the validation is unsuccessful we don't want the checkbox to toggle. Returning false from the change event handler has no effect; the default handler for this event can't be prevented. This means we want to capture the click event.

So now that we're writing our onclick function, how are we going to check the state of element? Our context is an event handler, which means this is bound to the element which fired the event. (You could also access the element via the target/srcElement properties of the event object, but for this example I'm going to ignore the event object altogether and use this; the results are identical.) The property of the input element that we want is checked; this will be true if the element is checked and false if it's not.

Okay, all our pieces are in place and we're ready to write our handler. Should be easy, right?

var clickHandler = function() {

  // verify the user is allowed to toggle the checkbox
  if (validateForm()) {
    // toggle the checkbox
    this.checked = !this.checked;

  else {
    // notify user why the checkbox didn't toggle

  // prevent the default action; we already toggled the element
  return false;

// bind the handler to the element
document.forms[0].elements[0].onclick = clickHandler;

Unfortunately, there are several problems with this function, starting with the detection of the element's state. Remember the order of events from above? The element actually toggles states before the onclick handler ever fires. In fact, it toggles before the onchange handler even fires (except for Firefox; see Special Note at the end). So by the time your onclick handler has a chance to read this.checked, the element has already toggled to its new state.

Simple fix, right? All we have to do is change this:

this.checked = !this.checked;

to this:

this.checked = this.checked;

Right? No! If you think this looks wrong, there's a good reason: it won't work at all. In fact, the entire logical structure of the function above is wrong for what we're trying to accomplish. Let's take a look behind the scenes and find out what the browser is actually up to:

mouseup/keyup/touchend event finishes
The browser toggles the state of the target element
The browser fires the change event (again, Firefox is special)
The browser then calls the onclick handler and, based on the return value, decides what to do with the checked state

If the onclick handler returned false, the browser reverts the element back to its pre-change state, regardless of the current state of the element

This means that the checkbox in the above example will never toggle no matter what changes you make to this.checked; the browser will overwrite the changes after the handler has returned. Note that this ONLY happens when the event handler returns false; if you return true (or don't return anything) your changes to checked will not be overwritten.

This is described in a note in the HTML DOM specification:

Note: During the handling of a click event on an input element with a type attribute that has the value "radio" or "checkbox", some implementations may change the value of this property before the event is being dispatched in the document. If the default action of the event is canceled, the value of the property may be changed back to its original value. This means that the value of this property during the handling of click events is implementation dependent.

So what does all this mean? The long and the short of it is that in the context of a click handler, the checked property of a checkbox is effectively read-only if the default action is prevented. Further, the state of the checked property inside the click handler is the state that the browser anticipates the property to be if the event's default handler is called, whether the default handler is called or not. In order to conditionally allow the state toggle, you'll need to account for this in the return value of your click handler:

var clickHandler = function() {

  // verify the user is allowed to toggle the checkbox
  if (validateForm()) {
    // checkbox is already toggled; allow this to carry through
    return true;

  else {
    // notify user why the checkbox didn't toggle

    // revert the checkbox to its previous state
    return false;


// bind the handler to the element
document.forms[0].elements[0].click = clickHandler;

Special note for Firefox:
Of course, Firefox has to be special. In Firefox, the change event fires after the click event. Returning false from the onclick handler also prevents the change event from firing. In all other browsers I tested, the change event fires regardless of the onclick event handler's return value. The value of the checked property inside the onclick handler is the same as I describe above.



posted @ Tuesday, December 4, 2018 10:46 AM |

Saturday, October 20, 2018

Copy User Accounts Between Servers, Server 2008 R2

Well, I wrote this before I realized that the Microsoft Migration tools do NOT allow for the export/import of user PASSWORDS, just the account names themselves.

That sucks.  So don't bother reading this post if you want to migrate user names with passwords.

Here's how to do it:

(1)  On both the Sourrce and Destination server, install the Microsoft Migration Tools.

First, Open a PowerShell Instance as Administror

Open a Windows PowerShell session with elevated user rights. To do this, click Start, click All Programs, click Accessories, click Windows PowerShell, right-click the Windows PowerShell shortcut, and then click Run as administrator.

Load the Server Manager module into your Windows PowerShell session. To load the Server Manager module,

type the following, and then press Enter.
Import-Module ServerManager

Type the following, and then press Enter.
Add-WindowsFeature Migration

Do this on both Servers

(2)  Export the User Accounts from the Source Server:

First, note that you now have a Migration Toos menu item under Administrative Tools:

Click on migration tools;  this opens a command line window:

Ender the Command:

Export-SmigServerSetting -User <Enabled | Disabled | All> -Group -Path <MigrationStorePath> -Verbose

Here's mine:

Export-SmigServerSetting -User All -Group -Path C:\ExportedUsers -Verbose

Note:  You don't specify a specifc file name (there is one created by default); just enter the folder name for the MigrationStorePath parameter

You will be prompted for to enter a Password; you will need this Password to Import the accounts on the Destination Server..

Enter it.

Now, be patient because this takes awhile.  I had about 350 users and it took about 25 minutes to build the export file.

The export file name is svrmig.mig

(3)  Import the User Accounts to the Destination Server:

Now copy the user accounts file from the source server to the destination server.  The easiest hing is just to copy it to a new folder at the root of the C:\ drice, like c:\ExportedUsers\

On the Destination server, open Migration Tools just like you did on the Source server.

Now, enter the command:

Import-SmigServerSetting -User  <Enabled | Disabled | All> -Group -Path <MigrationStorePath> –Verbose

Here's mine:

Import-SmigServerSetting -User  All -Group -Path c:\ExportedUsers –Verbose

Again, note the  <MigrationStorePath>  is the folder, not the full file path,

You will be prompted for the Password that you entered when creating the export file.  Enter it.

Again this takes time as it grinds away.

But Voila, it eventually finishes.

That's preety cool to copy 350 accounts.   Imagine trying to set them up manually.  Especially without making typos in names and passwords.

 But, like everythin Microsoft, the good always comes with the bad.

Two bad things:

(1)  All the user accounts are marked as Disabled; and

(2)  All the user accounts are marked as having to change their password on next login.

Like this:


What a pain!

But all is not lost.

 There is this blog post:

That has two scripts that be run to enable the accounts and to change the passwords to never expire.

The Scrips are VBScript.  I never knew you could run VBScript on a server.

in PowerShell, the command to run a VBScript  is:

c:\windows\system32\cscript.exe <File Name>

Mine is:

C:\windows\system32\cscript.exe c:\Scripts\script0.vbs

Script 1:


     'Script to set all user accounts to have their passwords never expire


     ' create network object for the local computer
     Set objNetwork = CreateObject("Wscript.Network")

     ' get the name of the local computer
     strComputer = objNetwork.ComputerName

     Set objComputer = GetObject("WinNT://" & strComputer)

     objComputer.Filter = Array("user")

     For Each objUser In objComputer

          lngUserFlags = objUser.userFlags

          lngUserFlags = lngUserFlags Or ADS_UF_DONT_EXPIRE_PASSWD

          objUser.userFlags = lngUserFlags




 Script 2:


     'Script to enable all user accounts

     'Execute following  VB script to enable all disable users except guest but still need to set password for imported users.


     ' Lists local accounts and enables all except Guest

     Set objShell = CreateObject("Wscript.Shell")

     Set objNetwork = CreateObject("Wscript.Network")

     strComputer = objNetwork.ComputerName

     Set colAccounts = GetObject("WinNT://" & strComputer & "")

     colAccounts.Filter = Array("user")

     Message = Message & "Local User accounts:" & vbCrLf & vbCrLf

     For Each objUser In colAccounts   

          If objUser.Name <> "Guest" Then

               Message = Message & objUser.Name

               If objUser.AccountDisabled = True then

                    Message = Message & " has been enabled" & vbCrLf

                    objUser.AccountDisabled = False



                    Message = Message & " is already enabled" & vbCrLf

               End if

          End If


     ' Initialize title text.

     Title = "Local User Accounts By Robiul"

     objShell.Popup Message, , Title, vbInformation + vbOKOnly

So run these scrips, and all is good!




posted @ Saturday, October 20, 2018 7:19 PM |

Thursday, October 18, 2018

Installing Windows Server 2016

So Yesterday, I started the saga of Installing Windows Sever 2016 on a brand new Shiny DELL Server. I though this would take an hour or two. Boy was I wrong. Here is my story.

Problem #1. The Server 2016 .ISO installer was too big to fit on a DVD.

Well, this is just plain stupid. By itself, this shows the utter disregard that Microsoft has for its customers. So, I don't have a USB drive handy, so I go bug my wife. Low and behold, she has a brand new one. I'm not polite; I don't ask to borrow it. I just assure her that I am taking it and I'll get her a new one. It has like 15GB free, so I think that I am in good shape. I go to copy the Server 2016 .ISO to the USB drive and it barks at me saying there is not enough free disk space. I try again, and get the same result. I then Google some, and I get the answer: "This is due to FAT32 limitation. Files larger than 4GB can NOT be stored on a FAT32 volume. Formatting the flash drive as NTFS will resolve this issue." I ask, why in this day and age would anything be formatted FAT32? So, I then figure out how to format it as NTFS. This is really simple. Just plug the USB drive in, open Windows Explorer and right-click on the drive. One of the options is Format:

Just select Format.  In the Format Window, make sure NTFS is selected and click OK

Voilà!.  I can now copy my 6 GB+ .ISO package to my USB.

Problem #2. I Realize I Actually Need a Bootable USB Drive with my .ISO Package.

Me Bad. I should have realized that I needed a bootable USB Drive, along with my .ISO.

So, I Google some more and find this really good post about someone doing the same thing as me.

Here is the URL:

My sever is actually a Dell T130, but it is still insightful.  I will embellish on the Post.

In order to make a bootable USB, with an .ISO image, first download a free utility: Rufus!

1) Format your USB stick  to UEFI and GPT by downloading the tool from:

2) Start the tool as administrator

3) Select (Browse) to you .ISO file.

4) Select the USB storage device (Already selected by default if only one device). Make the partition scheme to be GPT.  Make the Target System to be 'UEFI'.

5) Make sure file system is NTFS.  Do a quick format.  Click Start.

6) This took 10-15 minutes for me.

No hiccups here.

Problem #3. I Need to Get my Server to Boot from the USB 

No too hard.

On Initial boot, I select F2 and went into System Setup, selected Boot Settings, and changed BIOS to UEFI.

I then selected F12 and went into boot manager.  I selected One-Shot BIOS Boot Menu.  I then selected my USB Drive.

Rebooted.  Yes!  It started to to boot from my USB...I could see windows looking the install files.

Problem #4. The Installer Fails! 

So, now I rubbing may hands, getting to run through the Windows Installer.  I pick US & English....

I now get to select my drive to Install the Operating System on.  I know this is picky, but I want to install it on Drive 0, but setup program says I can only install on Drive 1.  Why?  Because Drive 0 is MBR not GPT.  How do I fix this?

I follow Microsoft's Directions:

To manually wipe a drive and convert it to GPT:

(1) Turn off the PC, and put in the Windows installation DVD or USB key.

(2) Boot the PC to the DVD or USB key in UEFI mode. For more info, see Boot to UEFI Mode or Legacy BIOS mode.

(3) From inside Windows Setup, press Shift+F10 to open a command prompt window.

(4) Open the diskpart tool:


(5) Identify the drive to reformat:

list disk

(6) Select the drive, and reformat it:

select disk <disk number>
convert gpt

(7) Close the command prompt window.

Continue the Windows Setup installation.


OK!.  So, I restart the installer.  I can now pick Drive 0.  I delete the exiting partition(s) so that it is the only partition and is marked unallocated.

I then go on my merry way.

I then get to the screen when Windows copies the required files and starts expanding them.  This task gets 55% done when I get this big ugly message:

Windows cannot install required files. The file may be corrupt or missing. Make sure all files required for installation are available and restart the installation. Error code: 0x80070570.

That's what Microsoft thinks is a helpful message.  Absolutely NO indication what the problem is.  I mean this is a clean install on bare metal.  This should be simple.  I am disgusted. I try a couple more times and I can't past this error.

Google is no help either.  I get a few tips on things to try, but nothing works.

I finally, resign myself to download the .ISO file again (thinking it might be corrupted).  I go through the process of creating a bootable USB again.  This process nearly an hour.

I try again on the install.  It still craps out at 55%.  This is no coincidence.  There is nothing wrong with the .ISO file or the USB.

It's time to go home.

Day 2.  I discover iDrac.

I have a sneaky suspicion that there is a conflict with the USB Driver, so I look for an alternative.

So, the blog post above ( also mentions a way to do a server install from your laptop.  Whoa?  What the hell is this?

Here goes:

(1) iDrac is a pre-installed piece of software that Dell now installs on its servers.  The main purpose on the software is to allow you to manage your server remotely, kind of like Remote Desktop.  But, it works through a Web Interface that works even if there is NO operating system on the server.  The trick here is that iDrac allows you to boot up from a Virtual Media source, which can be an .ISO file connected to a remote computer.  This is cool.  I can see that this may be slow....but, we have gigabit Ethernet in our office, so it can't be that slow.

(2) You need to configure iDrac for your network. On the Server, in the System Setup there is an option for iDrac Settings.  One of the settings here is for the Network. Under Network, there are IP4 settings that allow you to set the Static IP for iDrac, the Gateway and the subnet mask. Assign it a static IP address that works on your network.  I assigned mine to be  Note: this IP is for iDrac, which is different than the IP that you might assign to the actual Windows computer.  Save and reboot.

(3) You can now hit iDrac on the server using a browser.  Open a browser and enter https://<server ip>/.  In my case  I end up with a dumb message about the connection is not safe, but click on advanced and continue to the site.

I now get a login screen:

The user name is root.  The default password is calvin.  You will be prompted to change the password the first time you log on.

Here is the landing page:

From here, you can review manage many of the server settings.  The functionality provided here could take up a book, but what we really care about is being able to boot up from a remote network drive, like the one on my laptop.

That capability is hidden behind the Tab "Attached Media"

It turns out that this Tab is only available in the "Licensed" version of the iDrac Software.

Well, that's really helpful.  So what does a license cost?

$450.24 Smack-a-Roos!  But, look I can save $18.76.  Dell, I gotta tell ya, that sucks.

But, all is not lost.  Dell, to their credit, provides a 30-Day Trial License.

There are a bunch of options, but most importantly is what version of iDrac you have.  So, what version of iDrac do I have?

Good, question.  I looked on all the setup screens on the server and could not find it, other than it said build version

But, I did finally find it here:

So I have version 8.  I then download version 8 download the file and unzip it.

In the screen above, there is a drop down for license options:

That let's you import a file by browsing to the file on your laptop and then clicking apply:

I do that, but get this big ugly:

Now I know my file is a regular xml file because I can open it in notepad.  So, I know the message is bullshit.

I actually go and right-click on the file, and notice that my computer wants to block it because it wasn't from around here.

So, I click Unblock.  I actually have no idea what this does.

Alas, that does not fix the problem;  I still get the same error message.  Though, the thought does occur to me that the error is somehow security/permissions related.

So, the next thing I try s to open the .xml file in notepad.  I copy the contents to another notepad file and save the new file under a new name Xeek.xml.  I then retry the import...and hold my breath.  It works!

I'm licensed.  But, I need to get my operating system installed in 30 days or I am out $450.

So, the next 2 steps are to (1) identify the attached image file path; and (2) reboot the server using the attached media.

(1) identify the attached image file path.

That is here:

Note:  There is no browse button.  You need to type the full address.

For the full image path, use the IP local address of your computer.  This is so the server can find your computer.  Computer names might work, but I know the IP will.  After the IP, append the file share and the .ISO name.

Here is mine:$/SW_DVD9_Win_Server_STD_CORE_2016_64Bit_English_-4_DC_STD_MLF_X21-70526.ISO

Then type connect.  Bang. we're now connected.

Now, the last thing we need to do is reboot the server using the attached media

In order to do this, you need to stoke up the virtual machine console:

Click on launch console.  "Launching the Console" involves downloading a viewer.jlnp file.

Once downloaded, click open.

I presume this is a Java application.  It just worked on my laptop, so I assume I had a recent enough version.  But who knows, I was just glad I didn't have to fight with installing the "correct" version of Java, which always is a pain in the ass.


After clicking "open,"  you taken through a bunch of security screens.  Just Click OK to everything.

Finally, the console appears: 

This is the console.  The screen you see is the System Setup screen on the server.  At this point, you have full control of the server and can basically do anything you could as if you were sitting down in front of it.

Notice at the top there is a menu item:  'Next Boot'

Drop down that menu item.  Here is where we can tell the system to boot from our virtual .ISO file.

Select that option:

So, our next task is to do a reboot.

From the Power menu item, reboot the server:



The system will now reboot from your virtual .ISO file.

Ah, I see the windows 'Loading Files' scroll across the screen.  I am in.

I go through the server setup like before....I hold my breath as it expands the files... 53% 54% 55%.... 56%.  I'm finally past 55%.

After that, getting the server up and running was a piece of cake. 












posted @ Thursday, October 18, 2018 3:57 PM |

Saturday, June 28, 2014

Adding Bankers Rounding Function to Crystal Reports

We use bankers' rounding in much of our software.

For those of you who don't know what banker rounding is, it is commonly known as round to even.  For example if you have a number like 105.5550 and wand to round it to 2 decimal places, bankers' rounding would give you 105.56 because 6 is even and 5 is odd.  Likewise, if you had the number 105.5450, bankers' rounding would yield 105.54 because 4 is even.  The reason to use bankers' rounding is that it is more "fair" than always rounding .5 up or down.  Bankers' rounding is intended to round up half the time and round down half the time.  This type of rule seems to be the most neutral way to round numbers.

I find it interesting that all software languages have rounding functions, but their documentation rarely tells you their rounding rules.  Most of the time you just have to figure it out yourself.  Also, because real numbers are usually not stored with exact precision, rounding of numbers ending 0.5 is fraught with problems.

For example, the real number 105.555 is sometimes stored as 105.55499999999, which would always be rounded down.

In Crystal Reports (8.5) there is a rounding function 'Round' that appears to always round up.  Because of this, I wanted to see if I could add a function to implement bankers rounding in Crystal Reports.

After some sleuthing about, I found a really cool way to do this.

It turns out that since version 6 of Crystal Reports, you the ability of creating a user-defined formula using any language that can create a COM application. Voila! VB6.  This means I can basically do anything I want in Crystal Reports.  This is so Cool.

Let's take a quick look at how your COM application interacts with Crystal Reports.  One of the DLL's installed by Crystal Reports is a file called U2LCOM.DLL.  This DLL acts as a gateway between your COM application and Crystal Reports.  U2LCOM.DLL looks for any COM applications that follow Crystal's specific naming convention for user defined functions.  It then displays any public functions from your COM application in the "Additional Functions" category in the Crystal formula editor window.  That is really all there is to it.

Here are the rules you must follow.

(1) The COM application's file name must follow a strict naming convention. It must be exactly 8 characters in length and prefixed with "CRUFL".  This means you get the last 3 digits to make up your name. 

(2) The COM application must not contain any public functions by the same name as existing Crystal Report functions.

(3) The U2LCOM.DLL from Version 8.5 has a limit of 300 functions.  This is the total number of functions from all UFLs, not just yours alone.

Bankers' Rounding Example

(1)  In VB6, create a new activeX dll project.

As shown above, I have a VB6 project named CRUFLbrd - "CRUFL" + 3 digits "brd"

I have one class:  clsBRound.  There do not appear to be any limitations of the naming convention for a class.

Here is my Class Code:


Option Explicit

Public UFPrefixFunctions As Boolean

Private Sub Class_Initialize()

    UFPrefixFunctions = False

End Sub

Public Function BRound(ByVal X0 As Double, ByVal Factor As Double) As Double
    '  For smaller numbers:
    '  BRound = CLng(X * Factor) / Factor

    Dim Temp As Double
    Dim FixTemp As Double
    Dim n As Long
    Dim zeta As Double
    Dim X As Double
    On Error GoTo Err_Handler
    X = Abs(X0)
    If Abs(X) > 1# Then
        n = Fix(Log10(Abs(X)))
        zeta = 0.00000000001 * Pow10(10, n)
        zeta = 0.00000000001
    End If
    Temp = (X * Factor)
    FixTemp = Fix(Temp + 0.5 * Sgn(X) + zeta)
    ' Handle rounding of .5 in a special manner
    If Abs(Temp - Int(Temp) - 0.5) < zeta Then
      If FixTemp / 2 <> Int(FixTemp / 2) Then ' Is Temp odd
        ' Reduce Magnitude by 1 to make even
        FixTemp = FixTemp - Sgn(X)
      End If
    End If
    BRound = (FixTemp / Factor) * Sgn(X0)
    Exit Function
End Function

Private Function Log10(X) As Double
   Log10 = Log(X) / Log(10#)

End Function

Private Function Pow10(aVal As Double, dec As Long)
    Pow10 = Exp(dec * Log(aVal))

    Exit Function

End Function


The first part of the class code:

Public UFPrefixFunctions As Boolean

Private Sub Class_Initialize()

    UFPrefixFunctions = False

End Sub

Performs an important role.  The UFPrefixFunctions property is used by Crystal to use a default naming convention.  By default this property is True and will name your function using the class name, the letters "UFL" and your function name.  In my example, the default naming convention would generate a name: "clsBRoundUFLBround" -- now there's a mouthful.  This is the name that would appear in under the additonal functions heading in the formula window is Crystal.  I don't like this name.  I just want it to be named "BRound," thank you very much.  By setting UFPrefixFunctions = False, I can ensure that Crystal does NOT append the awkward prefix and just uses the name BRound.  Excellent.

I compile my project and I am ready to go.  Note, the compile process in VB6 will also register your COM object, so you don't need to register it separately.  But if you want to distribute it, you will need to register it before you can use it.  Obviously, if you are using a function from the .dll in an actual report, you will have to install the new .dll along with the other Crystal runtime components.

So Now when I open Crystal and go the formulas window, I see my function!!!

Somehow, Crystal searches the registry for classes with their precise naming convention and loads them up when you open Crystal.  You don't have to manually add is all automatic.

Sweet.  That's all there is to it.

Now on with the tedium of the Bankers Rounding code.

First, a couple of requirements:

(1)  The function must handle any number of rounded decimal places.

(2)  It must handle Negative numbers.

The function itself is called with 2 parameters:  The number you want to round and a "Factor" for how many decimal places you want to round to.  In my function, you would pass in the number 10 for one decimal place, 100 would be for two decimal places, 1000 would be three places and so on.

Let's work through the code with an example:  104.655 rounding to 2 decimal places.

This part:

    X = Abs(X0)
    If Abs(X) > 1# Then
        n = Fix(Log10(Abs(X)))
        zeta = 0.00000000001 * Pow10(10, n)
        zeta = 0.00000000001
    End If

begins by using the absolute value of the number passed in -- 104.655.

For a number greater than 1 (which ours is), I then want to create a value 'zeta' that is close to zero, but not zero.  The use for this is for handling imprecise real numbers like 104.654999999..., that I really want to treat as 104.655.

The line n = Fix(Log10(Abs(X))) takes the log (base 10) of 104.655 which is something like 2.02, and then returns only the integer part.

So, n = 2.  We then multiply 0.00000000001 by 10 raised to the power of 2 (=100), yielding a zeta of 0.000000001
The use for zeta will be clear down below.

Next we create 2 temp variables Temp and FixTemp:

    Temp = (X * Factor)
    FixTemp = Fix(Temp + 0.5 + zeta)

Temp takes our number 104.655 and multiplies it by 100, yielding 10465.5

FixTemp takes Temp and adds 0.5 (yielding 10466.0) and adds zeta, yielding 10466.000000001.  We then grab the integer part or 10466 (exactly)

Note (1): if we were rounding -104.655, Temp would still be 10465.5 and FixTemp would be the integer part of 10466.000000001 or 10466.0. Recall, from the beginning that we are dealing with all positive numbers.  We flip the sign at the end.

Note (2): if we were rounding 104.654999999...,  Temp would be 10465.4999999... and FixTemp would be something like 10466.000000000999. Again, we then grab the integer part or 10466.  The purpose of zeta is to prevent the returning of a FixTemp value of 10465.  

Now, we have 2 cases:

(1)  The mantissa of Temp is .5

(2)  Or it is not.

Case 2:  Simple.  Just return FixTemp divided by Factor (100) and multiplied by the sign of X0.

So, if our number had been 104.664, Temp would be 10466.4 and FixTemp would be 10466.0.   We would return 104.66 ( = (10466.0 / 100.0) * 1)

Case 1:  Harder.

First, calculate the mantissa of Temp.

= Temp - Int(Temp).  Then take its absolute value and compare it to 0.5. 

We do this comparison be taking the difference between Temp - Int(Temp) and 0.5 and then checking to see if the difference is arbitrarily small (meaning less than zeta).

Thus the expression Abs(Temp - Int(Temp) - 0.5) < zeta.

In our example this is Abs(10466.5 - 10466 - 0.5) < .000000001.  This is TRUE.

With the number -104.665, we would have the same result (recall...all positive numbers).

We use the zeta factor to stay away from the imprecise equality comparisons between two real numbers.

In the case of 104.654999999...,  Temp would be something like 10465.4999999... 

And the expression Abs(10465.4999999... - 10466 - 0.5) < .000000001. Would also be TRUE.

OK, so now we know the mantissa is 0.5. How do we handle, the odd/even of bankers rounding?

Look at this expression:

      If FixTemp / 2 <> Int(FixTemp / 2) Then ' Is Temp odd
        ' Reduce Magnitude by 1 to make even
        FixTemp = FixTemp - 1
      End If

We take FixTemp and divide it by 2.  In our example, FixTemp is 10466.0.  Dividing by 2, we get 5233.

The Int(5233) equals 5233, which is equal to 5233. 

So we do nothing and then return 104.66 = 10466.0 / 100. = FixTemp / 100.

Thus, we round 104.655 up to 104.66

If our number had been 104.665 (instead of 104.655), then FixTemp is 10467.0 and dividing by 2 yields 5233.5.  This does not equal Int(5233.5), so we deduct 1 from FixTemp and return 104.66 = 10466.0 / 100. = FixTemp / 100

Thus, we round 104.665 down to 104.66

For negative -104.655 (instead of 104.655), FixTemp is still 10466.0 and dividing by 2 yields 5233.

So we do nothing and then return -104.66 = (10466.0 / 100.) * Sgn(X0).  The Sgn(X0) = -1.

Thus, we round -104.655 down to -104.66

Likewise, for negative -104.665, we return -104.66. 

The moral, all .5's round to the nearest even number.





posted @ Saturday, June 28, 2014 11:04 AM | Feedback (2041) |

Sunday, June 1, 2014

I Hate General Electric

The stock that is.

I understand why people are enamored with GE's dividend of around 3.3%.  I like dividends too.

But let me show you a picture:

This Yahoo chart compares GE against United Technologies & Dow Chemical since 2000.  I bought GE in 2006 for around $35/share.  As everyone knows the stock collapsed in 2008/9 during the great recession.  While others have clawed there way back, GE wanders in the desert in the mid 20's.  That really sucks.  Did I mention that I hate GE?

The reason I show Dow is that, I also bought it in mid-2005 for around $40/Share.  DOW then proceeded to overpay for Rohm & Haas in the summer of 2008 and their stock also collapsed in 2008/9.  But, I have held on to them.  They have finally recovered and I am now in the black on the stock price itself, plus I have a bunch of extra shares from reinvested dividends.  I am not saying this has been a great investment, but it is the last of my stocks to have finally recovered from the 2008/9 debacle.

I also own UTX and have made a good deal of money on it.  I show it here because it is often trotted out as a "comparable" to GE.  Really???

Since 2000, UTX is up 250% and GE is down 50%.  I mean, its not like UTX is some unknown tech company.  Just a plain old fashioned industrial.  Enough said.

Where did GE's earnings go?

GE has not recovered because it has lost 50% of its income.

Check this out:

This shows is a clip from S&P's investment report.  GE's net income peaked at $22.5 billion in 2007.  But in, 2004, 05, & 06 net income was $16.6 billion, $18.3 billion & $20.7 billion, respectively.  That is real money.  In 2013, GE's operating income was $15.2 billion.  That is a drop of $7.3 billion from 2007 -- a drop of 32%.  That is just staggering.

I am sorry, GE may be a great company with great management and a great CEO, but this is embarrassing.

Here is the make up of GE segment profits from 2004-2008.  In 2007, GE Capital had a segment profit of $12.2; the rest of GE had a combined segment profit of $16.9 billion.

Here are the segment earning for 2009-2013.  In 2013, GE Capital had a segment profit of $8.3; the rest of GE had a combined segment profit of $16.2 billion.  Sinc 2007, GE Capital's segment profit dropped by $3.9 billion and the rest of GE was basically flat (a small drop of 0.7 billion).

The big drop in GE's net income is largely from GE Capital.  GE Capital used to be the crown jewel of GE and generated a huge profit.  Now it is the bastard child and GE wants nothing to do with it.  So, their strategy now is just to drive it into the ground.

If they no longer think the GE Capital fits in with GE's traditional mold, they should spin it off and let someone who has a passion for the business drive it forward.  Don't just let it rot on the vine.

So now we have a bunch of pundits telling me I should buy GE because of its dividend yield of 3.3%. Praytell where do I find the confidence in the management that will bring anything but tears.

And now comes along the Alstom bid.  Ughh.


posted @ Sunday, June 1, 2014 9:53 AM | Feedback (0) |

Friday, May 30, 2014

VMWare Workstation Windows 8

I hate Windows 8!

On a new Windows 8 Box, I went to launch VMWare Workstation and was greeted with this garbage:


“Not enough physical memory is available to power on this virtual machine with its configured settings.
To fix this problem, increase the amount of physical memory for all virtual machines to 2311 MB or adjust the additional memory settings to allow more virtual machine memory to be swapped.
If you were able to power on this virtual machine on this host computer in the past, try rebooting the host computer. Rebooting may allow you to use slightly more host memory to run virtual machines.”

Rebooting does NOT work.

After Googling a bunch, and getting a whole bunch of nonsense posts, I finally found this one:

You need to run VMWare Workstation as Administrator to solve the problem.


This needs to be shared!

posted @ Friday, May 30, 2014 9:46 AM | Feedback (2145) |

Tuesday, May 27, 2014

TSQL Paramaterized Views

I really like views in sql Server.

Today, I ran into a case where I wanted create a view that joined to another view in one circumstance and another view in an alternative circumstance.  Specifically, I have a bunch on payment data stored in one view that I wish to sometimes join to an EMPLOYEES table and sometimes join to a VENDORS table.  I could Union the EMPLOYEES and VENDORS, but what I really wanted was a way of dynamically joining 2 tables in a view.

After googling for a bit, I figured out a way to use a function that returns a TABLE as an alterative to a view.  In my function, I can pass a parameter that acts a switch to return the EMPLOYEES data or the VENDORS data.  The function acts just like a view.

My function looks like this:


FULL_NAME varchar(250),

ADDRESS_1 varchar(250),

ADDRESS_2 varchar(250),

CITY varchar(100),

STATE varchar(100),

ZIP varchar(20)







I can then "join" to this function to create another fiew.

For example:




I think this is way cool! 

posted @ Tuesday, May 27, 2014 6:41 PM | Feedback (373) |

Threading and Slow COM Objects

OK, so I got my multi-threaded app up and running.
But, just one problem: It is really SLOWWWWW...
After doing a little research, I discovered this gem:

"All things being equal one of the most common causes of slower execution from one thread to another occurs when the code uses a COM object that is configured to run in a single thread apartment (STA) and meets one of the following other conditions.
It is called from a thread other than the one that instantiated it.
It is called from a thread configured to run in a multithreaded apartment (MTA).
An expensive marshaling operation most occur on each and every access to the object. 
A factor of 4x slower is a completely reasonable symptom of this issue. 
Resolving this issue will be quite difficult if you continue to use the BeginInvoke invocation mechanism.
The reason is because that mechanism uses a ThreadPool thread for execution which cannot be easily (or at all) switched to STA mode." 
From here:

Interesting.  Yes, it turns out I am using a bunch of lower level VB6 COM objects that I did not want to convert yet.
When running my functions on the main thread, they run fine with the COM objects.  Whe I create new threads and try to use them, they are very slow.  I would guess 4x is a good guess.
So, I guess I'll speand the next couple of days in conversion hell.  So Sad.

posted @ Tuesday, May 27, 2014 4:06 PM | Feedback (1697) |

Saturday, May 10, 2014

Threading and SyncLock

So I have a program that calculates the pay for contractors that deliver logs to sawmills.  Every week a given mill might receive 500 to 1000 loads.  For each load, the mill might have to make separate payments to the trucker, the logging contractor, the landowner, etc.  Let's say for this example that the mill receives 1,000 loads and must make payments to 6 separate vendors, for a total 6,000 transactions.  Calculating the payment on each transaction is not simple and requires side calculations to contend with things like overloaded trucks, volume conserions, and rate schedule lookups based on the species, product type, location, terrain. etc.

My program takes all the rate schedule information and marries it up to each load and calculates the transaction amount.  The proecessing time for a pay run of this size is about 15 minutes, or about .15 seconds per transaction (= .9 seconds per load).  Not a long time, except that when clerks make data entry mistakes and/or rate mistakes, the routine has to be run multiple times.  The time can really add.

The good thing about this problem is that the pay for one load is independent from another load.  This is where I got the grand idea of making the pay routine a multi-threaded adventure.  I figure if I have a quad-core machine, I could make use of the extra power and spin up to say 4 threads at a time and greatly reduce the calculation time.


The pseudo-code for the base calculation function goes something like this:

 Sub CalcPay

     Dim i as integer

     Dim ldRS as recordset

     ldRS = LoadRecordset("SELECT * FROM LOADS WHERE LOAD_DATE BETWEEN '2014-05-01' AND '2014-05-07'")  'Load up a recordset of loads 

     Do Until ldRS.EOF = False

          Dim aLoad as clsLoad

          aLoad = new clsLoad

          aLoad.Load(ldRS("ID").value) 'Load up the data from the load recordset into a clsLoad Object

          iRet = CalcLoadPay(aLoad)  'Calc the Load Pay



End Sub

Sub CalcPay(aLoad clsLoad)


     Do a Bunch of Calculations


     id = getNextIndex

     UpdateDatabaseTableForPay(id, aLoad)

End Function

Function getNextIndex() as Integer 

    Dim aRs as recordset

    Dim nextID as integer


    nextID = aRS(0).Value

    sql = "UPDATE TBL_INDICES SET NEXT_ID = NEXT_ID + 1 WHERE MYINDEX = 'PAY_CALCS'"  'Update the Indices Table


End Function

So, what we have here is a main calculator routine tht loops over the list of loads.  For each load in load up an object with all the data for the load.  The routine then calls a calculator that does all the pay calculations.  When the calcutions are done, it then writes the result to the database.  The write the the database takes three steps:

1.  Get the next transation ID.

2.  Update the tables that stores the transaction IDs (in this example, the table is named TBL_INDICES).

3.  Write out the pay data using the transaction ID as the primary key.

The program runs as is.  But, I want to make it run faster using a threaded approach.

The changes to the main routine are as follows:

Imports System.Threading

Sub CalcPay

     Dim i as integer

     Dim ldRS as recordset

     Dim parmams as clsParams 'See Below

     Dim loadCollection as Collection 'collection of loads currently being processed

     loadCollection = new Collection

     ldRS = LoadRecordset("SELECT * FROM LOADS WHERE LOAD_DATE BETWEEN '2014-05-01' AND '2014-05-07'")  'Load up a recordset of loads 

     Do Until ldRS.EOF = False

          Dim aLoad as clsLoad

          aLoad = new clsLoad

          aLoad.Load(ldRS("ID").value) 'Load up the data from the load recordset into a clsLoad Object

          payThread = New Thread(AddressOf ProcessLoad)

          params = new clsParams

          params.aLoad = aLoad  'The Load

          loadCollection.Add(aLoad.ID)  'Add load to collection

          parmas.loadCollection = loadCollection


          Do Until threadColl.Count < 6  'Don't add another thread until there are less than 6 running





      Do Until threadColl.Count = 0  'Stall until done 



End Sub

Class clsParams

     Public aLoad as clsLoad

     Public loadCollection as collection

End class

Sub CalcPay(ldParams as clsParams)

     aLoad = ldParams.aLoad


     Do a Bunch of Calculations


     id = getNextIndex

     UpdateDatabaseTableForPay(id, aLoad)

     ldParams.loadCollection.Remove(aLoad.ID)  'Remove load from collection when done!

End Function

Things to note:

1.  I have created a collection of load IDs.  I add an ID every time I start a new thread.  I pass the collection along the thread.  When the thread finishes its work, it removes the load ID from the collection.  Yes, this works.  I then monitor the count of IDs in the collection and don't start a new thread until there are less than six.  Otherwise, I would end up spinning up a zillion threads and accomplish nothing.

2.  I can't pass more than one parameter to a thread.  Thus, I have to make a class (called clsParams).  I then add both my load object and my load collection to my class and then pass the class to the thread.  More work than I should have to do, but this too does work.

3.  So far so good.   Everything works for a bit and thing the program crashes and burns.  I get a bunch of errors for trying to insert transaction records with duplicate IDs.  I study the code for a while and realize that if 2 threads are real close in their timing that thread 1 can get a new ID.  And thread 2 can get the same ID IF thread 1 has NOT finished updating the table that stores the indices.

Here is the code again:

Function getNextIndex() as Integer 

    Dim aRs as recordset

    Dim nextID as integer


    nextID = aRS(0).Value

    sql = "UPDATE TBL_INDICES SET NEXT_ID = NEXT_ID + 1 WHERE MYINDEX = 'PAY_CALCS'"  'Update the Indices Table


End function

You can see that if the threads are close enough together that 2 transactions could get the same index.  The ordering would be:

1.  Thread 1:  Read the Next_ID

2.  Thread 2:  Read the Next_ID

3.  Thread 1:  Update the TBL_INDICES table.

4.  Thread 2:  Update the TBL_INDICES table.

What to do?

The first thing I tried was to wrap the 2 statements in a transaction, as so:This does NOT work!

Function getNextIndex() as Integer 

    Dim aRs as recordset

    Dim nextID as integer



    nextID = aRS(0).Value

    sql = "UPDATE TBL_INDICES SET NEXT_ID = NEXT_ID + 1 WHERE MYINDEX = 'PAY_CALCS'"  'Update the Indices Table



End function

I had always thought that a transaction would lock the row for selects and updates/inserts.  But it does not.  It may stop and insert/update into the TBL_INDICES table while the transaction is open, but it does NOT block the select statement.

Thus, I am no better with my duplicates.

So, I Google some and find the syncLock statement.

Basically, the SyncLock statement says:  No other thread can execute this code until I am finished with it.  By the way, it can be any code, not just database reads and writes.

The structure of syncLock is:



    Do a bunch of stuff


End SyncLock

The parameter "someObject" stores the state of the "locked code."  At first, I tried making it a local variable (see below)

Function getNextIndex() as Integer 

    Dim aRs as recordset

    Dim nextID as integer

    Dim someObject as New Object



        aRs = LoadRecordSet("SELECT NEXT_ID FROM TBL_INDICES WHERE MYINDEX = 'PAY_CALCS'")  'Get the Next ID

        nextID = aRS(0).Value

        sql = "UPDATE TBL_INDICES SET NEXT_ID = NEXT_ID + 1 WHERE MYINDEX = 'PAY_CALCS'"  'Update the Indices Table



     End SyncLock

End function

This does NOT work.  Although the executing thread knows about someObject, the other threads do not.  So they just thumb their noses and plow on.

You have to make someObject a Global variable so that all the threads can see it.  Once I made this change, my program finally worked.

Its now time for a DeSchutes IPA






posted @ Saturday, May 10, 2014 3:58 PM | Feedback (2101) |

Monday, April 28, 2014

MDI Child Form in DLL VB6

OK, so we're still working with VB6. Here's how to put an MDI child form in a DLL, which cannot be done right of the box.

Credit for this goes to:  "!topic/microsoft.public.vb.winapi/_AL4yJTiOwQ"

Step 1: Create an MDI Child forn in your main project file. The form is sparse, but does have some code:


Option Explicit

Public Declare Function SetParent& Lib "user32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long)
Public Declare Function SetWindowLong& Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long)
Declare Function LockWindowUpdate Lib "user32" (ByVal hwndLock As Long) As Long

Public Const WS_CHILD& = &H40000000
Public Const WS_EX_MDICHILD& = &H40&
Public Const GWL_EXSTYLE& = (-20)
Public Const GWL_STYLE& = (-16)
Public Const WS_VISIBLE& = &H10000000

Public Property Set DLLForm(RHS As Form)
    ' Set up our DLL Form
    Set m_frmDLL = RHS
    ' Stop drawing of the form
    LockWindowUpdate m_frmDLL.hWnd
    ' Set my parent as the container form
    SetParent m_frmDLL.hWnd, Me.hWnd
    ' Clear all styles
    SetWindowLong m_frmDLL.hWnd, GWL_STYLE, WS_VISIBLE
    SetWindowLong m_frmDLL.hWnd, GWL_EXSTYLE, 0&
    ' Move and redraw the form
    m_frmDLL.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight
    LockWindowUpdate 0&
End Property

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    ' If closing the container form, make sure we reset everything back
    ' to how it was for VB
    m_frmDLL.Visible = False
    SetParent m_frmDLL.hWnd, 0&
    Unload m_frmDLL

End Sub

Private Sub Form_Resize()

    ' Resize our dll form
    If Not m_frmDLL Is Nothing Then
        m_frmDLL.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight
    End If

End Sub

Private Sub m_frmDLL_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    ' If for some reason the dll form is going to be unloaded,
    ' unload the container form as well !
    Unload Me
End Sub

OK, so now we have a container form.  We can now create a dll that has any form and assign its container to be this MDI child.

Step 2 is to create a dll project.

Add your form (any form at all).   Let's call it: "myForm"  Note: do NOT set it to be an MDI child.  The main project and MDI Child container form will take care of that.

Add a class with a public interface.  Let's call it myClass.  Add a public method called LoadForm


Option Explict

Public Sub LoadForm(mdiChild As Object)

    Dim aForm As myForm
    Set aForm = New myFrom
    Set mdiChild.DLLForm = aForm  'This is what makes it work

    aForm.LoadMe 'Load up the form with data...

End Sub


All this does is instantiates your form and sets its container to be the MDI child that is passed in.  You can them load your form and do anything else you can with any other form.



posted @ Monday, April 28, 2014 9:09 AM | Feedback (265) |

Powered by:
Powered By Subtext Powered By ASP.NET