in math-emu/reg_ld_str.c [379:561]
int FPU_store_double(FPU_REG *st0_ptr, u_char st0_tag, double __user *dfloat)
{
unsigned long l[2];
unsigned long increment = 0; /* avoid gcc warnings */
int precision_loss;
int exp;
FPU_REG tmp;
l[0] = 0;
l[1] = 0;
if (st0_tag == TAG_Valid) {
reg_copy(st0_ptr, &tmp);
exp = exponent(&tmp);
if (exp < DOUBLE_Emin) { /* It may be a denormal */
addexponent(&tmp, -DOUBLE_Emin + 52); /* largest exp to be 51 */
denormal_arg:
if ((precision_loss = FPU_round_to_int(&tmp, st0_tag))) {
#ifdef PECULIAR_486
/* Did it round to a non-denormal ? */
/* This behaviour might be regarded as peculiar, it appears
that the 80486 rounds to the dest precision, then
converts to decide underflow. */
if (!
((tmp.sigh == 0x00100000) && (tmp.sigl == 0)
&& (st0_ptr->sigl & 0x000007ff)))
#endif /* PECULIAR_486 */
{
EXCEPTION(EX_Underflow);
/* This is a special case: see sec 16.2.5.1 of
the 80486 book */
if (!(control_word & CW_Underflow))
return 0;
}
EXCEPTION(precision_loss);
if (!(control_word & CW_Precision))
return 0;
}
l[0] = tmp.sigl;
l[1] = tmp.sigh;
} else {
if (tmp.sigl & 0x000007ff) {
precision_loss = 1;
switch (control_word & CW_RC) {
case RC_RND:
/* Rounding can get a little messy.. */
increment = ((tmp.sigl & 0x7ff) > 0x400) | /* nearest */
((tmp.sigl & 0xc00) == 0xc00); /* odd -> even */
break;
case RC_DOWN: /* towards -infinity */
increment =
signpositive(&tmp) ? 0 : tmp.
sigl & 0x7ff;
break;
case RC_UP: /* towards +infinity */
increment =
signpositive(&tmp) ? tmp.
sigl & 0x7ff : 0;
break;
case RC_CHOP:
increment = 0;
break;
}
/* Truncate the mantissa */
tmp.sigl &= 0xfffff800;
if (increment) {
if (tmp.sigl >= 0xfffff800) {
/* the sigl part overflows */
if (tmp.sigh == 0xffffffff) {
/* The sigh part overflows */
tmp.sigh = 0x80000000;
exp++;
if (exp >= EXP_OVER)
goto overflow;
} else {
tmp.sigh++;
}
tmp.sigl = 0x00000000;
} else {
/* We only need to increment sigl */
tmp.sigl += 0x00000800;
}
}
} else
precision_loss = 0;
l[0] = (tmp.sigl >> 11) | (tmp.sigh << 21);
l[1] = ((tmp.sigh >> 11) & 0xfffff);
if (exp > DOUBLE_Emax) {
overflow:
EXCEPTION(EX_Overflow);
if (!(control_word & CW_Overflow))
return 0;
set_precision_flag_up();
if (!(control_word & CW_Precision))
return 0;
/* This is a special case: see sec 16.2.5.1 of the 80486 book */
/* Overflow to infinity */
l[1] = 0x7ff00000; /* Set to + INF */
} else {
if (precision_loss) {
if (increment)
set_precision_flag_up();
else
set_precision_flag_down();
}
/* Add the exponent */
l[1] |= (((exp + DOUBLE_Ebias) & 0x7ff) << 20);
}
}
} else if (st0_tag == TAG_Zero) {
/* Number is zero */
} else if (st0_tag == TAG_Special) {
st0_tag = FPU_Special(st0_ptr);
if (st0_tag == TW_Denormal) {
/* A denormal will always underflow. */
#ifndef PECULIAR_486
/* An 80486 is supposed to be able to generate
a denormal exception here, but... */
/* Underflow has priority. */
if (control_word & CW_Underflow)
denormal_operand();
#endif /* PECULIAR_486 */
reg_copy(st0_ptr, &tmp);
goto denormal_arg;
} else if (st0_tag == TW_Infinity) {
l[1] = 0x7ff00000;
} else if (st0_tag == TW_NaN) {
/* Is it really a NaN ? */
if ((exponent(st0_ptr) == EXP_OVER)
&& (st0_ptr->sigh & 0x80000000)) {
/* See if we can get a valid NaN from the FPU_REG */
l[0] =
(st0_ptr->sigl >> 11) | (st0_ptr->
sigh << 21);
l[1] = ((st0_ptr->sigh >> 11) & 0xfffff);
if (!(st0_ptr->sigh & 0x40000000)) {
/* It is a signalling NaN */
EXCEPTION(EX_Invalid);
if (!(control_word & CW_Invalid))
return 0;
l[1] |= (0x40000000 >> 11);
}
l[1] |= 0x7ff00000;
} else {
/* It is an unsupported data type */
EXCEPTION(EX_Invalid);
if (!(control_word & CW_Invalid))
return 0;
l[1] = 0xfff80000;
}
}
} else if (st0_tag == TAG_Empty) {
/* Empty register (stack underflow) */
EXCEPTION(EX_StackUnder);
if (control_word & CW_Invalid) {
/* The masked response */
/* Put out the QNaN indefinite */
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(dfloat, 8);
FPU_put_user(0, (unsigned long __user *)dfloat);
FPU_put_user(0xfff80000,
1 + (unsigned long __user *)dfloat);
RE_ENTRANT_CHECK_ON;
return 1;
} else
return 0;
}
if (getsign(st0_ptr))
l[1] |= 0x80000000;
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(dfloat, 8);
FPU_put_user(l[0], (unsigned long __user *)dfloat);
FPU_put_user(l[1], 1 + (unsigned long __user *)dfloat);
RE_ENTRANT_CHECK_ON;
return 1;
}