mirror of
https://github.com/ish-app/ish.git
synced 2026-01-25 14:06:40 +00:00
456 lines
13 KiB
C
456 lines
13 KiB
C
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
#include "float80.h"
|
|
|
|
// If you don't understand why something is the way it is, change it and run
|
|
// the test suite and all will become clear.
|
|
|
|
// exponent is stored with a constant added to it, because that's apparently
|
|
// easier than just saying the exponent is two's complement
|
|
#define BIAS80 0x3fff
|
|
#define EXP_MAX 0x7ffe
|
|
#define EXP_MIN 0x0001
|
|
#define EXP_SPECIAL 0x7fff
|
|
#define EXP_DENORMAL 0
|
|
static unsigned bias(int exp) {
|
|
return exp + BIAS80;
|
|
}
|
|
static int unbias(unsigned exp) {
|
|
return exp - BIAS80;
|
|
}
|
|
// returns the correct answer for denormal numbers
|
|
static int unbias_denormal(unsigned exp) {
|
|
if (exp == EXP_DENORMAL)
|
|
return unbias(EXP_MIN);
|
|
return unbias(exp);
|
|
}
|
|
|
|
#define CURSED_BIT (1ul << 63)
|
|
|
|
__thread enum f80_rounding_mode f80_rounding_mode;
|
|
|
|
// shift a 128 bit integer right but using the floating point rounding mode
|
|
// used by f80_shift_right and to round the 128-bit result of multiplying significands
|
|
static unsigned __int128 u128_shift_right_round(unsigned __int128 i, int shift) {
|
|
switch (f80_rounding_mode) {
|
|
case round_chop:
|
|
i >>= shift;
|
|
break;
|
|
|
|
case round_to_nearest:
|
|
// we're going to be shifting stuff by shift - 1, so stay safe
|
|
if (shift == 0)
|
|
break;
|
|
|
|
// grab the last bit shifted out
|
|
int last_bit = (i >> (shift - 1)) & 1;
|
|
i >>= shift;
|
|
// if every bit being shifted out is zero except for the last one, round to even
|
|
if ((i & ~(-1ul << (shift - 1))) == 0) {
|
|
if (i & 1)
|
|
i++;
|
|
// otherwise if last bit is one, round up
|
|
} else if (last_bit) {
|
|
i++;
|
|
}
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
// may overflow
|
|
static float80 f80_shift_left(float80 f, int shift) {
|
|
f.signif <<= shift;
|
|
f.exp -= shift;
|
|
return f;
|
|
}
|
|
|
|
// may lose precision
|
|
static float80 f80_shift_right(float80 f, int shift) {
|
|
if (shift > 63)
|
|
// shifts beyond the size of the type are undefined behavior
|
|
f.signif = 0;
|
|
else
|
|
f.signif = u128_shift_right_round(f.signif, shift);
|
|
f.exp += shift;
|
|
return f;
|
|
}
|
|
|
|
// a number is unsupported if the cursed bit (first bit of the significand,
|
|
// also known as the integer bit) is incorrect. it must be 0 for denormals and
|
|
// 1 for any other type of number.
|
|
static bool f80_is_supported(float80 f) {
|
|
if (f.exp == EXP_DENORMAL)
|
|
return f.signif >> 63 == 0;
|
|
return f.signif >> 63 == 1;
|
|
}
|
|
|
|
bool f80_isnan(float80 f) {
|
|
return f.exp == EXP_SPECIAL && (f.signif & (-1ul >> 1)) != 0;
|
|
}
|
|
bool f80_isinf(float80 f) {
|
|
return f.exp == EXP_SPECIAL && (f.signif & (-1ul >> 1)) == 0;
|
|
}
|
|
static bool f80_iszero(float80 f) {
|
|
return f.exp == EXP_DENORMAL && f.signif == 0;
|
|
}
|
|
|
|
static float80 f80_normalize(float80 f) {
|
|
// this function probably can't handle unsupported numbers
|
|
// except cursed normals which are just unnormals, and working with them is the point of this function
|
|
if (f.exp == EXP_DENORMAL || f.exp == EXP_SPECIAL)
|
|
assert(f80_is_supported(f));
|
|
|
|
// denormals (and zero) are already normalized (unlike the name suggests)
|
|
if (f.exp == EXP_DENORMAL)
|
|
return f;
|
|
// shift left as many times as possible without overflow
|
|
// number of leading zeroes = how many times we can shift out a leading digit before overflow
|
|
int shift = __builtin_clzl(f.signif);
|
|
if (f.signif == 0)
|
|
shift = 64; // __builtin_clzl has undefined result with zero
|
|
if (f.exp - shift < EXP_MIN) {
|
|
// if we shifted this much, exponent would go below its minimum
|
|
// so shift as much as possible and create a denormal
|
|
f = f80_shift_left(f, f.exp - EXP_MIN);
|
|
f.exp = EXP_DENORMAL;
|
|
return f;
|
|
}
|
|
return f80_shift_left(f, shift);
|
|
}
|
|
|
|
static float80 u128_normalize_round(unsigned __int128 signif, int exp) {
|
|
// correctly counting leading zeros on a 128-bit int is interesting
|
|
int shift = __builtin_clzl((uint64_t) (signif >> 64));
|
|
if (signif >> 64 == 0)
|
|
shift = 64 + __builtin_clzl((uint64_t) signif);
|
|
if (signif == 0)
|
|
shift = 128;
|
|
// now shift left
|
|
if (exp - shift < unbias(EXP_MIN)) {
|
|
signif <<= exp - unbias(EXP_MIN);
|
|
exp = unbias(EXP_DENORMAL);
|
|
} else {
|
|
signif <<= shift;
|
|
exp -= shift;
|
|
}
|
|
// and round
|
|
float80 f;
|
|
f.exp = bias(exp);
|
|
f.signif = u128_shift_right_round(signif, 64);
|
|
return f;
|
|
}
|
|
|
|
float80 f80_from_int(int64_t i) {
|
|
// stick i in the significand, give it an exponent of 2^63 to offset the
|
|
// implicit binary point after the first bit, and then normalize
|
|
float80 f = {
|
|
.signif = i,
|
|
.exp = bias(63),
|
|
.sign = 0,
|
|
};
|
|
if (i == 0)
|
|
f.exp = 0;
|
|
if (i < 0) {
|
|
f.sign = 1;
|
|
f.signif = -i;
|
|
}
|
|
return f80_normalize(f);
|
|
}
|
|
|
|
int64_t f80_to_int(float80 f) {
|
|
if (!f80_is_supported(f))
|
|
return INT64_MIN; // indefinite
|
|
// if you need an exponent greater than 2^63 to represent this number, it
|
|
// can't be represented as a 64-bit integer
|
|
if (f.exp > bias(63))
|
|
return !f.sign ? INT64_MAX : INT64_MIN;
|
|
// shift right (reduce precision) until the exponent is 2^63
|
|
f = f80_shift_right(f, bias(63) - f.exp);
|
|
// and the answer should be the significand!
|
|
return !f.sign ? f.signif : -f.signif;
|
|
}
|
|
|
|
struct double_bits {
|
|
unsigned long signif:52;
|
|
unsigned exp:11;
|
|
unsigned sign:1;
|
|
};
|
|
#define EXP64_MAX 0x7fe
|
|
#define EXP64_MIN 0x001
|
|
#define EXP64_SPECIAL 0x7ff
|
|
#define EXP64_DENORMAL 0x000
|
|
|
|
// unsupported?
|
|
float80 f80_from_double(double d) {
|
|
struct double_bits db;
|
|
memcpy(&db, &d, sizeof(db));
|
|
float80 f;
|
|
|
|
if (db.exp == EXP64_SPECIAL)
|
|
f.exp = EXP_SPECIAL;
|
|
else if (db.exp == EXP64_DENORMAL)
|
|
// denormals actually have an exponent of EXP_MIN, the special exponent
|
|
// is needed to indicate the integer bit is 0
|
|
// zeroes have the same exponent as denormals but need to be handled
|
|
// differently
|
|
f.exp = db.signif == 0 ? 0 : bias(1 - 0x3ff);
|
|
else
|
|
f.exp = bias((int) db.exp - 0x3ff);
|
|
|
|
f.signif = db.signif << 11;
|
|
if (db.exp != EXP64_DENORMAL)
|
|
f.signif |= CURSED_BIT;
|
|
f.sign = db.sign;
|
|
return f80_normalize(f);
|
|
}
|
|
|
|
double f80_to_double(float80 f) {
|
|
if (!f80_is_supported(f))
|
|
return NAN;
|
|
struct double_bits db;
|
|
db.sign = f.sign;
|
|
int new_exp = unbias(f.exp) + 0x3ff;
|
|
if (f.exp == EXP_SPECIAL)
|
|
new_exp = EXP64_SPECIAL;
|
|
else if (new_exp > EXP64_MAX)
|
|
// out of range
|
|
return !f.sign ? INFINITY : -INFINITY;
|
|
if (new_exp <= 0) {
|
|
// number can only be represented in double precision as a denormal
|
|
// shift it enough to make the exponent into EXP64_MIN
|
|
// does it work on numbers that are not denormal but are too small to represent as double?
|
|
f.signif >>= 1;
|
|
f = f80_shift_right(f, -new_exp);
|
|
new_exp = unbias(f.exp) + 0x3ff;
|
|
}
|
|
db.exp = new_exp;
|
|
db.signif = f.signif >> 11;
|
|
double d;
|
|
memcpy(&d, &db, sizeof(db));
|
|
return d;
|
|
}
|
|
|
|
float80 f80_neg(float80 f) {
|
|
f.sign = ~f.sign;
|
|
return f;
|
|
}
|
|
float80 f80_abs(float80 f) {
|
|
f.sign = 0;
|
|
return f;
|
|
}
|
|
|
|
float80 f80_add(float80 a, float80 b) {
|
|
if (!f80_is_supported(a) || !f80_is_supported(b))
|
|
return F80_NAN;
|
|
|
|
// a has larger exponent, b has smaller exponent
|
|
if (a.exp < b.exp) {
|
|
float80 tmp = a;
|
|
a = b;
|
|
b = tmp;
|
|
}
|
|
|
|
if (f80_isnan(a))
|
|
return a;
|
|
|
|
// reduce the number of cases to deal with
|
|
bool flipped = false;
|
|
if (a.sign) {
|
|
a.sign = ~a.sign;
|
|
b.sign = ~b.sign;
|
|
flipped = true;
|
|
}
|
|
// now either both are positive (addition) or a is positive and b is
|
|
// negative (subtraction)
|
|
|
|
// shift b (smaller exponent) right until the exponents are equal
|
|
float80 orig_b = b;
|
|
b = f80_shift_right(b, a.exp - b.exp);
|
|
assert(a.exp == b.exp);
|
|
|
|
float80 f = {.exp = a.exp};
|
|
if (!b.sign) {
|
|
// b is postive, so add
|
|
if (__builtin_uaddl_overflow(a.signif, b.signif, &f.signif)) {
|
|
// overflow, shift right by 1 place and set the cursed bit (which the overflow is into)
|
|
// skip the shift if the exponent is special
|
|
if (f.exp != EXP_SPECIAL)
|
|
f = f80_shift_right(f, 1);
|
|
f.signif |= CURSED_BIT;
|
|
}
|
|
} else {
|
|
// b is negative, so subtract
|
|
|
|
// infinity - infinity is indefinite, not zero
|
|
if (f80_isinf(a) && f80_isinf(orig_b))
|
|
return F80_NAN;
|
|
|
|
if (a.signif >= b.signif) {
|
|
// we can subtract without underflow
|
|
f.signif = a.signif - b.signif;
|
|
} else {
|
|
// the answer will be negative
|
|
f.signif = b.signif - a.signif;
|
|
f.sign = 1;
|
|
}
|
|
|
|
if (f.signif == 0) {
|
|
f.exp = 0;
|
|
// only way to get negative zero would be from -0 + -0, which is
|
|
// handled by the other case
|
|
// so skip the flip
|
|
return f;
|
|
}
|
|
f = f80_normalize(f);
|
|
}
|
|
|
|
if (flipped)
|
|
f.sign = ~f.sign;
|
|
assert(f80_is_supported(f));
|
|
return f;
|
|
}
|
|
float80 f80_sub(float80 a, float80 b) {
|
|
return f80_add(a, f80_neg(b));
|
|
}
|
|
|
|
float80 f80_mul(float80 a, float80 b) {
|
|
if (!f80_is_supported(a) || !f80_is_supported(b))
|
|
return F80_NAN;
|
|
if (f80_isnan(a))
|
|
return F80_NAN;
|
|
if (f80_isnan(b))
|
|
return F80_NAN;
|
|
|
|
if (f80_isinf(a) || f80_isinf(b)) {
|
|
// infinity times zero is undefined
|
|
if (f80_iszero(a) || f80_iszero(b))
|
|
return F80_NAN;
|
|
// infinity times anything else is infinity
|
|
float80 f = F80_INF;
|
|
f.sign = a.sign ^ b.sign;
|
|
return f;
|
|
}
|
|
|
|
// add exponents (the +1 is necessary to be correct in 128-bit precision)
|
|
int f_exp = unbias_denormal(a.exp) + unbias_denormal(b.exp) + 1;
|
|
// multiply significands
|
|
unsigned __int128 f_signif = (unsigned __int128) a.signif * b.signif;
|
|
// normalize and round the 128-bit result
|
|
float80 f = u128_normalize_round(f_signif, f_exp);
|
|
// xor signs
|
|
f.sign = a.sign ^ b.sign;
|
|
return f;
|
|
}
|
|
|
|
// FIXME this is sort of broken for dividing by very small numbers (good enough for now though)
|
|
float80 f80_div(float80 a, float80 b) {
|
|
if (!f80_is_supported(a) || !f80_is_supported(b))
|
|
return F80_NAN;
|
|
if (f80_isnan(a))
|
|
return F80_NAN;
|
|
if (f80_isnan(b))
|
|
return F80_NAN;
|
|
|
|
float80 f;
|
|
if (f80_isinf(a)) {
|
|
// dividing into infinity gives infinity
|
|
f = F80_INF;
|
|
// except infinity / infinity is nan
|
|
if (f80_isinf(b))
|
|
return F80_NAN;
|
|
} else if (f80_isinf(b)) {
|
|
// dividing by infinity gives zero
|
|
f = (float80) {0};
|
|
} else if (f80_iszero(b)) {
|
|
// division by zero gives infinity
|
|
f = F80_INF;
|
|
// except 0 / 0 is nan
|
|
if (f80_iszero(a))
|
|
f = F80_NAN;
|
|
} else {
|
|
unsigned __int128 signif = ((unsigned __int128) a.signif << 64) / b.signif;
|
|
int exp = unbias_denormal(a.exp) - unbias_denormal(b.exp) + 63;
|
|
f = u128_normalize_round(signif, exp);
|
|
}
|
|
|
|
f.sign = a.sign ^ b.sign;
|
|
return f;
|
|
}
|
|
|
|
float80 f80_mod(float80 x, float80 y) {
|
|
float80 quotient = f80_div(x, y);
|
|
enum f80_rounding_mode old_mode = f80_rounding_mode;
|
|
f80_rounding_mode = round_chop;
|
|
quotient = f80_from_int(f80_to_int(quotient)); // TODO just make a f80_round_to_int function
|
|
f80_rounding_mode = old_mode;
|
|
return f80_sub(x, f80_mul(quotient, y));
|
|
}
|
|
|
|
bool f80_uncomparable(float80 a, float80 b) {
|
|
if (!f80_is_supported(a) || !f80_is_supported(b))
|
|
return true;
|
|
if (f80_isnan(a) || f80_isnan(b))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool f80_lt(float80 a, float80 b) {
|
|
if (f80_uncomparable(a, b))
|
|
return false;
|
|
// same signed infinities are equal, not less (though subtraction would produce nan)
|
|
if (f80_isinf(a) && f80_isinf(b) && a.sign == b.sign)
|
|
return false;
|
|
// zeroes are always equal
|
|
if (f80_iszero(a) && f80_iszero(b) && a.sign != b.sign)
|
|
return true;
|
|
// if a < b then a - b < 0
|
|
float80 diff = f80_sub(a, b);
|
|
return diff.sign == 1;
|
|
}
|
|
bool f80_eq(float80 a, float80 b) {
|
|
if (f80_uncomparable(a, b))
|
|
return false;
|
|
if (f80_iszero(a)) a.sign = 0;
|
|
if (f80_iszero(a)) b.sign = 0;
|
|
return a.sign == b.sign && a.exp == b.exp && a.signif == b.signif;
|
|
}
|
|
|
|
bool f80_lte(float80 a, float80 b) {
|
|
return f80_lt(a, b) || f80_eq(a, b);
|
|
}
|
|
bool f80_gt(float80 a, float80 b) {
|
|
return !f80_lte(a, b);
|
|
}
|
|
|
|
float80 f80_log2(float80 x) {
|
|
float80 zero = f80_from_int(0);
|
|
float80 one = f80_from_int(1);
|
|
float80 two = f80_from_int(2);
|
|
|
|
int ipart = 0;
|
|
while (f80_lt(x, one)) {
|
|
ipart--;
|
|
x = f80_mul(x, two);
|
|
}
|
|
while (f80_gt(x, two)) {
|
|
ipart++;
|
|
x = f80_div(x, two);
|
|
}
|
|
float80 res = f80_from_int(ipart);
|
|
|
|
float80 bit = one;
|
|
while (f80_gt(bit, zero)) {
|
|
while (f80_lte(x, two) && f80_gt(bit, zero)) {
|
|
x = f80_mul(x, x);
|
|
bit = f80_div(bit, two);
|
|
}
|
|
res = f80_add(res, bit);
|
|
x = f80_div(x, two);
|
|
}
|
|
return res;
|
|
}
|
|
|