moo/stix/lib/bigint.c
2015-11-01 09:47:27 +00:00

468 lines
12 KiB
C

/*
* $Id$
*
Copyright (c) 2014-2015 Chung, Hyung-Hwan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WAfRRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "stix-prv.h"
#define MAKE_WORD(hw1,hw2) ((stix_oow_t)(hw1) | (stix_oow_t)(hw2) << STIX_OOHW_BITS)
static STIX_INLINE int is_integer (stix_t* stix, stix_oop_t oop)
{
stix_oop_t c;
c = STIX_CLASSOF(stix,oop);
/* TODO: is it better to introduct a special integer mark into the class itself */
return c == stix->_small_integer ||
c == stix->_large_positive_integer ||
c == stix->_large_negative_integer;
}
static STIX_INLINE stix_oop_t make_bigint_with_ooi (stix_t* stix, stix_ooi_t i)
{
stix_oohw_t hw[2];
stix_oow_t w;
if (i >= 0)
{
w = i;
hw[0] = w & STIX_LBMASK(stix_oow_t,STIX_OOHW_BITS);
hw[1] = w >> STIX_OOHW_BITS;
return stix_instantiate (stix, stix->_large_positive_integer, &hw, (hw[1] > 0? 2: 1));
}
else
{
w = -i;
hw[0] = w & STIX_LBMASK(stix_oow_t,STIX_OOHW_BITS);
hw[1] = w >> STIX_OOHW_BITS;
return stix_instantiate (stix, stix->_large_negative_integer, &hw, (hw[1] > 0? 2: 1));
}
}
static STIX_INLINE stix_oop_t clone_bigint (stix_t* stix, stix_oop_t oop, stix_oow_t count)
{
stix_oop_t z;
stix_oow_t i;
STIX_ASSERT (STIX_OOP_IS_POINTER(oop));
if (count <= 0) count = STIX_OBJ_GET_SIZE(oop);
stix_pushtmp (stix, &oop);
z = stix_instantiate (stix, STIX_OBJ_GET_CLASS(oop), STIX_NULL, count);
stix_poptmp (stix);
if (!z) return STIX_NULL;
for (i = 0; i < count; i++)
{
((stix_oop_halfword_t)z)->slot[i] = ((stix_oop_halfword_t)oop)->slot[i];
}
return z;
}
static STIX_INLINE stix_oow_t count_effective_digits (stix_oop_t oop)
{
stix_oow_t i;
for (i = STIX_OBJ_GET_SIZE(oop); i > 1; )
{
--i;
if (((stix_oop_halfword_t)oop)->slot[i] != 0) return i + 1;
}
return 1;
}
static stix_oop_t normalize_bigint (stix_t* stix, stix_oop_t oop)
{
stix_oow_t count;
STIX_ASSERT (STIX_OOP_IS_POINTER(oop));
count = count_effective_digits (oop);
if (count == 1)
{
if (STIX_OBJ_GET_CLASS(oop) == stix->_large_positive_integer)
{
return STIX_OOP_FROM_SMINT(((stix_oop_halfword_t)oop)->slot[0]);
}
else
{
STIX_ASSERT (STIX_OBJ_GET_CLASS(oop) == stix->_large_negative_integer);
return STIX_OOP_FROM_SMINT(-(stix_oow_t)((stix_oop_halfword_t)oop)->slot[0]);
}
}
else if (count == 2)
{
stix_oow_t w;
w = MAKE_WORD (((stix_oop_halfword_t)oop)->slot[0], ((stix_oop_halfword_t)oop)->slot[1]);
if (STIX_OBJ_GET_CLASS(oop) == stix->_large_positive_integer)
{
if (w <= STIX_SMINT_MAX) return STIX_OOP_FROM_SMINT(w);
}
else
{
STIX_ASSERT (STIX_OBJ_GET_CLASS(oop) == stix->_large_negative_integer);
/*if (w <= -STIX_SMINT_MIN) */
if (w <= ((stix_oow_t)STIX_SMINT_MAX + 1)) return STIX_OOP_FROM_SMINT(-(stix_ooi_t)w);
}
}
if (STIX_OBJ_GET_SIZE(oop) == count)
{
/* no compaction is needed. return it as it is */
return oop;
}
return clone_bigint (stix, oop, count);
}
static STIX_INLINE int is_less_unsigned_array (const stix_oohw_t* x, stix_oow_t xs, const stix_oohw_t* y, stix_oow_t ys)
{
stix_oow_t i;
if (xs != ys) return xs < ys;
for (i = xs; i > 0; )
{
--i;
if (x[i] != y[i]) return x[i] < y[i];
}
return 0;
}
static STIX_INLINE int is_less_unsigned (stix_oop_t x, stix_oop_t y)
{
return is_less_unsigned_array (
((stix_oop_halfword_t)x)->slot, STIX_OBJ_GET_SIZE(x),
((stix_oop_halfword_t)y)->slot, STIX_OBJ_GET_SIZE(y));
}
static STIX_INLINE int is_less (stix_t* stix, stix_oop_t x, stix_oop_t y)
{
if (STIX_OBJ_GET_CLASS(x) != STIX_OBJ_GET_CLASS(y))
{
return STIX_OBJ_GET_CLASS(x) == stix->_large_negative_integer;
}
return is_less_unsigned (x, y);
}
static STIX_INLINE int is_equal (stix_t* stix, stix_oop_t x, stix_oop_t y)
{
/* check if two large integers are equal to each other */
return STIX_OBJ_GET_CLASS(x) == STIX_OBJ_GET_CLASS(y) && STIX_OBJ_GET_SIZE(x) == STIX_OBJ_GET_SIZE(y) &&
STIX_MEMCMP(((stix_oop_halfword_t)x)->slot, ((stix_oop_halfword_t)y)->slot, STIX_OBJ_GET_SIZE(x) * STIX_SIZEOF(stix_oohw_t)) == 0;
}
static STIX_INLINE stix_oow_t add_unsigned_array (const stix_oohw_t* x, stix_oow_t xs, const stix_oohw_t* y, stix_oow_t ys, stix_oohw_t* z)
{
stix_oow_t i, w;
stix_oow_t carry = 0;
STIX_ASSERT (xs >= ys);
for (i = 0; i < ys; i++)
{
w = (stix_oow_t)x[i] + (stix_oow_t)y[i] + carry;
carry = w >> STIX_OOHW_BITS;
z[i] = w & STIX_LBMASK(stix_oow_t, STIX_OOHW_BITS);
}
for (; i < xs; i++)
{
w = (stix_oow_t)x[i] + carry;
carry = w >> STIX_OOHW_BITS;
z[i] = w & STIX_LBMASK(stix_oow_t, STIX_OOHW_BITS);
}
if (i > 1 && carry == 0) return i - 1;
z[i] = carry;
return i;
}
static STIX_INLINE stix_oow_t subtract_unsigned_array (const stix_oohw_t* x, stix_oow_t xs, const stix_oohw_t* y, stix_oow_t ys, stix_oohw_t* z)
{
stix_oow_t i, w;
stix_oow_t borrow = 0;
stix_oow_t borrowed_word;
STIX_ASSERT (!is_less_unsigned_array(x, xs, y, ys));
borrowed_word = (stix_oow_t)1 << STIX_OOHW_BITS;
for (i = 0; i < ys; i++)
{
w = (stix_oow_t)y[i] + borrow;
if ((stix_oow_t)x[i] >= w)
{
z[i] = x[i] - w;
borrow = 0;
}
else
{
z[i] = (borrowed_word + (stix_oow_t)x[i]) - w;
borrow = 1;
}
}
for (; i < xs; i++)
{
if (x[i] >= borrow)
{
z[i] = x[i] - (stix_oohw_t)borrow;
borrow = 0;
}
else
{
z[i] = (borrowed_word + (stix_oow_t)x[i]) - borrow;
borrow = 1;
}
}
STIX_ASSERT (borrow == 0);
return i;
}
static stix_oop_t add_unsigned_integers (stix_t* stix, stix_oop_t x, stix_oop_t y)
{
stix_oohw_t* a, * b;
stix_oow_t as, bs, zs;
stix_oop_t z;
as = STIX_OBJ_GET_SIZE(x);
bs = STIX_OBJ_GET_SIZE(y);
zs = (as >= bs? as: bs) + 1;
stix_pushtmp (stix, &x);
stix_pushtmp (stix, &y);
z = stix_instantiate (stix, STIX_OBJ_GET_CLASS(x), STIX_NULL, zs);
stix_poptmps (stix, 2);
if (as >= bs)
{
a = ((stix_oop_halfword_t)x)->slot;
b = ((stix_oop_halfword_t)y)->slot;
}
else
{
a = ((stix_oop_halfword_t)y)->slot;
b = ((stix_oop_halfword_t)x)->slot;
}
add_unsigned_array (a, as, b, bs, ((stix_oop_halfword_t)z)->slot);
return z;
}
static stix_oop_t subtract_unsigned_integers (stix_t* stix, stix_oop_t x, stix_oop_t y)
{
stix_oop_t z;
STIX_ASSERT (!is_less(stix, x, y));
stix_pushtmp (stix, &x);
stix_pushtmp (stix, &y);
z = stix_instantiate (stix, stix->_large_positive_integer, STIX_NULL, STIX_OBJ_GET_SIZE(x));
stix_poptmps (stix, 2);
subtract_unsigned_array (
((stix_oop_halfword_t)x)->slot, STIX_OBJ_GET_SIZE(x),
((stix_oop_halfword_t)y)->slot, STIX_OBJ_GET_SIZE(y),
((stix_oop_halfword_t)z)->slot);
return z;
}
stix_oop_t stix_addints (stix_t* stix, stix_oop_t x, stix_oop_t y)
{
stix_oop_t z;
if (STIX_OOP_IS_SMINT(x) && STIX_OOP_IS_SMINT(y))
{
stix_ooi_t i;
/* no integer overflow/underflow must occur as the possible integer
* range is narrowed by the tag bits used */
i = STIX_OOP_TO_SMINT(x) + STIX_OOP_TO_SMINT(y);
if (STIX_OOI_IN_SMINT_RANGE(i)) return STIX_OOP_FROM_SMINT(i);
return make_bigint_with_ooi (stix, i);
}
else
{
stix_ooi_t v;
int neg;
if (STIX_OOP_IS_SMINT(x))
{
if (!is_integer(stix,y)) goto oops_einval;
v = STIX_OOP_TO_SMINT(x);
if (v == 0) return y;
stix_pushtmp (stix, &y);
x = make_bigint_with_ooi (stix, v);
stix_poptmp (stix);
if (!x) return STIX_NULL;
}
else if (STIX_OOP_IS_SMINT(y))
{
if (!is_integer(stix,x)) goto oops_einval;
v = STIX_OOP_TO_SMINT(y);
if (v == 0) return x;
stix_pushtmp (stix, &x);
y = make_bigint_with_ooi (stix, v);
stix_poptmp (stix);
if (!y) return STIX_NULL;
}
else
{
if (!is_integer(stix,x)) goto oops_einval;
if (!is_integer(stix,y)) goto oops_einval;
}
if (STIX_OBJ_GET_CLASS(x) != STIX_OBJ_GET_CLASS(y))
{
if (STIX_OBJ_GET_CLASS(x) == stix->_large_negative_integer)
{
/* x is negative, y is positive */
z = stix_subints (stix, y, x);
}
else
{
/* x is positive, y is negative */
z = stix_subints (stix, x, y);
}
if (!z) return STIX_NULL;
}
else
{
/* both are positive or negative */
neg = (STIX_OBJ_GET_CLASS(x) == stix->_large_negative_integer);
z = add_unsigned_integers (stix, x, y);
if (!z) return STIX_NULL;
if (neg) STIX_OBJ_SET_CLASS(z, stix->_large_negative_integer);
}
}
return normalize_bigint (stix, z);
oops_einval:
stix->errnum = STIX_EINVAL;
return STIX_NULL;
}
stix_oop_t stix_subints (stix_t* stix, stix_oop_t x, stix_oop_t y)
{
stix_oop_t z;
if (STIX_OOP_IS_SMINT(x) && STIX_OOP_IS_SMINT(y))
{
stix_ooi_t i;
/* no integer overflow/underflow must occur as the possible integer
* range is narrowed by the tag bits used */
i = STIX_OOP_TO_SMINT(x) - STIX_OOP_TO_SMINT(y);
if (STIX_OOI_IN_SMINT_RANGE(i)) return STIX_OOP_FROM_SMINT(i);
return make_bigint_with_ooi (stix, i);
}
else
{
stix_ooi_t v;
int neg;
if (STIX_OOP_IS_SMINT(x))
{
if (!is_integer(stix,y)) goto oops_einval;
v = STIX_OOP_TO_SMINT(x);
if (v == 0)
{
/* switch the sign to the opposite and return it */
neg = (STIX_OBJ_GET_CLASS(y) == stix->_large_negative_integer);
z = clone_bigint (stix, y, 0);
if (!neg) STIX_OBJ_SET_CLASS(z, stix->_large_negative_integer);
return z;
}
stix_pushtmp (stix, &y);
x = make_bigint_with_ooi (stix, v);
if (!x) return STIX_NULL;
stix_poptmp (stix);
}
else if (STIX_OOP_IS_SMINT(y))
{
if (!is_integer(stix,x)) goto oops_einval;
v = STIX_OOP_TO_SMINT(y);
if (v == 0) return x;
stix_pushtmp (stix, &x);
y = make_bigint_with_ooi (stix, v);
if (!y) return STIX_NULL;
stix_poptmp (stix);
}
else
{
if (!is_integer(stix,x)) goto oops_einval;
if (!is_integer(stix,y)) goto oops_einval;
}
if (STIX_OBJ_GET_CLASS(x) != STIX_OBJ_GET_CLASS(y))
{
neg = (STIX_OBJ_GET_CLASS(x) == stix->_large_negative_integer);
z = add_unsigned_integers (stix, x, y);
if (!z) return STIX_NULL;
if (neg) STIX_OBJ_SET_CLASS(z, stix->_large_negative_integer);
}
else
{
/* both are positive or negative */
if (is_less_unsigned (x, y))
{
neg = (STIX_OBJ_GET_CLASS(x) == stix->_large_negative_integer);
z = subtract_unsigned_integers (stix, y, x);
if (!z) return STIX_NULL;
if (!neg) STIX_OBJ_SET_CLASS(z, stix->_large_negative_integer);
}
else
{
neg = (STIX_OBJ_GET_CLASS(x) == stix->_large_negative_integer);
z = subtract_unsigned_integers (stix, x, y); /* take x's sign */
if (!z) return STIX_NULL;
if (neg) STIX_OBJ_SET_CLASS(z, stix->_large_negative_integer);
}
}
}
return normalize_bigint (stix, z);
oops_einval:
stix->errnum = STIX_EINVAL;
return STIX_NULL;
}