rizin/librz/util/float/float_internal.c
Dhruv Maroo 70fb3be17f 80-bit float initialization works now
* Added a test in `test_float.c` for it
    * It is kind of unfortunate to deal with the idiosyncrasy of 80-bit
      float this way. There are cleaner ways, but they would require
      more refactor, and are probably not worth it since we wouldn't be
      modifying the float code that often.
2024-02-17 15:46:11 +08:00

496 lines
14 KiB
C

// SPDX-FileCopyrightText: 2022 heersin <teablearcher@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
/**
* \file : Internal function for float
* \brief : Should be included directly in float.c
*/
static inline ut32 rz_float_info_bin16(RzFloatInfo which_info) {
switch (which_info) {
case RZ_FLOAT_INFO_BASE:
return 2;
case RZ_FLOAT_INFO_EXP_LEN:
return 5;
case RZ_FLOAT_INFO_MAN_LEN:
return 10;
case RZ_FLOAT_INFO_TOTAL_LEN:
return 16;
case RZ_FLOAT_INFO_BIAS:
return 15;
default:
rz_warn_if_reached();
return 0;
}
}
static inline ut32 rz_float_info_bin32(RzFloatInfo which_info) {
switch (which_info) {
case RZ_FLOAT_INFO_BASE:
return 2;
case RZ_FLOAT_INFO_EXP_LEN:
return 8;
case RZ_FLOAT_INFO_MAN_LEN:
return 23;
case RZ_FLOAT_INFO_TOTAL_LEN:
return 32;
case RZ_FLOAT_INFO_BIAS:
return 127;
default:
rz_warn_if_reached();
return 0;
}
}
static inline ut32 rz_float_info_bin64(RzFloatInfo which_info) {
switch (which_info) {
case RZ_FLOAT_INFO_BASE:
return 2;
case RZ_FLOAT_INFO_EXP_LEN:
return 11;
case RZ_FLOAT_INFO_MAN_LEN:
return 52;
case RZ_FLOAT_INFO_TOTAL_LEN:
return 64;
case RZ_FLOAT_INFO_BIAS:
return 1023;
default:
rz_warn_if_reached();
return 0;
}
}
static inline ut32 rz_float_info_bin80(RzFloatInfo which_info) {
switch (which_info) {
case RZ_FLOAT_INFO_BASE:
return 2;
case RZ_FLOAT_INFO_EXP_LEN:
return 15;
case RZ_FLOAT_INFO_MAN_LEN:
/* The mantissa is actually 63 bits, but we also include the integer bit
* in the mantissa. Doing this so that the invariant of
* man_len + exp_len + 1 == total_len holds true. */
return 64;
case RZ_FLOAT_INFO_TOTAL_LEN:
return 80;
case RZ_FLOAT_INFO_BIAS:
return 16383;
default:
rz_warn_if_reached();
return 0;
}
}
static inline ut32 rz_float_info_bin128(RzFloatInfo which_info) {
switch (which_info) {
case RZ_FLOAT_INFO_BASE:
return 2;
case RZ_FLOAT_INFO_EXP_LEN:
return 15;
case RZ_FLOAT_INFO_MAN_LEN:
return 112;
case RZ_FLOAT_INFO_TOTAL_LEN:
return 128;
case RZ_FLOAT_INFO_BIAS:
return 16383;
default:
rz_warn_if_reached();
return 0;
}
}
/**
* Shift right, but keeps LSB true if hit 1 during shift
* \param x RzBitVector, pointer to bv
* \param dist shift distance, positive or zero
* \return ret bool, return true if shift success
*/
static bool rz_bv_shift_right_jammed(RzBitVector *bv, ut32 dist) {
rz_return_val_if_fail(bv, false);
bool lsb = false;
for (ut32 i = 0; i < dist; ++i) {
bool b = rz_bv_get(bv, i);
if (b) {
lsb = true;
break;
}
}
rz_bv_rshift(bv, dist);
rz_bv_set(bv, 0, lsb);
return true;
}
/**
* Get a bitvector representation of exponent, have the same length of parameter `bv`
* \param bv RzBitVector, the bitvector interpreted as float
* \param format RzFloatFormat, specifying the format of float
* \return a bitvector representation of exponent
*/
static RZ_OWN RzBitVector *get_exp(RZ_NONNULL RzBitVector *bv, RzFloatFormat format) {
rz_return_val_if_fail(bv, NULL);
ut32 exp_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_EXP_LEN);
ut32 man_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_MAN_LEN);
RzBitVector *res = rz_bv_new(exp_len + man_len + 1);
if (!res) {
RZ_LOG_ERROR("rz_float : failed to create bitvector");
return NULL;
}
rz_bv_copy_nbits(bv, man_len, res, 0, exp_len);
return res;
}
/**
* Get a bitvector representation of mantissa, have the same length of parameter `bv`
* \param bv RzBitVector, the bitvector interpreted as float
* \param format RzFloatFormat, specifying the format of float
* \return a bitvector representation of mantissa
*/
static RZ_OWN RzBitVector *get_man(RZ_NONNULL RzBitVector *bv, RzFloatFormat format) {
rz_return_val_if_fail(bv, NULL);
ut32 exp_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_EXP_LEN);
ut32 man_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_MAN_LEN);
RzBitVector *res = rz_bv_new(exp_len + man_len + 1);
if (!res) {
RZ_LOG_ERROR("rz_float : failed to create bitvector");
return NULL;
}
rz_bv_copy_nbits(bv, 0, res, 0, man_len);
return res;
}
/**
* Get a bitvector representation of mantissa, twice as long as `bv` length.
* \param bv RzBitVector, the bitvector interpreted as float
* \param format RzFloatFormat, specifying the format of float
* \return a bitvector representation of mantissa
*/
static RZ_OWN RzBitVector *get_man_stretched(RZ_NONNULL RzBitVector *bv, RzFloatFormat format) {
rz_return_val_if_fail(bv, NULL);
ut32 man_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_MAN_LEN);
ut32 total = rz_float_get_format_info(format, RZ_FLOAT_INFO_TOTAL_LEN);
RzBitVector *res = rz_bv_new(total * 2);
if (!res) {
RZ_LOG_ERROR("rz_float : failed to create bitvector");
return NULL;
}
rz_bv_copy_nbits(bv, 0, res, 0, man_len);
return res;
}
/**
* Get a bitvector representation of exponent. The length is depending on the exponent width (specified by `format`)
* \param bv RzBitVector, the bitvector interpreted as float
* \param format RzFloatFormat, specifying the format of float
* \return a bitvector representation of exponent
*/
static RZ_OWN RzBitVector *get_exp_squashed(RZ_NONNULL RzBitVector *bv, RzFloatFormat format) {
rz_return_val_if_fail(bv, NULL);
ut32 exp_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_EXP_LEN);
ut32 man_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_MAN_LEN);
RzBitVector *res = rz_bv_new(exp_len);
if (!res) {
RZ_LOG_ERROR("rz_float : failed to create bitvector");
return NULL;
}
rz_bv_copy_nbits(bv, man_len, res, 0, exp_len);
return res;
}
/**
* Get a bitvector representation of mantissa. The length is depending on the mantissa width (specified by `format`)
* \param bv RzBitVector, the bitvector interpreted as float
* \param format RzFloatFormat, specifying the format of float
* \return a bitvector representation of mantissa
*/
static RZ_OWN RzBitVector *get_man_squashed(RZ_NONNULL RzBitVector *bv, RzFloatFormat format) {
rz_return_val_if_fail(bv, NULL);
ut32 man_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_MAN_LEN);
RzBitVector *res = rz_bv_new(man_len);
if (!res) {
RZ_LOG_ERROR("rz_float : failed to create bitvector");
return NULL;
}
rz_bv_copy_nbits(bv, 0, res, 0, man_len);
return res;
}
/**
* Get the sign of bv
* \param bv RzBitVector, the bitvector interpreted as float
* \param format RzFloatFormat, specifying the format of float
* \return bool sign of float bv
*/
static RZ_OWN bool get_sign(RZ_NONNULL RzBitVector *bv, RzFloatFormat format) {
rz_return_val_if_fail(bv, false);
return rz_bv_get(bv, bv->len - 1);
}
/**
* make a float becomes positive, would changed the float itself
* \param f float to be converted
* \return true if success
*/
static bool rz_make_fabs(RzFloat *f) {
return rz_bv_set(f->s, f->s->len - 1, false);
}
/**
* get the half value of a float (by decreasing exponent value)
* \param f float
* \return half value of a float
*/
static RzFloat *rz_half_float(RzFloat *f) {
ut32 total = rz_float_get_format_info(f->r, RZ_FLOAT_INFO_TOTAL_LEN);
ut32 exp_start = rz_float_get_format_info(f->r, RZ_FLOAT_INFO_MAN_LEN);
// for exp sub 1
RzBitVector *sub = rz_bv_new(total);
rz_bv_set(sub, exp_start, true);
RzFloat *half = rz_float_new(f->r);
rz_bv_free(half->s);
half->s = rz_bv_sub(f->s, sub, NULL);
rz_bv_free(sub);
return half;
}
/**
* Pack sign, exponent, and significant together to float bv
* \param sign sign of float
* \param exp exponent part, can be squashed or normal
* \param sig significant part (mantissa with a leading bit 1), can be squashed or normal
* \param format format of float
* \return RzBitVector the final bitvector representation of RzFloat
*/
static RZ_OWN RzBitVector *pack_float_bv(bool sign, RZ_BORROW RZ_NONNULL const RzBitVector *exp, RZ_BORROW RZ_NONNULL const RzBitVector *sig, RzFloatFormat format) {
ut32 exp_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_EXP_LEN);
ut32 man_len = rz_float_get_format_info(format, RZ_FLOAT_INFO_MAN_LEN);
ut32 total = rz_float_get_format_info(format, RZ_FLOAT_INFO_TOTAL_LEN);
RzBitVector *ret = rz_bv_new(total);
// copy exp to ret
rz_bv_copy_nbits(exp, 0, ret, man_len, exp_len);
if (format == RZ_FLOAT_IEEE754_BIN_80) {
/* 80-bit floats have a special bit at position 63 (man_len) which is
* called the integer bit. We need to account for that, hence the
* branching here. See https://en.wikipedia.org/wiki/Extended_precision
* for more. */
rz_bv_set(ret, man_len - 1, true);
rz_bv_copy_nbits(sig, 1, ret, 0, man_len - 1);
} else {
rz_bv_copy_nbits(sig, 0, ret, 0, man_len);
}
rz_bv_set(ret, total - 1, sign);
return ret;
}
/**
* detect if should drop extra tailing bits in rounding
* GRS konwn as G(guard bit), R(round bit), and S(sticky bit)
* they are 3 bits after the LSB bit of rounded result, which is drop in rounding
* note that this function has no concept to float format
* \param sign sign of given significant bitvector, 1 is negative
* \param sig bitvector, required to have 0..01M..M form, exponent is managed by caller
* assumption1: radix point is right after 1, that means the real value of such a bitvector is 1.MMM..M
* assumption2: `sig` is an unsigned bitvector
* \param precision number of how many `M` bits to be reserved in rounding
* \param mode rounding mode
* \param should_inc pointer to a bool:
* 0 if drop GRS,
* 1 means caller should round by adding ULP to `return bitv`
* \return new bitvector would be 0001MM...M, which length is `precision + 1 + 3`
*/
static RzBitVector *round_significant(bool sign, RzBitVector *sig, ut32 precision, RzFloatRMode mode, bool *should_inc) {
rz_return_val_if_fail(sig && should_inc, NULL);
ut32 sig_len = rz_bv_len(sig) - rz_bv_clz(sig);
ut32 mantissa_len = sig_len - 1;
ut32 ret_len = precision + 3 + 1;
RzBitVector *ret;
if (mantissa_len < ret_len) {
// copy and shift in one operation
// equal to the following operations
// 1. copy bv from sig to ret
// 2. align `ret` to `1 MM..M 000` form by shifting left
ret = rz_bv_new(ret_len);
rz_bv_copy_nbits(sig, 0, ret, ret_len - sig_len, sig_len);
} else {
// if it's greater than `ret`, right shift and cut
// use jammed version of right shift to get sticky bit
ut32 shift_dist = sig_len - ret_len;
RzBitVector *sig_dup = rz_bv_dup(sig);
rz_bv_shift_right_jammed(sig_dup, shift_dist);
ret = rz_bv_cut_head(sig_dup, rz_bv_len(sig) - ret_len);
rz_bv_free(sig_dup);
}
// default is drop
*should_inc = false;
if (mode == RZ_FLOAT_RMODE_RNE || mode == RZ_FLOAT_RMODE_RNA) {
bool guard_bit = rz_bv_get(ret, 2);
bool round_bit = rz_bv_get(ret, 1);
bool sticky_bit = rz_bv_get(ret, 0);
rz_bv_rshift(ret, 3);
// for G R S bits
// > 1 0 0 : round up
// = 1 0 0 : ties
// < 1 0 0 : round down
if (guard_bit == 0) {
*should_inc = 0;
} else if (!round_bit && !sticky_bit) {
// ties
if (mode == RZ_FLOAT_RMODE_RNE) {
bool is_odd = rz_bv_get(ret, 0);
*should_inc = is_odd ? 1 : 0;
}
if (mode == RZ_FLOAT_RMODE_RNA) {
*should_inc = 1;
}
} else {
*should_inc = 1;
}
return ret;
}
if (mode == (sign ? RZ_FLOAT_RMODE_RTN : RZ_FLOAT_RMODE_RTP)) {
*should_inc = 1;
// rshift to remove RGS
rz_bv_rshift(ret, 3);
return ret;
}
// mode == RTZ or others, simply drop bits
rz_bv_rshift(ret, 3);
return ret;
}
/**
* \brief Rounding method.
* this function is a wrapper of round_significant, it manage the rounded result and exponent change
* |f| = sig * 2^exp_no_bias
* \details it assumes first bit 1 as the hidden bit, and radix point is right after it.
* TODO : report exception
* \param sign sign of bitvector
* \param exp exponent value, biased
* \param sig significant, form: 1.MMMM...., for sub-normal,
* caller should set a fake hidden bit to match this format.
* \param format float sort of given exp and sig
* \param new_format format of target float sort
* \param mode rounding mode
* \return a float of type `format`, converted from `sig`
*/
static RZ_OWN RzFloat *round_float_bv_new(bool sign, st32 exp, RzBitVector *sig, RzFloatFormat format, RzFloatFormat new_format, RzFloatRMode mode) {
rz_return_val_if_fail(sig, NULL);
RzFloat *ret;
ut32 new_man_len = rz_float_get_format_info(new_format, RZ_FLOAT_INFO_MAN_LEN);
ut32 new_total_len = rz_float_get_format_info(new_format, RZ_FLOAT_INFO_TOTAL_LEN);
st32 bias = rz_float_get_format_info(format, RZ_FLOAT_INFO_BIAS);
st32 new_bias = rz_float_get_format_info(new_format, RZ_FLOAT_INFO_BIAS);
st32 exp_val = exp;
st32 exp_max = bias + bias;
st32 exp_min = 0;
// check overflow and underflow
if (exp_val > exp_max) {
ret = rz_float_new_inf(new_format, sign);
ret->exception = RZ_FLOAT_E_OVERFLOW;
return ret;
}
if (exp_val < exp_min) {
ret = rz_float_new_qnan(new_format);
ret->exception = RZ_FLOAT_E_UNDERFLOW;
return ret;
}
bool should_inc = false;
ut32 bit_prec = new_man_len;
RzBitVector *rounded_tmp = round_significant(sign, sig, bit_prec, mode, &should_inc);
if (rounded_tmp == NULL) {
// TODO: error and report in code
// should_inc / sig is NULL
return NULL;
}
// convert rounded bitv to fit given format
RzBitVector *rounded_sig = rz_bv_prepend_zero(rounded_tmp, new_total_len - rz_bv_len(rounded_tmp));
// free rounded tmp result
rz_bv_free(rounded_tmp);
rounded_tmp = NULL;
if (should_inc) {
ut32 sig_carry_pos = new_man_len + 1;
RzBitVector *one = rz_bv_new_one(new_total_len);
rounded_tmp = rz_bv_add(rounded_sig, one, NULL);
rz_bv_free(one);
bool sig_carry = rz_bv_get(rounded_tmp, sig_carry_pos);
if (sig_carry) {
// change exponent, renormalize
exp_val += 1;
if (exp_val > exp_max) {
// overflow
ret = rz_float_new_inf(new_format, sign);
ret->exception = RZ_FLOAT_E_OVERFLOW;
rz_bv_free(rounded_sig);
rz_bv_free(rounded_tmp);
return ret;
}
// renormalize significant
// it has a carry, so last bit is 0 after carry
// safely right shift
rz_bv_rshift(rounded_tmp, 1);
}
rz_bv_free(rounded_sig);
rounded_sig = rounded_tmp;
rounded_tmp = NULL;
}
RzBitVector *exp_bv;
if (exp_val == 0) {
// sub normal one, zero exp bv
exp_bv = rz_bv_new_zero(new_total_len - new_man_len - 1);
// hidden bit set to 0
rz_bv_set(rounded_sig, new_man_len, 0);
} else {
// normal one
exp_bv = rz_bv_new_from_ut64(new_total_len, exp_val - bias + new_bias);
}
// pack to float
ret = RZ_NEW0(RzFloat);
if (!ret) {
rz_bv_free(exp_bv);
rz_bv_free(rounded_sig);
return NULL;
}
ret->s = pack_float_bv(sign, exp_bv, rounded_sig, new_format);
ret->r = new_format;
rz_bv_free(exp_bv);
rz_bv_free(rounded_sig);
return ret;
}