Skip to content

Commit

Permalink
Arithmetic expressions (#1083)
Browse files Browse the repository at this point in the history
Add support for arithmetic operators:

- Unary `+`.
- Binary `+`, `-`, `*`, `/`, `%`.

Specify their behavior for integer and floating-point types. Signed integer overflow is a programming error, handled in various ways. Unsigned integer overflow is specified as wrapping around, intended for hashing / crypto / PRNG use cases.

Co-authored-by: jonmeow <46229924+jonmeow@users.noreply.github.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Co-authored-by: josh11b <josh11b@users.noreply.github.com>
  • Loading branch information
4 people authored Mar 25, 2022
1 parent af01d71 commit 5d9ff87
Show file tree
Hide file tree
Showing 3 changed files with 1,016 additions and 7 deletions.
37 changes: 30 additions & 7 deletions docs/design/expressions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,21 @@ graph BT
x.(...)"]
click memberAccess "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/member_access.md"
negation["-x"]
as["x as T"]
click as "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/implicit_conversions.md"
not["not x"]
click not "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/logical_operators.md"
multiplication>"x * y<br>
x / y"]
click multiplication "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/arithmetic.md"
addition>"x + y<br>
x - y"]
click addition "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/arithmetic.md"
modulo["x % y"]
click modulo "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/arithmetic.md"
comparison["x == y<br>
x != y<br>
Expand All @@ -78,6 +88,9 @@ graph BT
x >= y"]
click comparison "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/comparison_operators.md"
not["not x"]
click not "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/logical_operators.md"
and>"x and y"]
click and "https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/expressions/logical_operators.md"
Expand All @@ -90,8 +103,12 @@ graph BT
expressionEnd["x;"]
memberAccess --> parens & braces & unqualifiedName
as & not --> memberAccess
comparison --> as
negation --> memberAccess
%% Use a longer arrow here to put `not` next to `and` and `or`.
not -----> memberAccess
multiplication & modulo & as --> negation
addition --> multiplication
comparison --> modulo & addition & as
and & or --> comparison & not
if & expressionEnd --> and & or
```
Expand Down Expand Up @@ -203,16 +220,22 @@ Most expressions are modeled as operators:

| Category | Operator | Syntax | Function |
| ---------- | ------------------------------- | --------- | --------------------------------------------------------------------- |
| Arithmetic | [`-`](arithmetic.md) (unary) | `-x` | The negation of `x`. |
| Arithmetic | [`+`](arithmetic.md) | `x + y` | The sum of `x` and `y`. |
| Arithmetic | [`-`](arithmetic.md) (binary) | `x - y` | The difference of `x` and `y`. |
| Arithmetic | [`*`](arithmetic.md) | `x * y` | The product of `x` and `y`. |
| Arithmetic | [`/`](arithmetic.md) | `x / y` | `x` divided by `y`, or the quotient thereof. |
| Arithmetic | [`%`](arithmetic.md) | `x % y` | `x` modulo `y`. |
| Conversion | [`as`](as_expressions.md) | `x as T` | Converts the value `x` to the type `T`. |
| Logical | [`and`](logical_operators.md) | `x and y` | A short-circuiting logical AND: `true` if both operands are `true`. |
| Logical | [`or`](logical_operators.md) | `x or y` | A short-circuiting logical OR: `true` if either operand is `true`. |
| Logical | [`not`](logical_operators.md) | `not x` | Logical NOT: `true` if the operand is `false`. |
| Comparison | [`==`](comparison_operators.md) | `x == y` | Equality: `true` if `x` is equal to `y`. |
| Comparison | [`!=`](comparison_operators.md) | `x != y` | Inequality: `true` if `x` is not equal to `y`. |
| Comparison | [`<`](comparison_operators.md) | `x < y` | Less than: `true` if `x` is less than `y`. |
| Comparison | [`<=`](comparison_operators.md) | `x <= y` | Less than or equal: `true` if `x` is less than or equal to `y`. |
| Comparison | [`>`](comparison_operators.md) | `x > y` | Greater than: `true` if `x` is greater than to `y`. |
| Comparison | [`>=`](comparison_operators.md) | `x >= y` | Greater than or equal: `true` if `x` is greater than or equal to `y`. |
| Logical | [`and`](logical_operators.md) | `x and y` | A short-circuiting logical AND: `true` if both operands are `true`. |
| Logical | [`or`](logical_operators.md) | `x or y` | A short-circuiting logical OR: `true` if either operand is `true`. |
| Logical | [`not`](logical_operators.md) | `not x` | Logical NOT: `true` if the operand is `false`. |

## Conversions and casts

Expand Down
281 changes: 281 additions & 0 deletions docs/design/expressions/arithmetic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
# Arithmetic

<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->

<!-- toc -->

## Table of contents

- [Overview](#overview)
- [Precedence and associativity](#precedence-and-associativity)
- [Built-in types](#built-in-types)
- [Integer types](#integer-types)
- [Overflow and other error conditions](#overflow-and-other-error-conditions)
- [Floating-point types](#floating-point-types)
- [Strings](#strings)
- [Extensibility](#extensibility)
- [Alternatives considered](#alternatives-considered)
- [References](#references)

<!-- tocstop -->

## Overview

Carbon provides a conventional set of arithmetic operators:

```
var a: i32 = 5;
var b: i32 = 3;
// -5
var negation: i32 = -a;
// 8
var sum: i32 = a + b;
// 2
var difference: i32 = a - b;
// 15
var product: i32 = a * b;
// 1
var quotient: i32 = a / b;
// 2
var remainder: i32 = a % b;
```

These operators have predefined meanings for some of Carbon's
[built-in types](#built-in-types).

User-defined types can define the meaning of these operations by
[implementing an interface](#extensibility) provided as part of the Carbon
standard library.

## Precedence and associativity

```mermaid
graph TD
negation["-x"] --> multiplicative & modulo
multiplicative>"x * y<br> x / y"] --> additive
additive>"x + y<br> x - y"]
modulo["x % y"]
```

<small>[Instructions for reading this diagram.](README.md#precedence)</small>

Binary `+` and `-` can be freely mixed, and are left-associative.

```
// -2, same as `((1 - 2) + 3) - 4`.
var n: i32 = 1 - 2 + 3 - 4;
```

Binary `*` and `/` can be freely mixed, and are left-associative.

```
// 0.375, same as `((1.0 / 2.0) * 3.0) / 4.0`.
var m: f32 = 1.0 / 2.0 * 3.0 / 4.0;
```

Unary `-` has higher precedence than binary `*`, `/`, and `%`. Binary `*` and
`/` have higher precedence than binary `+` and `-`.

```
// 5, same as `(-1) + ((-2) * (-3))`.
var x: i32 = -1 + -2 * -3;
// Error, parentheses required: no precedence order between `+` and `%`.
var y: i32 = 2 + 3 % 5;
```

## Built-in types

For binary operators, if the operands have different built-in types, they are
converted as follows:

- If the types are `uN` and `uM`, or they are `iN` and `iM`, the operands are
converted to the larger type.
- If one type is `iN` and the other type is `uM`, and `M` < `N`, the `uM`
operand is converted to `iN`.
- If one type is `fN` and the other type is `iM` or `uM`, and there is an
[implicit conversion](implicit_conversions.md#data-types) from the integer
type to `fN`, then the integer operand is converted to `fN`.

More broadly, if one operand is of built-in type and the other operand can be
implicitly converted to that type, then it is, unless that behavior is
[overridden](#extensibility).

A built-in arithmetic operation is performed if, after the above conversion
step, the operands have the same built-in type. The result type is that type.
The result type is never wider than the operands, and the conversions applied to
the operands are always lossless, so arithmetic between a wider unsigned integer
type and a narrower signed integer is not defined.

Although the conversions are always lossless, the arithmetic may still
[overflow](#overflow-and-other-error-conditions).

### Integer types

Signed and unsigned integer types support all the arithmetic operators.

Signed integer arithmetic produces the usual mathematical result. Unsigned
integer arithmetic in `uN` wraps around modulo 2<sup>`N`</sup>.

Division truncates towards zero. The result of the `%` operator is defined by
the equation `a % b == a - a / b * b`.

#### Overflow and other error conditions

Integer arithmetic is subject to two classes of problems for which an operation
has no representable result:

- Overflow, where the resulting value is too large to be represented in the
type, or, for `%`, when the implied multiplication overflows.
- Division by zero.

Unsigned integer arithmetic cannot overflow, but division by zero can still
occur.

**Note:** All arithmetic operators can overflow for signed integer types. For
example, given a value `v: iN` that is the least possible value for its type,
`-v`, `v + v`, `v - 1`, `v * 2`, `v / -1`, and `v % -1` all result in overflow.

Signed integer overflow and signed or unsigned integer division by zero are
programming errors:

- In a development build, they will be caught immediately when they happen at
runtime.
- In a performance build, the optimizer can assume that such conditions don't
occur. As a consequence, if they do, the behavior of the program is not
defined.
- In a hardened build, overflow and division by zero do not result in
undefined behavior. On overflow and division by zero, either the program
will be aborted, or the arithmetic will evaluate to a mathematically
incorrect result, such as a two's complement result or zero. The program
might not in all cases be aborted immediately -- for example, multiple
overflow checks might be combined into one, and if the result of an
arithmetic operation is never observed, the abort may not happen at all.

**TODO:** In a hardened build, should we prefer to trap on overflow, give a
two's complement result, or produce zero? Using zero may defeat some classes of
exploit, but comes at a code size and performance cost.

### Floating-point types

Floating-point types support all the arithmetic operators other than `%`.
Floating-point types in Carbon have IEEE 754 semantics, use the round-to-nearest
rounding mode, and do not set any floating-point exception state.

Because floating-point arithmetic follows IEEE 754 rules: overflow results in
±∞, and division by zero results in either ±∞ or, for 0.0 / 0.0, a quiet NaN.

### Strings

**TODO:** Decide whether strings are built-in types, and whether they support
`+` for concatenation. See
[#457](https://github.com/carbon-language/carbon-lang/issues/457).

## Extensibility

Arithmetic operators can be provided for user-defined types by implementing the
following family of interfaces:

```
// Unary `-`.
interface Negatable {
let Result:! Type = Self;
fn Negate[me: Self]() -> Result;
}
```

```
// Binary `+`.
interface AddableWith(U:! Type) {
let Result:! Type = Self;
fn Add[me: Self](other: U) -> Result;
}
constraint Addable {
extends AddableWith(Self) where .Result = Self;
}
```

```
// Binary `-`.
interface SubtractableWith(U:! Type) {
let Result:! Type = Self;
fn Subtract[me: Self](other: U) -> Result;
}
constraint Subtractable {
extends SubtractableWith(Self) where .Result = Self;
}
```

```
// Binary `*`.
interface MultipliableWith(U:! Type) {
let Result:! Type = Self;
fn Multiply[me: Self](other: U) -> Result;
}
constraint Multipliable {
extends MultipliableWith(Self) where .Result = Self;
}
```

```
// Binary `/`.
interface DividableWith(U:! Type) {
let Result:! Type = Self;
fn Divide[me: Self](other: U) -> Result;
}
constraint Dividable {
extends DividableWith(Self) where .Result = Self;
}
```

```
// Binary `%`.
interface ModuloWith(U:! Type) {
let Result:! Type = Self;
fn Mod[me: Self](other: U) -> Result;
}
constraint Modulo {
extends ModuloWith(Self) where .Result = Self;
}
```

Given `x: T` and `y: U`:

- The expression `-x` is rewritten to `x.(Negatable.Negate)()`.
- The expression `x + y` is rewritten to `x.(AddableWith(U).Add)(y)`.
- The expression `x - y` is rewritten to
`x.(SubtractableWith(U).Subtract)(y)`.
- The expression `x * y` is rewritten to
`x.(MultipliableWith(U).Multiply)(y)`.
- The expression `x / y` is rewritten to `x.(DividableWith(U).Divide)(y)`.
- The expression `x % y` is rewritten to `x.(ModuloWith(U).Mod)(y)`.

Implementations of these interfaces are provided for built-in types as necessary
to give the semantics described above.

## Alternatives considered

- [Use a sufficiently wide result type to avoid overflow](/proposals/p1083.md#use-a-sufficiently-wide-result-type-to-avoid-overflow)
- [Guarantee that the program never proceeds with an incorrect value after overflow](/proposals/p1083.md#guarantee-that-the-program-never-proceeds-with-an-incorrect-value-after-overflow)
- [Guarantee that all integer arithmetic is two's complement](/proposals/p1083.md#guarantee-that-all-integer-arithmetic-is-twos-complement)
- [Treat overflow as an error but don't optimize on it](/proposals/p1083.md#treat-overflow-as-an-error-but-dont-optimize-on-it)
- [Don't let `Unsigned` arithmetic wrap](/proposals/p1083.md#dont-let-unsigned-arithmetic-wrap)
- [Provide separate wrapping types](/proposals/p1083.md#provide-separate-wrapping-types)
- [Do not provide an ordering or division for `uN`](/proposals/p1083.md#do-not-provide-an-ordering-or-division-for-un)
- [Give unary `-` lower precedence](/proposals/p1083.md#give-unary---lower-precedence)
- [Include a unary plus operator](/proposals/p1083.md#include-a-unary-plus-operator)
- [Floating-point modulo operator](/proposals/p1083.md#floating-point-modulo-operator)
- [Provide different division operators](/proposals/p1083.md#provide-different-division-operators)
- [Use different division and modulo semantics](/proposals/p1083.md#use-different-division-and-modulo-semantics)
- [Use different precedence groups for division and multiplication](/proposals/p1083.md#use-different-precedence-groups-for-division-and-multiplication)
- [Use the same precedence group for modulo and multiplication](/proposals/p1083.md#use-the-same-precedence-group-for-modulo-and-multiplication)
- [Use a different spelling for modulo](/proposals/p1083.md#use-a-different-spelling-for-modulo)

## References

- Proposal
[#1083: arithmetic](https://github.com/carbon-language/carbon-lang/pull/1083).
Loading

0 comments on commit 5d9ff87

Please sign in to comment.