gob/number/number.go

767 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package number
import (
"fmt"
"math/big"
"strings"
"gitea.zaclys.com/bvaudour/gob/option"
)
// NumberType représente le type dun nombre.
type NumberType uint
const (
Integer NumberType = iota
Scientific
Decimal
Fraction
)
// Number représente un nombre.
type Number struct {
number rat
sign int
tpe NumberType
base uint
}
func (n *Number) get() (r *big.Rat, ok bool) {
return n.number.Get()
}
func (n *Number) rat() (r *big.Rat, ok bool) {
if r, ok = n.get(); ok {
r = new(big.Rat).Set(r)
if n.Sign() < 0 {
r.Neg(r)
}
}
return
}
func (n *Number) format() Number {
n.base, n.sign, n.tpe = formatBase(n.base), signOf(n.sign), min(n.tpe, Fraction)
if nb, ok := n.get(); ok {
n.sign *= nb.Sign()
if n.tpe == Integer && !nb.IsInt() {
num, denom := nb.Num(), nb.Denom()
nb.SetInt(num.Quo(num, denom))
}
nb.Abs(nb)
} else {
n.tpe = Integer
}
return *n
}
func (n Number) set(r *big.Rat, tpe NumberType, sign ...int) Number {
s := 1
if len(sign) > 0 {
s = sign[0]
}
out := Number{
base: n.Base(),
tpe: tpe,
sign: s,
number: option.Some(r),
}
return out.format()
}
// Clone crée une copie profonde du nombre.
func (n Number) Clone() Number {
out := Number{
sign: n.sign,
tpe: n.tpe,
base: n.base,
}
if nb, ok := n.get(); ok {
out.number = option.Some(new(big.Rat).Set(nb))
}
return out
}
// Base retourne la base utilisée pour laffichage du nombre.
func (n Number) Base() uint { return n.base }
// Type retourne le type de nombre (entier, décimal, fraction ou scientifique).
func (n Number) Type() NumberType { return n.tpe }
// Sign retourne :
// - 0 si n = 0 ou NaN
// - 1 si n > 0
// - -1 si n < 0
func (n Number) Sign() int { return n.sign }
// ToBase convertit le nombre dans la base donnée.
func (n Number) ToBase(base uint) Number {
out := n.Clone()
out.base = formatBase(base)
return out
}
// ToType convertit le nombre dans le type donné. La base est conservée, sauf si une base est fournie.
func (n Number) ToType(tpe NumberType, base ...uint) Number {
out := n.Clone()
out.tpe = tpe
out = out.format()
if len(base) > 0 {
return out.ToBase(base[0])
}
return out
}
// IsDefined retourne vrai si le nombre est défini.
func (n Number) IsDefined() bool { return n.number.IsDefined() }
// IsInt retourne vrai si le nombre est entier.
func (n Number) IsInt() bool {
nb, ok := n.get()
return ok && nb.IsInt()
}
// IsInt64 retourne vrai si le nombre est convertible en int64.
func (n Number) IsInt64() bool {
nb, ok := n.get()
return ok && nb.IsInt() && nb.Num().IsInt64()
}
// IsUint64 retourne vrai si le nombre est convertible en uint64.
func (n Number) IsUint64() bool {
nb, ok := n.get()
return ok && nb.IsInt() && nb.Num().IsUint64()
}
// Is retourne vrai si n = i.
func (n Number) Is(i int64) bool {
if nb, ok := n.rat(); ok && nb.IsInt() {
num := nb.Num()
return num.IsInt64() && num.Int64() == i
}
return false
}
// IsUint retourne vrai si n = u.
func (n Number) IsUint(u uint64) bool {
if nb, ok := n.rat(); ok && nb.IsInt() {
num := nb.Num()
return num.IsUint64() && num.Uint64() == u
}
return false
}
// IsFloat retourne vrai si n = f.
func (n Number) IsFloat(f float64) bool {
if nb, ok := n.rat(); ok {
nf, ok := nb.Float64()
return nf == f && ok
}
return false
}
// IsNeg retourne vrai si n < 0.
func (n Number) IsNeg() bool { return n.Sign() < 0 }
// IsPos retourne vrai si n > 0.
func (n Number) IsPos() bool { return !n.IsNeg() }
// IsZero retourne vrai si n = 0.
func (n Number) IsZero() bool { return n.IsDefined() && n.Sign() == 0 }
// IsInf retourne vrai si n = +∞.
func (n Number) IsInf() bool { return !n.IsDefined() && n.Sign() > 0 }
// IsNegInf retourne vrai si n = -∞.
func (n Number) IsNegInf() bool { return !n.IsDefined() && n.Sign() < 0 }
// IsNan retourne vrai si n = NaN.
func (n Number) IsNan() bool { return !n.IsDefined() && n.Sign() == 0 }
// ToInt64 retourne le nombre converti en int64, et vrai si la conversion a réussi.
func (n Number) ToInt64() (i int64, ok bool) {
var nb *big.Rat
if nb, ok = n.rat(); ok {
if ok = nb.IsInt(); ok {
num := nb.Num()
if ok = num.IsInt64(); ok {
i = num.Int64()
}
}
}
return
}
// ToUint64 retourne le nombre converti en uint64, et vrai si la conversion a réussi.
func (n Number) ToUint64() (u uint64, ok bool) {
var nb *big.Rat
if nb, ok = n.rat(); ok {
if ok = nb.IsInt(); ok {
num := nb.Num()
if ok = num.IsUint64(); ok {
u = num.Uint64()
}
}
}
return
}
// ToFloat retourne le nombre converti en flottant, et vrai si la conversion a réussi et est exacte.
func (n Number) ToFloat() (f float64, ok bool) {
var nb *big.Rat
if nb, ok = n.rat(); ok {
f, ok = nb.Float64()
}
return
}
// IsEven retourne vrai si n est entier et pair.
func (n Number) IsEven() bool {
if nb, ok := n.get(); ok {
return nb.IsInt() && nb.Num().Bit(0) == 0
}
return false
}
// IsOdd retourne vrai si n est entier et impair.
func (n Number) IsOdd() bool {
if nb, ok := n.get(); ok {
return nb.IsInt() && nb.Num().Bit(0) != 0
}
return false
}
// Num retourne le numérateur.
func (n Number) Num() Number {
if n.Sign() == 0 {
return IntOf(0, n.Base())
} else if nb, ok := n.rat(); ok {
return IntOf0(nb.Num(), n.Base())
}
return IntOf(n.Sign(), n.Base())
}
// Denom retourne le dénominateur.
func (n Number) Denom() Number {
if nb, ok := n.get(); ok {
return IntOf0(nb.Denom(), n.Base())
}
return IntOf(0, n.Base())
}
// NumDenom retourne le numérateur et le dénominateur.
func (n Number) NumDenom() (Number, Number) { return n.Num(), n.Denom() }
// Neg retourne -n.
func (n Number) Neg() Number {
out := n.Clone()
out.sign = -out.sign
return out
}
// Abs retourne |n|.
func (n Number) Abs() Number {
out := n.Clone()
out.sign = abs(out.sign)
return out
}
// Cmp retourne :
// - 1 si n1 > n2
// - -1 si n1 < n2
// - 0 si n1 = n2
// - 2 ou -2 si les nombres ne sont pas comparables (ie. n1 = NaN ou n2 = NaN)
func (n1 Number) Cmp(n2 Number) int {
if n1.IsNan() {
if n2.IsNan() {
return 0
}
return -2
} else if n2.IsNan() {
return 2
} else if nb1, ok := n1.rat(); ok {
if nb2, ok := n2.rat(); ok {
return nb1.Cmp(nb2)
}
return -n2.Sign()
} else if n2.IsDefined() {
return n1.Sign()
}
return compare(n1.Sign(), n2.Sign())
}
// Eq retourne vrai si n1 = n2.
func (n1 Number) Eq(n2 Number) bool { return n1.Cmp(n2) == 0 }
// Ne retourne vrai si n1 ≠ n2.
func (n1 Number) Ne(n2 Number) bool { return n1.Cmp(n2) != 0 }
// Gt retourne vrai si n1 > n2.
func (n1 Number) Gt(n2 Number) bool { return n1.Cmp(n2) > 0 }
// Lt retourne vrai si n1 < n2.
func (n1 Number) Lt(n2 Number) bool { return n1.Cmp(n2) < 0 }
// Ge retourne vrai si n1 ≥ n2.
func (n1 Number) Ge(n2 Number) bool { return n1.Cmp(n2) >= 0 }
// Le retourne vrai si n1 ≤ n2.
func (n1 Number) Le(n2 Number) bool { return n1.Cmp(n2) <= 0 }
// Add retourne n1 + n2.
func (n1 Number) Add(n2 Number) Number {
if r1, ok := n1.rat(); ok {
if r2, ok := n2.rat(); ok {
return n1.set(new(big.Rat).Add(r1, r2), max(n1.Type(), n2.Type()))
}
return n2.Clone()
} else if n2.IsDefined() || n1.Sign() == n2.Sign() {
return Undefined(n1.Sign())
}
return Nan()
}
// Sub retourne n1 n2.
func (n1 Number) Sub(n2 Number) Number { return n1.Add(n2.Neg()) }
// Inc retourne n + 1.
func (n Number) Inc() Number { return n.Add(One()) }
// Dec retourne n 1.
func (n Number) Dec() Number { return n.Sub(One()) }
// Mul retourne n1 × n2.
func (n1 Number) Mul(n2 Number) Number {
s := n1.Sign() * n2.Sign()
if nb1, ok := n1.get(); ok {
if nb2, ok := n2.get(); ok {
return n1.set(new(big.Rat).Mul(nb1, nb2), max(n1.Type(), n2.Type()), s)
}
}
return Undefined(s)
}
// Div retourne n1 / n2 (division exacte).
func (n1 Number) Div(n2 Number) Number {
s := n1.Sign() * n2.Sign()
if nb1, ok := n1.get(); ok {
if nb2, ok := n2.get(); ok {
if nb2.IsInt() && nb2.Num().IsInt64() && nb2.Num().Int64() == 0 {
return Nan()
}
t := max(n1.Type(), n2.Type())
result := new(big.Rat).Quo(nb1, nb2)
if t == Integer && !result.IsInt() {
t = Decimal
}
return n1.set(result, t, s)
}
return Undefined(s)
} else if n2.IsDefined() {
return Undefined(s)
}
return Nan()
}
// inv retourne 1/n.
func (n Number) Inv() Number { return IntOf(1, n.Base()).Div(n) }
// Quo retourne n1 ÷ n2 (division entière).
func (n1 Number) Quo(n2 Number) Number {
if nb1, ok := n1.rat(); ok {
if nb2, ok := n2.rat(); ok {
if nb2.IsInt() && nb2.Num().IsInt64() && nb2.Num().Int64() == 0 {
return Nan()
}
q := new(big.Rat).Quo(nb1, nb2)
return IntOf0(new(big.Int).Quo(q.Num(), q.Denom()), n1.Base())
}
return Undefined(n1.Sign() * n2.Sign())
} else if n2.IsDefined() {
return Undefined(n1.Sign() * n2.Sign())
}
return Nan()
}
// QuoRem retourne q et r tels que n1 = q×n2 + r où q est entier.
func (n1 Number) QuoRem(n2 Number) (q, r Number) {
q = n1.Quo(n2)
r = n1.Sub(n2.Mul(q))
return
}
// Rem retourne n1 % n2.
func (n1 Number) Rem(n2 Number) Number {
_, r := n1.QuoRem(n2)
return r
}
func (n Number) pow(p int64) (out Number) {
inv := p < 0
p = abs(p)
switch {
case p == 0:
if n.IsNan() {
return Nan()
}
return IntOf(1, n.Base())
case p == 1:
out = n.Clone()
case p == 2:
out = n.Square()
case p&1 == 0:
out = n.Square().pow(p >> 1)
default:
out = n.Square().pow(p >> 1).Mul(n)
}
if inv {
out = out.Inv()
}
return
}
func (n Number) optimize(precision Number) (out Number) {
out = n
if n.Denom().Gt(precision) {
out = n.Num().Mul(precision).Quo(n.Denom()).Div(precision).ToType(n.Type())
}
return
}
func (n Number) sqrt(s uint64) (out Number) {
if n.IsNan() || s < 2 || (s&1 == 0 && n.IsNeg()) {
return Nan()
} else if !n.IsDefined() {
return n.Clone()
}
s0 := IntOf(s)
s1 := s0.Dec()
precision := pow[uint, uint](n.Base())
deltaMax := precision.Inv()
heron := func(partial Number) Number {
return ((partial.Mul(s1)).Add(n.Div(partial.pow(int64(s - 1))))).Div(s0).optimize(precision)
}
out = n
for {
tmp := heron(out)
if out.Eq(tmp) {
break
} else if (tmp.Sub(out)).Abs().Lt(deltaMax) {
out = tmp
break
}
out = tmp
}
if n.Type() == Integer && out.IsInt() {
out = out.ToType(Integer)
}
return
}
// Pow retourne n1 ^ n2.
func (n1 Number) Pow(n2 Number) Number {
if !n2.IsDefined() {
return Nan()
}
p2, s2 := n2.NumDenom()
p, pok := p2.ToInt64()
s, sok := s2.ToUint64()
if !pok || !sok {
return Nan()
}
out := n1.pow(p)
if s > 1 {
out = out.sqrt(s)
}
return out
}
// Square retourne n².
func (n Number) Square() Number { return n.Mul(n) }
// Sqrtn retourne la racine n2ième de n1.
func (n1 Number) Sqrtn(n2 Number) Number {
s, ok := n2.ToUint64()
if !ok {
return Nan()
}
return n1.sqrt(s)
}
// Sqrt retourne la racine carrée de n.
func (n Number) Sqrt() Number { return n.Sqrtn(Two()) }
func (n Number) lsh(s uint64) Number {
if nb, ok := n.rat(); ok {
num, denom := nb.Num(), nb.Denom()
return FracOf0(new(big.Int).Lsh(num, uint(s)), denom, n.Base()).ToType(n.Type())
}
return n.Clone()
}
func (n Number) rsh(s uint64) Number {
if nb, ok := n.rat(); ok {
t := n.Type()
if t == Integer {
return IntOf0(new(big.Int).Rsh(nb.Num(), uint(s)), n.Base())
}
return FracOf0(nb.Num(), new(big.Int).Lsh(nb.Denom(), uint(s)), n.Base()).ToType(t)
}
return n.Clone()
}
// Lsh retourne n1 << n2.
func (n1 Number) Lsh(n2 Number) Number {
if s, ok := n2.ToUint64(); ok {
return n1.lsh(s)
}
return Nan()
}
// Rsh retourne n1 >> n2.
func (n1 Number) Rsh(n2 Number) Number {
if s, ok := n2.ToUint64(); ok {
return n1.rsh(s)
}
return Nan()
}
// Len retourne le nombre de chiffre de n si n est entier ou -1 sinon.
func (n Number) Len() int {
if nb, ok := n.get(); ok {
if !nb.IsInt() {
return -1
}
num := nb.Num()
if num.IsInt64() && num.Int64() == 0 {
return 1
}
s := num.Text(int(n.Base()))
return len(s)
}
return -1
}
// Bit retourne le n2ième chiffre de n1 si n est entier, ou -1 sinon.
// Le n° de bit commence à 0 à partir de la droite.
func (n1 Number) Bit(n2 Number) int {
b, ok := n2.ToUint64()
if !ok || !n1.IsInt() {
return -1
}
out := n1
if b > 0 {
out = n1.Quo(pow(n1.Base(), b))
}
out = out.Rem(IntOf(n1.Base()))
if result, ok := out.ToInt64(); ok {
return int(result)
}
return -1
}
// Fact retourne n!.
func (n Number) Fact() Number {
switch {
case n.IsInf():
return Inf()
case !n.IsInt() || n.IsNeg():
return Nan()
default:
out := One(n.Base())
for e := Two(); e.Le(n); e = e.Inc() {
out = out.Mul(e)
}
return out
}
}
func (n Number) text() string {
if nb, ok := n.get(); ok {
return nb.Num().Text(int(n.Base()))
}
return ""
}
func (n Number) undefinedString() string {
switch n.Sign() {
case -1:
return "-∞"
case +1:
return "+∞"
default:
return "NaN"
}
}
func (n Number) intString() (out string) {
out = n.text()
if n.Sign() < 0 {
out = fmt.Sprintf("-%s", out)
}
if base := n.Base(); base != 10 {
out = fmt.Sprintf("(%d)%s", base, out)
}
return
}
func (n Number) fracString() string {
num, denom := n.NumDenom()
return fmt.Sprintf("%s/%s", num.intString(), denom.intString())
}
func (n Number) decString() (out string) {
base, sign := n.Base(), n.Sign()
num, denom := n.NumDenom()
precision := pow[uint, uint](base)
num = num.Abs().Mul(precision)
q := num.Quo(denom)
out = "0"
if num.Ge(denom) {
out = q.text()
}
p := int(FloatingPrecision)
if len(out) < p {
out = fmt.Sprintf("%s%s", strings.Repeat("0", p), out)
}
dot := len(out) - p
out = fmt.Sprintf("%s.%s", out[:dot], out[dot:])
for out[0] == '0' {
out = out[1:]
}
if !FixedPrecision {
l := len(out) - 1
for out[l] == '0' {
out, l = out[:l], l-1
}
}
if out == "." {
out = "0."
}
if sign < 0 {
out = fmt.Sprintf("-%s", out)
} else if q.IsZero() && sign > 0 {
out = fmt.Sprintf("+%s", out)
}
if base != 10 {
out = fmt.Sprintf("(%d)%s", base, out)
}
return
}
func (n Number) sciString() (out string) {
base, sign := n.Base(), n.Sign()
var exponent int
tmp := n
if !n.IsZero() {
num, denom := n.Abs().NumDenom()
nl, dl := num.Len(), denom.Len()
exponent = nl - dl
nBase := IntOf(base)
if exponent > 0 {
denom = denom.Mul(nBase.pow(int64(exponent)))
} else if exponent < 0 {
num = num.Mul(nBase.pow(int64(-exponent)))
}
if num.Lt(denom) {
exponent--
num = num.Mul(nBase)
}
tmp = num.Div(denom)
}
if sign < 0 {
tmp = tmp.Neg()
}
signExponent := ""
if exponent > 0 {
signExponent = "+"
}
out = tmp.decString()
if base == 10 {
return fmt.Sprintf("%sE%s%d", out, signExponent, exponent)
}
return fmt.Sprintf("%s×%d^%s%d", out, base, signExponent, exponent)
}
// String retourne la représentation du nombre sous forme de chaîne de caractères.
func (n Number) String() string {
if !n.IsDefined() {
return n.undefinedString()
}
switch n.Type() {
case Integer:
return n.intString()
case Fraction:
return n.fracString()
case Scientific:
return n.sciString()
default:
return n.decString()
}
}