Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: modulo operator #1256

Merged
merged 8 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions docs/api/safeds/data/tabular/containers/Cell.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,21 @@ This class cannot be instantiated directly. It is only used for arguments of cal
) -> result: Cell<Number>

/**
* Perform a modulo operation.
* Perform a modulo operation. This is equivalent to the `%` operator.
*
* @example
* pipeline example {
* val column = Column("example", [5, 6]);
* val result = column.transform((cell) -> cell.mod(3));
* // Column("example", [2, 0])
* }
*
* @example
* pipeline example {
* val column = Column("example", [5, 6]);
* val result = column.transform((cell) -> cell % 3);
* // Column("example", [2, 0])
* }
*/
@Pure
fun mod(
Expand Down Expand Up @@ -661,7 +668,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="309"
```sds linenums="316"
@Pure
fun eq(
other: Any?
Expand Down Expand Up @@ -730,7 +737,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="353"
```sds linenums="360"
@Pure
fun ge(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand Down Expand Up @@ -772,7 +779,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="375"
```sds linenums="382"
@Pure
fun gt(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand Down Expand Up @@ -814,7 +821,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="397"
```sds linenums="404"
@Pure
fun le(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand Down Expand Up @@ -856,7 +863,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="419"
```sds linenums="426"
@Pure
fun lt(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand All @@ -865,7 +872,7 @@ pipeline example {

## <code class="doc-symbol doc-symbol-function"></code> `mod` {#safeds.data.tabular.containers.Cell.mod data-toc-label='[function] mod'}

Perform a modulo operation.
Perform a modulo operation. This is equivalent to the `%` operator.

**Parameters:**

Expand All @@ -888,10 +895,17 @@ pipeline example {
// Column("example", [2, 0])
}
```
```sds
pipeline example {
val column = Column("example", [5, 6]);
val result = column.transform((cell) -> cell % 3);
// Column("example", [2, 0])
}
```

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="228"
```sds linenums="235"
@Pure
fun mod(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand Down Expand Up @@ -933,7 +947,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="250"
```sds linenums="257"
@Pure
fun mul(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand Down Expand Up @@ -1009,7 +1023,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="331"
```sds linenums="338"
@Pure
fun neq(
other: Any?
Expand Down Expand Up @@ -1122,7 +1136,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="265"
```sds linenums="272"
@Pure
fun pow(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand Down Expand Up @@ -1164,7 +1178,7 @@ pipeline example {

??? quote "Stub code in `Cell.sdsstub`"

```sds linenums="287"
```sds linenums="294"
@Pure
fun ^sub(
other: union<Number, Cell> // TODO, once cell types can be inferred: union<Number, Cell<Number>>
Expand Down
28 changes: 19 additions & 9 deletions docs/pipeline-language/expressions/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ The usual arithmetic operations are also supported for integers, floats and comb
- Subtraction: `#!sds 6 - 2.9` (result is a float)
- Multiplication: `#!sds 1.1 * 3` (result is a float)
- Division: `#!sds 1.0 / 4.2` (result is a float)
- Modulo: `#!sds 5 % 2` (result is an integer)

The `%` operator is well-known from other programming languages. However, there is no consensus on its result for negative operands. Safe-DS defines `n % d` as $n - \left\lfloor\frac{n}{d}\right\rfloor \cdot d$, where $\left\lfloor\text{ }\right\rfloor$ is the floor function (rounding down). The result always has the same sign as the divisor `d`. The following table shows some examples:

| `n` | `d` | `n % d` |
|-----|-----|---------|
| 5 | 3 | 2 |
| 5 | -3 | -1 |
| -5 | 3 | 1 |
| -5 | -3 | -2 |

Finally, two numbers can be compared, which results in a boolean. The integer `#!sds 3` for example is less than the integer `#!sds 5`. Safe-DS offers operators to do such checks for order:

Expand All @@ -33,22 +43,22 @@ To work with logic, Safe-DS has the two boolean literals `#!sds false` and `#!sd
- (Logical) **negation** (example `#!sds not a`): Output is `#!sds true` if and only if the operand is false:

| `#!sds not a` | false | true |
|---------|-------|-------|
| &nbsp; | true | false |
|---------------|-------|-------|
| &nbsp; | true | false |

- **Conjunction** (example `#!sds a and b`): Output is `#!sds true` if and only if both operands are `#!sds true`. Note that the second operand is always evaluated, even if the first operand is `#!sds false` and, thus, already determines the result of the expression. The operator is not short-circuited:

| `#!sds a and b` | false | true |
|-----------|-------|-------|
| **false** | false | false |
| **true** | false | true |
|-----------------|-------|-------|
| **false** | false | false |
| **true** | false | true |

- **Disjunction** (example `#!sds a or b`): Output is `#!sds true` if and only if at least one operand is `#!sds true`. Note that the second operand is always evaluated, even if the first operand is `#!sds true` and, thus, already determines the result of the expression. The operator is not short-circuited:

| `#!sds a or b` | false | true |
|-----------|-------|------|
| **false** | false | true |
| **true** | true | true |
| `#!sds a or b` | false | true |
|----------------|-------|------|
| **false** | false | true |
| **true** | true | true |

## Equality Checks

Expand Down
2 changes: 1 addition & 1 deletion docs/pipeline-language/expressions/precedence.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ We all know that `#!sds 2 + 3 * 7` is `#!sds 23` and not `#!sds 35`. The reason
- `#!sds -` (unary, [arithmetic negations][operations-on-numbers])
- `#!sds as` ([type casts][type-casts])
- `#!sds ?:` ([Elvis operators][elvis-operator])
- `#!sds *`, `#!sds /` ([multiplicative operators][operations-on-numbers])
- `#!sds *`, `#!sds /`, `#!sds %` ([multiplicative operators][operations-on-numbers])
- `#!sds +`, `#!sds -` (binary, [additive operators][operations-on-numbers])
- `#!sds <`, `#!sds <=`, `#!sds >=`, `#!sds >` ([comparison operators][operations-on-numbers])
- `#!sds ===`, `#!sds ==`, `#!sds !==`, `#!sds !=` ([equality operators][equality-checks])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ SdsMultiplicativeExpression returns SdsExpression:
;

SdsMultiplicativeOperator returns string:
'*' | '/'
'*' | '/' | '%'
;

SdsElvisExpression returns SdsExpression:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,18 @@ export class SafeDsPartialEvaluator {
(leftOperand, rightOperand) => leftOperand / rightOperand,
evaluatedRight,
);
case '%':
// Division by zero
if (zeroConstants.some((it) => it.equals(evaluatedRight))) {
return UnknownEvaluatedNode;
}

return this.evaluateArithmeticOp(
evaluatedLeft,
(leftOperand, rightOperand) => ((leftOperand % rightOperand) + rightOperand) % rightOperand,
(leftOperand, rightOperand) => ((leftOperand % rightOperand) + rightOperand) % rightOperand,
evaluatedRight,
);

/* c8 ignore next 2 */
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ export class SafeDsTypeComputer {
case '-':
case '*':
case '/':
case '%':
return this.computeTypeOfArithmeticInfixOperation(node);

// Elvis operator
Expand Down
1 change: 1 addition & 0 deletions packages/safe-ds-lang/src/language/validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export const infixOperationOperandsMustHaveCorrectType = (services: SafeDsServic
case '-':
case '*':
case '/':
case '%':
if (
node.leftOperand &&
!typeChecker.isSubtypeOf(leftType, coreTypes.Float) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,21 @@ class Cell<out T = Any?> {
) -> result: Cell<Number>

/**
* Perform a modulo operation.
* Perform a modulo operation. This is equivalent to the `%` operator.
*
* @example
* pipeline example {
* val column = Column("example", [5, 6]);
* val result = column.transform((cell) -> cell.mod(3));
* // Column("example", [2, 0])
* }
*
* @example
* pipeline example {
* val column = Column("example", [5, 6]);
* val result = column.transform((cell) -> cell % 3);
* // Column("example", [2, 0])
* }
*/
@Pure
fun mod(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pipeline myPipeline {
1 % 2;
}

// -----------------------------------------------------------------------------

pipeline myPipeline {
1 % 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,8 @@ def test():
f((cell()) / (h()))
f((h()) / (cell()))
f((cell()) / (cell()))
f((h()) % (h()))
f((cell()) % (h()))
f((h()) % (cell()))
f((cell()) % (cell()))
f(__gen_eager_elvis(i(), i()))

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ pipeline test {
f(h() / cell());
f(cell() / cell());

f(h() % h());
f(cell() % h());
f(h() % cell());
f(cell() % cell());


f(i() ?: i());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ syntax_error

pipeline myPipeline {
% 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ syntax_error

pipeline myPipeline {
1 %;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ no_syntax_error

pipeline myPipeline {
1 % 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tests.partialValidation.recursiveCases.infixOperations.modulo

pipeline test {
// $TEST$ serialization 0.25
»0.25 % 0.5«;

// $TEST$ serialization 0.5
»1.5 % 1«;

// $TEST$ serialization 0.375
»1 % 0.625«;

// $TEST$ serialization 0
»1 % 1«;

// $TEST$ serialization -1
»3 % -2«;

// $TEST$ serialization 1
»-3 % 2«;

// $TEST$ serialization -1
»-3 % -2«;


// $TEST$ serialization ?
»1 % 0«;

// $TEST$ serialization ?
»1 % 0.0«;

// $TEST$ serialization ?
»1 % -0.0«;


// $TEST$ serialization ?
»true % 1«;

// $TEST$ serialization ?
»1 % true«;

// $TEST$ serialization ?
»unresolved % 1«;

// $TEST$ serialization ?
»1 % unresolved«;
}
Loading