diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 6ec2b0e7..f7b1be7d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -39,7 +39,6 @@ jobs: --file results.json \ --branch '${{ github.head_ref || github.ref_name }}' \ --testbed "${MATRIX_OS}-${PLATFORM}-${CPU}-${NIM_VERSION}" \ - --err \ --threshold-measure latency \ --threshold-test t_test \ --threshold-lower-boundary _ \ diff --git a/benchmarks/latency/add.nim b/benchmarks/latency/add.nim index c05e6b2e..67cc309b 100644 --- a/benchmarks/latency/add.nim +++ b/benchmarks/latency/add.nim @@ -5,6 +5,12 @@ import ../utils proc runLatencyOverflowing() {.noinline.} = benchTypesAndImpls(benchLatencyOverflowing, overflowingAdd) +proc runLatencyRaising() {.noinline.} = + benchTypesAndImpls(benchLatencyRaising, raisingAdd) + +proc runLatencyWrapping() {.noinline.} = + benchTypesAndImpls(benchLatencyWrapping, wrappingAdd) + proc runLatencySaturating() {.noinline.} = benchTypesAndImpls(benchLatencySaturating, saturatingAdd) @@ -15,5 +21,7 @@ when isMainModule: echo "\n# Latency, Addition" runLatencyOverflowing() + runLatencyRaising() + runLatencyWrapping() runLatencySaturating() runLatencyCarrying() diff --git a/benchmarks/latency/sub.nim b/benchmarks/latency/sub.nim index c59c6471..47c6710a 100644 --- a/benchmarks/latency/sub.nim +++ b/benchmarks/latency/sub.nim @@ -5,6 +5,12 @@ import ../utils proc runLatencyOverflowing() {.noinline.} = benchTypesAndImpls(benchLatencyOverflowing, overflowingSub) +proc runLatencyRaising() {.noinline.} = + benchTypesAndImpls(benchLatencyRaising, raisingSub) + +proc runLatencyWrapping() {.noinline.} = + benchTypesAndImpls(benchLatencyWrapping, wrappingSub) + proc runLatencySaturating() {.noinline.} = benchTypesAndImpls(benchLatencySaturating, saturatingSub) @@ -15,5 +21,7 @@ when isMainModule: echo "\n# Latency, Subtraction" runLatencyOverflowing() + runLatencyRaising() + runLatencyWrapping() runLatencySaturating() runLatencyCarrying() diff --git a/benchmarks/throughput/add.nim b/benchmarks/throughput/add.nim index 9cfe4b64..3cd67c96 100644 --- a/benchmarks/throughput/add.nim +++ b/benchmarks/throughput/add.nim @@ -5,6 +5,12 @@ import ../utils proc runThroughputOverflowing() {.noinline.} = benchTypesAndImpls(benchThroughputOverflowing, overflowingAdd) +proc runThroughputRaising() {.noinline.} = + benchTypesAndImpls(benchThroughputRaising, raisingAdd) + +proc runThroughputWrapping() {.noinline.} = + benchTypesAndImpls(benchThroughputWrapping, wrappingAdd) + proc runThroughputSaturating() {.noinline.} = benchTypesAndImpls(benchThroughputSaturating, saturatingAdd) @@ -15,5 +21,7 @@ when isMainModule: echo "\n# Throughput, Addition" runThroughputOverflowing() + runThroughputRaising() + runThroughputWrapping() runThroughputSaturating() runThroughputCarrying() diff --git a/benchmarks/throughput/sub.nim b/benchmarks/throughput/sub.nim index a2eba0a7..2a743d09 100644 --- a/benchmarks/throughput/sub.nim +++ b/benchmarks/throughput/sub.nim @@ -5,6 +5,12 @@ import ../utils proc runThroughputOverflowing() {.noinline.} = benchTypesAndImpls(benchThroughputOverflowing, overflowingSub) +proc runThroughputRaising() {.noinline.} = + benchTypesAndImpls(benchThroughputRaising, raisingSub) + +proc runThroughputWrapping() {.noinline.} = + benchTypesAndImpls(benchThroughputWrapping, wrappingSub) + proc runThroughputSaturating() {.noinline.} = benchTypesAndImpls(benchThroughputSaturating, saturatingSub) @@ -15,5 +21,7 @@ when isMainModule: echo "\n# Throughput, Subtraction" runThroughputOverflowing() + runThroughputRaising() + runThroughputWrapping() runThroughputSaturating() runThroughputCarrying() diff --git a/benchmarks/utils.nim b/benchmarks/utils.nim index 570f6fe6..77f3144f 100644 --- a/benchmarks/utils.nim +++ b/benchmarks/utils.nim @@ -174,6 +174,51 @@ template benchLatencyOverflowing*(typ: typedesc, op: untyped) = doNotOptimize(resFlush) doNotOptimize(ovfFlush) +template benchLatencyRaising*(typ: typedesc, op: untyped) = + let opName = astToStr(op) + + when not compiles op(default(typ), default(typ)): + echo alignLeft(opName, 35), " -" + elif typeof(op(default(typ), default(typ))) isnot typ: + echo alignLeft(opName, 35), " -" + else: + measureLatency(typ, opName): + # Normalize inputs so that the operation never overflows: + # cap `inputsB` to 1/4 of the type range, + # constain `inputsA` between 1/4 and 1/2. + const quarter = (high(typ) shr 2) + + for i in 0 ..< bufSize: + inputsB[i] = inputsB[i] and (quarter - 1) + + var + flush {.inject.}: typ + currentA {.inject.} = (inputsA[0] and (quarter - 1)) + quarter + do: + let res = op(currentA, inputsB[idx]) + currentA = (res and (quarter - 1)) + quarter + flush = currentA + do: + doNotOptimize(flush) + +template benchLatencyWrapping*(typ: typedesc, op: untyped) = + let opName = astToStr(op) + + when not compiles op(default(typ), default(typ)): + echo alignLeft(opName, 35), " -" + elif typeof(op(default(typ), default(typ))) isnot typ: + echo alignLeft(opName, 35), " -" + else: + measureLatency(typ, opName): + var + currentA {.inject.} = inputsA[0] + flush {.inject.}: typ + do: + currentA = op(currentA, inputsB[idx]) + flush = currentA + do: + doNotOptimize(flush) + template benchLatencySaturating*(typ: typedesc, op: untyped) = let opName = astToStr(op) @@ -227,6 +272,44 @@ template benchThroughputOverflowing*(typ: typedesc, op: untyped) = do: doNotOptimize(flush) +template benchThroughputRaising*(typ: typedesc, op: untyped) = + let opName = astToStr(op) + + when not compiles op(default(typ), default(typ)): + echo alignLeft(opName, 35), " -" + elif typeof(op(default(typ), default(typ))) isnot typ: + echo alignLeft(opName, 35), " -" + else: + measureThroughput(typ, opName): + const quarter = (high(typ) shr 2) + + var flush {.inject.}: typ + + for i in 0 ..< bufSize: + inputsA[i] = (inputsA[i] and (quarter - 1)) + quarter + inputsB[i] = inputsB[i] and (quarter - 1) + do: + let res = op(inputsA[idx], inputsB[idx]) + flush = flush xor res + do: + doNotOptimize(flush) + +template benchThroughputWrapping*(typ: typedesc, op: untyped) = + let opName = astToStr(op) + + when not compiles op(default(typ), default(typ)): + echo alignLeft(opName, 35), " -" + elif typeof(op(default(typ), default(typ))) isnot typ: + echo alignLeft(opName, 35), " -" + else: + measureThroughput(typ, opName): + var flush {.inject.}: typ + do: + let res = op(inputsA[idx], inputsB[idx]) + flush = flush xor res + do: + doNotOptimize(flush) + template benchThroughputSaturating*(typ: typedesc, op: untyped) = let opName = astToStr(op) diff --git a/book/src/quickstart.md b/book/src/quickstart.md index dcfb1f72..369626f2 100644 --- a/book/src/quickstart.md +++ b/book/src/quickstart.md @@ -11,13 +11,19 @@ intops implements the following operations for signed and unsigned 32- and 64-bi | | addition | subtraction | multiplication | division | muladd | | ----------- | -------------------- | -------------------- | ------------------ | -------------------- | ------------------ | | overflowing | Nim, intrinsics | Nim, intrinsics | | | | +| raising | Nim, intrinsics | Nim, intrinsics | | | | +| wrapping | Nim | Nim | | | | | saturating | Nim, intrinsics, ASM | Nim, intrinsics, ASM | | | | | carrying | Nim, intrinsics, C | | | | | | borrowing | | Nim, intrinsics, C | | | | | widening | | | Nim, intrinsics, C | | Nim, intrinsics, C | | narrowing | | | | Nim, intrinsics, ASM | | -_Overflowing_ operations return an explicit `didOverflow` bool flag that tells you if an overflow happened during the operation. These operations wrap for both signed and unsigned integers. +_Overflowing_ operations return an explicit `didOverflow` bool flag that tells you if an overflow happened during the operation. These operations wrap for both signed and unsigned integers. Never raises overflow exceptions. + +_Raising_ operations are guaranteed to raise an exception if the operation overflows. This is unlike Nim's builtin `+` and `-` for signed ints, which won't raise if overflow checks are disabled during compilation (e.g. when compiled with `-d:danger`), and unlike `+` and `-` for unsigned ints, which silently wrap without raising. + +_Wrapping_ operations are guaranteed to wrap if the operation overflows. This is unlike Nim's builtin `+` and `-` for signed ints, which do not wrap. _Saturating_ operations do not return an overflow flag but silently return the highest or the lowest available value for a given type if an overflow is to occur. diff --git a/changelog.md b/changelog.md index 4bcee430..f071b37a 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,9 @@ - [t]—test suite improvement - [d]—docs improvement +## 1.0.8 (WIP) +- [+] Ops: Added raising and wrapping flavors to addition and subtraction (#15). + ## 1.0.7 (February 2, 2026) - [f] Ops: composite: Added missing imports that prevented individual import of `intops/ops/composite` (#23). diff --git a/intops.nimble b/intops.nimble index ccff90f1..c83ffc7d 100644 --- a/intops.nimble +++ b/intops.nimble @@ -28,6 +28,7 @@ task test, "Run tests": echo fmt"# Flags: {flags}" selfExec fmt"r {flags} tests/tintops.nim" + selfExec fmt"""r {flags} -d:danger tests/tintops.nim "Raising operations::"""" task bench, "Run benchmarks": var diff --git a/src/intops/impl/intrinsics/gcc.nim b/src/intops/impl/intrinsics/gcc.nim index 970fce5c..31181ef7 100644 --- a/src/intops/impl/intrinsics/gcc.nim +++ b/src/intops/impl/intrinsics/gcc.nim @@ -31,6 +31,16 @@ when compilerGccCompatible and canUseIntrinsics: (res, didOverflow) + func raisingAdd*[T: SomeInteger](a, b: T): T {.raises: [OverflowDefect].} = + var res {.noinit.}: T + + let didOverflow = builtinOverflowingAdd(a, b, res) + + if unlikely(didOverflow): + raise newException(OverflowDefect, "overflow") + + res + func saturatingAdd*[T: SomeUnsignedInt](a, b: T): T = var res {.noinit.}: T @@ -70,6 +80,16 @@ when compilerGccCompatible and canUseIntrinsics: (res, didBorrow) + func raisingSub*[T: SomeInteger](a, b: T): T {.raises: [OverflowDefect].} = + var res {.noinit.}: T + + let didOverflow = builtinOverflowingSub(a, b, res) + + if unlikely(didOverflow): + raise newException(OverflowDefect, "underflow") + + res + func saturatingSub*[T: SomeUnsignedInt](a, b: T): T = var res {.noinit.}: T diff --git a/src/intops/impl/pure.nim b/src/intops/impl/pure.nim index 25c3accd..b07f8b0a 100644 --- a/src/intops/impl/pure.nim +++ b/src/intops/impl/pure.nim @@ -27,21 +27,19 @@ func overflowingAdd*[T: SomeSignedInt](a, b: T): (T, bool) = (res, didOverflow) -func carryingAdd*[T: SomeUnsignedInt](a, b: T, carryIn: bool): (T, bool) = - let - sum = a + b - c1 = sum < a - res = sum + T(carryIn) - c2 = res < sum +func raisingAdd*[T: SomeInteger](a, b: T): T {.raises: [OverflowDefect].} = + let (res, didOverflow) = pure.overflowingAdd(a, b) - (res, c1 or c2) + if unlikely(didOverflow): + raise newException(OverflowDefect, "overflow") -func carryingAdd*[T: SomeSignedInt](a, b: T, carryIn: bool): (T, bool) = - let - (sum1, o1) = pure.overflowingAdd(a, b) - (final, o2) = pure.overflowingAdd(sum1, T(carryIn)) + res - (final, o1 or o2) +func wrappingAdd*[T: SomeUnsignedInt](a, b: T): T = + a + b + +func wrappingAdd*[T: SomeSignedInt](a, b: T): T = + a +% b func saturatingAdd*[T: SomeUnsignedInt](a, b: T): T = let (res, didOverflow) = pure.overflowingAdd(a, b) @@ -62,6 +60,22 @@ func saturatingAdd*[T: SomeSignedInt](a, b: T): T = res +func carryingAdd*[T: SomeUnsignedInt](a, b: T, carryIn: bool): (T, bool) = + let + sum = a + b + c1 = sum < a + res = sum + T(carryIn) + c2 = res < sum + + (res, c1 or c2) + +func carryingAdd*[T: SomeSignedInt](a, b: T, carryIn: bool): (T, bool) = + let + (sum1, o1) = pure.overflowingAdd(a, b) + (final, o2) = pure.overflowingAdd(sum1, T(carryIn)) + + (final, o1 or o2) + func overflowingSub*[T: SomeUnsignedInt](a, b: T): (T, bool) = let res = a - b @@ -71,26 +85,24 @@ func overflowingSub*[T: SomeUnsignedInt](a, b: T): (T, bool) = func overflowingSub*[T: SomeSignedInt](a, b: T): (T, bool) = let - res = T(a -% b) + res = a -% b didOverflow = ((a xor b) < 0) and ((a xor res) < 0) (res, didOverflow) -func borrowingSub*[T: SomeUnsignedInt](a, b: T, borrowIn: bool): (T, bool) = - let - diff = a - b - b1 = a < b - res = diff - T(borrowIn) - b2 = diff < T(borrowIn) +func raisingSub*[T: SomeInteger](a, b: T): T {.raises: [OverflowDefect].} = + let (res, didOverflow) = pure.overflowingSub(a, b) - (res, b1 or b2) + if didOverflow: + raise newException(OverflowDefect, "underflow") -func borrowingSub*[T: SomeSignedInt](a, b: T, borrowIn: bool): (T, bool) = - let - (diff1, o1) = pure.overflowingSub(a, b) - (final, o2) = pure.overflowingSub(diff1, T(borrowIn)) + res - (final, o1 or o2) +func wrappingSub*[T: SomeUnsignedInt](a, b: T): T = + a - b + +func wrappingSub*[T: SomeSignedInt](a, b: T): T = + a -% b func saturatingSub*[T: SomeUnsignedInt](a, b: T): T = let (res, didBorrow) = pure.overflowingSub(a, b) @@ -111,6 +123,22 @@ func saturatingSub*[T: SomeSignedInt](a, b: T): T = res +func borrowingSub*[T: SomeUnsignedInt](a, b: T, borrowIn: bool): (T, bool) = + let + diff = a - b + b1 = a < b + res = diff - T(borrowIn) + b2 = diff < T(borrowIn) + + (res, b1 or b2) + +func borrowingSub*[T: SomeSignedInt](a, b: T, borrowIn: bool): (T, bool) = + let + (diff1, o1) = pure.overflowingSub(a, b) + (final, o2) = pure.overflowingSub(diff1, T(borrowIn)) + + (final, o1 or o2) + func wideningMul*(a, b: uint64): (uint64, uint64) = const halfMask = 0xFFFFFFFF'u64 diff --git a/src/intops/ops/add.nim b/src/intops/ops/add.nim index 8496a640..0131a5ca 100644 --- a/src/intops/ops/add.nim +++ b/src/intops/ops/add.nim @@ -17,7 +17,7 @@ template overflowingAdd*[T: SomeInteger](a, b: T): tuple[res: T, didOverflow: bo Takes two integers and returns their sum along with the overflow flag (OF): ``true`` means overflow happened, ``false`` means overflow didn't happen. - Addition wraps for both signed and unsigned integers, so this operation never raises. + Wraps for both signed and unsigned integers, so this operation never raises. See also: - `overflowingSub `_ @@ -31,6 +31,31 @@ template overflowingAdd*[T: SomeInteger](a, b: T): tuple[res: T, didOverflow: bo else: pure.overflowingAdd(a, b) +template raisingAdd*[T: SomeInteger](a, b: T): T = + ##[ Raising addition. + + _Guaranteed_ to raise `OverflowDefect` if the operation overflows + regardless of the compilation flags, e.g. `-d:danger` + (unlike Nim's builtin `+` operator for signed ints). + ]## + + when nimvm: + pure.raisingAdd(a, b) + else: + when compilerGccCompatible and canUseIntrinsics: + intrinsics.gcc.raisingAdd(a, b) + else: + pure.raisingAdd(a, b) + +template wrappingAdd*[T: SomeInteger](a, b: T): T = + ##[ Wrapping addition. + + Silently wraps for both unsigned and signed ints + (unlike Nim's builtin `+` operator for signed ints). + ]## + + pure.wrappingAdd(a, b) + template saturatingAdd*[T: SomeInteger](a, b: T): T = ##[ Saturating addition. @@ -54,8 +79,9 @@ template saturatingAdd*[T: SomeInteger](a, b: T): T = template carryingAdd*(a, b: uint64, carryIn: bool): tuple[res: uint64, carryOut: bool] = ##[ Carrying addition for unsigned 64-bit integers. - Takes two integers and returns their sum along with the carrying flag (CF): - ``true`` means the previous addition had overflown, ``false`` means it hadn't. + Takes two integers and a carrying flag: ``true`` means the previous addition had overflown, ``false`` means it hadn't. + + Returns the sum along with the new carrying flag. Useful for chaining operations. @@ -79,8 +105,9 @@ template carryingAdd*(a, b: uint64, carryIn: bool): tuple[res: uint64, carryOut: template carryingAdd*(a, b: uint32, carryIn: bool): tuple[res: uint32, carryOut: bool] = ##[ Carrying addition for unsigned 32-bit integers. - Takes two integers and returns their sum along with the carrying flag (CF): - ``true`` means the previous addition had overflown, ``false`` means it hadn't. + Takes two integers and a carrying flag: ``true`` means the previous addition had overflown, ``false`` means it hadn't. + + Returns the sum along with the new carrying flag. Useful for chaining operations. @@ -101,8 +128,9 @@ template carryingAdd*(a, b: uint32, carryIn: bool): tuple[res: uint32, carryOut: template carryingAdd*(a, b: int64, carryIn: bool): tuple[res: int64, carryOut: bool] = ##[ Carrying addition for signed 64-bit integers. - Takes two integers and returns their sum along with the carrying flag (CF): - ``true`` means the previous addition had overflown, ``false`` means it hadn't. + Takes two integers and a carrying flag: ``true`` means the previous addition had overflown, ``false`` means it hadn't. + + Returns the sum along with the new carrying flag. Useful for chaining operations. @@ -121,8 +149,9 @@ template carryingAdd*(a, b: int64, carryIn: bool): tuple[res: int64, carryOut: b template carryingAdd*(a, b: int32, carryIn: bool): tuple[res: int32, carryOut: bool] = ##[ Carrying addition for signed 32-bit integers. - Takes two integers and returns their sum along with the carrying flag (CF): - ``true`` means the previous addition had overflown, ``false`` means it hadn't. + Takes two integers and a carrying flag: ``true`` means the previous addition had overflown, ``false`` means it hadn't. + + Returns the sum along with the new carrying flag. Useful for chaining operations. diff --git a/src/intops/ops/sub.nim b/src/intops/ops/sub.nim index 4fc38922..9f0986fc 100644 --- a/src/intops/ops/sub.nim +++ b/src/intops/ops/sub.nim @@ -17,7 +17,7 @@ template overflowingSub*[T: SomeInteger](a, b: T): tuple[res: T, didOverflow: bo Takes two integers and returns their difference along with the overflow flag (OF): ``true`` means overflow happened, ``false`` means overflow didn't happen. - Subtraction wraps for both signed and unsigned integers, so this operation never raises. + Wraps for both signed and unsigned integers, so this operation never raises. See also: - `overflowingAdd `_ @@ -31,6 +31,31 @@ template overflowingSub*[T: SomeInteger](a, b: T): tuple[res: T, didOverflow: bo else: pure.overflowingSub(a, b) +template raisingSub*[T: SomeInteger](a, b: T): T = + ##[ Raising subtraction. + + _Guaranteed_ to raise `OverflowDefect` if the operation overflows + regardless of the compilation flags, e.g. `-d:danger` + (unlike Nim's builtin `-` operator for signed ints). + ]## + + when nimvm: + pure.raisingSub(a, b) + else: + when compilerGccCompatible and canUseIntrinsics: + intrinsics.gcc.raisingSub(a, b) + else: + pure.raisingSub(a, b) + +template wrappingSub*[T: SomeInteger](a, b: T): T = + ##[ Wrapping subtraction. + + Silently wraps for both unsigned and signed ints + (unlike Nim's builtin `-` operator for signed ints). + ]## + + pure.wrappingSub(a, b) + template saturatingSub*[T: SomeInteger](a, b: T): T = ##[ Saturating subtraction. @@ -56,8 +81,9 @@ template borrowingSub*( ): tuple[res: uint64, borrowOut: bool] = ##[ Borrowing subtraction for unsigned 64-bit integers. - Takes two integers and returns their difference along with the borrow flag (BF): - ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + Takes two integers and a borrowing flag: ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + + Returns the difference along with the new borrowing flag. Useful for chaining operations. @@ -82,8 +108,9 @@ template borrowingSub*( ): tuple[res: uint32, borrowOut: bool] = ##[ Borrowing subtraction for unsigned 32-bit integers. - Takes two integers and returns their difference along with the borrow flag (BF): - ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + Takes two integers and a borrowing flag: ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + + Returns the difference along with the new borrowing flag. Useful for chaining operations. @@ -106,8 +133,9 @@ template borrowingSub*( ): tuple[res: int64, borrowOut: bool] = ##[ Borrowing subtraction for signed 64-bit integers. - Takes two integers and returns their difference along with the borrow flag (BF): - ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + Takes two integers and a borrowing flag: ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + + Returns the difference along with the new borrowing flag. Useful for chaining operations. @@ -126,8 +154,9 @@ template borrowingSub*( template borrowingSub*(a, b: int32, borrowIn: bool): (int32, bool) = ##[ Borrowing subtraction for signed 32-bit integers. - Takes two integers and returns their difference along with the borrow flag (BF): - ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + Takes two integers and a borrowing flag: ``true`` means the previous subtraction had overflown, ``false`` means it hadn't. + + Returns the difference along with the new borrowing flag. Useful for chaining operations. diff --git a/tests/tintops.nim b/tests/tintops.nim index eba776bf..67779172 100644 --- a/tests/tintops.nim +++ b/tests/tintops.nim @@ -46,6 +46,80 @@ suite "Overflowing operations": testOverflowingSub[int32]() testOverflowingSub[int64]() +suite "Raising operations": + test "Raising addition, unsigned": + template testRaisingAdd[T: SomeUnsignedInt]() = + check raisingAdd(T(1), T(1)) == T(2) + check raisingAdd(high(T), T(0)) == high(T) + expect OverflowDefect: + discard raisingAdd(high(T), T(1)) + expect OverflowDefect: + discard raisingAdd(high(T) - T(5), T(10)) + + testRaisingAdd[uint32]() + testRaisingAdd[uint64]() + + test "Raising addition, signed": + template testRaisingAdd[T: SomeSignedInt]() = + check raisingAdd(T(1), T(1)) == T(2) + check raisingAdd(high(T), T(0)) == high(T) + expect OverflowDefect: + discard raisingAdd(high(T), T(1)) + expect OverflowDefect: + discard raisingAdd(high(T) - T(5), T(10)) + expect OverflowDefect: + discard raisingAdd(low(T) + T(5), T(-10)) + + testRaisingAdd[int32]() + testRaisingAdd[int64]() + + test "Raising subtraction, unsigned": + template testRaisingSub[T: SomeUnsignedInt]() = + check raisingSub(T(5), T(2)) == T(3) + check raisingSub(low(T), T(0)) == low(T) + expect OverflowDefect: + discard raisingSub(low(T), T(1)) + expect OverflowDefect: + discard raisingSub(low(T) + T(5), T(10)) + + testRaisingSub[uint32]() + testRaisingSub[uint64]() + + test "Raising subtraction, signed": + template testRaisingSub[T: SomeSignedInt]() = + check raisingSub(T(5), T(2)) == T(3) + check raisingSub(low(T), T(0)) == low(T) + expect OverflowDefect: + discard raisingSub(low(T), T(1)) + expect OverflowDefect: + discard raisingSub(low(T) + T(5), T(10)) + expect OverflowDefect: + discard raisingSub(high(T) - T(5), T(-10)) + + testRaisingSub[int32]() + testRaisingSub[int64]() + +suite "Wrapping operations": + test "Wrapping addition, unsigned and signed": + template testWrappingAdd[T: SomeInteger]() = + check wrappingAdd(high(T), T(1)) == low(T) + check wrappingAdd(high(T) - 5, T(10)) == low(T) + T(4) + + testWrappingAdd[uint32]() + testWrappingAdd[uint64]() + testWrappingAdd[int32]() + testWrappingAdd[int64]() + + test "Wrapping subtraction, unsigned and signed": + template testWrappingSub[T: SomeInteger]() = + check wrappingSub(low(T), T(1)) == high(T) + check wrappingSub(low(T) + 5, T(10)) == high(T) - T(4) + + testWrappingSub[uint32]() + testWrappingSub[uint64]() + testWrappingSub[int32]() + testWrappingSub[int64]() + suite "Carrying and borrowing operations": test "Carrying addition (ADC), unsigned": template testCarryingAdd[T: SomeUnsignedInt]() =