Write down that the left operand decides integer or float arithmetic
No behaviour change, by decision. `A# * 0.45` is 0 and `0.45 * A#` is 1.35, because every operator branches on `self->valuetype` and converts the right operand to match. It is inherited from the Go reference, a C128 promotes to float instead, and this interpreter is at least consistent about it -- so a dialect saying the left operand wins is a defensible position, and changing it to promotion would alter the result of every mixed expression in every existing program. **The defect was that nobody said so.** Chapter 3's "Numbers" did not mention it, Chapter 13 did not list it among the differences, and nothing fails when a program gets it wrong -- it computes something else and carries on. The game in examples/ lost its per-level speed increase to `5.6 + LEVEL# * 0.45` evaluating to a flat 5.6, and bled velocity out of every bounce through `0 - BLVX%(B#)` quantising to whole pixels. Both read correctly. Neither produced a diagnostic. Now said in three places: a section in Chapter 3 with the demonstration and the two rules that keep a program out of it (put the float on the left, put the answer somewhere with a `%` on it), a row in Chapter 13 naming it as the difference from 7.0 most likely to turn a working listing into a quietly wrong one, and the reasoning on value.h where the operators are declared. tests/value_arithmetic.c pins it in both directions across multiply and subtract, with a comment saying it is the documented contract rather than an accident -- so promotion becomes a decision somebody takes deliberately rather than a change that could slip in under a passing suite. TODO.md section 9 item 4, struck as a documentation outcome. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -146,6 +146,57 @@ static void test_pool_starts_empty(void)
|
||||
AKBASIC_ERR_BOUNDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The left operand alone decides integer or float arithmetic.
|
||||
*
|
||||
* **This is the documented contract, not an accident**, which is the whole
|
||||
* reason it is asserted: every operator branches on `self->valuetype` and
|
||||
* converts the right operand to match, so an integer on the left truncates a
|
||||
* float on the right and `A# * 0.45` is 0 where `0.45 * A#` is 1.35.
|
||||
*
|
||||
* It is inherited from the Go reference (basicvalue.go:190-193) and a C128
|
||||
* promotes to float instead. It may well be the wrong choice -- what makes it
|
||||
* expensive is that **nothing fails**, so a program computes something else and
|
||||
* carries on; a game written against this interpreter lost a per-level speed
|
||||
* increase and bled velocity out of every bounce before either was noticed.
|
||||
*
|
||||
* Changing it to promotion is a real change of dialect semantics and wants its
|
||||
* own decision. This test is here so that it has to be one: docs/03 and docs/13
|
||||
* describe the behaviour below, and a change that makes them wrong fails here
|
||||
* first. TODO.md section 9 item 4.
|
||||
*/
|
||||
static void test_left_operand_decides_the_type(void)
|
||||
{
|
||||
akbasic_Value *out = NULL;
|
||||
|
||||
/* Integer on the left: the float on the right is truncated to 0. */
|
||||
set_int(&A, 3);
|
||||
set_float(&B, 0.45);
|
||||
TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
|
||||
TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_INTEGER);
|
||||
TEST_REQUIRE_INT(out->intval, 0);
|
||||
|
||||
/* Float on the left: the integer on the right is widened. */
|
||||
set_float(&A, 0.45);
|
||||
set_int(&B, 3);
|
||||
TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
|
||||
TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_FLOAT);
|
||||
TEST_REQUIRE_FEQ(out->floatval, 1.35);
|
||||
|
||||
/* The same asymmetry in subtraction, which is how a program negates. */
|
||||
set_int(&A, 0);
|
||||
set_float(&B, 6.4);
|
||||
TEST_REQUIRE_OK(akbasic_value_math_minus(&A, &B, &SCRATCH, &out));
|
||||
TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_INTEGER);
|
||||
TEST_REQUIRE_INT(out->intval, -6);
|
||||
|
||||
set_float(&A, 0.0);
|
||||
set_float(&B, 6.4);
|
||||
TEST_REQUIRE_OK(akbasic_value_math_minus(&A, &B, &SCRATCH, &out));
|
||||
TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_FLOAT);
|
||||
TEST_REQUIRE_FEQ(out->floatval, -6.4);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akbasic_Value *out = NULL;
|
||||
@@ -310,6 +361,7 @@ int main(void)
|
||||
|
||||
test_maximum_length_string();
|
||||
test_pool_starts_empty();
|
||||
test_left_operand_decides_the_type();
|
||||
|
||||
return akbasic_test_failures;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user