diff --git a/README.md b/README.md index 259f2fd..c7fff56 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ # overflow Check for int/int8/int16/int64/int32 integer overflow in Golang arithmetic. ### Install + ``` -go get github.com/johncgriffin/overflow +go get github.com/gnolang/overflow ``` Note that because Go has no template types, the majority of repetitive code is generated by overflow_template.sh. If you have to change an @@ -13,7 +14,7 @@ go generate ``` ### Synopsis -``` +```go package main import "fmt" @@ -22,18 +23,20 @@ import "github.com/JohnCGriffin/overflow" func main() { - addend := math.MaxInt64 - 5 + addend := math.MaxInt64 - 5 - for i := 0; i < 10; i++ { - sum, ok := overflow.Add(addend, i) - fmt.Printf("%v+%v -> (%v,%v)\n", - addend, i, sum, ok) - } + for i := 0; i < 10; i++ { + sum, ok := overflow.Add(addend, i) + fmt.Printf("%v+%v -> (%v,%v)\n", + addend, i, sum, ok) + } } ``` + yields the output -``` + +```plain 9223372036854775802+0 -> (9223372036854775802,true) 9223372036854775802+1 -> (9223372036854775803,true) 9223372036854775802+2 -> (9223372036854775804,true) @@ -54,10 +57,3 @@ Unsigned types not covered at the moment, but such additions are welcome. There's a good case to be made that a panic is an unidiomatic but proper response. Iff you believe that there's no valid way to continue your program after math goes wayward, you can use the easier Addp, Mulp, Subp, and Divp versions which return the normal result or panic. - - - - - - - diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dd7197a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/gnolang/overflow + +go 1.23.1 diff --git a/overflow.go b/overflow.go index 17e2417..854a4a7 100644 --- a/overflow.go +++ b/overflow.go @@ -1,4 +1,5 @@ -/*Package overflow offers overflow-checked integer arithmetic operations +/* +Package overflow offers overflow-checked integer arithmetic operations for int, int32, and int64. Each of the operations returns a result,bool combination. This was prompted by the need to know when to flow into higher precision types from the math.big library. @@ -16,17 +17,13 @@ overflow.Add(math.MaxInt64,1) -> (0, false) Add, Sub, Mul, Div are for int. Add64, Add32, etc. are specifically sized. If anybody wishes an unsigned version, submit a pull request for code -and new tests. */ +and new tests. +*/ package overflow //go:generate ./overflow_template.sh -import "math" - -func _is64Bit() bool { - maxU32 := uint(math.MaxUint32) - return ((maxU32 << 1) >> 1) == maxU32 -} +const is64Bit = ^uint(0) == (1<<64)-1 /********** PARTIAL TEST COVERAGE FROM HERE DOWN ************* @@ -42,7 +39,7 @@ So, FEEL FREE to carefully review the code visually. // Add sums two ints, returning the result and a boolean status. func Add(a, b int) (int, bool) { - if _is64Bit() { + if is64Bit { r64, ok := Add64(int64(a), int64(b)) return int(r64), ok } @@ -52,7 +49,7 @@ func Add(a, b int) (int, bool) { // Sub returns the difference of two ints and a boolean status. func Sub(a, b int) (int, bool) { - if _is64Bit() { + if is64Bit { r64, ok := Sub64(int64(a), int64(b)) return int(r64), ok } @@ -62,7 +59,7 @@ func Sub(a, b int) (int, bool) { // Mul returns the product of two ints and a boolean status. func Mul(a, b int) (int, bool) { - if _is64Bit() { + if is64Bit { r64, ok := Mul64(int64(a), int64(b)) return int(r64), ok } @@ -72,7 +69,7 @@ func Mul(a, b int) (int, bool) { // Div returns the quotient of two ints and a boolean status func Div(a, b int) (int, bool) { - if _is64Bit() { + if is64Bit { r64, ok := Div64(int64(a), int64(b)) return int(r64), ok } @@ -82,7 +79,7 @@ func Div(a, b int) (int, bool) { // Quotient returns the quotient, remainder and status of two ints func Quotient(a, b int) (int, int, bool) { - if _is64Bit() { + if is64Bit { q64, r64, ok := Quotient64(int64(a), int64(b)) return int(q64), int(r64), ok } @@ -90,6 +87,16 @@ func Quotient(a, b int) (int, int, bool) { return int(q32), int(r32), ok } +// Quo returns the quotient, remainder and status of two ints +func Quo(a, b int) (int, int, bool) { + if is64Bit { + q64, r64, ok := Quo64(int64(a), int64(b)) + return int(q64), int(r64), ok + } + q32, r32, ok := Quo32(int32(a), int32(b)) + return int(q32), int(r32), ok +} + /************* Panic versions for int ****************/ // Addp returns the sum of two ints, panicking on overflow diff --git a/overflow_impl.go b/overflow_impl.go index d1cbe86..bd61909 100644 --- a/overflow_impl.go +++ b/overflow_impl.go @@ -3,384 +3,752 @@ package overflow // This is generated code, created by overflow_template.sh executed // by "go generate" - - +import "math" // Add8 performs + operation on two int8 operands // returning a result and status func Add8(a, b int8) (int8, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false } -// Add8p is the unchecked panicing version of Add8 +// Add8p is the unchecked panicking version of Add8 func Add8p(a, b int8) int8 { - r, ok := Add8(a, b) - if !ok { - panic("addition overflow") - } - return r + r, ok := Add8(a, b) + if !ok { + panic("addition overflow") + } + return r } - // Sub8 performs - operation on two int8 operands // returning a result and status func Sub8(a, b int8) (int8, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false } -// Sub8p is the unchecked panicing version of Sub8 +// Sub8p is the unchecked panicking version of Sub8 func Sub8p(a, b int8) int8 { - r, ok := Sub8(a, b) - if !ok { - panic("subtraction overflow") - } - return r + r, ok := Sub8(a, b) + if !ok { + panic("subtraction overflow") + } + return r } - // Mul8 performs * operation on two int8 operands // returning a result and status func Mul8(a, b int8) (int8, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul8p is the unchecked panicing version of Mul8 + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul8p is the unchecked panicking version of Mul8 func Mul8p(a, b int8) int8 { - r, ok := Mul8(a, b) - if !ok { - panic("multiplication overflow") - } - return r + r, ok := Mul8(a, b) + if !ok { + panic("multiplication overflow") + } + return r } - - // Div8 performs / operation on two int8 operands // returning a result and status func Div8(a, b int8) (int8, bool) { - q, _, ok := Quotient8(a, b) - return q, ok + q, _, ok := Quotient8(a, b) + return q, ok } -// Div8p is the unchecked panicing version of Div8 +// Div8p is the unchecked panicking version of Div8 func Div8p(a, b int8) int8 { - r, ok := Div8(a, b) - if !ok { - panic("division failure") - } - return r + r, ok := Div8(a, b) + if !ok { + panic("division failure") + } + return r } // Quotient8 performs + operation on two int8 operands // returning a quotient, a remainder and status +// +// Deprecated: Quotient8 is deprecated, use Quo8 instead func Quotient8(a, b int8) (int8, int8, bool) { - if b == 0 { - return 0, 0, false - } - c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + if b == 0 { + return 0, 0, false + } + c := a / b + status := (c < 0) == ((a < 0) != (b < 0)) + return c, a % b, status } +// Quo8 performs + operation on two int8 operands +// returning a quotient, a remainder and status +func Quo8(a, b int8) (int8, int8, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == math.MinInt8 { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// UAdd8 performs + operation on two uint8 operands +// returning a result and status +func UAdd8(a, b uint8) (uint8, bool) { + c := a + b + if c >= a { + return c, true + } + return c, false +} + +// UAdd8p is the unchecked panicking version of UAdd8 +func UAdd8p(a, b uint8) uint8 { + r, ok := UAdd8(a, b) + if !ok { + panic("addition overflow") + } + return r +} +// USub8 performs - operation on two uint8 operands +// returning a result and status +func USub8(a, b uint8) (uint8, bool) { + if b <= a { + return a - b, true + } + return 0, false +} + +// USub8p is the unchecked panicking version of USub8 +func USub8p(a, b uint8) uint8 { + r, ok := USub8(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// UMul8 performs * operation on two uint8 operands +// returning a result and status +func UMul8(a, b uint8) (uint8, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (a <= c/b) && (b <= c/a) { + return c, true + } + return c, false +} + +// UMul8p is the unchecked panicking version of UMul8 +func UMul8p(a, b uint8) uint8 { + r, ok := UMul8(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// UDiv8 performs / operation on two uint8 operands +// returning a result and status +func UDiv8(a, b uint8) (uint8, bool) { + q, _, ok := UQuotient8(a, b) + return q, ok +} + +// UDiv8p is the unchecked panicking version of UDiv8 +func UDiv8p(a, b uint8) uint8 { + r, ok := UDiv8(a, b) + if !ok { + panic("division failure") + } + return r +} + +// UQuotient8 performs / operation on two uint8 operands +// returning a quotient, a remainder and status +func UQuotient8(a, b uint8) (uint8, uint8, bool) { + if b == 0 { + return 0, 0, false + } + return a / b, a % b, true +} // Add16 performs + operation on two int16 operands // returning a result and status func Add16(a, b int16) (int16, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false } -// Add16p is the unchecked panicing version of Add16 +// Add16p is the unchecked panicking version of Add16 func Add16p(a, b int16) int16 { - r, ok := Add16(a, b) - if !ok { - panic("addition overflow") - } - return r + r, ok := Add16(a, b) + if !ok { + panic("addition overflow") + } + return r } - // Sub16 performs - operation on two int16 operands // returning a result and status func Sub16(a, b int16) (int16, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false } -// Sub16p is the unchecked panicing version of Sub16 +// Sub16p is the unchecked panicking version of Sub16 func Sub16p(a, b int16) int16 { - r, ok := Sub16(a, b) - if !ok { - panic("subtraction overflow") - } - return r + r, ok := Sub16(a, b) + if !ok { + panic("subtraction overflow") + } + return r } - // Mul16 performs * operation on two int16 operands // returning a result and status func Mul16(a, b int16) (int16, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul16p is the unchecked panicing version of Mul16 + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul16p is the unchecked panicking version of Mul16 func Mul16p(a, b int16) int16 { - r, ok := Mul16(a, b) - if !ok { - panic("multiplication overflow") - } - return r + r, ok := Mul16(a, b) + if !ok { + panic("multiplication overflow") + } + return r } - - // Div16 performs / operation on two int16 operands // returning a result and status func Div16(a, b int16) (int16, bool) { - q, _, ok := Quotient16(a, b) - return q, ok + q, _, ok := Quotient16(a, b) + return q, ok } -// Div16p is the unchecked panicing version of Div16 +// Div16p is the unchecked panicking version of Div16 func Div16p(a, b int16) int16 { - r, ok := Div16(a, b) - if !ok { - panic("division failure") - } - return r + r, ok := Div16(a, b) + if !ok { + panic("division failure") + } + return r } // Quotient16 performs + operation on two int16 operands // returning a quotient, a remainder and status +// +// Deprecated: Quotient16 is deprecated, use Quo16 instead func Quotient16(a, b int16) (int16, int16, bool) { - if b == 0 { - return 0, 0, false - } - c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + if b == 0 { + return 0, 0, false + } + c := a / b + status := (c < 0) == ((a < 0) != (b < 0)) + return c, a % b, status } +// Quo16 performs + operation on two int16 operands +// returning a quotient, a remainder and status +func Quo16(a, b int16) (int16, int16, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == math.MinInt16 { + return 0, 0, false + } + c := a / b + return c, a % b, true +} +// UAdd16 performs + operation on two uint16 operands +// returning a result and status +func UAdd16(a, b uint16) (uint16, bool) { + c := a + b + if c >= a { + return c, true + } + return c, false +} + +// UAdd16p is the unchecked panicking version of UAdd16 +func UAdd16p(a, b uint16) uint16 { + r, ok := UAdd16(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// USub16 performs - operation on two uint16 operands +// returning a result and status +func USub16(a, b uint16) (uint16, bool) { + if b <= a { + return a - b, true + } + return 0, false +} + +// USub16p is the unchecked panicking version of USub16 +func USub16p(a, b uint16) uint16 { + r, ok := USub16(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// UMul16 performs * operation on two uint16 operands +// returning a result and status +func UMul16(a, b uint16) (uint16, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (a <= c/b) && (b <= c/a) { + return c, true + } + return c, false +} + +// UMul16p is the unchecked panicking version of UMul16 +func UMul16p(a, b uint16) uint16 { + r, ok := UMul16(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// UDiv16 performs / operation on two uint16 operands +// returning a result and status +func UDiv16(a, b uint16) (uint16, bool) { + q, _, ok := UQuotient16(a, b) + return q, ok +} + +// UDiv16p is the unchecked panicking version of UDiv16 +func UDiv16p(a, b uint16) uint16 { + r, ok := UDiv16(a, b) + if !ok { + panic("division failure") + } + return r +} + +// UQuotient16 performs / operation on two uint16 operands +// returning a quotient, a remainder and status +func UQuotient16(a, b uint16) (uint16, uint16, bool) { + if b == 0 { + return 0, 0, false + } + return a / b, a % b, true +} // Add32 performs + operation on two int32 operands // returning a result and status func Add32(a, b int32) (int32, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false } -// Add32p is the unchecked panicing version of Add32 +// Add32p is the unchecked panicking version of Add32 func Add32p(a, b int32) int32 { - r, ok := Add32(a, b) - if !ok { - panic("addition overflow") - } - return r + r, ok := Add32(a, b) + if !ok { + panic("addition overflow") + } + return r } - // Sub32 performs - operation on two int32 operands // returning a result and status func Sub32(a, b int32) (int32, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false } -// Sub32p is the unchecked panicing version of Sub32 +// Sub32p is the unchecked panicking version of Sub32 func Sub32p(a, b int32) int32 { - r, ok := Sub32(a, b) - if !ok { - panic("subtraction overflow") - } - return r + r, ok := Sub32(a, b) + if !ok { + panic("subtraction overflow") + } + return r } - // Mul32 performs * operation on two int32 operands // returning a result and status func Mul32(a, b int32) (int32, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul32p is the unchecked panicing version of Mul32 + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul32p is the unchecked panicking version of Mul32 func Mul32p(a, b int32) int32 { - r, ok := Mul32(a, b) - if !ok { - panic("multiplication overflow") - } - return r + r, ok := Mul32(a, b) + if !ok { + panic("multiplication overflow") + } + return r } - - // Div32 performs / operation on two int32 operands // returning a result and status func Div32(a, b int32) (int32, bool) { - q, _, ok := Quotient32(a, b) - return q, ok + q, _, ok := Quotient32(a, b) + return q, ok } -// Div32p is the unchecked panicing version of Div32 +// Div32p is the unchecked panicking version of Div32 func Div32p(a, b int32) int32 { - r, ok := Div32(a, b) - if !ok { - panic("division failure") - } - return r + r, ok := Div32(a, b) + if !ok { + panic("division failure") + } + return r } // Quotient32 performs + operation on two int32 operands // returning a quotient, a remainder and status +// +// Deprecated: Quotient32 is deprecated, use Quo32 instead func Quotient32(a, b int32) (int32, int32, bool) { - if b == 0 { - return 0, 0, false - } - c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + if b == 0 { + return 0, 0, false + } + c := a / b + status := (c < 0) == ((a < 0) != (b < 0)) + return c, a % b, status } +// Quo32 performs + operation on two int32 operands +// returning a quotient, a remainder and status +func Quo32(a, b int32) (int32, int32, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == math.MinInt32 { + return 0, 0, false + } + c := a / b + return c, a % b, true +} +// UAdd32 performs + operation on two uint32 operands +// returning a result and status +func UAdd32(a, b uint32) (uint32, bool) { + c := a + b + if c >= a { + return c, true + } + return c, false +} + +// UAdd32p is the unchecked panicking version of UAdd32 +func UAdd32p(a, b uint32) uint32 { + r, ok := UAdd32(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// USub32 performs - operation on two uint32 operands +// returning a result and status +func USub32(a, b uint32) (uint32, bool) { + if b <= a { + return a - b, true + } + return 0, false +} + +// USub32p is the unchecked panicking version of USub32 +func USub32p(a, b uint32) uint32 { + r, ok := USub32(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// UMul32 performs * operation on two uint32 operands +// returning a result and status +func UMul32(a, b uint32) (uint32, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (a <= c/b) && (b <= c/a) { + return c, true + } + return c, false +} + +// UMul32p is the unchecked panicking version of UMul32 +func UMul32p(a, b uint32) uint32 { + r, ok := UMul32(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// UDiv32 performs / operation on two uint32 operands +// returning a result and status +func UDiv32(a, b uint32) (uint32, bool) { + q, _, ok := UQuotient32(a, b) + return q, ok +} + +// UDiv32p is the unchecked panicking version of UDiv32 +func UDiv32p(a, b uint32) uint32 { + r, ok := UDiv32(a, b) + if !ok { + panic("division failure") + } + return r +} + +// UQuotient32 performs / operation on two uint32 operands +// returning a quotient, a remainder and status +func UQuotient32(a, b uint32) (uint32, uint32, bool) { + if b == 0 { + return 0, 0, false + } + return a / b, a % b, true +} // Add64 performs + operation on two int64 operands // returning a result and status func Add64(a, b int64) (int64, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false } -// Add64p is the unchecked panicing version of Add64 +// Add64p is the unchecked panicking version of Add64 func Add64p(a, b int64) int64 { - r, ok := Add64(a, b) - if !ok { - panic("addition overflow") - } - return r + r, ok := Add64(a, b) + if !ok { + panic("addition overflow") + } + return r } - // Sub64 performs - operation on two int64 operands // returning a result and status func Sub64(a, b int64) (int64, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false } -// Sub64p is the unchecked panicing version of Sub64 +// Sub64p is the unchecked panicking version of Sub64 func Sub64p(a, b int64) int64 { - r, ok := Sub64(a, b) - if !ok { - panic("subtraction overflow") - } - return r + r, ok := Sub64(a, b) + if !ok { + panic("subtraction overflow") + } + return r } - // Mul64 performs * operation on two int64 operands // returning a result and status func Mul64(a, b int64) (int64, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul64p is the unchecked panicing version of Mul64 + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul64p is the unchecked panicking version of Mul64 func Mul64p(a, b int64) int64 { - r, ok := Mul64(a, b) - if !ok { - panic("multiplication overflow") - } - return r + r, ok := Mul64(a, b) + if !ok { + panic("multiplication overflow") + } + return r } - - // Div64 performs / operation on two int64 operands // returning a result and status func Div64(a, b int64) (int64, bool) { - q, _, ok := Quotient64(a, b) - return q, ok + q, _, ok := Quotient64(a, b) + return q, ok } -// Div64p is the unchecked panicing version of Div64 +// Div64p is the unchecked panicking version of Div64 func Div64p(a, b int64) int64 { - r, ok := Div64(a, b) - if !ok { - panic("division failure") - } - return r + r, ok := Div64(a, b) + if !ok { + panic("division failure") + } + return r } // Quotient64 performs + operation on two int64 operands // returning a quotient, a remainder and status +// +// Deprecated: Quotient64 is deprecated, use Quo64 instead func Quotient64(a, b int64) (int64, int64, bool) { - if b == 0 { - return 0, 0, false - } - c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + if b == 0 { + return 0, 0, false + } + c := a / b + status := (c < 0) == ((a < 0) != (b < 0)) + return c, a % b, status +} + +// Quo64 performs + operation on two int64 operands +// returning a quotient, a remainder and status +func Quo64(a, b int64) (int64, int64, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == math.MinInt64 { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// UAdd64 performs + operation on two uint64 operands +// returning a result and status +func UAdd64(a, b uint64) (uint64, bool) { + c := a + b + if c >= a { + return c, true + } + return c, false +} + +// UAdd64p is the unchecked panicking version of UAdd64 +func UAdd64p(a, b uint64) uint64 { + r, ok := UAdd64(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// USub64 performs - operation on two uint64 operands +// returning a result and status +func USub64(a, b uint64) (uint64, bool) { + if b <= a { + return a - b, true + } + return 0, false +} + +// USub64p is the unchecked panicking version of USub64 +func USub64p(a, b uint64) uint64 { + r, ok := USub64(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// UMul64 performs * operation on two uint64 operands +// returning a result and status +func UMul64(a, b uint64) (uint64, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (a <= c/b) && (b <= c/a) { + return c, true + } + return c, false +} + +// UMul64p is the unchecked panicking version of UMul64 +func UMul64p(a, b uint64) uint64 { + r, ok := UMul64(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// UDiv64 performs / operation on two uint64 operands +// returning a result and status +func UDiv64(a, b uint64) (uint64, bool) { + q, _, ok := UQuotient64(a, b) + return q, ok +} + +// UDiv64p is the unchecked panicking version of UDiv64 +func UDiv64p(a, b uint64) uint64 { + r, ok := UDiv64(a, b) + if !ok { + panic("division failure") + } + return r } +// UQuotient64 performs / operation on two uint64 operands +// returning a quotient, a remainder and status +func UQuotient64(a, b uint64) (uint64, uint64, bool) { + if b == 0 { + return 0, 0, false + } + return a / b, a % b, true +} diff --git a/overflow_template.sh b/overflow_template.sh index b21fb04..697aaa2 100755 --- a/overflow_template.sh +++ b/overflow_template.sh @@ -7,13 +7,14 @@ echo "package overflow // This is generated code, created by overflow_template.sh executed // by \"go generate\" +import \"math\" + " for SIZE in 8 16 32 64 do echo " - // Add${SIZE} performs + operation on two int${SIZE} operands // returning a result and status func Add${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { @@ -24,7 +25,7 @@ func Add${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { return c, false } -// Add${SIZE}p is the unchecked panicing version of Add${SIZE} +// Add${SIZE}p is the unchecked panicking version of Add${SIZE} func Add${SIZE}p(a, b int${SIZE}) int${SIZE} { r, ok := Add${SIZE}(a, b) if !ok { @@ -33,7 +34,6 @@ func Add${SIZE}p(a, b int${SIZE}) int${SIZE} { return r } - // Sub${SIZE} performs - operation on two int${SIZE} operands // returning a result and status func Sub${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { @@ -44,7 +44,7 @@ func Sub${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { return c, false } -// Sub${SIZE}p is the unchecked panicing version of Sub${SIZE} +// Sub${SIZE}p is the unchecked panicking version of Sub${SIZE} func Sub${SIZE}p(a, b int${SIZE}) int${SIZE} { r, ok := Sub${SIZE}(a, b) if !ok { @@ -53,7 +53,6 @@ func Sub${SIZE}p(a, b int${SIZE}) int${SIZE} { return r } - // Mul${SIZE} performs * operation on two int${SIZE} operands // returning a result and status func Mul${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { @@ -69,7 +68,7 @@ func Mul${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { return c, false } -// Mul${SIZE}p is the unchecked panicing version of Mul${SIZE} +// Mul${SIZE}p is the unchecked panicking version of Mul${SIZE} func Mul${SIZE}p(a, b int${SIZE}) int${SIZE} { r, ok := Mul${SIZE}(a, b) if !ok { @@ -78,8 +77,6 @@ func Mul${SIZE}p(a, b int${SIZE}) int${SIZE} { return r } - - // Div${SIZE} performs / operation on two int${SIZE} operands // returning a result and status func Div${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { @@ -87,7 +84,7 @@ func Div${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { return q, ok } -// Div${SIZE}p is the unchecked panicing version of Div${SIZE} +// Div${SIZE}p is the unchecked panicking version of Div${SIZE} func Div${SIZE}p(a, b int${SIZE}) int${SIZE} { r, ok := Div${SIZE}(a, b) if !ok { @@ -98,6 +95,8 @@ func Div${SIZE}p(a, b int${SIZE}) int${SIZE} { // Quotient${SIZE} performs + operation on two int${SIZE} operands // returning a quotient, a remainder and status +// +// Deprecated: Quotient${SIZE} is deprecated, use Quo${SIZE} instead func Quotient${SIZE}(a, b int${SIZE}) (int${SIZE}, int${SIZE}, bool) { if b == 0 { return 0, 0, false @@ -106,5 +105,104 @@ func Quotient${SIZE}(a, b int${SIZE}) (int${SIZE}, int${SIZE}, bool) { status := (c < 0) == ((a < 0) != (b < 0)) return c, a % b, status } + +// Quo${SIZE} performs + operation on two int${SIZE} operands +// returning a quotient, a remainder and status +func Quo${SIZE}(a, b int${SIZE}) (int${SIZE}, int${SIZE}, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == math.MinInt${SIZE} { + return 0, 0, false + } + c := a / b + return c, a % b, true +} +" + +# generate unsigned integer operations +echo " +// UAdd${SIZE} performs + operation on two uint${SIZE} operands +// returning a result and status +func UAdd${SIZE}(a, b uint${SIZE}) (uint${SIZE}, bool) { + c := a + b + if c >= a { + return c, true + } + return c, false +} + +// UAdd${SIZE}p is the unchecked panicking version of UAdd${SIZE} +func UAdd${SIZE}p(a, b uint${SIZE}) uint${SIZE} { + r, ok := UAdd${SIZE}(a, b) + if !ok { + panic(\"addition overflow\") + } + return r +} + +// USub${SIZE} performs - operation on two uint${SIZE} operands +// returning a result and status +func USub${SIZE}(a, b uint${SIZE}) (uint${SIZE}, bool) { + if b <= a { + return a - b, true + } + return 0, false +} + +// USub${SIZE}p is the unchecked panicking version of USub${SIZE} +func USub${SIZE}p(a, b uint${SIZE}) uint${SIZE} { + r, ok := USub${SIZE}(a, b) + if !ok { + panic(\"subtraction overflow\") + } + return r +} + +// UMul${SIZE} performs * operation on two uint${SIZE} operands +// returning a result and status +func UMul${SIZE}(a, b uint${SIZE}) (uint${SIZE}, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c / b) == a { + return c, true + } + return c, false +} + +// UMul${SIZE}p is the unchecked panicking version of UMul${SIZE} +func UMul${SIZE}p(a, b uint${SIZE}) uint${SIZE} { + r, ok := UMul${SIZE}(a, b) + if !ok { + panic(\"multiplication overflow\") + } + return r +} + +// UDiv${SIZE} performs / operation on two uint${SIZE} operands +// returning a result and status +func UDiv${SIZE}(a, b uint${SIZE}) (uint${SIZE}, bool) { + q, _, ok := UQuotient${SIZE}(a, b) + return q, ok +} + +// UDiv${SIZE}p is the unchecked panicking version of UDiv${SIZE} +func UDiv${SIZE}p(a, b uint${SIZE}) uint${SIZE} { + r, ok := UDiv${SIZE}(a, b) + if !ok { + panic(\"division failure\") + } + return r +} + +// UQuotient${SIZE} performs / operation on two uint${SIZE} operands +// returning a quotient, a remainder and status +func UQuotient${SIZE}(a, b uint${SIZE}) (uint${SIZE}, uint${SIZE}, bool) { + if b == 0 { + return 0, 0, false + } + return a / b, a % b, true +} " done diff --git a/overflow_test.go b/overflow_test.go index 419191d..fb5244a 100644 --- a/overflow_test.go +++ b/overflow_test.go @@ -4,7 +4,6 @@ import ( "math" "testing" ) -import "fmt" // sample all possibilities of 8 bit numbers // by checking against 64 bit numbers @@ -95,25 +94,323 @@ func TestAlgorithms(t *testing.T) { } -func TestQuotient(t *testing.T) { - q, r, ok := Quotient(100, 3) - if r != 1 || q != 33 || !ok { - t.Errorf("expected 100/3 => 33, r=1") +func TestQuo8(t *testing.T) { + tests := []struct { + name string + a int8 + b int8 + wantQuot int8 + wantRem int8 + wantBool bool + }{ + {"simple division", 10, 3, 3, 1, true}, + {"exact division", 12, 4, 3, 0, true}, + {"zero dividend", 0, 5, 0, 0, true}, + {"one divisor", 42, 1, 42, 0, true}, + + {"positive / positive", 7, 3, 2, 1, true}, + {"positive / negative", 7, -3, -2, 1, true}, + {"negative / positive", -7, 3, -2, -1, true}, + {"negative / negative", -7, -3, 2, -1, true}, + + {"max value / 1", math.MaxInt8, 1, math.MaxInt8, 0, true}, + {"min value / 1", math.MinInt8, 1, math.MinInt8, 0, true}, + {"max value / 2", math.MaxInt8, 2, 63, 1, true}, + {"min value / 2", math.MinInt8, 2, -64, 0, true}, + + {"min value / -1", math.MinInt8, -1, 0, 0, false}, + {"min value / min value", math.MinInt8, math.MinInt8, 1, 0, true}, + {"-1 / min value", -1, math.MinInt8, 0, -1, true}, + + {"division by zero", 42, 0, 0, 0, false}, + {"zero division by zero", 0, 0, 0, 0, false}, + + {"max value with remainder", math.MaxInt8, 3, 42, 1, true}, // 127 / 3 = 42 remainder 1 + {"min value with remainder", math.MinInt8, 3, -42, -2, true}, // -128 / 3 = -42 remainder -2 + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + quot, rem, ok := Quo8(tt.a, tt.b) + if ok != tt.wantBool { + t.Errorf("Quo8(%v, %v) status = %v, want %v", + tt.a, tt.b, ok, tt.wantBool) + } + if ok { + if quot != tt.wantQuot { + t.Errorf("Quo8(%v, %v) quotient = %v, want %v", + tt.a, tt.b, quot, tt.wantQuot) + } + if rem != tt.wantRem { + t.Errorf("Quo8(%v, %v) remainder = %v, want %v", + tt.a, tt.b, rem, tt.wantRem) + } + + if tt.b != 0 { + // a = b * quot + rem + if tt.a != tt.b*tt.wantQuot+tt.wantRem { + t.Errorf("Quo8(%v, %v) relation check failed: %v != %v * %v + %v", + tt.a, tt.b, tt.a, tt.b, tt.wantQuot, tt.wantRem) + } + + // |rem| < |b| (except MinInt8) + if tt.b != math.MinInt8 && uint8(abs8(t, tt.wantRem)) >= uint8(abs8(t, tt.b)) { + t.Errorf("Quo8(%v, %v) remainder %v larger than or equal to divisor %v", + tt.a, tt.b, tt.wantRem, tt.b) + } + + // rem's sign must be the same as dividend's sign + if (tt.wantRem < 0) != (tt.a < 0) && tt.wantRem != 0 { + t.Errorf("Quo8(%v, %v) remainder %v has wrong sign", + tt.a, tt.b, tt.wantRem) + } + } + } + }) + } +} + +func TestDiv8Panic(t *testing.T) { + tests := []struct { + name string + a int8 + b int8 + wantPanic bool + }{ + {"normal division", 10, 2, false}, + {"division by zero", 42, 0, true}, + {"min value by -1", math.MinInt8, -1, true}, + {"valid negative division", -128, 2, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + r := recover() + if (r != nil) != tt.wantPanic { + t.Errorf("%v: panic = %v, wantPanic = %v", + tt.name, r != nil, tt.wantPanic) + } + }() + _ = Div8p(tt.a, tt.b) + }) + } +} + +func TestUnsignedAdd(t *testing.T) { + tests := []struct { + name string + a uint8 + b uint8 + want uint8 + wantBool bool + }{ + {"zero addition", 0, 0, 0, true}, + {"simple addition", 1, 2, 3, true}, + {"max value", math.MaxUint8, 0, math.MaxUint8, true}, + {"overflow", math.MaxUint8, 1, 0, false}, + {"near overflow", math.MaxUint8 - 1, 1, math.MaxUint8, true}, + {"half values", math.MaxUint8 / 2, math.MaxUint8 / 2, math.MaxUint8 - 1, true}, + {"overflow large", 200, 100, 44, false}, // 300 % 256 = 44 + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := UAdd8(tt.a, tt.b) + if ok != tt.wantBool { + t.Errorf("UAdd8(%v, %v) overflow check = %v, want %v", + tt.a, tt.b, ok, tt.wantBool) + } + if got != tt.want { + t.Errorf("UAdd8(%v, %v) = %v, want %v", + tt.a, tt.b, got, tt.want) + } + }) + } +} + +func TestUnsignedSub(t *testing.T) { + tests := []struct { + name string + a uint8 + b uint8 + want uint8 + wantBool bool + }{ + {"zero subtraction", 0, 0, 0, true}, + {"simple subtraction", 3, 2, 1, true}, + {"max value", math.MaxUint8, 1, math.MaxUint8 - 1, true}, + {"underflow", 0, 1, 0, false}, + {"near underflow", 1, 1, 0, true}, + {"equal values", 128, 128, 0, true}, + {"max minus max", math.MaxUint8, math.MaxUint8, 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := USub8(tt.a, tt.b) + if ok != tt.wantBool { + t.Errorf("USub8(%v, %v) overflow check = %v, want %v", + tt.a, tt.b, ok, tt.wantBool) + } + if got != tt.want { + t.Errorf("USub8(%v, %v) = %v, want %v", + tt.a, tt.b, got, tt.want) + } + }) + } +} + +func TestUnsignedMul(t *testing.T) { + tests := []struct { + name string + a uint8 + b uint8 + want uint8 + wantBool bool + }{ + {"zero multiplication", 0, 5, 0, true}, + {"simple multiplication", 2, 3, 6, true}, + {"max value", math.MaxUint8, 1, math.MaxUint8, true}, + {"overflow", math.MaxUint8, 2, 254, false}, // 255 * 2 = 510 -> 254 (mod 256) + {"near overflow", math.MaxUint8 / 2, 2, math.MaxUint8 - 1, true}, + {"small values 1", 10, 10, 100, true}, + {"small values 2", 15, 16, 240, true}, // 15 * 16 = 240 + {"overflow large", 16, 16, 0, false}, // 256 % 256 = 0 (mod 256) + {"max * max", math.MaxUint8, math.MaxUint8, 1, false}, // 255 * 255 = 65025 -> 1 (mod 256) + {"half max", 128, 2, 0, false}, // 128 * 2 = 256 -> 0 (mod 256) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := UMul8(tt.a, tt.b) + if ok != tt.wantBool { + t.Errorf("UMul8(%v, %v) overflow check = %v, want %v", + tt.a, tt.b, ok, tt.wantBool) + } + if got != tt.want { + t.Errorf("UMul8(%v, %v) = %v, want %v", + tt.a, tt.b, got, tt.want) + } + }) + } +} + +func TestUnsignedDiv(t *testing.T) { + tests := []struct { + name string + a uint8 + b uint8 + wantQuot uint8 + wantRem uint8 + wantBool bool + }{ + {"simple division", 10, 2, 5, 0, true}, + {"division with remainder", 10, 3, 3, 1, true}, + {"divide by zero", 10, 0, 0, 0, false}, + {"max value", math.MaxUint8, 2, math.MaxUint8 / 2, 1, true}, + {"divide by one", 127, 1, 127, 0, true}, + {"zero dividend", 0, 5, 0, 0, true}, + {"equal values", 128, 128, 1, 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + quot, rem, ok := UQuotient8(tt.a, tt.b) + if ok != tt.wantBool { + t.Errorf("UQuotient8(%v, %v) status = %v, want %v", + tt.a, tt.b, ok, tt.wantBool) + } + if ok { + if quot != tt.wantQuot { + t.Errorf("UQuotient8(%v, %v) quotient = %v, want %v", + tt.a, tt.b, quot, tt.wantQuot) + } + if rem != tt.wantRem { + t.Errorf("UQuotient8(%v, %v) remainder = %v, want %v", + tt.a, tt.b, rem, tt.wantRem) + } + } + }) + } +} + +func TestUnsignedPanic(t *testing.T) { + tests := []struct { + name string + fn func() + wantPanic bool + }{ + { + name: "addition overflow panic", + fn: func() { UAdd8p(math.MaxUint8, 1) }, + wantPanic: true, + }, + { + name: "subtraction underflow panic", + fn: func() { USub8p(0, 1) }, + wantPanic: true, + }, + { + name: "multiplication overflow panic", + fn: func() { UMul8p(math.MaxUint8, 2) }, + wantPanic: true, + }, + { + name: "division by zero panic", + fn: func() { UDiv8p(10, 0) }, + wantPanic: true, + }, + { + name: "valid addition no panic", + fn: func() { UAdd8p(1, 1) }, + wantPanic: false, + }, } - if _, _, ok = Quotient(1, 0); ok { - t.Error("unexpected lack of failure") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + r := recover() + if (r != nil) != tt.wantPanic { + t.Errorf("panic = %v, wantPanic = %v", r != nil, tt.wantPanic) + } + }() + tt.fn() + }) } } -//func TestAdditionInt(t *testing.T) { -// fmt.Printf("\nminint8 = %v\n", math.MinInt8) -// fmt.Printf("maxint8 = %v\n\n", math.MaxInt8) -// fmt.Printf("maxint32 = %v\n", math.MaxInt32) -// fmt.Printf("minint32 = %v\n\n", math.MinInt32) -// fmt.Printf("maxint64 = %v\n", math.MaxInt64) -// fmt.Printf("minint64 = %v\n\n", math.MinInt64) -//} - -func Test64(t *testing.T) { - fmt.Println("64bit:", _is64Bit()) +func TestHigherBitOperations(t *testing.T) { + t.Run("16-bit operations", func(t *testing.T) { + result, ok := UAdd16(math.MaxUint16-1, 1) + if !ok || result != math.MaxUint16 { + t.Errorf("UAdd16(MaxUint16-1, 1) = %v, %v; want %v, true", + result, ok, math.MaxUint16) + } + }) + + t.Run("32-bit operations", func(t *testing.T) { + result, ok := UAdd32(math.MaxUint32-1, 1) + if !ok || result != math.MaxUint32 { + t.Errorf("UAdd32(MaxUint32-1, 1) = %v, %v; want %v, true", + result, ok, math.MaxUint32) + } + }) + + t.Run("64-bit operations", func(t *testing.T) { + result, ok := UAdd64(math.MaxUint64-1, 1) + if !ok || result != math.MaxUint64 { + t.Errorf("UAdd64(MaxUint64-1, 1) = %v, %v; want %s, true", + result, ok, "18446744073709551615") + } + }) +} + +func abs8(t *testing.T, x int8) int8 { + t.Helper() + if x >= 0 { + return x + } + return -x }