diff --git a/benchmarks.txt b/benchmarks.txt new file mode 100644 index 0000000..7c84007 --- /dev/null +++ b/benchmarks.txt @@ -0,0 +1,57 @@ +goos: linux +goarch: arm64 +pkg: github.com/goloop/trit +BenchmarkBasicOperations/IsTrue-6 1000000000 0.3874 ns/op 0 B/op 0 allocs/op +BenchmarkBasicOperations/IsFalse-6 1000000000 0.3856 ns/op 0 B/op 0 allocs/op +BenchmarkBasicOperations/IsUnknown-6 1000000000 0.3926 ns/op 0 B/op 0 allocs/op +BenchmarkBasicOperations/Not-6 1000000000 0.4373 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/True/True-6 234911097 5.150 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/True/True-6 507487512 2.346 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/True/True-6 256011415 4.640 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/True/False-6 517155758 2.314 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/True/False-6 516872677 2.359 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/True/False-6 280535136 4.262 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/True/Unknown-6 258602820 4.629 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/True/Unknown-6 516034815 2.313 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/True/Unknown-6 440733981 2.703 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/False/True-6 621918684 1.939 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/False/True-6 442660225 2.740 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/False/True-6 280399836 4.288 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/False/False-6 620087143 1.935 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/False/False-6 308936335 3.900 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/False/False-6 388179104 3.132 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/False/Unknown-6 620732149 1.950 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/False/Unknown-6 278881376 4.308 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/False/Unknown-6 513034950 2.343 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/Unknown/True-6 280650795 4.289 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/Unknown/True-6 383859872 3.120 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/Unknown/True-6 509072382 2.337 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/Unknown/False-6 509856184 2.339 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/Unknown/False-6 279811515 4.288 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/Unknown/False-6 509464034 2.355 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/And/Unknown/Unknown-6 275796327 4.267 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Or/Unknown/Unknown-6 256793510 4.652 ns/op 0 B/op 0 allocs/op +BenchmarkLogicOperations/Xor/Unknown/Unknown-6 514765712 2.329 ns/op 0 B/op 0 allocs/op +BenchmarkParallelOperations/All/size=100-6 1292320 917.3 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Any/size=100-6 1270347 892.1 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Known/size=100-6 1310016 928.1 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/All/size=1000-6 1253952 944.0 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Any/size=1000-6 1258168 883.4 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Known/size=1000-6 1312274 927.6 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/All/size=10000-6 1274380 904.0 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Any/size=10000-6 1212654 1013 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Known/size=10000-6 1225298 911.5 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/All/size=100000-6 1000000 1012 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Any/size=100000-6 1000000 1044 ns/op 448 B/op 9 allocs/op +BenchmarkParallelOperations/Known/size=100000-6 1297084 886.2 ns/op 448 B/op 9 allocs/op +BenchmarkConversions/FromBool/true-6 566544928 2.236 ns/op 0 B/op 0 allocs/op +BenchmarkConversions/FromInt/positive-6 238871332 5.028 ns/op 0 B/op 0 allocs/op +BenchmarkConversions/FromFloat64/negative-6 281565132 4.306 ns/op 0 B/op 0 allocs/op +BenchmarkConversions/ToString-6 1000000000 0.3881 ns/op 0 B/op 0 allocs/op +BenchmarkExtendedOperations/Nand-6 308205770 3.860 ns/op 0 B/op 0 allocs/op +BenchmarkExtendedOperations/Nor-6 257230102 4.729 ns/op 0 B/op 0 allocs/op +BenchmarkExtendedOperations/Nxor-6 181579539 6.767 ns/op 0 B/op 0 allocs/op +BenchmarkExtendedOperations/Imp-6 216840918 5.597 ns/op 0 B/op 0 allocs/op +BenchmarkExtendedOperations/Eq-6 278241660 4.299 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/goloop/trit 80.146s diff --git a/benchmarks_test.go b/benchmarks_test.go new file mode 100644 index 0000000..9e6a761 --- /dev/null +++ b/benchmarks_test.go @@ -0,0 +1,169 @@ +package trit + +import ( + "fmt" + "testing" +) + +// BenchmarkBasicOperations benchmarks basic trit operations +func BenchmarkBasicOperations(b *testing.B) { + b.Run("IsTrue", func(b *testing.B) { + t := True + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.IsTrue() + } + }) + + b.Run("IsFalse", func(b *testing.B) { + t := False + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.IsFalse() + } + }) + + b.Run("IsUnknown", func(b *testing.B) { + t := Unknown + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.IsUnknown() + } + }) + + b.Run("Not", func(b *testing.B) { + t := True + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.Not() + } + }) +} + +// BenchmarkLogicOperations benchmarks binary logic operations +func BenchmarkLogicOperations(b *testing.B) { + values := []Trit{True, False, Unknown} + + for _, v1 := range values { + for _, v2 := range values { + b.Run("And/"+v1.String()+"/"+v2.String(), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.And(v2) + } + }) + + b.Run("Or/"+v1.String()+"/"+v2.String(), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.Or(v2) + } + }) + + b.Run("Xor/"+v1.String()+"/"+v2.String(), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.Xor(v2) + } + }) + } + } +} + +// BenchmarkParallelOperations benchmarks operations that use goroutines +func BenchmarkParallelOperations(b *testing.B) { + // Create large slices of different sizes to test parallel processing + sizes := []int{100, 1000, 10000, 100000} + + for _, size := range sizes { + values := make([]Trit, size) + for i := range values { + if i%3 == 0 { + values[i] = True + } else if i%3 == 1 { + values[i] = False + } else { + values[i] = Unknown + } + } + + b.Run("All/size="+fmt.Sprint(size), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = All(values...) + } + }) + + b.Run("Any/size="+fmt.Sprint(size), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Any(values...) + } + }) + + b.Run("Known/size="+fmt.Sprint(size), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Known(values...) + } + }) + } +} + +// BenchmarkConversions benchmarks type conversion operations +func BenchmarkConversions(b *testing.B) { + b.Run("FromBool/true", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Define(true) + } + }) + + b.Run("FromInt/positive", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Define(42) + } + }) + + b.Run("FromFloat64/negative", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Define(-42.0) + } + }) + + b.Run("ToString", func(b *testing.B) { + t := True + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = t.String() + } + }) +} + +// BenchmarkExtendedOperations benchmarks more complex operations +func BenchmarkExtendedOperations(b *testing.B) { + v1, v2 := True, False + + b.Run("Nand", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.Nand(v2) + } + }) + + b.Run("Nor", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.Nor(v2) + } + }) + + b.Run("Nxor", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.Nxor(v2) + } + }) + + b.Run("Imp", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.Imp(v2) + } + }) + + b.Run("Eq", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = v1.Eq(v2) + } + }) +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..051095d --- /dev/null +++ b/doc.go @@ -0,0 +1,153 @@ +// Package trit implements three-valued logic operations with False, +// Unknown, and True states. +// +// # Overview +// +// Trit (short for "trinary digit") is an information unit that can take three +// states: False (-1), Unknown (0), and True (1). It serves as the fundamental +// unit of trinary/ternary logic systems, with applications in: +// - Database systems (SQL NULL handling) +// - Logic circuits and digital design +// - Decision systems with uncertainty +// - Artificial intelligence and expert systems +// - Configuration management +// +// Key Features +// - Basic type conversion (from/to bool, int, float) +// - Fundamental unary operations (NOT, MA, LA, IA) +// - Binary logic operations (AND, OR, XOR, etc.) +// - Extended operations (IMP, EQ, MIN, MAX) +// - Thread-safe parallel operations for slices +// - Full set of comparison and testing methods +// +// # Quick Start +// +// Basic usage example: +// +// t1 := trit.True +// t2 := trit.Unknown +// result := t1.And(t2) // Unknown +// isTrue := result.IsTrue() // false +// +// # Type System +// +// The package implements type Trit as int8 with three main states: +// - False: Any negative value (-1 and below) +// - Unknown: Zero (0) +// - True: Any positive value (1 and above) +// +// # Truth Tables +// +// The package implements the following truth tables for three-valued logic: +// +// 1. Unary Operations: +// +// - NA (Not): Logical negation +// +// - MA (Modus Ponens Absorption) +// +// - LA (Law of Absorption) +// +// - IA (Implication Absorption) +// +// A | NA A | MA A | LA A | IA +// ----+---- ----+---- ----+---- ----+---- +// F | T F | F F | F F | F +// U | U U | T U | F U | T +// T | F T | T T | T T | F +// +// 2. Basic Binary Operations: +// +// - AND: Logical conjunction +// +// - OR: Logical disjunction +// +// - XOR: Exclusive OR +// +// A | B | AND A | B | OR A | B | XOR +// ---+---+------ ---+---+------ ---+---+------ +// F | F | F F | F | F F | F | F +// F | U | F F | U | U F | U | U +// F | T | F F | T | T F | T | T +// U | F | F U | F | U U | F | U +// U | U | U U | U | U U | U | U +// U | T | U U | T | T U | T | U +// T | F | F T | F | T T | F | T +// T | U | U T | U | T T | U | U +// T | T | T T | T | T T | T | F +// +// 3. Negative Binary Operations: +// +// - NAND: Logical NAND (NOT AND) +// +// - NOR: Logical NOR (NOT OR) +// +// - NXOR: Logical XNOR (NOT XOR) +// +// A | B | NAND A | B | NOR A | B | NXOR +// ---+---+------ ---+---+------ ---+---+------ +// F | F | T F | F | T F | F | T +// F | U | T F | U | U F | U | U +// F | T | T F | T | F F | T | F +// U | F | T U | F | U U | F | U +// U | U | U U | U | U U | U | U +// U | T | U U | T | F U | T | U +// T | F | T T | F | F T | F | F +// T | U | U T | U | F T | U | U +// T | T | F T | T | F T | T | T +// +// 4. Extended Operations: +// +// - IMP: Implication (Lukasiewicz Logic) +// +// - EQ: Equivalence (If and only if) +// +// - MIN: Minimum value +// +// - NIMP: Inverse implication +// +// - NEQ: Non-equivalence +// +// - MAX: Maximum value +// +// A | B | IMP A | B | EQ A | B | MIN +// ---+---+------ ---+---+------ ---+---+------ +// F | F | T F | F | T F | F | F +// F | U | T F | U | U F | U | F +// F | T | T F | T | F F | T | F +// U | F | U U | F | U U | F | F +// U | U | T U | U | U U | U | U +// U | T | T U | T | U U | T | U +// T | F | F T | F | F T | F | F +// T | U | U T | U | U T | U | U +// T | T | T T | T | T T | T | T +// +// A | B | NIMP A | B | NEQ A | B | MAX +// ---+---+------ ---+---+------ ---+---+------ +// F | F | F F | F | F F | F | F +// F | U | F F | U | U F | U | U +// F | T | F F | T | T F | T | T +// U | F | U U | F | U U | F | U +// U | U | F U | U | U U | U | U +// U | T | F U | T | U U | T | T +// T | F | T T | F | T T | F | T +// T | U | U T | U | U T | U | T +// T | T | F T | T | F T | T | T +// +// # Thread Safety +// +// All operations in this package are thread-safe. The parallel operations +// (All, Any, Known) use goroutines for processing large slices and include +// proper synchronization mechanisms. +// +// # Performance Considerations +// +// The package optimizes performance by: +// - Using int8 as the underlying type +// - Implementing efficient parallel processing for slice operations +// - Providing direct value access methods +// - Minimizing memory allocations +// +// For more examples and detailed API documentation, see the individual method +// documentation and the package examples. +package trit diff --git a/tables_test.go b/tables_test.go new file mode 100644 index 0000000..747efc6 --- /dev/null +++ b/tables_test.go @@ -0,0 +1,214 @@ +package trit_test + +import ( + "testing" + + "github.com/goloop/trit" +) + +// TestTruthTables tests of truth tables. +func TestTruthTables(t *testing.T) { + values := []trit.Trit{trit.False, trit.Unknown, trit.True} + names := map[trit.Trit]string{ + trit.False: "F", + trit.Unknown: "U", + trit.True: "T", + } + + // Test unary operations + t.Run("Unary Operations", func(t *testing.T) { + type testCase struct { + op string + fn func(trit.Trit) trit.Trit + want map[trit.Trit]trit.Trit + } + + tests := []testCase{ + { + op: "NA", + fn: func(a trit.Trit) trit.Trit { return a.Not() }, + want: map[trit.Trit]trit.Trit{ + trit.False: trit.True, + trit.Unknown: trit.Unknown, + trit.True: trit.False, + }, + }, + { + op: "MA", + fn: func(a trit.Trit) trit.Trit { return a.Ma() }, + want: map[trit.Trit]trit.Trit{ + trit.False: trit.False, + trit.Unknown: trit.True, + trit.True: trit.True, + }, + }, + { + op: "LA", + fn: func(a trit.Trit) trit.Trit { return a.La() }, + want: map[trit.Trit]trit.Trit{ + trit.False: trit.False, + trit.Unknown: trit.False, + trit.True: trit.True, + }, + }, + { + op: "IA", + fn: func(a trit.Trit) trit.Trit { return a.Ia() }, + want: map[trit.Trit]trit.Trit{ + trit.False: trit.False, + trit.Unknown: trit.True, + trit.True: trit.False, + }, + }, + } + + for _, test := range tests { + t.Run(test.op, func(t *testing.T) { + for _, v := range values { + got := test.fn(v) + want := test.want[v] + if got != want { + t.Errorf("%s(%s) = %s, want %s", + test.op, names[v], names[got], names[want]) + } + } + }) + } + }) + + // Test binary operations + t.Run("Binary Operations", func(t *testing.T) { + type testCase struct { + op string + fn func(trit.Trit, trit.Trit) trit.Trit + want map[string]trit.Trit + } + + tests := []testCase{ + { + op: "AND", + fn: func(a, b trit.Trit) trit.Trit { return a.And(b) }, + want: map[string]trit.Trit{ + "F,F": trit.False, "F,U": trit.False, "F,T": trit.False, + "U,F": trit.False, "U,U": trit.Unknown, "U,T": trit.Unknown, + "T,F": trit.False, "T,U": trit.Unknown, "T,T": trit.True, + }, + }, + { + op: "OR", + fn: func(a, b trit.Trit) trit.Trit { return a.Or(b) }, + want: map[string]trit.Trit{ + "F,F": trit.False, "F,U": trit.Unknown, "F,T": trit.True, + "U,F": trit.Unknown, "U,U": trit.Unknown, "U,T": trit.True, + "T,F": trit.True, "T,U": trit.True, "T,T": trit.True, + }, + }, + { + op: "XOR", + fn: func(a, b trit.Trit) trit.Trit { return a.Xor(b) }, + want: map[string]trit.Trit{ + "F,F": trit.False, "F,U": trit.Unknown, "F,T": trit.True, + "U,F": trit.Unknown, "U,U": trit.Unknown, "U,T": trit.Unknown, + "T,F": trit.True, "T,U": trit.Unknown, "T,T": trit.False, + }, + }, + { + op: "NAND", + fn: func(a, b trit.Trit) trit.Trit { return a.Nand(b) }, + want: map[string]trit.Trit{ + "F,F": trit.True, "F,U": trit.True, "F,T": trit.True, + "U,F": trit.True, "U,U": trit.Unknown, "U,T": trit.Unknown, + "T,F": trit.True, "T,U": trit.Unknown, "T,T": trit.False, + }, + }, + { + op: "NOR", + fn: func(a, b trit.Trit) trit.Trit { return a.Nor(b) }, + want: map[string]trit.Trit{ + "F,F": trit.True, "F,U": trit.Unknown, "F,T": trit.False, + "U,F": trit.Unknown, "U,U": trit.Unknown, "U,T": trit.False, + "T,F": trit.False, "T,U": trit.False, "T,T": trit.False, + }, + }, + { + op: "NXOR", + fn: func(a, b trit.Trit) trit.Trit { return a.Nxor(b) }, + want: map[string]trit.Trit{ + "F,F": trit.True, "F,U": trit.Unknown, "F,T": trit.False, + "U,F": trit.Unknown, "U,U": trit.Unknown, "U,T": trit.Unknown, + "T,F": trit.False, "T,U": trit.Unknown, "T,T": trit.True, + }, + }, + { + op: "IMP", + fn: func(a, b trit.Trit) trit.Trit { return a.Imp(b) }, + want: map[string]trit.Trit{ + "F,F": trit.True, "F,U": trit.True, "F,T": trit.True, + "U,F": trit.Unknown, "U,U": trit.True, "U,T": trit.True, + "T,F": trit.False, "T,U": trit.Unknown, "T,T": trit.True, + }, + }, + { + op: "EQ", + fn: func(a, b trit.Trit) trit.Trit { return a.Eq(b) }, + want: map[string]trit.Trit{ + "F,F": trit.True, "F,U": trit.Unknown, "F,T": trit.False, + "U,F": trit.Unknown, "U,U": trit.Unknown, "U,T": trit.Unknown, + "T,F": trit.False, "T,U": trit.Unknown, "T,T": trit.True, + }, + }, + { + op: "MIN", + fn: func(a, b trit.Trit) trit.Trit { return a.Min(b) }, + want: map[string]trit.Trit{ + "F,F": trit.False, "F,U": trit.False, "F,T": trit.False, + "U,F": trit.False, "U,U": trit.Unknown, "U,T": trit.Unknown, + "T,F": trit.False, "T,U": trit.Unknown, "T,T": trit.True, + }, + }, + { + op: "NIMP", + fn: func(a, b trit.Trit) trit.Trit { return a.Nimp(b) }, + want: map[string]trit.Trit{ + "F,F": trit.False, "F,U": trit.False, "F,T": trit.False, + "U,F": trit.Unknown, "U,U": trit.False, "U,T": trit.False, + "T,F": trit.True, "T,U": trit.Unknown, "T,T": trit.False, + }, + }, + { + op: "NEQ", + fn: func(a, b trit.Trit) trit.Trit { return a.Neq(b) }, + want: map[string]trit.Trit{ + "F,F": trit.False, "F,U": trit.Unknown, "F,T": trit.True, + "U,F": trit.Unknown, "U,U": trit.Unknown, "U,T": trit.Unknown, + "T,F": trit.True, "T,U": trit.Unknown, "T,T": trit.False, + }, + }, + { + op: "MAX", + fn: func(a, b trit.Trit) trit.Trit { return a.Max(b) }, + want: map[string]trit.Trit{ + "F,F": trit.False, "F,U": trit.Unknown, "F,T": trit.True, + "U,F": trit.Unknown, "U,U": trit.Unknown, "U,T": trit.True, + "T,F": trit.True, "T,U": trit.True, "T,T": trit.True, + }, + }, + } + + for _, test := range tests { + t.Run(test.op, func(t *testing.T) { + for _, a := range values { + for _, b := range values { + key := names[a] + "," + names[b] + got := test.fn(a, b) + want := test.want[key] + if got != want { + t.Errorf("%s(%s,%s) = %s, want %s", + test.op, names[a], names[b], names[got], names[want]) + } + } + } + }) + } + }) +} diff --git a/trit.go b/trit.go index 1282611..43eeea4 100644 --- a/trit.go +++ b/trit.go @@ -1,99 +1,13 @@ -// Package trit provides three-level logic with the states False, -// Unknown and True. -// -// Trit (short for "trinary digit") is an information unit that can take three -// states, usually expressed as False, Unknown, and True. Trit is a fundamental -// unit of trinary or ternary logic systems, including trinary computers and -// balanced ternary systems. This package provides basic logic operations -// including NOT, AND, OR, XOR, NAND, NOR, and XNOR. -// -// The three-level logic (trinary logic) has various applications in computer -// science, particularly in scenarios where a "maybe" or "unknown" state -// is beneficial, such as database systems and logic circuits. -// -// Truth Tables of Three-valued logic -// (T=True, U=Unknown, F=False) -// -// NA - Not -// MA - Modus Ponens Absorption -// LA - Law of Absorption -// IA - Implication Absorption -// -// AND - Logical AND -// OR - Logical OR -// XOR - Exclusive OR -// -// NAND - Logical not AND -// NOR - Logical not OR -// NXOR - Logical not XOR -// -// IMP - Implication in Lukasevich's Logic -// EQ - If and only if -// MIN - Minimum -// -// NIMP - NOT IMP -// NEQ - NOT EQ -// MAX - Maximum -// -// A | NA A | MA A | LA A | IA -// ----+---- ----+---- ----+---- ----+---- -// F | T F | F F | F F | F -// U | U U | T U | F U | T -// T | F T | T T | T T | F -// -// -// A | B | AND A | B | OR A | B | XOR -// ---+---+------ ---+---+------ ---+---+------ -// F | F | F F | F | F F | F | F -// F | U | F F | U | U F | U | U -// F | T | F F | T | T F | T | T -// U | F | F U | F | U U | F | U -// U | U | U U | U | U U | U | U -// U | T | U U | T | T U | T | U -// T | F | F T | F | T T | F | T -// T | U | U T | U | T T | U | U -// T | T | T T | T | T T | T | F -// -// -// A | B | NAND A | B | NOR A | B | NXOR -// ---+---+------ ---+---+------ ---+---+------ -// F | F | T F | F | T F | F | T -// F | U | T F | U | U F | U | U -// F | T | T F | T | F F | T | F -// U | F | T U | F | U U | F | U -// U | U | U U | U | U U | U | U -// U | T | U U | T | F U | T | U -// T | F | T T | F | F T | F | F -// T | U | U T | U | F T | U | U -// T | T | F T | T | F T | T | T -// -// -// A | B | IMP A | B | EQ A | B | MIN -// ---+---+------ ---+---+------ ---+---+------ -// F | F | T F | F | T F | F | F -// F | U | T F | U | U F | U | F -// F | T | T F | T | F F | T | F -// U | F | U U | F | U U | F | F -// U | U | T U | U | U U | U | U -// U | T | T U | T | U U | T | U -// T | F | F T | F | F T | F | F -// T | U | U T | U | U T | U | U -// T | T | T T | T | T T | T | T -// -// -// A | B | NIMP A | B | NEQ A | B | MAX -// ---+---+------ ---+---+------ ---+---+------ -// F | F | F F | F | F F | F | F -// F | U | F F | U | U F | U | U -// F | T | F F | T | T F | T | T -// U | F | U U | F | U U | F | U -// U | U | F U | U | U U | U | U -// U | T | F U | T | U U | T | T -// T | F | T T | F | T T | F | T -// T | U | U T | U | U T | U | T -// T | T | F T | T | F T | T | T package trit +import ( + "encoding/json" + "errors" +) + +// ErrUnknownValue is returned when we try to convert Unknown to bool. +var ErrUnknownValue = errors.New("cannot convert Unknown to bool") + // Trit represents a trinary digit, which can take on three distinct // states: False, Unknown, or True. This type is a fundamental unit of // trinary or ternary logic systems, including trinary computers and @@ -718,3 +632,50 @@ func (t Trit) Eq(trit Trit) Trit { func (t Trit) Neq(trit Trit) Trit { return t.Eq(trit).Not() } + +// CanBeBool checks if the value can be converted to bool. +// Returns true only if the value is True or False. +func (t Trit) CanBeBool() bool { + return !t.IsUnknown() +} + +// ToBool converts Trit to bool. +// Returns (false, ErrUnknownValue) if the value is Unknown. +func (t Trit) ToBool() (bool, error) { + if !t.CanBeBool() { + return false, ErrUnknownValue + } + return t.IsTrue(), nil +} + +// MarshalJSON implements the json.Marshaler interface. +// Unknown is converted to null, True to true, False to false. +func (t Trit) MarshalJSON() ([]byte, error) { + if t.IsUnknown() { + return []byte("null"), nil + } + return json.Marshal(t.IsTrue()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// null is converted to Unknown, true to True, false to False. +func (t *Trit) UnmarshalJSON(data []byte) error { + // Перевіряємо на null + if string(data) == "null" { + *t = Unknown + return nil + } + + var b bool + if err := json.Unmarshal(data, &b); err != nil { + return err + } + + if b { + *t = True + } else { + *t = False + } + + return nil +} diff --git a/trit_test.go b/trit_test.go index 55db37f..c12a4bc 100644 --- a/trit_test.go +++ b/trit_test.go @@ -1,6 +1,9 @@ package trit -import "testing" +import ( + "encoding/json" + "testing" +) // TestMethodDefault tests the Default method. func TestMethodDefault(t *testing.T) { @@ -1079,3 +1082,111 @@ func TestMethodNeq(t *testing.T) { }) } } + +// TestCanBeBool tests CanBeBool method. +func TestCanBeBool(t *testing.T) { + tests := []struct { + name string + trit Trit + want bool + }{ + {"True can be bool", True, true}, + {"False can be bool", False, true}, + {"Unknown cannot be bool", Unknown, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.trit.CanBeBool(); got != tt.want { + t.Errorf("CanBeBool() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestToBool tests ToBool method. +func TestToBool(t *testing.T) { + tests := []struct { + name string + trit Trit + want bool + wantErr bool + }{ + {"True to true", True, true, false}, + {"False to false", False, false, false}, + {"Unknown returns error", Unknown, false, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.trit.ToBool() + if (err != nil) != tt.wantErr { + t.Errorf("ToBool() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got != tt.want { + t.Errorf("ToBool() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestTritJSON tests Marshal/Unmarshal. +func TestTritJSON(t *testing.T) { + type testStruct struct { + Value Trit `json:"value"` + } + + tests := []struct { + name string + data testStruct + json string + }{ + { + name: "True to JSON", + data: testStruct{Value: True}, + json: `{"value":true}`, + }, + { + name: "False to JSON", + data: testStruct{Value: False}, + json: `{"value":false}`, + }, + { + name: "Unknown to JSON", + data: testStruct{Value: Unknown}, + json: `{"value":null}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := json.Marshal(tt.data) + if err != nil { + t.Errorf("Marshal() error = %v", err) + return + } + if string(got) != tt.json { + t.Errorf("Marshal() = %v, want %v", string(got), tt.json) + } + + var result testStruct + err = json.Unmarshal([]byte(tt.json), &result) + if err != nil { + t.Errorf("Unmarshal() error = %v", err) + return + } + if result.Value != tt.data.Value { + t.Errorf("Unmarshal() = %v, want %v", result.Value, tt.data.Value) + } + }) + } + + t.Run("Invalid JSON", func(t *testing.T) { + var result testStruct + err := json.Unmarshal([]byte(`{"value":"invalid"}`), &result) + if err == nil { + t.Error("Expected error for invalid JSON value") + } + }) +}