Skip to content

Commit a5255d2

Browse files
committed
Revised README
1 parent 6d9b263 commit a5255d2

File tree

1 file changed

+93
-41
lines changed

1 file changed

+93
-41
lines changed

README.md

Lines changed: 93 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,118 @@
11
# OverflowContexts.jl
22

3-
This package conceptually extends `CheckedArithmetic.jl` to provide the following overall features:
4-
1. Ability to set a Module-level default to overflow-checked, overflow-permissive (unchecked), or saturating operations.
5-
2. Ability to specify whether a block of code should use overflow-checked, overflow-permissive (unchecked), or saturating operations regardless of the default.
3+
OverflowContexts provides easy manipulation of (integer) arithmetic modes.
64

7-
Together, these provide checked and unchecked contexts, as in other languages like C#:
8-
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/checked-and-unchecked
5+
By default, Julia generally uses overflowing (unchecked) integer math (other than division methods), which silently wraps around from the maximum to the minimum value when it gets too large or small, respectively. This choice is because checking for overflow is slower and means that any integer arithmetic could throw an exception, which the compiler would need to account for.
96

10-
The expression-level `@checked`, `@unchecked`, and `@saturating` rewrite instances of `+`, `-`, `*`, `^`, and `abs` functions, to functions specific to the
11-
checked or permissive operation, and thus are not affected by switching the default. Symbols for the functions will also be replaced, to support
12-
calls like `foldl(+, v)`. If these macros are nested, the lowest level takes precedence so that an unchecked context can be nested inside a checked
13-
context and vice versa.
14-
15-
`@default_checked`, `@default_unchecked`, and `@default_saturating` create shadow copies of the `+`, `-`, `*`, `^`, and `abs` functions that redirect to overflow-checked
16-
or overflow-permissive operations, respectively, within the module it was executed in. All arguments that don't support overflow checking are passed
17-
through to their respective Base methods. **Important:** If you wish to use this feature, the first usage of this macro must occur earlier than the first usage of the affected Base functions. It should only be set once per module, though switching is allowed for interactive use. It is not necessary to set a default to use the expression-level macros.
7+
With base Julia, if a user wishes to use checked arithmetic, they would need to bring in `Base.Checked` and explicitly use e.g., `checked_add(x, y)` rather than the natural operator.
188

9+
Using the macros in OverflowContexts, an expression or code block can be rewritten to replace operators and certain methods (`+`, `-`, `*`, `^`, `abs`) on the fly with appropriate method calls:
1910
```julia
20-
using OverflowContexts
21-
@default_unchecked # Julia default, but need to place first so later usages will work for this example
11+
@checked -typemin(Int64)
12+
# Expands to `OverflowContexts.checked_neg(typemin(Int64))`
13+
# Throws `ERROR: OverflowError: 0 - -9223372036854775808 overflowed for type Int64`
14+
15+
@checked typemax(Int64) + 1
16+
# Expands to `OverflowContexts.checked_add(typemax(Int64), 1)`
17+
# Throws `ERROR: OverflowError: 9223372036854775807 + 1 overflowed for type Int64`
2218

23-
x = typemax(Int) # 9223372036854775807
24-
x + 1 # -9223372036854775808
19+
@checked typemin(Int64) - 1
20+
# Expands to `OverflowContexts.checked_sub(typemin(Int64), 1)`
21+
# Throws `ERROR: OverflowError: -9223372036854775807 + 1 overflowed for type Int64`
2522

26-
@default_checked
27-
x + 1 # ERROR: OverflowError: 9223372036854775807 + 1 overflowed for type Int64
23+
@checked typemax(Int64) * 2
24+
# Expands to `OverflowContexts.checked_mul(typemax(Int64), 2)`
25+
# Throws `ERROR: OverflowError: 9223372036854775807 * 2 overflowed for type Int64`
2826

29-
@unchecked x * 2 # -2
27+
@checked typemax(Int64) ^ 2
28+
# Expands to `OverflowContexts.checked_pow(typemax(Int64), 2)`
29+
# Throws `ERROR: OverflowError: 9223372036854775807 * 9223372036854775807overflowed for type Int64`
3030

31-
@unchecked begin
32-
x * 2 # -2
33-
@checked x + 1 # ERROR: OverflowError: 9223372036854775807 + 1 overflowed for type Int64
31+
@checked abs(typemin(Int64))
32+
# Expands to `OverflowContexts.checked_abs(typemin(Int64))`
33+
# Throws `ERROR: OverflowError: checked arithmetic: cannot compute |x| for x = -9223372036854775808::Int64`
34+
```
35+
36+
Code blocks can also be nested, with the innermost block taking priority:
37+
```julia
38+
@checked begin
39+
@unchecked typemax(Int64) * 2
3440
end
41+
# Expands to `OverflowContexts.unchecked_mul(typemax(Int64), 2)`
42+
# Evaluates to `-2`
43+
```
3544

