// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // operand-fraction.go package expr import ( "errors" "fmt" "strconv" "strings" ) type fraction struct { num, den int64 } func newFraction(num, den int64) *fraction { /* if den < 0 { den = -den num = -num }*/ num, den = simplifyIntegers(num, den) return &fraction{num, den} } func (f *fraction) toFloat() float64 { return float64(f.num) / float64(f.den) } func (f *fraction) String() string { return f.ToString(0) } func (f *fraction) ToString(opt FmtOpt) string { var sb strings.Builder if opt&MultiLine == 0 { sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den)) } else { var s, num string if f.num < 0 && opt&TTY == 0 { num = strconv.FormatInt(-f.num, 10) s = "-" } else { num = strconv.FormatInt(f.num, 10) } den := strconv.FormatInt(f.den, 10) size := max(len(num), len(den)) if opt&TTY != 0 { sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num))) } else { if len(s) > 0 { sb.WriteString(" ") } sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num))) sb.WriteByte('\n') if len(s) > 0 { sb.WriteString(s) sb.WriteByte(' ') } sb.WriteString(strings.Repeat("-", size)) sb.WriteByte('\n') if len(s) > 0 { sb.WriteString(" ") } } sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))) } return sb.String() } // -------- fraction term func newFractionTerm(tk *Token) *term { return &term{ tk: *tk, parent: nil, children: make([]*term, 0, 2), position: posInfix, priority: priFraction, evalFunc: evalFraction, } } // -------- eval func func evalFraction(ctx ExprContext, self *term) (v any, err error) { var numValue, denValue any var num, den int64 var ok bool if numValue, denValue, err = self.evalInfix(ctx); err != nil { return } if num, ok = numValue.(int64); !ok { err = fmt.Errorf("numerator must be integer, got %T (%v)", numValue, numValue) return } if den, ok = denValue.(int64); !ok { err = fmt.Errorf("denominator must be integer, got %T (%v)", denValue, denValue) return } if den == 0 { err = errors.New("division by zero") return } if den < 0 { den = -den num = -num } g := gcd(num, den) num = num / g den = den / g if den == 1 { v = num } else { v = &fraction{num, den} } return } func gcd(a, b int64) (g int64) { if a < 0 { a = -a } if b < 0 { b = -b } if a < b { a, b = b, a } r := a % b for r > 0 { a, b = b, r r = a % b } g = b return } func lcm(a, b int64) (l int64) { g := gcd(a, b) l = a * b / g return } func sumFract(f1, f2 *fraction) (sum *fraction) { m := lcm(f1.den, f2.den) sum = newFraction(f1.num*(m/f1.den) + f2.num*(m/f2.den), m) return } func mulFract(f1, f2 *fraction) (prod *fraction) { prod = newFraction(f1.num * f2.num, f1.den * f2.den) return } func anyToFract(v any) (f *fraction, err error) { var ok bool if f, ok = v.(*fraction); !ok { if n, ok := v.(int64); ok { f = intToFraction(n) } } if f == nil { err = errExpectedGot("fract", typeFraction, v) } return } func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) { if f1, err = anyToFract(v1); err != nil { return } if f2, err = anyToFract(v2); err != nil { return } return } func sumAnyFract(af1, af2 any) (sum any, err error) { var f1, f2 *fraction if f1, f2, err = anyPairToFract(af1, af2); err != nil { return } f := sumFract(f1, f2) if f.num == 0 { sum = 0 } else { sum = simplifyFraction(f) } return } func subAnyFract(af1, af2 any) (sum any, err error) { var f1, f2 *fraction if f1, f2, err = anyPairToFract(af1, af2); err != nil { return } f2.num = -f2.num f := sumFract(f1, f2) if f.num == 0 { sum = 0 } else { sum = simplifyFraction(f) } return } func mulAnyFract(af1, af2 any) (prod any, err error) { var f1, f2 *fraction if f1, f2, err = anyPairToFract(af1, af2); err != nil { return } if f1.num == 0 || f2.num == 0 { prod = 0 } else { f := &fraction{f1.num * f2.num, f1.den * f2.den} prod = simplifyFraction(f) } return } func divAnyFract(af1, af2 any) (quot any, err error) { var f1, f2 *fraction if f1, f2, err = anyPairToFract(af1, af2); err != nil { return } if f2.num == 0 { err = errors.New("division by zero") return return } if f1.num == 0 || f2.den == 0 { quot = 0 } else { f := &fraction{f1.num * f2.den, f1.den * f2.num} quot = simplifyFraction(f) } return } func simplifyFraction(f *fraction) (v any) { f.num, f.den = simplifyIntegers(f.num, f.den) if f.den == 1 { v = f.num } else { v = &fraction{f.num, f.den} } return v } func simplifyIntegers(num, den int64) (a, b int64) { if num == 0 { return 0, 1 } if den == 0 { panic("fraction with denominator == 0") } if den < 0 { den = -den num = -num } g := gcd(num, den) a = num / g b = den / g return } func intToFraction(n int64) *fraction { return &fraction{n, 1} } func isFraction(v any) (ok bool) { _, ok = v.(*fraction) return ok } // init func init() { registerTermConstructor(SymVertBar, newFractionTerm) }