275 lines
5.0 KiB
Go
275 lines
5.0 KiB
Go
// 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
|
|
}
|
|
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 = &fraction{f1.num*(m/f1.den) + f2.num*(m/f2.den), m}
|
|
return
|
|
}
|
|
|
|
func mulFract(f1, f2 *fraction) (prod *fraction) {
|
|
prod = &fraction{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) any {
|
|
return simplifyIntegers(f.num, f.den)
|
|
}
|
|
|
|
func simplifyIntegers(num, den int64) (v any) {
|
|
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 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)
|
|
}
|