123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646 | // Integer type infos and methods
#include <stdio.h> // Must be before gmp.h
#include <ctype.h>
#include <gc.h>
#include <gmp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "../util.h"
#include "datatypes.h"
#include "datatypes/typeinfo.h"
#include "integers.h"
#include "metamethods.h"
#include "number.h"
#include "optionals.h"
#include "print.h"
#include "siphash.h"
#include "text.h"
#define mpz_init_set_int(mpz, i) \
do { \
if likely ((i).small & 1L) mpz_init_set_si(mpz, (i).small >> 2L); \
else mpz_init_set(mpz, (i).big); \
} while (0)
#define Int$from_mpz(mpz) \
(mpz_cmpabs_ui(mpz, BIGGEST_SMALL_INT) <= 0 \
? ((Int_t){.small = (mpz_get_si(mpz) << 2L) | 1L}) \
: ((Int_t){.big = memcpy(GC_MALLOC(sizeof(__mpz_struct)), mpz, sizeof(__mpz_struct))}))
#define Int_mpz(i) (__mpz_struct *)((i).big)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfloat-equal"
// Truncating toward zero, so 1.9 becomes 1 and -1.9 becomes -1. With
// truncate=no the value must already be a whole number, which for an
// irrational like sqrt(2) it provably isn't.
public
Int_t Int$from_num(Num_t n, bool truncate) {
if unlikely (!truncate && !number_is_integer(n))
fail("Could not convert this Num to an Int without truncation: ", number_to_symbolic(n));
Num_t whole = number_trunc(n);
if unlikely (number_is_error(whole)) fail("Could not convert this Num to an Int: ", number_error_message(whole));
// Via the decimal expansion rather than int64: an Int has no width limit,
// and neither does a Num.
OptionalInt_t i = Int$from_str(number_to_string(whole, 0, NULL));
if unlikely (i.small == 0) fail("Could not convert this Num to an Int: ", number_to_symbolic(n));
return i;
}
public
PUREFUNC Int_t Int$from_float64(double n, bool truncate) {
mpz_t result;
mpz_init_set_d(result, n);
if (!truncate && unlikely(mpz_get_d(result) != n)) fail("Could not convert to an integer without truncation: ", n);
return Int$from_mpz(result);
}
#pragma clang diagnostic pop
public
PUREFUNC Int_t Int$from_int64(int64_t i) {
if likely (i >= SMALLEST_SMALL_INT && i <= BIGGEST_SMALL_INT) return (Int_t){.small = (i << 2L) | 1L};
mpz_t result;
mpz_init_set_si(result, i);
return Int$from_mpz(result);
}
public
int Int$print(FILE *f, Int_t i) {
if (likely(i.small & 1L)) {
return Int64$print(f, (int64_t)((i.small) >> 2L));
} else {
// Not gmp_fprintf(): its generic %-engine has to handle %Ff, so it
// drags mpf (and with it GMP's whole Toom/FFT multiply ladder) into
// every binary. mpz_out_str writes the digits straight to the stream
// and returns the byte count, which is all this needs.
return (int)mpz_out_str(f, 10, Int_mpz(i));
}
}
public
Text_t Int$value_as_text(Int_t i) {
if (likely(i.small & 1L)) {
char buf[INT64_DECIMAL_MAX];
size_t len;
char *digits = int64_to_decimal(i.small >> 2L, buf, &len);
return Text$from_strn(digits, len);
} else {
char *str = mpz_get_str(NULL, 10, Int_mpz(i));
return Text$from_str(str);
}
}
public
Text_t Int$as_text(const void *i, bool colorize, const TypeInfo_t *info) {
(void)info;
if (!i) return Text("Int");
Text_t text = Int$value_as_text(*(Int_t *)i);
if (colorize) text = Text$concat(Text("\x1b[35m"), text, Text("\x1b[m"));
return text;
}
static bool Int$is_none(const void *i, const TypeInfo_t *info) {
(void)info;
return ((Int_t *)i)->small == 0L;
}
public
PUREFUNC int32_t Int$compare_value(const Int_t x, const Int_t y) {
if (likely(x.small & y.small & 1L)) return (x.small > y.small) - (x.small < y.small);
else if (x.small & 1) return -mpz_cmp_si(Int_mpz(y), (x.small >> 2));
else if (y.small & 1) return mpz_cmp_si(Int_mpz(x), (y.small >> 2));
else return x.big == y.big ? 0 : mpz_cmp(Int_mpz(x), Int_mpz(y));
}
public
PUREFUNC int32_t Int$compare(const void *x, const void *y, const TypeInfo_t *info) {
(void)info;
return Int$compare_value(*(Int_t *)x, *(Int_t *)y);
}
public
PUREFUNC bool Int$equal_value(const Int_t x, const Int_t y) {
if (likely((x.small | y.small) & 1L)) return x.small == y.small;
else return x.big == y.big ? 0 : (mpz_cmp(Int_mpz(x), Int_mpz(y)) == 0);
}
public
PUREFUNC bool Int$equal(const void *x, const void *y, const TypeInfo_t *info) {
(void)info;
return Int$equal_value(*(Int_t *)x, *(Int_t *)y);
}
public
CONSTFUNC Int_t Int$clamped(Int_t x, Int_t low, Int_t high) {
return (Int$compare(&x, &low, &Int$info) <= 0) ? low : (Int$compare(&x, &high, &Int$info) >= 0 ? high : x);
}
public
CONSTFUNC bool Int$is_between(const Int_t x, const Int_t low, const Int_t high) {
int32_t low_cmp = Int$compare_value(x, low);
int32_t high_cmp = Int$compare_value(x, high);
return (low_cmp >= 0 && high_cmp <= 0) || (low_cmp <= 0 && high_cmp >= 0);
}
public
PUREFUNC uint64_t Int$hash(const void *vx, const TypeInfo_t *info) {
(void)info;
Int_t *x = (Int_t *)vx;
if (likely(x->small & 1L)) {
return siphash24((void *)x, sizeof(Int_t));
} else {
char *str = mpz_get_str(NULL, 16, Int_mpz(*x));
return siphash24((void *)str, strlen(str));
}
}
public
Text_t Int$hex(Int_t i, Int_t digits_int, bool uppercase, bool prefix) {
if (Int$is_negative(i)) return Text$concat(Text("-"), Int$hex(Int$negative(i), digits_int, uppercase, prefix));
if (likely(i.small & 1L)) {
uint64_t u64 = (uint64_t)(i.small >> 2);
return Text$from_str(String(
hex(u64, .no_prefix = !prefix, .digits = Int32$from_int(digits_int, false), .uppercase = uppercase)));
} else {
char *str = mpz_get_str(NULL, 16, Int_mpz(i));
if (uppercase) {
for (char *c = str; *c; c++)
*c = (char)toupper(*c);
}
int64_t digits = Int64$from_int(digits_int, false);
int64_t needed_zeroes = digits - (int64_t)strlen(str);
if (needed_zeroes <= 0) return prefix ? Text$concat(Text("0x"), Text$from_str(str)) : Text$from_str(str);
char *zeroes = GC_MALLOC_ATOMIC((size_t)(needed_zeroes));
memset(zeroes, '0', (size_t)(needed_zeroes));
if (prefix) return Text$concat(Text("0x"), Text$from_str(zeroes), Text$from_str(str));
else return Text$concat(Text$from_str(zeroes), Text$from_str(str));
}
}
public
Text_t Int$octal(Int_t i, Int_t digits_int, bool prefix) {
if (Int$is_negative(i)) return Text$concat(Text("-"), Int$octal(Int$negative(i), digits_int, prefix));
if (likely(i.small & 1L)) {
uint64_t u64 = (uint64_t)(i.small >> 2);
return Text$from_str(String(oct(u64, .no_prefix = !prefix, .digits = Int32$from_int(digits_int, false))));
} else {
int64_t digits = Int64$from_int(digits_int, false);
char *str = mpz_get_str(NULL, 8, Int_mpz(i));
int64_t needed_zeroes = digits - (int64_t)strlen(str);
if (needed_zeroes <= 0) return prefix ? Text$concat(Text("0o"), Text$from_str(str)) : Text$from_str(str);
char *zeroes = GC_MALLOC_ATOMIC((size_t)(needed_zeroes));
memset(zeroes, '0', (size_t)(needed_zeroes));
if (prefix) return Text$concat(Text("0o"), Text$from_str(zeroes), Text$from_str(str));
else return Text$concat(Text$from_str(zeroes), Text$from_str(str));
}
}
public
Int_t Int$slow_plus(Int_t x, Int_t y) {
mpz_t result;
mpz_init_set_int(result, x);
if (y.small & 1L) {
if (y.small < 0L) mpz_sub_ui(result, result, (uint64_t)(-(y.small >> 2L)));
else mpz_add_ui(result, result, (uint64_t)(y.small >> 2L));
} else {
mpz_add(result, result, Int_mpz(y));
}
return Int$from_mpz(result);
}
public
Int_t Int$slow_minus(Int_t x, Int_t y) {
mpz_t result;
mpz_init_set_int(result, x);
if (y.small & 1L) {
if (y.small < 0L) mpz_add_ui(result, result, (uint64_t)(-(y.small >> 2L)));
else mpz_sub_ui(result, result, (uint64_t)(y.small >> 2L));
} else {
mpz_sub(result, result, Int_mpz(y));
}
return Int$from_mpz(result);
}
public
Int_t Int$slow_times(Int_t x, Int_t y) {
mpz_t result;
mpz_init_set_int(result, x);
if (y.small & 1L) mpz_mul_si(result, result, y.small >> 2L);
else mpz_mul(result, result, Int_mpz(y));
return Int$from_mpz(result);
}
public
Int_t Int$slow_divided_by(Int_t dividend, Int_t divisor) {
// Euclidean division, see:
// https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf
mpz_t quotient, remainder;
mpz_init_set_int(quotient, dividend);
mpz_init_set_int(remainder, divisor);
mpz_tdiv_qr(quotient, remainder, quotient, remainder);
if (mpz_sgn(remainder) < 0) {
bool d_positive = likely(divisor.small & 1L) ? divisor.small > 0x1L : mpz_sgn(Int_mpz(divisor)) > 0;
if (d_positive) mpz_sub_ui(quotient, quotient, 1);
else mpz_add_ui(quotient, quotient, 1);
}
return Int$from_mpz(quotient);
}
public
Int_t Int$slow_modulo(Int_t x, Int_t modulus) {
mpz_t result;
mpz_init_set_int(result, x);
mpz_t divisor;
mpz_init_set_int(divisor, modulus);
mpz_mod(result, result, divisor);
return Int$from_mpz(result);
}
public
Int_t Int$slow_modulo1(Int_t x, Int_t modulus) {
mpz_t result;
mpz_init_set_int(result, x);
mpz_sub_ui(result, result, 1);
mpz_t divisor;
mpz_init_set_int(divisor, modulus);
mpz_mod(result, result, divisor);
mpz_add_ui(result, result, 1);
return Int$from_mpz(result);
}
public
Int_t Int$slow_left_shifted(Int_t x, Int_t y) {
mp_bitcnt_t bits = (mp_bitcnt_t)Int64$from_int(y, false);
mpz_t result;
mpz_init_set_int(result, x);
mpz_mul_2exp(result, result, bits);
return Int$from_mpz(result);
}
public
Int_t Int$slow_right_shifted(Int_t x, Int_t y) {
mp_bitcnt_t bits = (mp_bitcnt_t)Int64$from_int(y, false);
mpz_t result;
mpz_init_set_int(result, x);
mpz_tdiv_q_2exp(result, result, bits);
return Int$from_mpz(result);
}
public
Int_t Int$slow_bit_and(Int_t x, Int_t y) {
mpz_t result;
mpz_init_set_int(result, x);
mpz_t y_mpz;
mpz_init_set_int(y_mpz, y);
mpz_and(result, result, y_mpz);
return Int$from_mpz(result);
}
public
Int_t Int$slow_bit_or(Int_t x, Int_t y) {
mpz_t result;
mpz_init_set_int(result, x);
mpz_t y_mpz;
mpz_init_set_int(y_mpz, y);
mpz_ior(result, result, y_mpz);
return Int$from_mpz(result);
}
public
Int_t Int$slow_bit_xor(Int_t x, Int_t y) {
mpz_t result;
mpz_init_set_int(result, x);
mpz_t y_mpz;
mpz_init_set_int(y_mpz, y);
mpz_xor(result, result, y_mpz);
return Int$from_mpz(result);
}
public
Int_t Int$slow_negated(Int_t x) {
mpz_t result;
mpz_init_set_int(result, x);
mpz_neg(result, result);
mpz_sub_ui(result, result, 1);
return Int$from_mpz(result);
}
public
Int_t Int$slow_negative(Int_t x) {
if (likely(x.small & 1L)) return (Int_t){.small = 4L * -((x.small) >> 2L) + 1L};
mpz_t result;
mpz_init_set_int(result, x);
mpz_neg(result, result);
return Int$from_mpz(result);
}
public
Int_t Int$abs(Int_t x) {
if (likely(x.small & 1L)) return (Int_t){.small = 4L * labs((x.small) >> 2L) + 1L};
mpz_t result;
mpz_init_set_int(result, x);
mpz_abs(result, result);
return Int$from_mpz(result);
}
public
Int_t Int$power(Int_t base, Int_t exponent) {
int64_t exp = Int64$from_int(exponent, false);
if (unlikely(exp < 0)) fail("Cannot take a negative power of an integer!");
mpz_t result;
mpz_init_set_int(result, base);
mpz_pow_ui(result, result, (uint64_t)exp);
return Int$from_mpz(result);
}
public
Int_t Int$gcd(Int_t x, Int_t y) {
if (likely(x.small & y.small & 0x1L)) return I_small(Int32$gcd(x.small >> 2L, y.small >> 2L));
mpz_t result;
mpz_init(result);
if (x.small & 0x1L) mpz_gcd_ui(result, Int_mpz(y), (uint64_t)labs(x.small >> 2L));
else if (y.small & 0x1L) mpz_gcd_ui(result, Int_mpz(x), (uint64_t)labs(y.small >> 2L));
else mpz_gcd(result, Int_mpz(x), Int_mpz(y));
return Int$from_mpz(result);
}
public
OptionalInt_t Int$sqrt(Int_t i) {
if (Int$compare_value(i, I(0)) < 0) return NONE_INT;
mpz_t result;
mpz_init_set_int(result, i);
mpz_sqrt(result, result);
return Int$from_mpz(result);
}
public
bool Int$get_bit(Int_t x, Int_t bit_index) {
mpz_t i;
mpz_init_set_int(i, x);
if (Int$compare_value(bit_index, I(1)) < 0) fail("Invalid bit index (expected 1 or higher): ", bit_index);
if (Int$compare_value(bit_index, Int$from_int64(INT64_MAX)) > 0) fail("Bit index is too large! ", bit_index);
int is_bit_set = mpz_tstbit(i, (mp_bitcnt_t)(Int64$from_int(bit_index, true) - 1));
return (bool)is_bit_set;
}
typedef struct {
OptionalInt_t current, last;
Int_t step;
} IntRange_t;
static OptionalInt_t _next_int(IntRange_t *info) {
OptionalInt_t i = info->current;
if (!Int$is_none(&i, &Int$info)) {
Int_t next = Int$plus(i, info->step);
if (!Int$is_none(&info->last, &Int$info)
&& Int$compare_value(next, info->last) == Int$compare_value(info->step, I(0)))
next = NONE_INT;
info->current = next;
}
return i;
}
public
PUREFUNC Closure_t Int$to(Int_t first, Int_t last, OptionalInt_t step) {
IntRange_t *range = GC_MALLOC(sizeof(IntRange_t));
range->current = first;
range->last = last;
range->step = Int$is_none(&step, &Int$info) ? Int$compare_value(last, first) >= 0
? (Int_t){.small = (1L << 2L) | 1L}
: (Int_t){.small = (-1L >> 2L) | 1L}
: step;
return (Closure_t){.fn = _next_int, .userdata = range};
}
public
PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) {
IntRange_t *range = GC_MALLOC(sizeof(IntRange_t));
range->current = first;
range->last = NONE_INT;
range->step = step;
return (Closure_t){.fn = _next_int, .userdata = range};
}
public
Int_t Int$from_str(const char *str) {
return Int$parse(Text$from_str(str), NONE_INT, NULL);
}
public
OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
const char *str = Text$as_c_string(text);
bool negative = (*str == '-');
if (negative || *str == '+') str += 1;
const char *end = str;
int32_t base32;
if (base.small != 0) {
base32 = Int32$from_int(base, false);
switch (base32) {
case 16:
if (strncmp(str, "0x", 2) == 0) {
base16_prefix:
str += 2;
}
end = str + strspn(str, "0123456789abcdefABCDEF");
break;
case 10:
base10:
end = str + strspn(str, "0123456789");
break;
case 8:
if (strncmp(str, "0o", 2) == 0) {
base8_prefix:
str += 2;
}
end = str + strspn(str, "01234567");
break;
case 2:
if (strncmp(str, "0b", 2) == 0) {
base2_prefix:
str += 2;
}
end = str + strspn(str, "01");
break;
case 1: {
str += strspn(str, "0");
size_t n = strspn(str, "1");
end = str + n;
if (remainder) *remainder = Text$from_str(end);
else if (*end != '\0') return NONE_INT;
return Int$from_int64((int64_t)n);
}
default: {
if (base32 < 1 || base32 > 36) {
if (remainder) *remainder = text;
return NONE_INT;
}
for (; *end; end++) {
char c = *end;
int32_t digit;
if ('0' <= c && c <= '9') {
digit = (c - (int)'0');
} else if ('a' <= c && c <= 'z') {
digit = (c - (int)'a');
} else if ('A' <= c && c <= 'Z') {
digit = (c - (int)'A');
} else {
break;
}
if (digit >= base32) break;
}
}
}
} else if (strncmp(str, "0x", 2) == 0) {
base32 = 16;
goto base16_prefix;
} else if (strncmp(str, "0o", 2) == 0) {
base32 = 8;
goto base8_prefix;
} else if (strncmp(str, "0b", 2) == 0) {
base32 = 2;
goto base2_prefix;
} else {
base32 = 10;
goto base10;
}
if (remainder) *remainder = Text$from_str(end);
else if (*end != '\0') return NONE_INT;
mpz_t i;
int result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), base32);
if (result != 0) {
if (remainder) *remainder = text;
return NONE_INT;
}
if (negative) {
mpz_neg(i, i);
}
return Int$from_mpz(i);
}
public
bool Int$is_prime(Int_t x, Int_t reps) {
mpz_t p;
mpz_init_set_int(p, x);
if (unlikely(Int$compare_value(reps, I(9999)) > 0))
fail("Number of prime-test repetitions should not be above 9999");
int reps_int = Int32$from_int(reps, false);
return (mpz_probab_prime_p(p, reps_int) != 0);
}
public
Int_t Int$next_prime(Int_t x) {
mpz_t p;
mpz_init_set_int(p, x);
mpz_nextprime(p, p);
return Int$from_mpz(p);
}
public
Int_t Int$choose(Int_t n, Int_t k) {
if unlikely (Int$compare_value(n, I_small(0)) < 0) fail("Negative inputs are not supported for choose()");
mpz_t ret;
mpz_init(ret);
int64_t k_i64 = Int64$from_int(k, false);
if unlikely (k_i64 < 0) fail("Negative inputs are not supported for choose()");
if likely (n.small & 1L) {
mpz_bin_uiui(ret, (unsigned long)(n.small >> 2L), (unsigned long)k_i64);
} else {
mpz_t n_mpz;
mpz_init_set_int(n_mpz, n);
mpz_bin_ui(ret, n_mpz, (unsigned long)k_i64);
}
return Int$from_mpz(ret);
}
public
Int_t Int$factorial(Int_t n) {
mpz_t ret;
mpz_init(ret);
int64_t n_i64 = Int64$from_int(n, false);
if unlikely (n_i64 < 0) fail("Factorials are not defined for negative numbers");
mpz_fac_ui(ret, (unsigned long)n_i64);
return Int$from_mpz(ret);
}
static void Int$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *info) {
(void)info;
Int_t i = *(Int_t *)obj;
if (likely(i.small & 1L)) {
fputc(0, out);
int64_t i64 = i.small >> 2L;
Int64$serialize(&i64, out, pointers, &Int64$info);
} else {
fputc(1, out);
mpz_t n;
mpz_init_set_int(n, *(Int_t *)obj);
mpz_out_raw(out, n);
}
}
static void Int$deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *info) {
(void)info;
int kind = fgetc(in);
if (kind == 0) {
int64_t i = 0;
Int64$deserialize(in, &i, pointers, &Int64$info);
// Only values in the tagged-pointer range were ever *written* as a
// small int, and anything outside it would overflow the `<< 2` below
// into a silently wrong number:
if (i < SMALLEST_SMALL_INT || i > BIGGEST_SMALL_INT) deserialization_failed();
*(Int_t *)obj = Int$from_int64(i);
} else if (kind == 1) {
// `mpz_inp_raw()` starts with a four-byte big-endian count of the bytes
// that follow (negated for a negative number) and then allocates that
// much, so vet the count against the data we actually have before
// handing the stream over to GMP:
long header_pos = ftell(in);
uint8_t header[4];
if (header_pos < 0 || fread(header, 1, sizeof(header), in) != sizeof(header)) deserialization_failed();
int32_t count = (int32_t)(((uint32_t)header[0] << 24) | ((uint32_t)header[1] << 16) | ((uint32_t)header[2] << 8)
| (uint32_t)header[3]);
int64_t magnitude = count < 0 ? -(int64_t)count : (int64_t)count;
if (magnitude > deserialization_bytes_remaining(in)) deserialization_failed();
if (fseek(in, header_pos, SEEK_SET) != 0) deserialization_failed();
mpz_t n;
mpz_init(n);
if (mpz_inp_raw(n, in) == 0) deserialization_failed();
*(Int_t *)obj = Int$from_mpz(n);
} else {
deserialization_failed();
}
}
public
const TypeInfo_t Int$info = {
.size = sizeof(Int_t),
.align = __alignof__(Int_t),
.metamethods =
{
.compare = Int$compare,
.equal = Int$equal,
.hash = Int$hash,
.as_text = Int$as_text,
.is_none = Int$is_none,
.serialize = Int$serialize,
.deserialize = Int$deserialize,
},
};
|