package number import ( "math/big" "gitea.zaclys.com/bvaudour/gob/option" ) type atom struct { number option.Option[*big.Rat] sign int } func undefined[S integer | float](sign S) atom { out := atom{ sign: signOf(sign), } return out.format() } func rational[S integer | float](rat *big.Rat, sign ...S) atom { s := 1 if len(sign) > 0 { s = signOf(sign[0]) } out := atom{ number: option.Some(rat), sign: s, } return out.format() } func frac0[S integer | float](num, denom *big.Int, sign ...S) atom { if denom.IsInt64() && denom.Int64() == 0 { s := num.Sign() if len(sign) > 0 { s *= signOf(sign[0]) } return undefined(s) } rat := new(big.Rat).SetFrac(num, denom) return rational(rat, sign...) } func frac[N integer | float, S integer | float](num, denom N, sign ...S) atom { if denom == 0 { s := signOf(num) if len(sign) > 0 { s *= signOf(sign[0]) } return undefined(s) } rat := new(big.Rat).SetFrac64(int64(num), int64(denom)) return rational(rat, sign...) } func entire0[S integer | float](n *big.Int, sign ...S) atom { rat := new(big.Rat).SetInt(n) return rational(rat, sign...) } func entire[N integer | float, S integer | float](n N, sign ...S) atom { rat := new(big.Rat).SetInt64(int64(n)) return rational(rat, sign...) } func decimal[N integer | float, S integer | float](n N, sign ...S) atom { rat := new(big.Rat).SetFloat64(float64(n)) return rational(rat, sign...) } func pow[B integer | float, P integer | float](base B, precision ...P) atom { p := FloatingPrecision if len(precision) > 0 { p = uint64(precision[0]) } a := entire(formatBase(base), 1) return a.pow(p) } func (a atom) get() (*big.Rat, bool) { return a.number.Get() } func (a *atom) format() atom { if nb, ok := a.get(); ok { a.sign *= nb.Sign() nb.Abs(nb) } a.sign = signOf(a.sign) return *a } func (a atom) clone(format ...bool) atom { out := atom{ sign: a.sign, } if nb, ok := a.get(); ok { out.number = option.Some(new(big.Rat).Set(nb)) } if len(format) > 0 && format[0] { return out.format() } return out } func (a atom) rat() (nb *big.Rat, ok bool) { if nb, ok = a.get(); ok { nb = new(big.Rat).Set(nb) if a.sign < 0 { nb.Neg(nb) } else if a.sign == 0 { nb.SetInt64(0) } } return } func (a atom) Sign() int { return a.sign } func (a atom) IsNeg() bool { return a.Sign() < 0 } func (a atom) IsPos() bool { return !a.IsNeg() } func (a atom) IsZero() bool { return a.Is(0) } func (a atom) neg() atom { out := a.clone() out.sign = -out.sign return out.format() } func (a atom) abs() atom { out := a.clone() if out.sign < 0 { out.sign = -out.sign } return out.format() } func (a atom) IsDefined() bool { return a.number.IsDefined() } func (a atom) IsInf() bool { return !a.IsDefined() && a.Sign() > 0 } func (a atom) IsNegInf() bool { return !a.IsDefined() && a.Sign() < 0 } func (a atom) IsNan() bool { return !a.IsDefined() && a.Sign() == 0 } func (a atom) Is(i int64) bool { if nb, ok := a.get(); ok { return nb.IsInt() && nb.Num().Int64()*int64(a.Sign()) == i } return false } func (a atom) IsFloat(f float64) bool { if nb, ok := a.get(); ok { e, ok := nb.Float64() return ok && e*float64(a.Sign()) == f } return false } func (a atom) isInt() bool { if nb, ok := a.get(); ok { return nb.IsInt() } return false } func (a atom) isInt64() bool { if nb, ok := a.get(); ok { return nb.IsInt() && nb.Num().IsInt64() } return false } func (a atom) toInt64() (n int64, ok bool) { var nb *big.Rat if nb, ok = a.get(); ok { if nb.IsInt() { if num := nb.Num(); num.IsInt64() { n = num.Int64() } } } return } func (a *atom) setInt() atom { if nb, ok := a.get(); ok { nb.SetInt(new(big.Int).Quo(nb.Num(), nb.Denom())) } return a.format() } func (a atom) num() atom { if a.Sign() == 0 { return entire(0, 0) } else if nb, ok := a.get(); ok { return entire0(nb.Num(), a.Sign()) } return entire(a.Sign(), 1) } func (a atom) denom() atom { if nb, ok := a.get(); ok { return entire0(nb.Denom(), 1) } return entire(0, 0) } func (a1 atom) cmp(a2 atom) int { if a1.IsNan() { if a2.IsNan() { return 0 } return -2 } else if a2.IsNan() { return 2 } else if nb1, ok := a1.rat(); ok { if nb2, ok := a2.rat(); ok { return nb1.Cmp(nb2) } return -a2.Sign() } else if a2.IsDefined() { return a1.Sign() } return compare(a1.Sign(), a2.Sign()) } func (a1 atom) add(a2 atom) atom { if r1, ok := a1.rat(); ok { if r2, ok := a2.rat(); ok { return rational(new(big.Rat).Add(r1, r2), 1) } return a2.clone() } else if a2.IsDefined() || a1.Sign() == a2.Sign() { return undefined(a1.Sign()) } return undefined(0) } func (a1 atom) sub(a2 atom) atom { return a1.add(a2.neg()) } func (a atom) inc() atom { return a.add(entire(1, 1)) } func (a atom) dec() atom { return a.sub(entire(1, 1)) } func (a1 atom) mul(a2 atom) atom { s := a1.Sign() * a2.Sign() if nb1, ok := a1.get(); ok { if nb2, ok := a2.get(); ok { return rational(new(big.Rat).Mul(nb1, nb2), s) } } return undefined(s) } func (a1 atom) div(a2 atom) atom { if nb1, ok := a1.get(); ok { if nb2, ok := a2.get(); ok { if nb2.IsInt() && nb2.Num().IsInt64() && nb2.Num().Int64() == 0 { return undefined(0) } return rational(new(big.Rat).Quo(nb1, nb2), a1.Sign()*a2.Sign()) } return undefined(a1.Sign() * a2.Sign()) } else if a2.IsDefined() { return undefined(a1.Sign() * a2.Sign()) } return undefined(0) } func (a1 atom) quo(a2 atom) atom { if nb1, ok := a1.get(); ok { if nb2, ok := a2.get(); ok { if nb2.IsInt() && nb2.Num().IsInt64() && nb2.Num().Int64() == 0 { return undefined(0) } n := new(big.Int).Mul(nb1.Num(), nb2.Num()) d := new(big.Int).Mul(nb1.Denom(), nb2.Denom()) return entire0(new(big.Int).Quo(n, d), a1.Sign()*a2.Sign()) } return undefined(a1.Sign() * a2.Sign()) } else if a2.IsDefined() { return undefined(a1.Sign() * a2.Sign()) } return undefined(0) } func (a1 atom) quoRem(a2 atom) (q, r atom) { q = a1.quo(a2) r = a1.sub(a2.mul(q)) return } func (a1 atom) rem(a2 atom) atom { _, r := a1.quoRem(a2) return r } func (a atom) inv() atom { if nb, ok := a.get(); ok { n, d := nb.Num(), nb.Denom() return frac0(d, n, a.Sign()) } if a.Sign() == 0 { return undefined(0) } return entire(0, 0) } func (a atom) pow(p uint64) atom { switch { case p == 0: if a.IsNan() { return undefined(0) } return entire(0, 0) case p == 1: return a.clone(true) case p == 2: return a.mul(a) case p&1 == 0: return a.mul(a).pow(p >> 1) default: return a.mul(a).pow(p >> 1).mul(a) } } func (a atom) optimize(precision atom) atom { if a.isInt() || !a.IsDefined() { return a } n1, d1 := a.num().abs(), a.denom() t := n1.mul(precision).quo(d1) an := t.div(precision) n2, d2 := an.num(), an.denom() if n2.cmp(n1) < 0 || d2.cmp(d1) < 0 { t.sign = a.sign return t.format() } return a } func (a atom) heron(n uint64, tmp atom) atom { n0 := entire(int64(n), 1) n1 := n0.dec() return ((tmp.mul(n1)).add(a.div(tmp.pow(n - 1)))).div(n0) } func (a atom) sqrtn(n uint64, base uint) atom { switch { case a.IsNan() || n < 2 || (a.IsNeg() && n&1 == 0): return undefined(0) case a.IsZero() || a.Is(1) || !a.IsDefined(): return a.clone(true) default: precision := pow(base, FloatingPrecision) pi := precision.inv() out := a.heron(n, a).optimize(precision) for { t := a.heron(n, out).optimize(precision) if out.cmp(t) == 0 { break } else if out.sub(t).abs().cmp(pi) < 0 { out = t break } } return out } } func (a atom) IsEven() bool { if nb, ok := a.get(); ok { return nb.IsInt() && nb.Num().Bit(0) == 0 } return false } func (a atom) IsOdd() bool { if nb, ok := a.get(); ok { return nb.IsInt() && nb.Num().Bit(0) != 0 } return false } func (a atom) lsh(n uint) atom { if nb, ok := a.get(); ok { if nb.IsInt() { num, denom := new(big.Int).Lsh(nb.Num(), n), nb.Denom() return frac0(num, denom, a.Sign()) } return a.mul(pow(2, n)) } return undefined(a.Sign()) } func (a atom) rsh(n uint) atom { if nb, ok := a.get(); ok { if nb.IsInt() { num, denom := new(big.Int).Rsh(nb.Num(), n), nb.Denom() return frac0(num, denom, a.Sign()) } return a.div(pow(2, n)) } return undefined(a.Sign()) } func (a atom) len(base uint) int { if nb, ok := a.get(); ok { if !nb.IsInt() { return -1 } num := nb.Num() if num.IsInt64() && num.Int64() == 0 { return 1 } s := num.Text(int(base)) return len(s) } return -1 } func (a atom) bit(n uint64, base uint) int { if !a.isInt() { return -1 } out := a if n > 0 { out = a.quo(pow(base, n)) } out = out.rem(entire(base, 1)) if nb, ok := out.get(); ok { return int(nb.Num().Int64()) } return -1 }