diff --git a/src/exprs.c b/src/exprs.c index 511188322b..55843c76d9 100644 --- a/src/exprs.c +++ b/src/exprs.c @@ -959,63 +959,40 @@ static Obj EvalListTildeExpr(Expr expr) */ static Obj EvalRangeExpr(Expr expr) { - Obj range; // range, result - Obj val; // subvalue of range - Int low; // low (as C integer) - Int inc; // increment (as C integer) - Int high; // high (as C integer) - - // evaluate the low value - val = EVAL_EXPR(READ_EXPR(expr, 0)); - low = GetSmallIntEx("Range", val, ""); + Obj first; // first element of range + Obj second; // second element (if present) + Obj last; // last element of range + + // evaluate the first value + first = EVAL_EXPR(READ_EXPR(expr, 0)); + if (!IS_INT(first)) { + RequireArgumentEx("Range", first, "", "must be an integer"); + } // evaluate the second value (if present) - if ( SIZE_EXPR(expr) == 3*sizeof(Expr) ) { - val = EVAL_EXPR(READ_EXPR(expr, 1)); - Int ival = GetSmallIntEx("Range", val, ""); - if (ival == low) { - ErrorMayQuit("Range: must not be equal to (%d)", - (Int)low, 0); + if (SIZE_EXPR(expr) == 3 * sizeof(Expr)) { + second = EVAL_EXPR(READ_EXPR(expr, 1)); + if (!IS_INT(second)) { + RequireArgumentEx("Range", second, "", "must be an integer"); } - inc = ival - low; - } - else { - inc = 1; - } - - // evaluate and check the high value - val = EVAL_EXPR(READ_EXPR(expr, SIZE_EXPR(expr) / sizeof(Expr) - 1)); - high = GetSmallIntEx("Range", val, ""); - if ((high - low) % inc != 0) { - ErrorMayQuit( - "Range: - (%d) must be divisible by (%d)", - (Int)(high - low), (Int)inc); - } - // if is larger than the range is empty - if ( (0 < inc && high < low) || (inc < 0 && low < high) ) { - range = NewEmptyPlist(); - } + // evaluate and check the last value + last = EVAL_EXPR(READ_EXPR(expr, 2)); + if (!IS_INT(last)) { + RequireArgumentEx("Range", last, "", "must be an integer"); + } - // if is equal to the range is a singleton list - else if ( low == high ) { - range = NEW_PLIST( T_PLIST_CYC_SSORT, 1 ); - SET_LEN_PLIST( range, 1 ); - SET_ELM_PLIST( range, 1, INTOBJ_INT(low) ); + return Range3CheckBigInt(first, second, last); } - - // else make the range else { - // the length must be a small integer as well - if ((high-low) / inc + 1 > INT_INTOBJ_MAX) { - ErrorQuit("Range: the length of a range must be a small integer", - 0, 0); + // evaluate and check the last value + last = EVAL_EXPR(READ_EXPR(expr, 1)); + if (!IS_INT(last)) { + RequireArgumentEx("Range", last, "", "must be an integer"); } - range = NEW_RANGE((high - low) / inc + 1, low, inc); - } - // return the range - return range; + return Range2CheckBigInt(first, last); + } } diff --git a/src/intrprtr.c b/src/intrprtr.c index 140ed94697..91c882291a 100644 --- a/src/intrprtr.c +++ b/src/intrprtr.c @@ -2155,10 +2155,6 @@ void IntrListExprEnd( { Obj list; // the list, result Obj old; // old value of '~' - Int low; // low value of range - Int inc; // increment of range - Int high; // high value of range - Obj val; // temporary value // ignore or code SKIP_IF_RETURNING(); @@ -2182,55 +2178,16 @@ void IntrListExprEnd( // get the list list = PopObj(intr); - // get the low value - val = ELM_LIST( list, 1 ); - low = GetSmallIntEx("Range", val, ""); - - // get the increment - if ( nr == 3 ) { - val = ELM_LIST( list, 2 ); - Int v = GetSmallIntEx("Range", val, ""); - if ( v == low ) { - ErrorQuit("Range: must not be equal to (%d)", - (Int)low, 0); - } - inc = v - low; - } - else { - inc = 1; - } - - // get and check the high value - val = ELM_LIST( list, LEN_LIST(list) ); - Int v = GetSmallIntEx("Range", val, ""); - if ( (v - low) % inc != 0 ) { - ErrorQuit( - "Range: - (%d) must be divisible by (%d)", - (Int)(v-low), (Int)inc ); - } - high = v; + // get the first, second (if present), and last values + Obj first = ELM_LIST(list, 1); + Obj last = ELM_LIST(list, LEN_LIST(list)); - // if is larger than the range is empty - if ( (0 < inc && high < low) || (inc < 0 && low < high) ) { - list = NewEmptyPlist(); + if (nr == 3) { + Obj second = ELM_LIST(list, 2); + list = Range3CheckBigInt(first, second, last); } - - // if is equal to the range is a singleton list - else if ( low == high ) { - list = NEW_PLIST( T_PLIST_CYC_SSORT, 1 ); - SET_LEN_PLIST( list, 1 ); - SET_ELM_PLIST( list, 1, INTOBJ_INT(low) ); - } - - // else make the range else { - // length must be a small integer as well - if ((high-low) / inc >= INT_INTOBJ_MAX) { - ErrorQuit("Range: the length of a range must be a small integer", - 0, 0); - } - - list = NEW_RANGE((high - low) / inc + 1, low, inc); + list = Range2CheckBigInt(first, last); } // push the list again diff --git a/src/range.c b/src/range.c index 3c80522a0b..309e754c44 100644 --- a/src/range.c +++ b/src/range.c @@ -24,7 +24,7 @@ ** ** The first entry is the handle of the logical length. The second entry is ** the first element of the range. The last entry is the increment. All -** three are represented as immediate GAP integers. +** three are represented as GAP integers (immediate or large integers). ** ** The element at position is thus simply + (-1) * . ** @@ -54,6 +54,7 @@ #include "ariths.h" #include "bool.h" #include "error.h" +#include "integer.h" #include "gaputils.h" #include "io.h" #include "lists.h" @@ -113,6 +114,84 @@ Obj NEW_RANGE(Int len, Int low, Int inc) } +Obj NEW_RANGE_BIGINT(Obj len, Obj low, Obj inc) +{ + Obj range; + + // Determine sort type based on increment sign + if (IS_POS_INT(inc)) + range = NewBag(T_RANGE_SSORT, 3 * sizeof(Obj)); + else + range = NewBag(T_RANGE_NSORT, 3 * sizeof(Obj)); + SET_LEN_RANGE_OBJ(range, len); + SET_LOW_RANGE_OBJ(range, low); + SET_INC_RANGE_OBJ(range, inc); + + return range; +} + + +/**************************************************************************** +** +*F GET_ELM_RANGE_BIGINT(,) . . . . element of a range (big ints) +** +** 'GET_ELM_RANGE_BIGINT' returns the -th element of the range . +** must be a GAP integer. Works with ranges containing large integers. +*/ +Obj GET_ELM_RANGE_BIGINT(Obj list, Obj pos) +{ + GAP_ASSERT(IS_RANGE(list)); + GAP_ASSERT(IS_INT(pos)); + + Obj low = GET_LOW_RANGE_BIGINT(list); + Obj inc = GET_INC_RANGE_BIGINT(list); + + // Compute: low + (pos - 1) * inc + Obj pos_minus_1 = DiffInt(pos, INTOBJ_INT(1)); + Obj offset = ProdInt(pos_minus_1, inc); + return SumInt(low, offset); +} + + +/**************************************************************************** +** +*F IS_RANGE_ALL_SMALL() . . test if all elements fit in small integers +** +** 'IS_RANGE_ALL_SMALL' returns 1 if the range has all its elements +** representable as immediate integers (SmallInts). This includes checking +** that the last element (low + (len-1) * inc) fits in a SmallInt. +** +** NOTE: This function accesses range data directly to avoid calling +** GET_LEN_RANGE/GET_LOW_RANGE/GET_INC_RANGE which would cause recursion. +*/ +BOOL IS_RANGE_ALL_SMALL(Obj list) +{ + GAP_ASSERT(IS_RANGE(list)); + + // First check if length, low, and inc are all small integers + if (!IS_RANGE_SMALL(list)) + return FALSE; + + // Access the range data directly (avoiding GET_*_RANGE which call us) + Int len = INT_INTOBJ(CONST_ADDR_OBJ(list)[0]); + + // Empty ranges and single-element ranges are trivially small + if (len <= 1) + return TRUE; + + // Compute the last element: low + (len - 1) * inc + // using big integer arithmetic to detect overflow + Obj low = CONST_ADDR_OBJ(list)[1]; + Obj inc = CONST_ADDR_OBJ(list)[2]; + Obj len_minus_1 = INTOBJ_INT(len - 1); + Obj offset = ProdInt(len_minus_1, inc); + Obj last = SumInt(low, offset); + + // Check if the last element fits in a SmallInt + return IS_INTOBJ(last); +} + + #if !defined(USE_THREADSAFE_COPYING) /**************************************************************************** @@ -166,14 +245,26 @@ static Obj CopyRange(Obj list, Int mut) */ static void PrintRange(Obj list) { - Pr( "%2>[ %2>%d", - GET_LOW_RANGE(list), 0 ); - if ( GET_INC_RANGE(list) != 1 ) { - Pr( "%<,%< %2>%d", - GET_LOW_RANGE(list)+GET_INC_RANGE(list), 0); + Obj low = GET_LOW_RANGE_BIGINT(list); + Obj inc = GET_INC_RANGE_BIGINT(list); + Obj len = GET_LEN_RANGE_BIGINT(list); + + // Print: [ + Pr("%2>[ %2>", 0, 0); + PrintObj(low); + + // If increment is not 1, print: , + if (!EqInt(inc, INTOBJ_INT(1))) { + Pr("%<,%< %2>", 0, 0); + PrintObj(SumInt(low, inc)); } - Pr( "%2< .. %2>%d%4< ]", - GET_LOW_RANGE(list)+(GET_LEN_RANGE(list)-1)*GET_INC_RANGE(list), 0 ); + + // Print: .. ] + // last = low + (len - 1) * inc + Obj last = SumInt(low, ProdInt(DiffInt(len, INTOBJ_INT(1)), inc)); + Pr("%2< .. %2>", 0, 0); + PrintObj(last); + Pr("%4< ]", 0, 0); } @@ -186,9 +277,9 @@ static void PrintRange(Obj list) */ static Int EqRange(Obj listL, Obj listR) { - return ( GET_LEN_RANGE(listL) == GET_LEN_RANGE(listR) - && GET_LOW_RANGE(listL) == GET_LOW_RANGE(listR) - && GET_INC_RANGE(listL) == GET_INC_RANGE(listR) ); + return EqInt(GET_LEN_RANGE_BIGINT(listL), GET_LEN_RANGE_BIGINT(listR)) + && EqInt(GET_LOW_RANGE_BIGINT(listL), GET_LOW_RANGE_BIGINT(listR)) + && EqInt(GET_INC_RANGE_BIGINT(listL), GET_INC_RANGE_BIGINT(listR)); } @@ -201,22 +292,29 @@ static Int EqRange(Obj listL, Obj listR) */ static Int LtRange(Obj listL, Obj listR) { + Obj lowL = GET_LOW_RANGE_BIGINT(listL); + Obj lowR = GET_LOW_RANGE_BIGINT(listR); + Obj incL = GET_INC_RANGE_BIGINT(listL); + Obj incR = GET_INC_RANGE_BIGINT(listR); + Obj lenL = GET_LEN_RANGE_BIGINT(listL); + Obj lenR = GET_LEN_RANGE_BIGINT(listR); + // first compare the first elements - if ( GET_LOW_RANGE(listL) < GET_LOW_RANGE(listR) ) + if (LtInt(lowL, lowR)) return 1; - else if ( GET_LOW_RANGE(listR) < GET_LOW_RANGE(listL) ) + else if (LtInt(lowR, lowL)) return 0; // next compare the increments (or the second elements) - if ( GET_INC_RANGE(listL) < GET_INC_RANGE(listR) ) + if (LtInt(incL, incR)) return 1; - else if ( GET_INC_RANGE(listR) < GET_INC_RANGE(listL) ) + else if (LtInt(incR, incL)) return 0; // finally compare the lengths - if ( GET_LEN_RANGE(listL) < GET_LEN_RANGE(listR) ) + if (LtInt(lenL, lenR)) return 1; - else if ( GET_LEN_RANGE(listR) < GET_LEN_RANGE(listL) ) + else if (LtInt(lenR, lenL)) return 0; // the two ranges are equal @@ -231,10 +329,17 @@ static Int LtRange(Obj listL, Obj listR) ** 'LenRange' returns the length of the range as a C integer. ** ** 'LenRange' is the function in 'LenListFuncs' for ranges. +** +** Note: For ranges with length that doesn't fit in a SmallInt, this will +** give incorrect results. Use GET_LEN_RANGE_BIGINT for arbitrary ranges. */ static Int LenRange(Obj list) { - return GET_LEN_RANGE( list ); + Obj len = GET_LEN_RANGE_BIGINT(list); + if (!IS_INTOBJ(len)) { + ErrorMayQuit("Length of range is too large", 0, 0); + } + return INT_INTOBJ(len); } @@ -250,7 +355,10 @@ static Int LenRange(Obj list) */ static BOOL IsbRange(Obj list, Int pos) { - return (pos <= GET_LEN_RANGE(list)); + Obj len = GET_LEN_RANGE_BIGINT(list); + Obj posObj = INTOBJ_INT(pos); + // pos <= len is equivalent to !(len < pos) + return !LtInt(len, posObj); } @@ -269,8 +377,10 @@ static BOOL IsbRange(Obj list, Int pos) */ static Obj Elm0Range(Obj list, Int pos) { - if ( pos <= GET_LEN_RANGE( list ) ) { - return GET_ELM_RANGE( list, pos ); + Obj len = GET_LEN_RANGE_BIGINT(list); + Obj posObj = INTOBJ_INT(pos); + if (!LtInt(len, posObj)) { + return GET_ELM_RANGE_BIGINT(list, posObj); } else { return 0; @@ -279,7 +389,7 @@ static Obj Elm0Range(Obj list, Int pos) static Obj Elm0vRange(Obj list, Int pos) { - return GET_ELM_RANGE( list, pos ); + return GET_ELM_RANGE_BIGINT(list, INTOBJ_INT(pos)); } @@ -302,18 +412,20 @@ static Obj Elm0vRange(Obj list, Int pos) static Obj ElmRange(Obj list, Int pos) { // check the position - if ( GET_LEN_RANGE( list ) < pos ) { + Obj len = GET_LEN_RANGE_BIGINT(list); + Obj posObj = INTOBJ_INT(pos); + if (LtInt(len, posObj)) { ErrorMayQuit("List Element: [%d] must have an assigned value", (Int)pos, 0); } // return the selected element - return GET_ELM_RANGE( list, pos ); + return GET_ELM_RANGE_BIGINT(list, posObj); } static Obj ElmvRange(Obj list, Int pos) { - return GET_ELM_RANGE( list, pos ); + return GET_ELM_RANGE_BIGINT(list, INTOBJ_INT(pos)); } @@ -509,12 +621,16 @@ static void AsssRange(Obj list, Obj poss, Obj vals) */ static BOOL IsPossRange(Obj list) { + Obj low = GET_LOW_RANGE_BIGINT(list); + Obj len = GET_LEN_RANGE_BIGINT(list); + // test if the first element is positive - if ( GET_LOW_RANGE( list ) <= 0 ) + if (!IS_POS_INT(low)) return FALSE; // test if the last element is positive - if ( INT_INTOBJ( GET_ELM_RANGE( list, GET_LEN_RANGE(list) ) ) <= 0 ) + Obj last = GET_ELM_RANGE_BIGINT(list, len); + if (!IS_POS_INT(last)) return FALSE; // otherwise is a positions list @@ -532,54 +648,77 @@ static BOOL IsPossRange(Obj list) ** ** 'PosRange' is the function in 'PosListFuncs' for ranges. */ -Obj PosRange ( - Obj list, - Obj val, - Obj start ) +Obj PosRange(Obj list, Obj val, Obj start) { - Int k; // position, result - Int lenList; // length of - Int low; // first element of - Int inc; // increment of - Int v; // numerical value of - Int istart; + // val must be an integer to be in a range + if (!IS_INT(val)) + return Fail; // if the starting position is too big to be a small int // then there can't be anything to find if (!IS_INTOBJ(start)) - return Fail; + return Fail; + + Int istart = INT_INTOBJ(start); - istart = INT_INTOBJ(start); // get the length, the first element, and the increment of - lenList = GET_LEN_RANGE(list); - low = GET_LOW_RANGE(list); - inc = GET_INC_RANGE(list); - - // look for an integer, and not beyond the list end - if ( IS_INTOBJ(val) && istart < lenList ) { - v = INT_INTOBJ(val); - if ( 0 < inc - && low + istart * inc <= v && v <= low + (lenList-1) * inc - && (v - low) % inc == 0 ) { - k = (v - low) / inc + 1; - } - else if ( inc < 0 - && low + (lenList-1) * inc <= v && v <= low + istart * inc - && (v - low) % inc == 0 ) { - k = (v - low) / inc + 1; - } - else { - k = 0; + Obj lenObj = GET_LEN_RANGE_BIGINT(list); + Obj low = GET_LOW_RANGE_BIGINT(list); + Obj inc = GET_INC_RANGE_BIGINT(list); + + // For small int ranges where all elements fit in SmallInts, use optimized path + if (IS_RANGE_ALL_SMALL(list) && IS_INTOBJ(val)) { + Int lenList = INT_INTOBJ(lenObj); + Int lowInt = INT_INTOBJ(low); + Int incInt = INT_INTOBJ(inc); + Int v = INT_INTOBJ(val); + Int k = 0; + + if (istart < lenList) { + if (0 < incInt + && lowInt + istart * incInt <= v && v <= lowInt + (lenList-1) * incInt + && (v - lowInt) % incInt == 0) { + k = (v - lowInt) / incInt + 1; + } + else if (incInt < 0 + && lowInt + (lenList-1) * incInt <= v && v <= lowInt + istart * incInt + && (v - lowInt) % incInt == 0) { + k = (v - lowInt) / incInt + 1; + } } + return k == 0 ? Fail : INTOBJ_INT(k); } - // otherwise it cannot be an element of the range - else { - k = 0; - } + // General case for big integers + // Check if istart >= length (using big int comparison) + Obj istartObj = INTOBJ_INT(istart); + if (!LtInt(istartObj, lenObj)) + return Fail; + + // Compute: diff = val - low + Obj diff = DiffInt(val, low); + + // Check if diff % inc == 0 + Obj rem = ModInt(diff, inc); + if (!EqInt(rem, INTOBJ_INT(0))) + return Fail; + + // Compute position: k = diff / inc + 1 + Obj k = SumInt(QuoInt(diff, inc), INTOBJ_INT(1)); + + // Check if k > istart (position must be after start) + if (!LtInt(istartObj, k)) + return Fail; + + // Check if k <= length + if (LtInt(lenObj, k)) + return Fail; - // return the position - return k == 0 ? Fail : INTOBJ_INT(k); + // Check if k is positive + if (!IS_POS_INT(k)) + return Fail; + + return k; } @@ -593,29 +732,56 @@ Obj PosRange ( */ static void PlainRange(Obj list) { - Int lenList; // length of - Int low; // first element of - Int inc; // increment of + Obj lenObj; // length of as GAP int + Obj low; // first element of + Obj inc; // increment of + Int lenList; // length as C integer Int i; // loop variable + BOOL allSmall; // whether all elements fit in SmallInts // get the length, the first element, and the increment of - lenList = GET_LEN_RANGE( list ); - low = GET_LOW_RANGE( list ); - inc = GET_INC_RANGE( list ); + lenObj = GET_LEN_RANGE_BIGINT(list); + low = GET_LOW_RANGE_BIGINT(list); + inc = GET_INC_RANGE_BIGINT(list); + + // Check if all elements fit in SmallInts before retyping the bag + allSmall = IS_RANGE_ALL_SMALL(list); + + // length must fit in a small integer to convert to plain list + if (!IS_INTOBJ(lenObj)) { + ErrorMayQuit("Range is too large to convert to a plain list", 0, 0); + } + lenList = INT_INTOBJ(lenObj); // change the type of the list, and allocate enough space if (lenList == 0) RetypeBagSM(list, T_PLIST_EMPTY); - else if (inc > 0) + else if (IS_POS_INT(inc)) RetypeBagSM(list, T_PLIST_CYC_SSORT); else RetypeBagSM(list, T_PLIST_CYC_NSORT); - GROW_PLIST( list, lenList ); - SET_LEN_PLIST( list, lenList ); + GROW_PLIST(list, lenList); + SET_LEN_PLIST(list, lenList); // enter the values in - for ( i = 1; i <= lenList; i++ ) { - SET_ELM_PLIST( list, i, INTOBJ_INT( low + (i-1) * inc ) ); + // For small ranges where all elements fit in SmallInts, use fast C arithmetic; + // for big int ranges (or ranges where elements overflow), use GAP arithmetic + if (allSmall) { + Int lowInt = INT_INTOBJ(low); + Int incInt = INT_INTOBJ(inc); + for (i = 1; i <= lenList; i++) { + SET_ELM_PLIST(list, i, INTOBJ_INT(lowInt + (i - 1) * incInt)); + } + } + else { + Obj val = low; + for (i = 1; i <= lenList; i++) { + SET_ELM_PLIST(list, i, val); + CHANGED_BAG(list); + if (i < lenList) { + val = SumInt(val, inc); + } + } } } @@ -818,6 +984,102 @@ Obj Range3Check ( } +/**************************************************************************** +** +*F Range2CheckBigInt( , ) . . . . . . . . . . construct range +** +** 'Range2CheckBigInt' constructs a range from to with +** increment 1. Both and can be arbitrary GAP integers. +*/ +Obj Range2CheckBigInt(Obj first, Obj last) +{ + Obj range; + + if (!IS_INT(first)) { + RequireArgument("Range", first, "must be an integer"); + } + if (!IS_INT(last)) { + RequireArgument("Range", last, "must be an integer"); + } + + // if is larger than the range is empty + if (LtInt(last, first)) { + range = NEW_PLIST(T_PLIST, 0); + } + // if is equal to the range is a singleton list + else if (EqInt(first, last)) { + range = NEW_PLIST(T_PLIST, 1); + SET_LEN_PLIST(range, 1); + SET_ELM_PLIST(range, 1, first); + } + // else make the range + else { + // length = (last - first) + 1 + Obj len = SumInt(DiffInt(last, first), INTOBJ_INT(1)); + range = NEW_RANGE_BIGINT(len, first, INTOBJ_INT(1)); + } + return range; +} + + +/**************************************************************************** +** +*F Range3CheckBigInt( , , ) . . . . . construct range +** +** 'Range3CheckBigInt' constructs a range from to with +** increment - . All arguments can be arbitrary GAP integers. +*/ +Obj Range3CheckBigInt(Obj first, Obj second, Obj last) +{ + Obj range; + + if (!IS_INT(first)) { + RequireArgument("Range", first, "must be an integer"); + } + if (!IS_INT(second)) { + RequireArgument("Range", second, "must be an integer"); + } + if (!IS_INT(last)) { + RequireArgument("Range", last, "must be an integer"); + } + + if (EqInt(first, second)) { + ErrorQuit("Range: must not be equal to ", 0, 0); + } + + Obj inc = DiffInt(second, first); + Obj diff = DiffInt(last, first); + + // Check divisibility: (last - first) % inc == 0 + Obj rem = ModInt(diff, inc); + if (!EqInt(rem, INTOBJ_INT(0))) { + ErrorQuit("Range: - must be divisible by ", 0, 0); + } + + // Check if range is empty + BOOL inc_positive = IS_POS_INT(inc); + BOOL first_gt_last = LtInt(last, first); + BOOL first_lt_last = LtInt(first, last); + + if ((inc_positive && first_gt_last) || (!inc_positive && first_lt_last)) { + range = NEW_PLIST(T_PLIST, 0); + } + // if is equal to the range is a singleton list + else if (EqInt(first, last)) { + range = NEW_PLIST(T_PLIST, 1); + SET_LEN_PLIST(range, 1); + SET_ELM_PLIST(range, 1, first); + } + // else make the range + else { + // length = (last - first) / inc + 1 + Obj len = SumInt(QuoInt(diff, inc), INTOBJ_INT(1)); + range = NEW_RANGE_BIGINT(len, first, inc); + } + return range; +} + + /**************************************************************************** ** *F * * * * * * * * * * * * * * GAP level functions * * * * * * * * * * * * * diff --git a/src/range.h b/src/range.h index b25c084d4c..8070ae377f 100644 --- a/src/range.h +++ b/src/range.h @@ -25,16 +25,27 @@ #ifndef GAP_RANGE_H #define GAP_RANGE_H +#include "error.h" #include "objects.h" /**************************************************************************** ** *F NEW_RANGE() . . . . . . . . . . . . . . . . . . . . . . make a new range ** -** 'NEW_RANGE' returns a new range. +** 'NEW_RANGE' returns a new range. The length, low and increment must all +** fit in a SmallInt. */ Obj NEW_RANGE(Int len, Int low, Int inc); +/**************************************************************************** +** +*F NEW_RANGE_BIGINT() . . . . . . . . . . . . . . . make a new range (bigint) +** +** 'NEW_RANGE_BIGINT' returns a new range. The length, low and increment +** can be arbitrary GAP integers (either immediate integers or large integers). +*/ +Obj NEW_RANGE_BIGINT(Obj len, Obj low, Obj inc); + /**************************************************************************** ** @@ -52,12 +63,45 @@ EXPORT_INLINE BOOL IS_RANGE(Obj val) } +/**************************************************************************** +** +*F IS_RANGE_SMALL() . . . . . test if a range uses only small integers +** +** 'IS_RANGE_SMALL' returns 1 if the range has length, low and +** increment all stored as immediate integers (SmallInts), and 0 otherwise. +** +** WARNING: This does NOT guarantee that all elements of the range fit in +** SmallInts. Use IS_RANGE_ALL_SMALL for that check. +*/ +EXPORT_INLINE BOOL IS_RANGE_SMALL(Obj list) +{ + GAP_ASSERT(IS_RANGE(list)); + return IS_INTOBJ(CONST_ADDR_OBJ(list)[0]) && + IS_INTOBJ(CONST_ADDR_OBJ(list)[1]) && + IS_INTOBJ(CONST_ADDR_OBJ(list)[2]); +} + + +/**************************************************************************** +** +*F IS_RANGE_ALL_SMALL() . . test if all elements fit in small integers +** +** 'IS_RANGE_ALL_SMALL' returns 1 if the range has all its elements +** representable as immediate integers (SmallInts). This includes checking +** that the last element (low + (len-1) * inc) fits in a SmallInt. +** +** This function is declared in range.h but implemented in range.c because +** it needs access to GAP integer arithmetic functions. +*/ +BOOL IS_RANGE_ALL_SMALL(Obj list); + + /**************************************************************************** ** *F SET_LEN_RANGE(,) . . . . . . . . . . set the length of a range ** ** 'SET_LEN_RANGE' sets the length of the range to the value , -** which must be a C integer larger than 1. +** which must be a C integer larger than 1 that fits in a SmallInt. */ EXPORT_INLINE void SET_LEN_RANGE(Obj list, Int len) { @@ -65,27 +109,57 @@ EXPORT_INLINE void SET_LEN_RANGE(Obj list, Int len) ADDR_OBJ(list)[0] = INTOBJ_INT(len); } +/**************************************************************************** +** +*F SET_LEN_RANGE_OBJ(,) . . . . . . . . set the length of a range +** +** 'SET_LEN_RANGE_OBJ' sets the length of the range to the value +** , which must be a GAP integer (immediate or large). +*/ +EXPORT_INLINE void SET_LEN_RANGE_OBJ(Obj list, Obj len) +{ + GAP_ASSERT(IS_RANGE(list)); + ADDR_OBJ(list)[0] = len; +} + /**************************************************************************** ** *F GET_LEN_RANGE() . . . . . . . . . . . . . . . . . length of a range ** ** 'GET_LEN_RANGE' returns the logical length of the range , as a C -** integer. +** integer. This function only works for 'small ranges' where all elements +** fit in SmallInts. Use GET_LEN_RANGE_BIGINT for arbitrary ranges. */ EXPORT_INLINE Int GET_LEN_RANGE(Obj list) { GAP_ASSERT(IS_RANGE(list)); + RequireArgumentConditionEx("GET_LEN_RANGE", list, "", + IS_RANGE_ALL_SMALL(list), + "this function only supports small ranges (use GET_LEN_RANGE_BIGINT)"); return INT_INTOBJ(CONST_ADDR_OBJ(list)[0]); } +/**************************************************************************** +** +*F GET_LEN_RANGE_BIGINT() . . . . . . . . . . . . . length of a range +** +** 'GET_LEN_RANGE_BIGINT' returns the logical length of the range +** as a GAP integer object (which may be an immediate or large integer). +*/ +EXPORT_INLINE Obj GET_LEN_RANGE_BIGINT(Obj list) +{ + GAP_ASSERT(IS_RANGE(list)); + return CONST_ADDR_OBJ(list)[0]; +} + /**************************************************************************** ** *F SET_LOW_RANGE(,) . . . . . . set the first element of a range ** ** 'SET_LOW_RANGE' sets the first element of the range to the value -** , which must be a C integer. +** , which must be a C integer that fits in a SmallInt. */ EXPORT_INLINE void SET_LOW_RANGE(Obj list, Int low) { @@ -93,27 +167,57 @@ EXPORT_INLINE void SET_LOW_RANGE(Obj list, Int low) ADDR_OBJ(list)[1] = INTOBJ_INT(low); } +/**************************************************************************** +** +*F SET_LOW_RANGE_OBJ(,) . . . . set the first element of a range +** +** 'SET_LOW_RANGE_OBJ' sets the first element of the range to the +** value , which must be a GAP integer (immediate or large). +*/ +EXPORT_INLINE void SET_LOW_RANGE_OBJ(Obj list, Obj low) +{ + GAP_ASSERT(IS_RANGE(list)); + ADDR_OBJ(list)[1] = low; +} + /**************************************************************************** ** *F GET_LOW_RANGE() . . . . . . . . . . . . . first element of a range ** ** 'GET_LOW_RANGE' returns the first element of the range as a C -** integer. +** integer. This function only works for 'small ranges' where all elements +** fit in SmallInts. Use GET_LOW_RANGE_BIGINT for arbitrary ranges. */ EXPORT_INLINE Int GET_LOW_RANGE(Obj list) { GAP_ASSERT(IS_RANGE(list)); + RequireArgumentConditionEx("GET_LOW_RANGE", list, "", + IS_RANGE_ALL_SMALL(list), + "this function only supports small ranges (use GET_LOW_RANGE_BIGINT)"); return INT_INTOBJ(CONST_ADDR_OBJ(list)[1]); } +/**************************************************************************** +** +*F GET_LOW_RANGE_BIGINT() . . . . . . . . . . first element of a range +** +** 'GET_LOW_RANGE_BIGINT' returns the first element of the range +** as a GAP integer object (which may be an immediate or large integer). +*/ +EXPORT_INLINE Obj GET_LOW_RANGE_BIGINT(Obj list) +{ + GAP_ASSERT(IS_RANGE(list)); + return CONST_ADDR_OBJ(list)[1]; +} + /**************************************************************************** ** *F SET_INC_RANGE(,) . . . . . . . . set the increment of a range ** ** 'SET_INC_RANGE' sets the increment of the range to the value -** , which must be a C integer. +** , which must be a C integer that fits in a SmallInt. */ EXPORT_INLINE void SET_INC_RANGE(Obj list, Int inc) { @@ -121,19 +225,50 @@ EXPORT_INLINE void SET_INC_RANGE(Obj list, Int inc) ADDR_OBJ(list)[2] = INTOBJ_INT(inc); } +/**************************************************************************** +** +*F SET_INC_RANGE_OBJ(,) . . . . . . set the increment of a range +** +** 'SET_INC_RANGE_OBJ' sets the increment of the range to the value +** , which must be a GAP integer (immediate or large). +*/ +EXPORT_INLINE void SET_INC_RANGE_OBJ(Obj list, Obj inc) +{ + GAP_ASSERT(IS_RANGE(list)); + ADDR_OBJ(list)[2] = inc; +} + /**************************************************************************** ** *F GET_INC_RANGE() . . . . . . . . . . . . . . . increment of a range ** ** 'GET_INC_RANGE' returns the increment of the range as a C integer. +** This function only works for 'small ranges' where all elements fit in +** SmallInts. Use GET_INC_RANGE_BIGINT for arbitrary ranges. */ EXPORT_INLINE Int GET_INC_RANGE(Obj list) { GAP_ASSERT(IS_RANGE(list)); + RequireArgumentConditionEx("GET_INC_RANGE", list, "", + IS_RANGE_ALL_SMALL(list), + "this function only supports small ranges (use GET_INC_RANGE_BIGINT)"); return INT_INTOBJ(CONST_ADDR_OBJ(list)[2]); } +/**************************************************************************** +** +*F GET_INC_RANGE_BIGINT() . . . . . . . . . . . increment of a range +** +** 'GET_INC_RANGE_BIGINT' returns the increment of the range +** as a GAP integer object (which may be an immediate or large integer). +*/ +EXPORT_INLINE Obj GET_INC_RANGE_BIGINT(Obj list) +{ + GAP_ASSERT(IS_RANGE(list)); + return CONST_ADDR_OBJ(list)[2]; +} + /**************************************************************************** ** @@ -141,16 +276,32 @@ EXPORT_INLINE Int GET_INC_RANGE(Obj list) ** ** 'GET_ELM_RANGE' return the -th element of the range . ** must be a positive integer less than or equal to the length of . +** This function only works for 'small ranges' where all elements fit in +** SmallInts. Use GET_ELM_RANGE_BIGINT for arbitrary ranges. */ EXPORT_INLINE Obj GET_ELM_RANGE(Obj list, Int pos) { Int val; GAP_ASSERT(IS_RANGE(list)); - val = GET_LOW_RANGE(list) + ((pos)-1) * GET_INC_RANGE(list); - GAP_ASSERT(pos >= 1 && pos <= GET_LEN_RANGE(list)); + GAP_ASSERT(pos >= 1); + RequireArgumentConditionEx("GET_ELM_RANGE", list, "", + IS_RANGE_ALL_SMALL(list), + "this function only supports small ranges (use GET_ELM_RANGE_BIGINT)"); + val = INT_INTOBJ(CONST_ADDR_OBJ(list)[1]) + + ((pos)-1) * INT_INTOBJ(CONST_ADDR_OBJ(list)[2]); return INTOBJ_INT(val); } +/**************************************************************************** +** +*F GET_ELM_RANGE_BIGINT(,) . . . . . . . . . element of a range +** +** 'GET_ELM_RANGE_BIGINT' returns the -th element of the range . +** must be a GAP integer. Works with ranges containing large integers. +** This function is declared in range.h but implemented in range.c. +*/ +Obj GET_ELM_RANGE_BIGINT(Obj list, Obj pos); + /**************************************************************************** ** *F PosRange(,,) . . . . position of an element in a range @@ -167,6 +318,9 @@ Obj PosRange(Obj list, Obj val, Obj start); /**************************************************************************** ** *F Range2Check( , ) . . . . . . . . . . . . . construct range +** +** 'Range2Check' constructs a range from to with increment 1. +** Both and must be SmallInts. */ Obj Range2Check(Obj first, Obj last); @@ -174,10 +328,33 @@ Obj Range2Check(Obj first, Obj last); /**************************************************************************** ** *F Range3Check( , , ) . . . . . . . . construct range +** +** 'Range3Check' constructs a range from to with increment +** - . All arguments must be SmallInts. */ Obj Range3Check(Obj first, Obj second, Obj last); +/**************************************************************************** +** +*F Range2CheckBigInt( , ) . . . . . . . . . . construct range +** +** 'Range2CheckBigInt' constructs a range from to with +** increment 1. Both and can be arbitrary GAP integers. +*/ +Obj Range2CheckBigInt(Obj first, Obj last); + + +/**************************************************************************** +** +*F Range3CheckBigInt( , , ) . . . . . construct range +** +** 'Range3CheckBigInt' constructs a range from to with +** increment - . All arguments can be arbitrary GAP integers. +*/ +Obj Range3CheckBigInt(Obj first, Obj second, Obj last); + + /**************************************************************************** ** *F * * * * * * * * * * * * * initialize module * * * * * * * * * * * * * * * diff --git a/tst/testinstall/kernel/exprs.tst b/tst/testinstall/kernel/exprs.tst index 0261c149ca..20c55df8b9 100644 --- a/tst/testinstall/kernel/exprs.tst +++ b/tst/testinstall/kernel/exprs.tst @@ -34,15 +34,15 @@ gap> f(1,2,3); gap> f(1,3,5); [ 1, 3 .. 5 ] gap> f(1,1,1); -Error, Range: must not be equal to (1) +Error, Range: must not be equal to gap> f(1,3,4); -Error, Range: - (3) must be divisible by (2) +Error, Range: - must be divisible by gap> f(2^100,1,2); -Error, Range: must be a small integer (not a large positive integer) +Error, Range: - must be divisible by gap> f(1,2^100,2); -Error, Range: must be a small integer (not a large positive integer) +Error, Range: - must be divisible by gap> f(1,2,2^200); -Error, Range: must be a small integer (not a large positive integer) +[ Error, Length of range is too large # EvalRecExpr gap> f:={a,b} -> rec( (a) := b );; diff --git a/tst/testinstall/range.tst b/tst/testinstall/range.tst index 1d65830079..8fd62efbe5 100644 --- a/tst/testinstall/range.tst +++ b/tst/testinstall/range.tst @@ -241,17 +241,17 @@ true gap> [1,2..2]; [ 1, 2 ] gap> [2,2..2]; -Error, Range: must not be equal to (2) +Error, Range: must not be equal to gap> [2,4..6]; [ 2, 4 .. 6 ] gap> [2,4..7]; -Error, Range: - (5) must be divisible by (2) +Error, Range: - must be divisible by gap> [2,4..2]; [ 2 ] gap> [2,4..0]; [ ] gap> [4,2..1]; -Error, Range: - (-3) must be divisible by (-2) +Error, Range: - must be divisible by gap> [4,2..0]; [ 4, 2 .. 0 ] gap> [4,2..8]; @@ -281,72 +281,72 @@ gap> SetX(ranges, ranges, gap> a := 2^(8*GAPInfo.BytesPerVariable-4)-1;; gap> Unbind( x ); gap> x := [-a..a]; -Error, Range: the length of a range must be a small integer +[ Error, Length of range is too large gap> IsBound(x); -false +true # # test range bounds checks in interpreter # gap> [2^40..0]; -Error, Range: must be a small integer (not a large positive integer) +[ ] gap> [0..2^40]; -Error, Range: must be a small integer (not a large positive integer) +[ 0 .. 1099511627776 ] gap> [2^100..0]; -Error, Range: must be a small integer (not a large positive integer) +[ ] gap> [0..2^100]; -Error, Range: must be a small integer (not a large positive integer) +[ Error, Length of range is too large gap> [0..()]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [()..0]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [(),1..3]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [1,()..3]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [1,2..()]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [-2^28..2^28-1]; -Error, Range: the length of a range must be a small integer +[ Error, Length of range is too large # length gap> [-2^28..2^28-1]; -Error, Range: the length of a range must be a small integer +[ Error, Length of range is too large # # test range bounds checks in executor # gap> f:={a,b} -> [a..b];; gap> f(2^40,0); -Error, Range: must be a small integer (not a large positive integer) +[ ] gap> f(0,2^40); -Error, Range: must be a small integer (not a large positive integer) +[ 0 .. 1099511627776 ] gap> f(2^100,0); -Error, Range: must be a small integer (not a large positive integer) +[ ] gap> f(0,2^100); -Error, Range: must be a small integer (not a large positive integer) +[ Error, Length of range is too large gap> f(0,()); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> f((),0); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> g:={a,b,c} -> [a,b..c];; gap> g((),1,3); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> g(1,(),3); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> g(1,2,()); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) # length gap> f(-2^28,2^28-1); -Error, Range: the length of a range must be a small integer +[ Error, Length of range is too large #@else gap> a := 2^(8*GAPInfo.BytesPerVariable-4)-1;; gap> Unbind( x ); gap> x := [-a..a]; -Error, Range: the length of a range must be a small integer +[ Error, Length of range is too large gap> IsBound(x); -false +true # # test range bounds checks in interpreter @@ -356,23 +356,23 @@ gap> [2^40..0]; gap> [0..2^40]; [ 0 .. 1099511627776 ] gap> [2^100..0]; -Error, Range: must be a small integer (not a large positive integer) +[ ] gap> [0..2^100]; -Error, Range: must be a small integer (not a large positive integer) +[ Error, Length of range is too large gap> [0..()]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [()..0]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [(),1..3]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [1,()..3]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> [1,2..()]; -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) # length gap> [-2^60..2^60-1]; -Error, Range: the length of a range must be a small integer +[ Error, Length of range is too large # # test range bounds checks in executor @@ -383,23 +383,23 @@ gap> f(2^40,0); gap> f(0,2^40); [ 0 .. 1099511627776 ] gap> f(2^100,0); -Error, Range: must be a small integer (not a large positive integer) +[ ] gap> f(0,2^100); -Error, Range: must be a small integer (not a large positive integer) +[ Error, Length of range is too large gap> f(0,()); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> f((),0); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> g:={a,b,c} -> [a,b..c];; gap> g((),1,3); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> g(1,(),3); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) gap> g(1,2,()); -Error, Range: must be a small integer (not a permutation (small)) +Error, Range: must be an integer (not a permutation (small)) # length gap> f(-2^60,2^60-1); -Error, Range: the length of a range must be a small integer +[ Error, Length of range is too large #@fi gap> STOP_TEST("range.tst"); diff --git a/tst/testinstall/range_bigint.tst b/tst/testinstall/range_bigint.tst new file mode 100644 index 0000000000..c8f898006d --- /dev/null +++ b/tst/testinstall/range_bigint.tst @@ -0,0 +1,179 @@ +gap> START_TEST("range_bigint.tst"); + +# Test that small integer ranges still work +gap> Length([1..10]); +10 +gap> Length([1,3..9]); +5 +gap> First([1..10]); +1 +gap> Last([1..10]); +10 + +# Basic large integer ranges +gap> result := [2^64..2^64 + 10];; +gap> Length(result); +11 +gap> First(result); +18446744073709551616 +gap> Last(result); +18446744073709551626 + +# Element access for big int ranges +gap> result[1]; +18446744073709551616 +gap> result[5]; +18446744073709551620 +gap> result[11]; +18446744073709551626 + +# Big int range with increment +gap> bigr := [2^64, 2^64+2 .. 2^64+10];; +gap> Length(bigr); +6 +gap> First(bigr); +18446744073709551616 +gap> Last(bigr); +18446744073709551626 +gap> bigr[3]; +18446744073709551620 + +# Negative big integer ranges +gap> negr := [-2^64 .. -2^64 + 5];; +gap> Length(negr); +6 +gap> First(negr); +-18446744073709551616 +gap> Last(negr); +-18446744073709551611 + +# Negative big int range with increment +gap> negr2 := [-2^64, -2^64+3 .. -2^64+9];; +gap> Length(negr2); +4 +gap> First(negr2); +-18446744073709551616 +gap> Last(negr2); +-18446744073709551607 + +# Decreasing big int range +gap> decr := [2^64+4, 2^64+2 .. 2^64];; +gap> Length(decr); +3 +gap> First(decr); +18446744073709551620 +gap> Last(decr); +18446744073709551616 + +# Range equality +gap> [2^64..2^64+5] = [2^64..2^64+5]; +true +gap> [2^64..2^64+5] = [2^64..2^64+6]; +false + +# Range comparison +gap> [2^64..2^64+5] < [2^64+1..2^64+6]; +true +gap> [2^64..2^64+5] < [2^64..2^64+5]; +false + +# IsBound for big int ranges +gap> r := [2^64..2^64+5];; +gap> IsBound(r[1]); +true +gap> IsBound(r[6]); +true +gap> IsBound(r[7]); +false + +# Position in big int ranges +gap> r := [2^64..2^64+5];; +gap> Position(r, 2^64); +1 +gap> Position(r, 2^64+3); +4 +gap> Position(r, 2^64+10); +fail + +# For loop over small big-int range (keep it small!) +gap> sum := 0;; for x in [2^64..2^64+4] do sum := sum + x; od; sum; +92233720368547758090 +gap> sum = 5 * 2^64 + 10; +true + +# For loop over negative big-int range +gap> sum := 0;; for x in [-2^64-2..-2^64] do sum := sum + x; od; sum; +-55340232221128654851 +gap> sum = -3 * 2^64 - 3; +true + +# For loop with increment +gap> vals := [];; for x in [2^64, 2^64+2..2^64+6] do Add(vals, x); od; vals; +[ 18446744073709551616, 18446744073709551618, 18446744073709551620, + 18446744073709551622 ] + +# List with function on big int range +gap> List([2^64..2^64+3], x -> x - 2^64); +[ 0, 1, 2, 3 ] +gap> List([2^64..2^64+3], x -> x mod 2); +[ 0, 1, 0, 1 ] + +# List with function on negative big int range +gap> List([-2^64-2..-2^64], x -> x + 2^64); +[ -2, -1, 0 ] + +# Filtered on big int range +gap> Filtered([2^64..2^64+5], x -> x mod 2 = 0); +[ 18446744073709551616, 18446744073709551618, 18446744073709551620 ] + +# ForAll/ForAny on big int range +gap> ForAll([2^64..2^64+3], x -> x > 0); +true +gap> ForAny([2^64..2^64+3], x -> x mod 2 = 1); +true + +# Number on big int range +gap> Number([2^64..2^64+5], x -> x mod 2 = 0); +3 + +# Sum/Product on small big-int range +gap> Sum([2^64..2^64+2]); +55340232221128654851 +gap> Product([2^64..2^64+1]); +340282366920938463481821351505477763072 + +# in operator +gap> 2^64 in [2^64..2^64+5]; +true +gap> 2^64+3 in [2^64..2^64+5]; +true +gap> 2^64+10 in [2^64..2^64+5]; +false +gap> 2^64-1 in [2^64..2^64+5]; +false + +# Print representation +gap> [2^64..2^64+3]; +[ 18446744073709551616 .. 18446744073709551619 ] +gap> [2^64, 2^64+2..2^64+6]; +[ 18446744073709551616, 18446744073709551618 .. 18446744073709551622 ] +gap> [-2^64..-2^64+2]; +[ -18446744073709551616 .. -18446744073709551614 ] + +# Empty ranges with big integers still work +gap> [2^64+5..2^64]; +[ ] +gap> Length([2^64+5..2^64]); +0 + +# Singleton ranges with big integers +gap> [2^64..2^64]; +[ 18446744073709551616 ] +gap> Length([2^64..2^64]); +1 + +# Concatenation with big int ranges (converted to plain lists) +gap> Concatenation([2^64..2^64+1], [2^64+2..2^64+3]); +[ 18446744073709551616, 18446744073709551617, 18446744073709551618, + 18446744073709551619 ] +gap> STOP_TEST("range_bigint.tst");