aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2026-01-11 15:08:46 -0500
committerBruce Hill <bruce@bruce-hill.com>2026-01-11 15:08:46 -0500
commitfd0c9762684821ed9931b3e59c812f1849f1a7d4 (patch)
treecd7a87d985a16efdae7b9c092d89a0b9d2a40a15
parenta4eb2d1ad35f2ee2da2aa442b19d2e8267e71afa (diff)
Add missing functionality.
-rw-r--r--src/environment.c20
-rw-r--r--src/stdlib/reals.c346
-rw-r--r--src/stdlib/reals.h38
-rw-r--r--test/reals.tm138
4 files changed, 528 insertions, 14 deletions
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;
@@ -259,6 +282,98 @@ Real_t Real$divided_by(Real_t a, Real_t b) {
}
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)) {
double d = sqrt(a.d);
@@ -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!")