Assembly that just seems to be a jumble

I've disassembled an old DOS program into assembly and I'm trying to figure out a function call. Here is the ASM:

seg000:373C ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
seg000:373C
seg000:373C
seg000:373C sub_373C        proc far                ; CODE XREF: sub_72C6+16BP
seg000:373C                                         ; sub_72C6+18FP ...
seg000:373C                 push    si              ; Temp. store si on stack so we can restore it later
seg000:373D                 xchg    ax, si
seg000:373E                 xchg    ax, dx
seg000:373F                 test    ax, ax
seg000:3741                 jz      short loc_3745
seg000:3743                 mul     bx              ; Multiply b by a IIF a is non-zero
seg000:3745
seg000:3745 loc_3745:                               ; CODE XREF: sub_373C+5j
seg000:3745                 jcxz    short loc_374C
seg000:3747                 xchg    ax, cx
seg000:3748                 mul     si
seg000:374C
seg000:374C loc_374C:                               ; CODE XREF: sub_373C:loc_3745j
seg000:374C                 xchg    ax, si
seg000:374D                 mul     bx
seg000:3751                 pop     si              ; Restore old si
seg000:3752                 retf
seg000:3752 sub_373C        endp

Frankly it just seems to be jumbling the registers around to me. My best guess is that it's some kind of primitive pseudo-random number generator. Can anyone confirm this or if not, tell me what it's actually meant to do?

EDIT:
I've tried dry-running the code, and as far as I can tell, the following is the end result of the registers (can anyone confirm I've got this right and tell me what useful mathematical function it might be doing?):

ax: ( ax * bx )
bx: bx
cx: cx
dx: ax + ( (bx * dx) + (ax * cx) )
• Look up what a one-operand mul instruction does, what are its inputs and outputs. Feb 25 '19 at 22:41
• Multiplies the operand with AX and stores the result in DX:AX. I still don't see what this code is doing messing around with CX and SI :-)
– Jez
Feb 25 '19 at 23:00
• si is just used as a temporary storage throughout the code, it is not part of the input and its value is never used inside the function. Feb 26 '19 at 14:41

This appears to be a 32 bit multiplication implemented on a 16 bit architecture.

Input numbers are dx:ax and cx:bx, result in dx:ax.

The xchgs make the code confusing but if you play it through you notice it does a bunch of a seperate multiplications with the high and low 16 bit of the input numbers.

At this point I had a hunch it may be 32 bit multiplication so I tried to understand how that would look with the input numbers split and the result also split and approach the problem from the other way round.

Assuming the above inputs, one can deduce the following formulas:

dx:ax = (dx << 16) + ax
cx:bx = (cx << 16) + bx

then just multiple them:

(dx:ax * cx:bx) = (dx << 16)*(cx << 16)  + (dx << 16)*bx + ax*(cx << 16) + ax*bx

If you take a look, we have 3 additions here instead of two. The reason is that multiplying the upper parts overflows 32 bit so the first multiplicant is dropped in the above code silently.

That leaves us

(dx:ax * cx:bx) = (dx << 16)*bx + ax*(cx << 16) + ax*bx

Now if you think about bitshifts and multiplications in this case every case, as a bitshift is nothing more than a multiplication by powers of 2, they commute meaning this is equivalent to:

(dx:ax * cx:bx) = (dx*bx << 16) + (ax*cx << 16) + ax*bx

and then we can split it back into 16 bit easily:

dx = dx*bx + ax*cx
ax = ax*bx

and there you have it, the result of the multiplication of two 32 bit numbers given as 16 bit words.

And this seems to match what the code is doing (safe for bx cx swapped possibly, you may want to have a closer look on that), so it just seems to multiply numbers.

Edit: Armed with that knowledge and the response to your previous question by Igor I found this source code:

https://github.com/gandrewstone/GameMaker/blob/master/tools/BORLANDC/CRTL/CLIB/F_LXMUL.ASM

which confirms the finding.

• Could you explain what this is doing: seg000:374F add dx, si? It seems to add the original value of ax to dx, meaning dx ends up as ax + ( (bx * dx) + (ax * cx) ). What am I missing?
– Jez
Feb 26 '19 at 23:18
• No, before the add it does xchg ax, si but ax at this point is not the initial ax. There are multiple code paths that lead to it, for example with dx = cx = 0 (so only 16 bit numbers as input), ax at that point contains dx instead (which is 0). Do note that mul outputs to dx:ax so the other code paths are more complicated. Feb 27 '19 at 0:12

It looks like the code is specifically generating these outputs:

ax: (ax * bx)
dx: (bx * dx + ax * cx)

This is under the assumption that the mul instructions never have operands large enough to set dx to a non-zero value. (If that is true, it seems kinda weird that seg000:374F is an add and not just an xchg.)

Maybe it's calculating the addition of two fractions?

dx   cx   (bx * dx + ax * cx)
-- + -- = -------------------
ax   bx        (ax * bx)
• Doesn't it then add ax to dx, so it's ax + ( (bx * dx) + (ax * cx) )?
– Jez
Feb 26 '19 at 18:55