package number import ( "fmt" "math/big" "strings" "gitea.zaclys.com/bvaudour/gob/option" ) // NumberType représente le type d’un 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 l’affichage 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() } }