The source code for the Apple ][ version of Wizardry 1 has been reconstructed some time ago (
you can find it here) so it is possible to find the relevant logic. I took a look and the part that governs gain/loss of attributes seems to be this:
Code:
BEGIN (* GAINLOST *)
FOR ATTRIBX := STRENGTH TO LUCK DO
BEGIN
IF (RANDOM MOD 4) <> 0 THEN
BEGIN
ATTRVAL := CHARACTR[ PARTYX].ATTRIB[ ATTRIBX];
IF (RANDOM MOD 130) <
(CHARACTR[ PARTYX].AGE DIV 52) THEN
IF (ATTRVAL = 18) AND
((RANDOM MOD 6) <> 4) THEN
(* NOTHING *)
ELSE
BEGIN
ATTRVAL := ATTRVAL - 1;
WRITE( 'YOU LOST ');
PRATTRIB;
IF ATTRIBX = VITALITY THEN
IF ATTRVAL = 2 THEN
OLDAGE
END
ELSE
BEGIN
IF ATTRVAL <> 18 THEN
BEGIN
ATTRVAL := ATTRVAL + 1;
WRITE( 'YOU GAINED ');
PRATTRIB
END
END;
CHARACTR[ PARTYX].ATTRIB[ ATTRIBX] := ATTRVAL
END
END
END; (* GAINLOST *)
The "FOR ATTRIBX := STRENGTH TO LUCK DO" is just the loop that goes over each attribute, "PARTYX" is the current party member index, "CHARACTR" is an array containing all the party characters, "PRATTRIB" just writes the attribute name of "ATTRIBX" to the string (e.g. "YOU LOST LUCK") and "OLDAGE" kills the character with a message about dying due to old age (its HP is set to 0 and status to "LOST").
The crucial part is this: "IF (RANDOM MOD 130) < (CHARACTR[ PARTYX].AGE DIV 52) THEN"
"RANDOM" is a function that returns a random value between 0 to 32767. The expression "RANDOM MOD 130" evaluates to the remainder of the division "RANDOM / 130", essentially giving a random value between 0 and 129. So the check to decrease a stat (ignoring the special case where the stat is 18 here) is "if your age is greater than a random value between 0 and 129" (the AGE is stored in terms of weeks, thus the division with 52).
Now, if we assume that the code is the same in the PC version, i think the answer for the problem is simple: the RANDOM function used in the Apple ][ version has a different value distribution than the RANDOM function used in the PC version. The DOS version is really a bootloader for the booter disks though and considering that it is written in UCSD Pascal, which is actually interpreted from a custom bytecode (p-code) instead of native code, it most likely isn't that easy to disassemble it.
However considering that the original RANDOM function is written in assembly, they'd have to rewrite that function anyway. UCSD Pascal does not have a RANDOM function (unlike, say, Turbo Pascal) so they'd have to write one themselves. An important part is that the original RANDOM function used a bunch of bit shift operations - and UCSD Pascal does NOT have bit shifting operators! So at best they'd have to approximate using multiplication and division - or just use a different algorithm, which is probably what they did and why there is a difference in behavior between the two versions as these modulo checks are very susceptible to biasing (this can be seen in other checks too, e.g. resurrection is more likely to fail in the PC version too and it is also done via a RANDOM MOD check).
And basically that is my guess why in the PC version you are more likely to lose stats. Later ports most likely fixed their checks to be less biased.
EDIT: forgot a word, UCSD Pascal does NOT have bit shifting operators, hence why the need for approximation :-P