Overflow and wrapping
Integer arithmetic wraps when a result does not fit its type: one past the
largest int32 is the smallest, and nothing reports it. std/numeric lets a
program say what should happen instead.
#import "std/numeric"
dim as int32 top = 2147483647
'' Stored back in an int32, a sum past the top wraps, silently.
dim as int32 wrapped = top + 1
print "top + 1 as int32 ="; wrapped
'' The same happens to unsigned types, at both ends.
dim as uint8 small = 255
small += 1
print "255 + 1 as uint8 = "; small
small -= 1
print "0 - 1 as uint8 = "; small
'' Checked: an empty Optional instead of a wrong number.
var sum = numeric.AddChecked( top, 1 )
print "AddChecked( top, 1 ) has a value: "; sum.HasValue( )
print "AddChecked( top, -1 ) ="; numeric.AddChecked( top, -1 ).Value( )
'' Saturating: stop at the end of the range.
print "AddSaturating( top, 1 ) ="; numeric.AddSaturating( top, 1 )
'' Wrapping: what + does, spelled so a reader knows it was meant.
print "AddWrapping( top, 1 ) ="; numeric.AddWrapping( top, 1 )
'' ^ works in float64, so above 2^53 an integer power is rounded.
dim as int64 b = 3, e = 38
dim as int64 viaPow = b ^ e
print "3 ^ 38 ="; viaPow
print "IPow( 3, 38 ) ="; numeric.IPow( b, e )
Output
top + 1 as int32 =-2147483648
255 + 1 as uint8 = 0
0 - 1 as uint8 = 255
AddChecked( top, 1 ) has a value: false
AddChecked( top, -1 ) = 2147483646
AddSaturating( top, 1 ) = 2147483647
AddWrapping( top, 1 ) =-2147483648
3 ^ 38 = 1350851717672992000
IPow( 3, 38 ) = 1350851717672992089
Unsigned types wrap at both ends: a uint8 holding 255 plus one is 0, and 0
minus one is 255.
| Family | On overflow |
|---|---|
AddChecked, SubChecked, MulChecked, DivChecked, NegChecked, AbsChecked, IPowChecked | return an empty Optional |
AddSaturating and its siblings | stop at the end of the range |
AddWrapping and its siblings | wrap, exactly as the operators do |
The wrapping family exists so that deliberate wrapping, in a hash or a
checksum, says it is deliberate. A checked result is an Optional:
HasValue( ) says whether there is one and Value( ) reads it; see
Error Handling.
^ on integers is not exact above 2^53. It converts both sides to
float64, and a float64 holds 53 bits of precision, so 3 ^ 38 comes back
rounded, by 89. Use numeric.IPow for integer powers.
These functions protect the calls you thought to protect; overflow elsewhere is still silent.
Activities
- Use
MulCheckedto find the first factorial that does not fit in anint32. - Give two
uint8variables the values 0 and 1, and comparenumeric.SubSaturatingof them with their difference stored in auint8.