mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 09:01:05 +01:00
This PR corrects misspellings identified by the [check-spelling action](https://github.com/marketplace/actions/check-spelling). The misspellings have been reported at https://github.com/jsoref/carbon-lang/commit/38a1c1640151899fd6da0442a92557f9543b6280#commitcomment-79197316 The action reports that the changes in this PR would make it happy: https://github.com/jsoref/carbon-lang/commit/173c8f9083a68aa61f7cfe94f720f1e5dc7f1ea3 Note: this PR does not include the action. If you're interested in running a spell check on every PR and push, that can be offered separately. Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
706 lines
34 KiB
Markdown
706 lines
34 KiB
Markdown
# Arithmetic expressions
|
||
|
||
<!--
|
||
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
|
||
-->
|
||
|
||
[Pull request](https://github.com/carbon-language/carbon-lang/pull/1083)
|
||
|
||
<!-- toc -->
|
||
|
||
## Table of contents
|
||
|
||
- [Problem](#problem)
|
||
- [Background](#background)
|
||
- [Symbols](#symbols)
|
||
- [Semantics](#semantics)
|
||
- [Proposal](#proposal)
|
||
- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
|
||
- [Choice of operator symbols](#choice-of-operator-symbols)
|
||
- [Signed integer semantics](#signed-integer-semantics)
|
||
- [Alternatives considered](#alternatives-considered)
|
||
- [Use a sufficiently wide result type to avoid overflow](#use-a-sufficiently-wide-result-type-to-avoid-overflow)
|
||
- [Guarantee that the program never proceeds with an incorrect value after overflow](#guarantee-that-the-program-never-proceeds-with-an-incorrect-value-after-overflow)
|
||
- [Guarantee that all integer arithmetic is two's complement](#guarantee-that-all-integer-arithmetic-is-twos-complement)
|
||
- [Treat overflow as an error but don't optimize on it](#treat-overflow-as-an-error-but-dont-optimize-on-it)
|
||
- [Don't let `Unsigned` arithmetic wrap](#dont-let-unsigned-arithmetic-wrap)
|
||
- [Provide separate wrapping types](#provide-separate-wrapping-types)
|
||
- [Do not provide an ordering or division for `uN`](#do-not-provide-an-ordering-or-division-for-un)
|
||
- [Give unary `-` lower precedence](#give-unary---lower-precedence)
|
||
- [Include a unary plus operator](#include-a-unary-plus-operator)
|
||
- [Floating-point modulo operator](#floating-point-modulo-operator)
|
||
- [Provide different division operators](#provide-different-division-operators)
|
||
- [Use different division and modulo semantics](#use-different-division-and-modulo-semantics)
|
||
- [Use different precedence groups for division and multiplication](#use-different-precedence-groups-for-division-and-multiplication)
|
||
- [Use the same precedence group for modulo and multiplication](#use-the-same-precedence-group-for-modulo-and-multiplication)
|
||
- [Use a different spelling for modulo](#use-a-different-spelling-for-modulo)
|
||
- [Future work](#future-work)
|
||
- [Provide separate wrapping operators](#provide-separate-wrapping-operators)
|
||
- [Provide separate operations to detect overflow](#provide-separate-operations-to-detect-overflow)
|
||
|
||
<!-- tocstop -->
|
||
|
||
## Problem
|
||
|
||
Carbon needs a set of arithmetic operators in order to perform basic
|
||
calculations on various kinds of built-in and user-defined numbers and
|
||
number-like values: integers, real numbers, complex numbers, vectors, matrices,
|
||
and so on.
|
||
|
||
## Background
|
||
|
||
### Symbols
|
||
|
||
Conventions for arithmetic operators have a very long tradition. The following
|
||
symbols have common meaning across C, C++, Java, JavaScript, Rust, Swift, and
|
||
many other languages:
|
||
|
||
- `+` and `-` mean addition and subtraction, as in mathematics. Unary `-`
|
||
forms a negated value -- an additive inverse. Sometimes, unary `+` is
|
||
permitted, as a no-op.
|
||
- `*` means multiplication, diverging from the use of '×', '⋅', or simply
|
||
juxtaposition in normal mathematical notation. However, this symbol does
|
||
have some visual similarity to '×'.
|
||
- `/` means division, diverging from the use of '÷', fraction notation, or an
|
||
exponent of -1 in mathematics. However, this symbol does somewhat visually
|
||
resemble fractional notation that has "fallen over" to the left.
|
||
- `%` means remainder, diverging from the use of the word "mod" in
|
||
mathematics, and, perhaps confusingly, resembling the '÷' division operator
|
||
from mathematics.
|
||
|
||
### Semantics
|
||
|
||
Efficient integer types often have finite bounds on the numbers they can
|
||
represent, and we expect Carbon's to be no different. This presents a problem
|
||
for operations that might produce values outside those bounds: what should the
|
||
behavior of a primitive arithmetic operation be if the result cannot be
|
||
represented? Moreover, some operations, such as division by zero, have no
|
||
mathematically-defined result.
|
||
|
||
Different languages take different approaches to this problem.
|
||
|
||
- In C and C++, signed integer overflow and division by zero -- including
|
||
floating-point division by zero -- have undefined behavior, meaning that
|
||
there are no constraints on the behavior of a program that performs such a
|
||
calculation.
|
||
- In [Rust](https://rust-lang.github.io/rfcs/0560-integer-overflow.html),
|
||
these conditions are classified as being errors, which, depending on various
|
||
factors, will either panic or give a two's complement result. Rust also
|
||
provides a `Wrapping<T>` type, where `T` is a signed or unsigned integer
|
||
type, that provides arithmetic with guaranteed two's complement wrapping
|
||
semantics.
|
||
- In Swift, overflow triggers a runtime fatal error. Arithmetic operators can
|
||
be prefixed with `&`, such as `big_num &* other_big_num`, to request two's
|
||
complement wrapping behavior instead.
|
||
- In Java, integer arithmetic has two's complement wrapping behavior. Division
|
||
by zero throws an exception.
|
||
- In JavaScript, all arithmetic is (at least notionally) performed in IEEE 754
|
||
double-precision floating-point, so all operations have defined results,
|
||
although some integer operations may produce non-integer results, such as
|
||
infinities or NaNs, and some produce incorrect integer results. For example,
|
||
in JavaScript, `100000001 * 100000001` evaluates to `10000000200000000` not
|
||
to `10000000200000001`.
|
||
- In LLVM, the result of overflow is a poison value, which results in
|
||
undefined behavior only when it is observed, for example by a branch, and
|
||
otherwise propagates to dependent values. This permits speculative execution
|
||
of arithmetic that might overflow.
|
||
|
||
## Proposal
|
||
|
||
Carbon will provide the five usual binary arithmetic operators -- `+`, `-`, `*`,
|
||
`/`, and `%` -- with their usual semantics. A unary minus `-` operator will also
|
||
be provided, but no unary plus `+` operator.
|
||
|
||
| Notation | Meaning |
|
||
| -------- | ------------------ |
|
||
| `-a` | Negation |
|
||
| `a + b` | Addition |
|
||
| `a - b` | Subtraction |
|
||
| `a * b` | Multiplication |
|
||
| `a / b` | Division |
|
||
| `a % b` | Modulo / remainder |
|
||
|
||
These operators follow the same precedence rules as in C-family languages --
|
||
multiplicative operators bind tighter than additive operators, and negation
|
||
binds tighter than multiplicative operators. Unlike in other C-family languages,
|
||
no precedence is defined between `%` and other binary operators, so
|
||
`a + b % c * d` is an error, and does not mean `a + (b % c) * d` as it would in
|
||
C++.
|
||
|
||
Unlike in C++, lossy conversions are never performed. Instead, built-in
|
||
arithmetic is not permitted unless one of the operand types can represent all
|
||
the values of the other operand. The calculation is performed in that operand
|
||
type; there is no implicit widening to Carbon's equivalent of `int`.
|
||
|
||
Unsigned integer types `uN` model arithmetic modulo 2<sup>`N`</sup>, and we
|
||
strongly advise that they are not used except when those semantics are desired,
|
||
such as in random number generation, cryptography, hashing, and similar domains.
|
||
|
||
For signed integer types `iN`, it is a programming error if overflow occurs. We
|
||
guarantee that in development build modes such errors will result in a runtime
|
||
trap, and that in hardened build modes the result will either be a trap or the
|
||
two's complement value.
|
||
|
||
Initially, no support will be provided for wrapping signed integer arithmetic,
|
||
nor for non-wrapping unsigned integer arithmetic. This can be added by a future
|
||
proposal if we find there is sufficient demand for either.
|
||
|
||
Floating-point types use IEEE 754 semantics, with round-to-nearest rounding
|
||
mode, no signaling NaNs, and no floating-point exceptions.
|
||
|
||
This proposal takes no position on whether the `+` operator is supported on
|
||
strings to perform concatenation.
|
||
|
||
See the changes to the design for more details; the rest of this proposal will
|
||
focus on the rationale and alternatives.
|
||
|
||
## Rationale based on Carbon's goals
|
||
|
||
- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
|
||
- Treating overflow as a programming error empowers static and dynamic
|
||
analysis tools to distinguish arithmetic bugs from intentional
|
||
wraparound.
|
||
- [Performance-critical software](/docs/project/goals.md#performance-critical-software)
|
||
- Allowing the optimizer to assume that signed integer overflow does not
|
||
occur in performance build modes allows certain important loop
|
||
optimizations to fire that would otherwise be incorrect.
|
||
- Avoiding extending into larger types when performing arithmetic on small
|
||
operands aids in the ability to vectorize.
|
||
- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution)
|
||
- Each subexpression of an arithmetic expression is given a specific type,
|
||
rather than being computed in a type of sufficient width for any
|
||
possible value, in order to make it simple to factor out subexpressions.
|
||
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
|
||
- Using the same operators as other languages, with largely the same
|
||
semantics as C++, should aid readability and writability especially for
|
||
those with less Carbon-specific knowledge.
|
||
- Performing built-in arithmetic in the larger operand type and refusing
|
||
cases where a lossy conversion would be performed in C++ reduces the
|
||
scope for confusion and surprises.
|
||
- [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms)
|
||
- Treating signed integer overflow as an error condition, and having it
|
||
produce a runtime error in some build modes, will assist with certain
|
||
forms of testing and with analysis of program behavior. For example,
|
||
fuzz testing can be used to locate overflow bugs, with confidence that
|
||
any overflow detected is unintentional.
|
||
- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments)
|
||
- The choice of division and modulo semantics aims to provide fast
|
||
execution and small code size on modern hardware architectures.
|
||
- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
|
||
- Using the same operator set as C++ may aid migration of C++ developers
|
||
and make code using interop between Carbon and C++ easier to follow.
|
||
- Making similar choices for the behavior of signed and unsigned types
|
||
will make a correct but unidiomatic conversion of C++ into Carbon
|
||
easier; however, in idiomatic Carbon, signed types are expected to be
|
||
used more frequently.
|
||
|
||
### Choice of operator symbols
|
||
|
||
We want Carbon to provide a close analogue of standard mathematical expression
|
||
notation, with some unsurprising set of operators. While accessibility to
|
||
beginners is important, the most important audience for which the operators set
|
||
should be unsurprising is programmers with C++ or Rust experience. The choice of
|
||
`+`, `-`, `*`, `/`, and `%` is ubiquitous among programming languages, and any
|
||
significant change to this symbol set for the benefit of programming novices
|
||
with a mathematical background would be a detriment to those with a background
|
||
in other programming languages.
|
||
|
||
Therefore we choose the conventional symbol set.
|
||
|
||
### Signed integer semantics
|
||
|
||
Carbon prioritizes fast and predictable performance above all other factors.
|
||
Allowing an optimizer to assume that certain forms of arithmetic do not result
|
||
in overflow can improve program performance and allow the generation of smaller
|
||
code. Moreover, while in general we only want to provide the means for accessing
|
||
the fastest possible implementation of an operation, we expect integer
|
||
arithmetic to be so ubiquitous that it's important that the most efficient
|
||
option be the default option.
|
||
|
||
At the same time, it's important that Carbon code can be used in safety-critical
|
||
scenarios where unbounded undefined behavior on integer arithmetic is
|
||
problematic, so in a hardened build mode, we provide stronger guarantees.
|
||
|
||
## Alternatives considered
|
||
|
||
### Use a sufficiently wide result type to avoid overflow
|
||
|
||
We could define that integer operators always produce a sufficiently wide result
|
||
type such that overflow never occurs, and defer all overflow handling --
|
||
checking for out-of-bounds values, wrapping around, or a programmer assertion
|
||
that overflow simply doesn't happen -- until the end of the computation or some
|
||
explicit step.
|
||
|
||
For example:
|
||
|
||
```
|
||
fn F(a: i32, b: i32, c: i32) {
|
||
// a * b is computed in i63,
|
||
// ... + c is computed in i64.
|
||
// =! is an assertion that the value is in-range.
|
||
let d: i32 =! a * b + c;
|
||
|
||
// Same, but =% says to wrap around.
|
||
let e: i32 =% a * b + c;
|
||
|
||
// If the value doesn't fit in the specified type, pattern-matching fails.
|
||
let f: i32 = a * b + c else { return; }
|
||
}
|
||
```
|
||
|
||
We could put a limit on how large an intermediate type can be, and reject if it
|
||
would require a larger intermediate operand than the largest we can efficiently
|
||
support.
|
||
|
||
This approach seems quite promising, but close inspection finds a number of
|
||
non-trivial issues.
|
||
|
||
Advantages:
|
||
|
||
- No implicit undefined behavior or incorrect results on overflow.
|
||
- Forces developer to think about the possibility of overflow and write down
|
||
what they intend to happen.
|
||
- If the final assignment is `=%` or `=!`, all intermediate arithmetic other
|
||
than division and remainder can be done in the result type, avoiding the
|
||
need for wide computations.
|
||
|
||
Disadvantages:
|
||
|
||
- This approach is novel and it's unknown whether developers would accept its
|
||
ergonomic burden.
|
||
- Decreases the uniformity of the model. While we can generalize `=!` to mean
|
||
"assign assuming the RHS fits into the LHS", there doesn't seem to be any
|
||
good generalization of `=%` to other situations and types.
|
||
- The largest type for which we can truly efficiently perform arithmetic is
|
||
`i64` / `u64`. While 128-bit arithmetic is typically available on our target
|
||
architectures, use of it will often be less efficient and may increase
|
||
register pressure. Calculations as simple as `(a + b) / c` may be rejected
|
||
if the operands are already in the largest efficient type.
|
||
- Operations such as negation and division can increase the width of the
|
||
operand: `-i32.Min` and `i32.Min / -1` don't fit in `i32`. The following
|
||
approaches to this problem were considered:
|
||
- Remove the `Min` value from `iN` types, so the negative and positive
|
||
range are identical. However, this would severely violate programmer
|
||
expectations, for example in some important bit-manipulation cases.
|
||
- Give integer types a range of values rather than simply a bit-width. For
|
||
example, we can say that negation on `i32` produces a type that can
|
||
represent [-2<sup>31</sup>+1, 2<sup>31</sup>], which still fits in 32
|
||
bits. However, this would add significant complexity to the type system,
|
||
and with this approach, division would still increase the bit width: for
|
||
example, `a / b`, where `a` and `b` are `iN`s, has 2<sup>`N`</sup>+1
|
||
distinct possible values. This is especially surprising because integer
|
||
division is usually expected to make a number smaller!
|
||
- Refactoring code becomes more challenging, as the appropriate intermediate
|
||
type must be determined. Mitigating this, the type system would inform the
|
||
programmer when they make a mistake.
|
||
- The `!` and `%` annotations become quite viral: they would be needed not
|
||
only in initialization and assignment, but likely also in `case`, `return`,
|
||
and other initialization contexts not using `=`. As an alternative, the
|
||
annotation could be put on the outermost arithmetic operator, but that is
|
||
likely to be syntactically awkward, especially in unparenthesized
|
||
expressions such as `a + b +% c`.
|
||
- It will likely become idiomatic in many communities to either always use `%`
|
||
or always use `!`.
|
||
- Always using `!` means that the developers see no benefit compared to
|
||
the behavior as proposed here, and nonetheless pay an ergonomic cost.
|
||
- Always using `%` means that we lose any ability to distinguish between
|
||
intentional wraparound and overflow, making a class of bugs that would
|
||
otherwise be easily detectible be undetectable without making the
|
||
program behavior correct.
|
||
- Division by zero is still not handled.
|
||
|
||
### Guarantee that the program never proceeds with an incorrect value after overflow
|
||
|
||
We could say that overflow errors always result in program termination, possibly
|
||
with the permission for the compiler to give the mathematically correct result
|
||
instead if it so chooses.
|
||
|
||
This would result in slower and larger code being generated in a lot of cases.
|
||
Some optimizations would still be possible if the optimizer could ensure that it
|
||
only increased the set of cases for which the correct result is given, but
|
||
current optimizers are not well-suited to perform that task.
|
||
|
||
Given Carbon's emphasis on performance, this approach is rejected without
|
||
prejudice until we have a demonstration that no important performance metric is
|
||
harmed by it.
|
||
|
||
### Guarantee that all integer arithmetic is two's complement
|
||
|
||
Instead of making overflow a programming error, we could define it as two's
|
||
complement. This is, for example, the approach taken by Java.
|
||
|
||
The major problem with this approach is that it makes erroneous wrapping and
|
||
intentional wrapping indistinguishable. This would make finding such bugs much
|
||
harder for readers of the code, and all but impossible for static and dynamic
|
||
analysis tools. Making overflow issues programming errors allows problems to be
|
||
caught earlier and more reliably.
|
||
|
||
### Treat overflow as an error but don't optimize on it
|
||
|
||
We could follow Rust and say that overflow is an error, but that we promise
|
||
we'll either catch it or give two's complement semantics. This approach is
|
||
currently rejected for the same reason we reject
|
||
[guaranteeing we catch all cases where we can't give a correct result](#guarantee-that-the-program-never-proceeds-with-an-incorrect-value-after-overflow).
|
||
|
||
### Don't let `Unsigned` arithmetic wrap
|
||
|
||
We could treat `Unsigned(N)` like `Integer(N)`, and make it an error by default
|
||
if it overflows. However, this doesn't seem like a great fit for the problem
|
||
domain.
|
||
|
||
There are, broadly speaking, two different classes of use cases we want to
|
||
support:
|
||
|
||
- Cases where the developer wants a number. They might have some expectation
|
||
of the range of the number -- eg, non-negative, or strictly positive, or
|
||
between -1 and 100 -- or they might just want a number and not have explicit
|
||
bounds. We expect `iN` to be used for all such cases, and do not want to
|
||
treat the non-negative cases as a distinct and special type.
|
||
- Cases where the developer wants the ring ℤ/2<sup>n</sup>ℤ of integers modulo
|
||
2<sup>n</sup>, for example in cryptography, hashing, or random number
|
||
generation.
|
||
|
||
The second class is certainly rarer than the first, but contains many important
|
||
use cases.
|
||
|
||
The typical arguments for using an unsigned type for the first class of use
|
||
cases are:
|
||
|
||
- Better expression of developer intent. It is generally preferable to make
|
||
invalid states unrepresentable, and if negative numbers are invalid, then
|
||
unsigned types are better suited than signed types. However, supporting this
|
||
use case with the same types used to support the wrapping use cases results
|
||
in a situation where one side or the other has to make compromises. The
|
||
alternative would be to have three different kinds of type: signed,
|
||
unsigned, and modulo. But in that setup, it's not clear that the value added
|
||
by unsigned types is worthwhile. Also, it's common to want to take the
|
||
difference of such unsigned quantities, and it's generally preferable for
|
||
such subtractions to produce a negative result rather than a subtle bug.
|
||
Moreover, while a restriction to non-negative values is common, supporting
|
||
only the case of a range restriction to [0, 2<sup>N</sup>-1], but not any
|
||
other range, does not do a good job of addressing the general desire to
|
||
capture intent and to make invalid states unrepresentable.
|
||
- Ability to reduce storage size. Spending a sign bit every time a number is
|
||
stored, even when it's known to be non-negative is wasteful. This is an
|
||
important concern, and one we should address, but it's thought to be better
|
||
to address this by annotating a field with a description of how it should be
|
||
packed -- such as in a bit-field -- rather than by changing the type of the
|
||
data and possibly the behavior of operations on it.
|
||
|
||
Additionally, providing unsigned types for which overflow is an error introduces
|
||
a much larger risk of that error state being inadvertently reached than for
|
||
signed types, because calculations typically involve numbers that are close to
|
||
zero, which is far from overflowing in signed types but near to overflowing in
|
||
unsigned types. For example, a calculation such as `a - b + c` may be known to
|
||
always produce a non-negative result, and the developer may be tempted to use
|
||
non-negative non-wrapping types for `a`, `b`, and `c`, but doing so introduces
|
||
the risk that the intermediate `a - b` calculation is negative. By contrast,
|
||
overflow would only occur in a signed calculation if the numbers involved were
|
||
very large.
|
||
|
||
### Provide separate wrapping types
|
||
|
||
We could provide distinct types for wrapping versus non-wrapping use cases,
|
||
independent of the choice of signedness. This approach is being followed by
|
||
Rust.
|
||
|
||
Advantages:
|
||
|
||
- Avoids coupling two decisions that are logically independent.
|
||
- Allows developer intent to be expressed more explicitly, in the case where
|
||
the intent is a non-negative but non-wrapping integer.
|
||
|
||
Disadvantages:
|
||
|
||
- Twice as many kinds of integer types, likely meaning twice as many `impl`s
|
||
need to be defined for each operation, twice as many overloads in each
|
||
overload set, and so on.
|
||
- The presence of unsigned types for which overflow is an error has a greater
|
||
risk of inadvertent overflow, as described in the previous section.
|
||
- Less familiar to those coming from C++.
|
||
|
||
### Do not provide an ordering or division for `uN`
|
||
|
||
For arithmetic purposes, we treat `uN` as the integers modulo 2<sup>N</sup>.
|
||
There is no single canonical total order for that ring, and because
|
||
multiplication by even numbers loses information by discarding the high-order
|
||
bit, not all non-zero numbers have
|
||
[multiplicative inverses](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse),
|
||
and so division is not well-defined. We could be more mathematically pure by
|
||
refusing to implement `<` and friends for `uN`, and similarly refusing to
|
||
support `/`.
|
||
|
||
However, the `uN` types exist as much for pragmatic purposes as for mathematical
|
||
ones. Making the choice to treat all `uN` values as non-negative is somewhat
|
||
arbitrary, but is both unsurprising to those coming from C++ and useful for the
|
||
cases where some ordering is desired.
|
||
|
||
### Give unary `-` lower precedence
|
||
|
||
In this proposal, `- a * b` is interpreted as `(-a) * b`. We could instead
|
||
interpret it as `- (a * b)`.
|
||
|
||
Advantages:
|
||
|
||
- Can be argued as better following mathematical convention.
|
||
|
||
Disadvantages:
|
||
|
||
- Unfamiliar to people coming from most other programming languages, including
|
||
C++.
|
||
- If we followed our normal precedence partial ordering rule, this would mean
|
||
that `a * -b` is invalid, because `*` has higher precedence than `-`.
|
||
|
||
Comparison to other languages:
|
||
|
||
- C and C++ give all unary operators (including unary `-` and unary `+`)
|
||
higher precedence than any binary operator.
|
||
- Go, Rust, and Swift give unary `-` higher precedence than multiplication.
|
||
- Python binds unary `-` more tightly than multiplication but less tightly
|
||
than exponentiation, so `- a * b` is `(- a) * b` but `- a ** b` is
|
||
`- (a ** b)`.
|
||
- Haskell gives unary `-` the same precedence as binary `-` and rejects
|
||
`a * - b`.
|
||
|
||
### Include a unary plus operator
|
||
|
||
C and C++ include a unary `+` operator. In principle, this operator permits
|
||
lists of numbers to be written with an operator attached to each:
|
||
|
||
```cpp
|
||
int arr[] = {
|
||
-100,
|
||
-50,
|
||
+20,
|
||
+400
|
||
};
|
||
```
|
||
|
||
... but in practice this use case is rare at best, and unary `+` in C++ is
|
||
instead mainly used to coerce operands to prvalues of built-in types. For
|
||
example, `auto *p = +[]{ /*...*/ };` might be used to create a function pointer
|
||
(`auto` deduction would fail without the unary `+` operator), and
|
||
`min(+Class::static_member, x)` might be used to force `Class::static_member` to
|
||
be loaded, to avoid requiring the static member to be defined.
|
||
|
||
Such usage of `+` is a design wart that we need not replicate. If we want a
|
||
mechanism to decay an operand, we can pick a better name for it than `+`.
|
||
|
||
### Floating-point modulo operator
|
||
|
||
It would be possible and sometimes useful to support the `%` operator for
|
||
floating-point types.
|
||
|
||
Advantages:
|
||
|
||
- When desired, `f1 % f2` would be a more concise notation than a call to an
|
||
`fmod` function (however it is named).
|
||
|
||
Disadvantages:
|
||
|
||
- Uses of this operation would likely be rare and unfamiliar enough that
|
||
people would assume an integer operation is being performed when they see
|
||
the operator.
|
||
- This operation is not available in hardware in many modern architectures,
|
||
and providing it as a built-in operator may create a false impression of its
|
||
implementation as a non-trivial library function.
|
||
- We lack sufficient evidence of utility to propose it at this time.
|
||
|
||
### Provide different division operators
|
||
|
||
We could follow Python3 and provide an `/` operator for integers that produces a
|
||
floating-point (or perhaps rational) type. This would be mathematically clean:
|
||
ignoring overflow and precision loss, all standard properties for division would
|
||
be maintained. Or we could follow Haskell and refuse to provide an `/` operator
|
||
between integers on the basis that such division is not mathematically defined
|
||
for that type. Or we could provide a division operator that produces a pair of
|
||
dividend and modulo, or an `Optional(Int)` that is absent whenever the division
|
||
is inexact. In each case, functionality not available through operators could be
|
||
provided with named functions instead.
|
||
|
||
All of these options are likely to be surprising to programmers coming from C++,
|
||
to a level that outweighs the perceived benefit.
|
||
|
||
### Use different division and modulo semantics
|
||
|
||
There are multiple somewhat-reasonable ways to define division operations for
|
||
signed integers (whether we provide those operations as operators or library
|
||
functions). Assuming operator notation for now, and that we define modulo as
|
||
`a % b == a - a / b * b`, the following options all have merit:
|
||
|
||
<!-- prettier-ignore -->
|
||
| Property | Round towards zero (truncating division) | Round towards negative infinity (floor division) | Round based on sign of divisor\[1] (Euclidean division) |
|
||
| ------------------------------------ | -------- | -------- | --------- |
|
||
| `(-a) / b ==`<br>` a / (-b)` | :+1: Yes | :+1: Yes | No |
|
||
| `(-a) / b ==`<br>` -(a / b)` | :+1: Yes | No | :+1: Yes |
|
||
| `a / (-b) ==`<br>` -(a / b)` | :+1: Yes | No | No |
|
||
| `(a + k * b) / b`<br>` == a / b + k` | No | :+1: Yes | :+1: Yes |
|
||
| Sign of `a % b` | Same as `a` | :+1: Same as `b` | :+1: Never negative |
|
||
| x86 instruction? | :+1: Yes: `cqo` (or similar) + `idiv` | First option + fixup:<br> `s * (a / (s * b))` where `s` is `sign(a) * sign(b)`[2] | First option + fixup:<br>`a / b - (a % b < 0)` |
|
||
| LLVM IR + optimization support | :+1: Yes | No | No |
|
||
| Use in existing languages | C, C++, Rust, Swift <br> `quotRem` in Haskell | `//` and `%` in Python <br> `/` in Python 2 only <br> `divMod` in Haskell | None? |
|
||
|
||
The cells marked :+1: suggest generally desirable properties. For further
|
||
reading, see
|
||
[this Microsoft Research paper](http://research.microsoft.com/pubs/151917/divmodnote-letter.pdf).
|
||
|
||
Our options here are as follows:
|
||
|
||
- Pick one of the two interpretations as the meaning of `/`, and (optionally)
|
||
pick one of the above interpretations as the meaning of `%`); perhaps
|
||
provide the others as library functions or as additional operators.
|
||
- Do not provide any of these operators for integers and provide only named
|
||
functions.
|
||
|
||
Note that we are not required to provide a `%` that is consistent with our
|
||
chosen `/` operator. (We could pick truncate-towards-zero for `/` and
|
||
truncate-towards-negative-infinity for `%`, for example.) There is
|
||
long-established tradition here, but it's unclear to what extent practicing
|
||
programmers really care about the relationship between `/` and `%`.
|
||
|
||
It is likely that most Carbon code that performs division and modulo between
|
||
signed integer types does not actually care about what happens when either
|
||
operand is negative. Therefore, following our goals of supporting
|
||
high-performance code and current CPU architectures, we will choose to implement
|
||
`/` and `%` as division with truncation towards zero. The other variants can be
|
||
provided by a library function, if at all.
|
||
|
||
[1]: That is: for positive divisors, round towards negative infinity; for
|
||
negative divisors, round towards positive infinity.
|
||
|
||
[2]: Here, `sign(a)` is 1 if `a >= 0` and is -1 if `a < 0`.
|
||
|
||
### Use different precedence groups for division and multiplication
|
||
|
||
Under this proposal, division and multiplication are in the same precedence
|
||
group, and are left-associative: `a * b / c * d` is `((a * b) / c) / d`, and not
|
||
`(a * b) / (c * d)` or some other grouping.
|
||
|
||
It's not feasible to provide a different interpretation here due to the risk of
|
||
confusing developers migrating from other languages. However, we could reject
|
||
such expressions and require explicit parentheses.
|
||
|
||
While the value of accepting code such as this is relatively low, the fact that
|
||
both existing programming languages and common mathematical education treat
|
||
multiplication and division as the same, left-associative, precedence group
|
||
means that it's unlikely to be a significant burden to expect Carbon developers
|
||
to remember this precedence rule.
|
||
|
||
### Use the same precedence group for modulo and multiplication
|
||
|
||
In most languages with the set of arithmetic operators discussed in this
|
||
proposal, `%` is considered a multiplicative operator, and so a sequence of `*`,
|
||
`/`, and `%` operators is processed left-to-right. In some sense this is
|
||
reasonable: `%` is notionally performing a division, after all. Moreover, in a
|
||
code search, I was unable to find evidence that it's common for precedence
|
||
errors with `%` to be checked in to source control, and C++ compilers don't have
|
||
warnings for mixing `%` with `+` without parentheses.
|
||
|
||
Giving `%` and `/` the same precedence also allows some kinds of code to be
|
||
written symmetrically:
|
||
|
||
```c++
|
||
char two_digit_number[] = {
|
||
'0' + m / 10,
|
||
'0' + m % 10,
|
||
0
|
||
};
|
||
char four_digit_number[] = {
|
||
'0' + n / 1000,
|
||
'0' + n / 100 % 10,
|
||
'0' + n / 10 % 10,
|
||
'0' + n % 10,
|
||
0
|
||
};
|
||
```
|
||
|
||
With minimal parentheses, that example would be written as follows under this
|
||
proposal:
|
||
|
||
```carbon
|
||
var two_digit_number: Array(Char, 3) = (
|
||
'0' + m / 10,
|
||
'0' + (m % 10),
|
||
0
|
||
);
|
||
var four_digit_number: Array(Char, 5) = (
|
||
'0' + n / 1000,
|
||
'0' + ((n / 100) % 10),
|
||
'0' + ((n / 10) % 10),
|
||
'0' + (n % 10),
|
||
0
|
||
);
|
||
```
|
||
|
||
We could use the same precedence rule as other languages, and permit examples
|
||
similar to the above to be written without any parentheses.
|
||
|
||
However, our [rule for precedence](p0555.md#when-to-add-precedence-edges) is:
|
||
|
||
> For every combination of operators, either it should be reasonable to expect
|
||
> most or all developers who regularly use Carbon to reliably remember the
|
||
> precedence, or there should not be a precedence rule.
|
||
|
||
It is not clear that a precedence rule that gives a defined meaning to, for
|
||
example, `a + b % c + d` would satisfy this rule. Moreover, in mathematics, the
|
||
"mod" operator generally binds very loosely: in 'n = m + 1 (mod 5)', the '(mod
|
||
5)' applies to the entire equality, certainly not to the '1'.
|
||
|
||
Therefore, we do not give modulo higher precedence than addition, and require
|
||
parentheses when mixing the two. This decision should be revisited if it is a
|
||
significant source of friction in practice.
|
||
|
||
### Use a different spelling for modulo
|
||
|
||
We could use a different spelling for the modulo operation. For example, we
|
||
could spell it as `mod`.
|
||
|
||
Advantages:
|
||
|
||
- The percent sign has no relation to modulo or remainder. The only known
|
||
justification for this particular choice of symbol is that it resembles the
|
||
'÷' symbol; however, that symbol means division, not remainder.
|
||
- Would free up the `%` symbol for other uses that may be more prevalent than
|
||
modulo. However, we would need to be cautious when adding any alternative
|
||
uses to avoid confusion for people who expect `%` to mean modulo.
|
||
|
||
Disadvantages:
|
||
|
||
- This would be unfamiliar to developers coming from C++ and other languages
|
||
with similar operator sets.
|
||
- There is no other common established symbol for this operation. Using a
|
||
keyword such as `mod` would break our loose convention of using keywords for
|
||
non-overloadable operators and operator symbols for overloadable operators.
|
||
Using a function would substantially increase the verbosity of certain kinds
|
||
of code.
|
||
|
||
## Future work
|
||
|
||
### Provide separate wrapping operators
|
||
|
||
We could provide distinct operators with wrapping semantics for types where
|
||
overflow is normally a programming error. For example, Swift provides
|
||
[overflow operators](https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html#ID37)
|
||
`&+`, `&-`, and `&*` for this purpose.
|
||
|
||
This proposal takes no position on whether this would be useful. However, given
|
||
that in this proposal, unsigned types already have wrapping semantics, the
|
||
pressure to provide operators for signed types with those semantics is somewhat
|
||
reduced.
|
||
|
||
### Provide separate operations to detect overflow
|
||
|
||
It would be useful to provide a way to perform an arithmetic operation if
|
||
possible, and to provide a separate codepath to handle the case where the
|
||
arithmetic would have overflowed. For example, we could imagine the Carbon
|
||
standard library providing a facility such as:
|
||
|
||
```
|
||
fn AddWithOverflow[N:! BigInt](a: Integer(N), b: Integer(N)) -> Optional(Integer(N));
|
||
```
|
||
|
||
While this would undoubtedly be useful, this proposal provides no facility for
|
||
this operation.
|