36-
@default_unchecked
37-
x + 1 # -9223372036854775808
45+
This package also adds a saturating mode, where values accumulate at the maximum and minimum for the type:
46+
```julia
47+
@saturating -typemin(Int64)
48+
# Expands to `OverflowContexts.saturating_neg(typemin(Int64))`
49+
# Evaluates to `typemax(Int64)`
3850

39-
d() = x + 1; c() = d(); b() = c(); a() = b();
51+
@saturating typemax(Int64) + 1
52+
# Expands to `OverflowContexts.saturating_add(typemax(Int64), 1)`
53+
# Evaluates to `typemax(Int64)`
4054

41-
a() #-9223372036854775808
42-
@checked a() # doesn't cross function boundary; no OverflowError
55+
@saturating typemin(Int64) - 1
56+
# Expands to `OverflowContexts.saturating_sub(typemin(Int64), 1)`
57+
# Evaluates to `typemin(Int64)`
4358

44-
@default_checked # previous uses of operator in this module recompiled with new default
45-
a() # ERROR: OverflowError: 9223372036854775807 + 1 overflowed for type Int64
59+
@saturating typemax(Int64) * 2
60+
# Expands to `OverflowContexts.saturating_mul(typemax(Int64), 2)`
61+
# Evaluates to `typemax(Int64)`
4662

47-
@unchecked a() # doesn't cross function boundary; still throws OverflowError
48-
@default_unchecked
63+
@saturating typemax(Int64) ^ 2
64+
# Expands to `OverflowContexts.saturating_pow(typemax(Int64), 2)`
65+
# Evaluates to `typemax(Int64)`
4966

50-
a() # -9223372036854775808
67+
@saturating abs(typemin(Int64))
68+
# Expands to `OverflowContexts.checked_abs(typemin(Int64))`
69+
# Throws `ERROR: OverflowError: checked arithmetic: cannot compute |x| for x = -9223372036854775808::Int64`
70+
```
5171

52-
# rewrite a symbol
53-
@checked foldl(+, (typemax(Int), 1))
72+
Broadcasted operators/methods and elementwise array operators, and assignment operators are also rewritten:
73+
```julia
74+
@checked .-fill(typemin(Int64), 2)
75+
# Expands to `OverflowContexts.checked_neg.(fill(typemin(Int64), 2))`
76+
# Throws `ERROR: OverflowError: 0 - -9223372036854775808 overflowed for type Int64`
77+
78+
@checked fill(typemax(Int64), 2) + fill(1, 2)
79+
# Expands to `OverflowContexts.checked_add(fill(typemax(Int64), 2), fill(1, 2))`
80+
# Throws `ERROR: OverflowError: 9223372036854775807 + 1 overflowed for type Int64`
81+
82+
a = fill(1, 2)
83+
@saturating a += fill(typemax(Int64), 2)
84+
# Expands to `a = OverflowContexts.saturating_add(a, fill(typemax(Int64), 2))`
85+
# Evaluates to `[typemax(Int64), typemax(Int64)]`
86+
```
87+
88+
Functions passed as an argument are also rewritten:
89+
```julia
90+
@saturating map(-, fill(typemin(Int64), 2))
91+
# Expands to `map(OverflowContexts.saturating_neg, fill(typemin(Int64), 2))`
92+
# Evaluates to `[typemax(Int64), typemax(Int64)]`
93+
```
5494

55-
# assignment operators
56-
aa = typemax(Int)
57-
@checked aa += 1 # OverflowError: 9223372036854775807 + 1 overflowed for type Int64
95+
Division-related operators and methods (`÷`, `div`, `fld`, `cld`, `%`, `rem`, `mod`), in contrast, are checked by default in Julia. This is primarily because the LLVM compiler deems division by 0, or `-typemin(T) ÷ -one(T)` to be undefined behavior. Also, integer division on CPUs are generally quite slow and so this choice doesn't make much difference for performance. This package provides unchecked and saturating variants of these methods. The main benefit of the unchecked methods is that they are guaranteed to not throw an exception, however the result of a bad division should not be relied on. The saturating variant defines division by zero by treating `typemin(T)` and `typemax(T)` as saturating towards infinity, and returning `0` for `0 ÷ 0`. The saturating remainder methods produce complementary values.
96+
97+
Julia has a more complex `div` API than is supported here (e.g. supporting rounding modes) but this package just covers the two-argument methods available inside `Base.Checked`.
98+
99+
If you are writing a module and desire to set the default type of arithemtic for the module, place e.g., `@default_unchecked`, `@default_checked`, `@default_saturating` at the top of the module. This macro defines module-local copies of all of the supported arithemtic operators and methods, mapping them to the appropriate `checked_` or `saturating_` methods. The defaults do not affect anything inside the expression/block-level macros. These defaults may also be used on the REPL to switch between modes, although keep in mind that it will also cause previous methods defined on the REPL (in the `Main` module) to be recompiled with the new default.
100+
```julia
101+
module Foo
102+
using OverflowContexts
103+
@default_checked
104+
bar(x, y) = x + y
105+
baz(x, y) = @saturating x + y
106+
end
107+
Foo.bar(typemax(Int64), 1)
108+
# Throws `ERROR: OverflowError: 9223372036854775807 + 1 overflowed for type Int64`
109+
Foo.baz(typemax(Int64), 1)
110+
# Returns `typemax(Int64)`
58111
```
59112

60-
If you are implementing your own numeric types, this package should just work for you so long as you extend the Base operators and the Base.Checked `checked_` methods.
113+
If you are implementing your own `Number` types, this package should just work for you so long as you extend the Base operators and the Base.Checked `checked_` methods. For `saturating_`, your package will need to either take OverflowContexts as a dependency or a weak dependency in order to import the `saturating_` methods, until/unless Julia implements them directly.
61114

62115
## Related Packages
63116

64117
* [CheckedArithmetic.jl](https://github.com/JuliaMath/CheckedArithmetic.jl) - Predescessor to this package with more limited functionality, but also provides a utility to promote types for safer accumulators.
65-
* [SaferIntegers.jl](https://github.com/JeffreySarnoff/SaferIntegers.jl) - Uses the type system to
66-
enforce overflow checking even in code you don't control.
118+
* [SaferIntegers.jl](https://github.com/JeffreySarnoff/SaferIntegers.jl) - Uses the type system to enforce overflow checking even in code you don't control. OverflowContexts does not override this behavior, so they can work together well.

0 commit comments

Comments
 (0)