From fd0c9762684821ed9931b3e59c812f1849f1a7d4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 11 Jan 2026 15:08:46 -0500 Subject: Add missing functionality. --- src/environment.c | 20 +++- src/stdlib/reals.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/stdlib/reals.h | 38 ++++-- test/reals.tm | 138 +++++++++++++++++++++ 4 files changed, 528 insertions(+), 14 deletions(-) create mode 100644 test/reals.tm diff --git a/src/environment.c b/src/environment.c index 8087820a..1dfeb6bc 100644 --- a/src/environment.c +++ b/src/environment.c @@ -292,15 +292,33 @@ env_t *global_env(bool source_mapping) { #undef C MAKE_TYPE( // "Real", REAL_TYPE, Text("Real_t"), Text("Real$info"), // + {"abs", "Real$abs", "func(x:Real -> Real)"}, // + {"acos", "Real$acos", "func(x:Real -> Real)"}, // + {"asin", "Real$asin", "func(x:Real -> Real)"}, // + {"atan", "Real$atan", "func(x:Real -> Real)"}, // + {"atan2", "Real$atan2", "func(y,x:Real -> Real)"}, // + {"ceil", "Real$ceil", "func(x:Real -> Real)"}, // + {"clamped", "Real$clamped", "func(x,low,high:Real -> Real)"}, + {"cos", "Real$cos", "func(x:Real -> Real)"}, // {"divided_by", "Real$divided_by", "func(x,y:Real -> Real)"}, // + {"exp", "Real$exp", "func(x:Real -> Real)"}, // + {"floor", "Real$floor", "func(x:Real -> Real)"}, // + {"is_between", "Real$is_between", "func(x,low,high:Real -> Bool)"}, // + {"log", "Real$log", "func(x:Real -> Real)"}, // + {"log10", "Real$log10", "func(x:Real -> Real)"}, // {"minus", "Real$minus", "func(x,y:Real -> Real)"}, // + {"mix", "Real$mix", "func(amount,x,y:Real -> Real)"}, // + {"mod", "Real$mod", "func(n,modulus:Real -> Real)"}, // + {"mod1", "Real$mod1", "func(n,modulus:Real -> Real)"}, // {"negative", "Real$negative", "func(x:Real -> Real)"}, // {"parse", "Real$parse", "func(text:Text, remainder:&Text?=none -> Real?)"}, // {"plus", "Real$plus", "func(x,y:Real -> Real)"}, // {"power", "Real$power", "func(base:Real,exponent:Real -> Real)"}, // + {"rounded_to", "Real$rounded_to", "func(x:Real, rounded_to:Real = 1.0 -> Real)"}, // + {"sin", "Real$sin", "func(x:Real -> Real)"}, // {"sqrt", "Real$sqrt", "func(x:Real -> Real)"}, // + {"tan", "Real$tan", "func(x:Real -> Real)"}, // {"times", "Real$times", "func(x,y:Real -> Real)"}, // - {"rounded_to", "Real$rounded_to", "func(x:Real, rounded_to:Real = 1.0 -> Real)"}, // ), MAKE_TYPE( // "CString", Type(CStringType), Text("char*"), Text("CString$info"), // diff --git a/src/stdlib/reals.c b/src/stdlib/reals.c index 5a9c1f85..87d1e64a 100644 --- a/src/stdlib/reals.c +++ b/src/stdlib/reals.c @@ -23,7 +23,30 @@ typedef struct { void *context; } constructive_t; -typedef enum { SYM_ADD, SYM_SUB, SYM_MUL, SYM_DIV, SYM_SQRT, SYM_POW, SYM_PI, SYM_E } sym_op_t; +typedef enum { + SYM_ADD, + SYM_SUB, + SYM_MUL, + SYM_DIV, + SYM_MOD, + SYM_SQRT, + SYM_POW, + SYM_SIN, + SYM_COS, + SYM_TAN, + SYM_ASIN, + SYM_ACOS, + SYM_ATAN, + SYM_ATAN2, + SYM_EXP, + SYM_LOG, + SYM_LOG10, + SYM_ABS, + SYM_FLOOR, + SYM_CEIL, + SYM_PI, + SYM_E +} sym_op_t; typedef struct symbolic { sym_op_t op; @@ -258,6 +281,98 @@ Real_t Real$divided_by(Real_t a, Real_t b) { return box_ptr(sym, REAL_TAG_SYMBOLIC); } +public +Real_t Real$mod(Real_t n, Real_t modulus) { + // Euclidean division, see: + // https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf + + // For fast path with doubles + if (!is_boxed(n) && !is_boxed(modulus)) { + double r = remainder(n.d, modulus.d); + r -= (r < 0.0) * (2.0 * (modulus.d < 0.0) - 1.0) * modulus.d; + return make_double(r); + } + + // For rationals, compute exactly + if (get_tag(n) == REAL_TAG_RATIONAL && get_tag(modulus) == REAL_TAG_RATIONAL) { + rational_t *rn = get_ptr(n); + rational_t *rm = get_ptr(modulus); + + // Compute n - floor(n/modulus) * modulus + mpq_t quotient, floored, result; + mpq_init(quotient); + mpq_init(floored); + mpq_init(result); + + mpq_div(quotient, &rn->value, &rm->value); + + // Floor the quotient + mpz_t floor_z; + mpz_init(floor_z); + mpz_fdiv_q(floor_z, mpq_numref(quotient), mpq_denref(quotient)); + mpq_set_z(floored, floor_z); + + // result = n - floor * modulus + mpq_mul(result, floored, &rm->value); + mpq_sub(result, &rn->value, result); + + // Apply Euclidean correction + if (mpq_sgn(result) < 0) { + if (mpq_sgn(&rm->value) < 0) { + mpq_sub(result, result, &rm->value); + } else { + mpq_add(result, result, &rm->value); + } + } + + rational_t *res = GC_MALLOC(sizeof(rational_t)); + mpq_init(&res->value); + mpq_set(&res->value, result); + + mpq_clear(quotient); + mpq_clear(floored); + mpq_clear(result); + mpz_clear(floor_z); + + return box_ptr(res, REAL_TAG_RATIONAL); + } + + // Fallback to symbolic + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_MOD; + sym->left = n; + sym->right = modulus; + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$mod1(Real_t n, Real_t modulus) { + Real_t one = make_double(1.0); + return Real$plus(one, Real$mod(Real$minus(n, one), modulus)); +} + +public +Real_t Real$mix(Real_t amount, Real_t x, Real_t y) { + // mix = (1 - amount) * x + amount * y + Real_t one = make_double(1.0); + Real_t one_minus_amount = Real$minus(one, amount); + Real_t left_term = Real$times(one_minus_amount, x); + Real_t right_term = Real$times(amount, y); + return Real$plus(left_term, right_term); +} + +public +bool Real$is_between(Real_t x, Real_t low, Real_t high) { + return Real$compare(&low, &x, NULL) <= 0 && Real$compare(&x, &high, NULL) <= 0; +} + +public +Real_t Real$clamped(Real_t x, Real_t low, Real_t high) { + if (Real$compare(&x, &low, NULL) <= 0) return low; + if (Real$compare(&x, &high, NULL) >= 0) return high; + return x; +} + public Real_t Real$sqrt(Real_t a) { if (!is_boxed(a)) { @@ -718,6 +833,235 @@ Real_t Real$rounded_to(Real_t x, Real_t round_to) { return box_ptr(result, REAL_TAG_RATIONAL); } +// Trigonometric functions - return symbolic expressions +public +Real_t Real$sin(Real_t x) { + if (!is_boxed(x)) { + double result = sin(x.d); + // Check if result is exact (e.g., sin(0) = 0) + if (result == 0.0 || result == 1.0 || result == -1.0) { + return make_double(result); + } + } + + // Create symbolic expression + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_SIN; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$cos(Real_t x) { + if (!is_boxed(x)) { + double result = cos(x.d); + if (result == 0.0 || result == 1.0 || result == -1.0) { + return make_double(result); + } + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_COS; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$tan(Real_t x) { + if (!is_boxed(x)) { + double result = tan(x.d); + if (result == 0.0) return make_double(0.0); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_TAN; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$asin(Real_t x) { + if (!is_boxed(x)) { + double result = asin(x.d); + if (result == 0.0) return make_double(0.0); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_ASIN; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$acos(Real_t x) { + if (!is_boxed(x)) { + double result = acos(x.d); + if (result == 0.0) return make_double(0.0); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_ACOS; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$atan(Real_t x) { + if (!is_boxed(x)) { + double result = atan(x.d); + if (result == 0.0) return make_double(0.0); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_ATAN; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$atan2(Real_t y, Real_t x) { + if (!is_boxed(y) && !is_boxed(x)) { + double result = atan2(y.d, x.d); + if (result == 0.0) return make_double(0.0); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_ATAN2; + sym->left = y; + sym->right = x; + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$exp(Real_t x) { + if (!is_boxed(x)) { + feclearexcept(FE_INEXACT); + double result = exp(x.d); + if (!fetestexcept(FE_INEXACT) && isfinite(result)) { + return make_double(result); + } + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_EXP; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$log(Real_t x) { + if (!is_boxed(x)) { + feclearexcept(FE_INEXACT); + double result = log(x.d); + if (!fetestexcept(FE_INEXACT) && isfinite(result)) { + return make_double(result); + } + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_LOG; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$log10(Real_t x) { + if (!is_boxed(x)) { + feclearexcept(FE_INEXACT); + double result = log10(x.d); + if (!fetestexcept(FE_INEXACT) && isfinite(result)) { + return make_double(result); + } + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_LOG10; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$abs(Real_t x) { + if (!is_boxed(x)) { + return make_double(fabs(x.d)); + } + + if (get_tag(x) == REAL_TAG_RATIONAL) { + rational_t *r = get_ptr(x); + rational_t *result = GC_MALLOC(sizeof(rational_t)); + mpq_init(&result->value); + mpq_abs(&result->value, &r->value); + return box_ptr(result, REAL_TAG_RATIONAL); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_ABS; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$floor(Real_t x) { + if (!is_boxed(x)) { + return make_double(floor(x.d)); + } + + if (get_tag(x) == REAL_TAG_RATIONAL) { + rational_t *r = get_ptr(x); + mpz_t result; + mpz_init(result); + mpz_fdiv_q(result, mpq_numref(&r->value), mpq_denref(&r->value)); + + rational_t *rat = GC_MALLOC(sizeof(rational_t)); + mpq_init(&rat->value); + mpq_set_z(&rat->value, result); + mpz_clear(result); + return box_ptr(rat, REAL_TAG_RATIONAL); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_FLOOR; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + +public +Real_t Real$ceil(Real_t x) { + if (!is_boxed(x)) { + return make_double(ceil(x.d)); + } + + if (get_tag(x) == REAL_TAG_RATIONAL) { + rational_t *r = get_ptr(x); + mpz_t result; + mpz_init(result); + mpz_cdiv_q(result, mpq_numref(&r->value), mpq_denref(&r->value)); + + rational_t *rat = GC_MALLOC(sizeof(rational_t)); + mpq_init(&rat->value); + mpq_set_z(&rat->value, result); + mpz_clear(result); + return box_ptr(rat, REAL_TAG_RATIONAL); + } + + symbolic_t *sym = GC_MALLOC(sizeof(symbolic_t)); + sym->op = SYM_CEIL; + sym->left = x; + sym->right = make_double(0); + return box_ptr(sym, REAL_TAG_SYMBOLIC); +} + public int Real$test() { GC_INIT(); diff --git a/src/stdlib/reals.h b/src/stdlib/reals.h index ddb91f66..10c071f9 100644 --- a/src/stdlib/reals.h +++ b/src/stdlib/reals.h @@ -19,28 +19,42 @@ #define NONE_REAL ((Real_t){.u64 = QNAN_MASK | REAL_TAG_NONE}) -Text_t Real$value_as_text(Real_t x); - +Int_t Real$as_int(Real_t x, bool truncate); OptionalReal_t Real$parse(Text_t text, Text_t *remainder); -Real_t Real$from_text(Text_t text); +Real_t Real$abs(Real_t x); +Real_t Real$acos(Real_t x); +Real_t Real$asin(Real_t x); +Real_t Real$atan(Real_t x); +Real_t Real$atan2(Real_t y, Real_t x); +Real_t Real$ceil(Real_t x); +Real_t Real$clamped(Real_t x, Real_t low, Real_t high); +Real_t Real$cos(Real_t x); +Real_t Real$divided_by(Real_t x, Real_t y); +Real_t Real$exp(Real_t x); +Real_t Real$floor(Real_t x); Real_t Real$from_float64(double n); Real_t Real$from_int(Int_t i); - -double Real$as_float64(Real_t n, bool truncate); -Int_t Real$as_int(Real_t x, bool truncate); - +Real_t Real$from_text(Text_t text); +Real_t Real$log(Real_t x); +Real_t Real$log10(Real_t x); +Real_t Real$minus(Real_t x, Real_t y); +Real_t Real$mix(Real_t amount, Real_t x, Real_t y); +Real_t Real$mod(Real_t n, Real_t modulus); +Real_t Real$mod1(Real_t n, Real_t modulus); Real_t Real$negative(Real_t x); Real_t Real$plus(Real_t x, Real_t y); -Real_t Real$minus(Real_t x, Real_t y); -Real_t Real$times(Real_t x, Real_t y); -Real_t Real$divided_by(Real_t x, Real_t y); Real_t Real$power(Real_t base, Real_t exp); +Real_t Real$rounded_to(Real_t x, Real_t round_to); +Real_t Real$sin(Real_t x); Real_t Real$sqrt(Real_t x); +Real_t Real$tan(Real_t x); +Real_t Real$times(Real_t x, Real_t y); +Text_t Real$value_as_text(Real_t x); bool Real$equal(const void *va, const void *vb, const TypeInfo_t *t); +bool Real$is_between(Real_t x, Real_t low, Real_t high); +double Real$as_float64(Real_t n, bool truncate); int32_t Real$compare(const void *va, const void *vb, const TypeInfo_t *t); -Real_t Real$rounded_to(Real_t x, Real_t round_to); - int Real$test(); extern const TypeInfo_t Real$info; diff --git a/test/reals.tm b/test/reals.tm new file mode 100644 index 00000000..dae4a465 --- /dev/null +++ b/test/reals.tm @@ -0,0 +1,138 @@ +func main() + # Basic arithmetic + n := Real(1.5) + assert n == 1.5 + assert n + n == 3.0 + assert n * 2 == 3.0 + assert n - n == 0.0 + assert n / 2 == 0.75 + + # Exact rational arithmetic + third := Real(1) / Real(3) + assert third * 3 == 1 + assert (third + third + third) == 1 + + # Decimal representation + assert Real(0.1) + Real(0.2) == Real(0.3) + assert (Real(0.1) + Real(0.2)).value_as_text() == "0.3" + + # Fraction representation + assert (Real(1) / Real(3)).value_as_text() == "1/3" + assert (Real(5) / Real(7)).value_as_text() == "5/7" + + # Terminating decimals + assert (Real(3) / Real(4)).value_as_text() == "0.75" + assert (Real(1) / Real(8)).value_as_text() == "0.125" + + # Square root + assert Real(4).sqrt() == 2 + assert Real(9).sqrt() == 3 + assert (Real(1) / Real(4)).sqrt() == Real(1) / Real(2) + + # Symbolic sqrt simplification + sqrt2 := Real(2).sqrt() + assert (sqrt2 * sqrt2) == 2 + + # Rounding + assert sqrt2.rounded_to(0.01) == 1.41 + assert sqrt2.rounded_to(0.001) == 1.414 + assert Real(1234).rounded_to(100) == 1200 + assert Real(1250).rounded_to(100) == 1300 + + # Trigonometric functions + assert Real(0).sin().value_as_text() == "0" + assert Real(0).cos().value_as_text() == "1" + assert Real(0).tan().value_as_text() == "0" + + # Exponential and logarithm + assert Real(0).exp() == 1 + + # Absolute value + assert Real(-5).abs() == 5 + assert Real(5).abs() == 5 + assert (Real(-3) / Real(4)).abs() == Real(3) / Real(4) + + # Floor and ceiling + assert Real(2.7).floor() == 2 + assert Real(2.3).floor() == 2 + assert Real(-2.3).floor() == -3 + assert Real(2.3).ceil() == 3 + assert Real(2.7).ceil() == 3 + assert Real(-2.3).ceil() == -2 + + # Floor/ceil on rationals + assert (Real(7) / Real(3)).floor() == 2 + assert (Real(7) / Real(3)).ceil() == 3 + assert (Real(-7) / Real(3)).floor() == -3 + assert (Real(-7) / Real(3)).ceil() == -2 + + # Modulo + assert Real(7).mod(3) == 1 + assert Real(-7).mod(3) == 2 # Euclidean division + assert Real(7).mod(-3) == -2 + assert Real(2.5).mod(1.0) == 0.5 + + # Mod1 + assert Real(3.7).mod1(2) == 1.7 + + # Mix (linear interpolation) + assert Real(0.25).mix(10, 20) == 12.5 + assert Real(0.0).mix(10, 20) == 10 + assert Real(1.0).mix(10, 20) == 20 + assert Real(0.5).mix(10, 20) == 15 + + # Is between + assert Real(5).is_between(1, 10) + assert Real(1).is_between(1, 10) + assert Real(10).is_between(1, 10) + assert Real(0).is_between(1, 10) == no + assert Real(11).is_between(1, 10) == no + + # Clamped + assert Real(5).clamped(1, 10) == 5 + assert Real(0).clamped(1, 10) == 1 + assert Real(15).clamped(1, 10) == 10 + assert Real(1).clamped(1, 10) == 1 + assert Real(10).clamped(1, 10) == 10 + + # Comparison + assert Real(3) > Real(2) + assert Real(2) < Real(3) + assert Real(3) >= Real(3) + assert Real(3) <= Real(3) + assert (Real(1) / Real(3)) < (Real(1) / Real(2)) + + # Negation + assert -Real(5) == Real(-5) + assert -(Real(1) / Real(3)) == Real(-1) / Real(3) + + # Power + assert Real(2).power(3) == 8 + assert Real(2).power(0) == 1 + + # Parsing + assert Real.parse("123") == 123 + assert Real.parse("1.5") == 1.5 + assert Real.parse("0.3333333333333333") == Real(0.3333333333333333) + assert Real.parse("-5") == -5 + assert Real.parse("-2.5") == -2.5 + + # None handling + none_real : Real? = none + assert none_real == none + assert Real.parse("not_a_number") == none + assert Real.parse("") == none + + # Large integers + big := Real(9999999999999999) + assert big + 1 == Real(10000000000000000) + + # Mixed operations preserve exactness + result := Real(1) / Real(3) + Real(2) / Real(3) + assert result == 1 + + # Symbolic expressions maintain structure + expr := sqrt2 + Real(1) + assert expr.value_as_text().contains("sqrt") + + print("All Real tests passed!") -- cgit v1.2.3