[quote]// I’ve got no idea what this operation is called;
// it’s kinda like a cross product… but gives a scalar, not a vector
double cross = ab.x * normalA.y - normalA.x * ab.y;
if(cross<0) theta = -theta;
[/quote]
Abuse: It’s still the cross product 
You are generating just the z component of the cross product from the x and y. You can tell whether the y is to the right/left of the x by the sign.
Hata: Do you really need the angle & the sign?
If you just wish to orient the object A to face object B, then the best way is to build the orientation matrix directly, otherwise you will be doing a lot of normalising & trig to get the same result.
For this code, I will assume X is the axis for the ‘facing’ vector, and Y is up:
(Pseudocode)
// Build the true x axis:
vectorX = posA-posB;
vectorX.normalize();
// Now build a true z axis:
vectorY.set(0.0f, 1.0f, 0.0f);
vectorZ.cross( vectorX, vectorY );
vectorZ.normalise();
// Now build the true Y axis:
vectorY.cross( vectorZ, vectorX);
The orientation matrix is actually these three vectors packed into the matrix, either as rows (vectorX, VectorY, VectorZ) or as columns depending on your coordinate scheme.