expr/fraction-type.go

362 lines
7.1 KiB
Go

// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// fraction-type.go
package expr
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
type FractionType struct {
num, den int64
}
func newFraction(num, den int64) *FractionType {
num, den = simplifyIntegers(num, den)
return &FractionType{num, den}
}
func float64ToFraction(f float64) (fract *FractionType, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign = "-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
return makeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
func makeGeneratingFraction(s string) (f *FractionType, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
// if strings.HasSuffix(s, "()") {
// s = s[0 : len(s)-2]
// }
s = strings.TrimSuffix(s, "()")
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = newFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = newFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = newFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *FractionType) toFloat() float64 {
return float64(f.num) / float64(f.den)
}
func (f *FractionType) String() string {
return f.ToString(0)
}
func (f *FractionType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d:%d", f.num, f.den))
} else {
var sign, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
sign = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sNum := fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, sign+num))
sb.WriteString(sNum)
} else {
if len(sign) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(sign) > 0 {
sb.WriteString(sign)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(sign) > 0 {
sb.WriteString(" ")
}
}
sDen := fmt.Sprintf("%[1]*s", size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))
sb.WriteString(sDen)
}
return sb.String()
}
func (f *FractionType) TypeName() string {
return "fraction"
}
// -------- fraction utility functions
// greatest common divider
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
}
// lower common multiple
func lcm(a, b int64) (l int64) {
g := gcd(a, b)
l = a * b / g
return
}
// Sum two fractions
func sumFract(f1, f2 *FractionType) (sum *FractionType) {
m := lcm(f1.den, f2.den)
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
return
}
// Multiply two fractions
func mulFract(f1, f2 *FractionType) (prod *FractionType) {
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
return
}
func anyToFract(v any) (f *FractionType, err error) {
var ok bool
if f, ok = v.(*FractionType); !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 *FractionType, 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 *FractionType
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
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
// err if af1 or af2 is not convertible to fraction
func cmpAnyFract(af1, af2 any) (result int, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
result = cmpFract(f1, f2)
return
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
func cmpFract(f1, f2 *FractionType) (result int) {
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num < 0 {
result = -1
} else if f.num > 0 {
result = 1
} else {
result = 0
}
return
}
func subAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *FractionType
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 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f1.num == 0 || f2.num == 0 {
prod = 0
} else {
f := &FractionType{f1.num * f2.num, f1.den * f2.den}
prod = simplifyFraction(f)
}
return
}
func divAnyFract(af1, af2 any) (quot any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f2.num == 0 {
err = errors.New("division by zero")
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
} else {
f := &FractionType{f1.num * f2.den, f1.den * f2.num}
quot = simplifyFraction(f)
}
return
}
func simplifyFraction(f *FractionType) (v any) {
f.num, f.den = simplifyIntegers(f.num, f.den)
if f.den == 1 {
v = f.num
} else {
v = &FractionType{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) *FractionType {
return &FractionType{n, 1}
}
func isFraction(v any) (ok bool) {
_, ok = v.(*FractionType)
return ok
}