266 lines
6.0 KiB
Go
266 lines
6.0 KiB
Go
package number
|
||
|
||
import (
|
||
"fmt"
|
||
"math/big"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"gitea.zaclys.com/bvaudour/gob/option"
|
||
)
|
||
|
||
// Undefined retourne un nombre indéfini, signé :
|
||
// - si sign < 0, retourne -∞
|
||
// - si sign > 0, retourne +∞
|
||
// - sinon, retourne NaN
|
||
func Undefined[N integer | float](sign N) Number {
|
||
return Number{
|
||
base: 10,
|
||
tpe: Integer,
|
||
sign: signOf(sign),
|
||
}
|
||
}
|
||
|
||
// Nan retourne NaN.
|
||
func Nan() Number { return Undefined(0) }
|
||
|
||
// Inf retourne +∞.
|
||
func Inf() Number { return Undefined(1) }
|
||
|
||
// NegInf retourne -∞.
|
||
func NegInf() Number { return Undefined(-1) }
|
||
|
||
// IntOf0 retourne un nombre entier à partir d’un entier, et éventuellement une base.
|
||
func IntOf0(i *big.Int, base ...uint) Number {
|
||
if i == nil {
|
||
return Nan()
|
||
}
|
||
|
||
n := Number{
|
||
number: option.Some(new(big.Rat).SetInt(i)),
|
||
sign: 1,
|
||
base: formatBase(base...),
|
||
tpe: Integer,
|
||
}
|
||
|
||
return n.format()
|
||
}
|
||
|
||
// IntOf retourne un nombre entier à partir d’un entier, et éventuellement une base.
|
||
func IntOf[N integer | float](i N, base ...uint) Number {
|
||
return IntOf0(new(big.Int).SetInt64(int64(i)), base...)
|
||
}
|
||
|
||
func pow[N integer | float, M integer | float](b N, p ...M) Number {
|
||
if len(p) > 0 {
|
||
return IntOf(b).pow(int64(p[0]))
|
||
}
|
||
return IntOf(b).pow(int64(FloatingPrecision))
|
||
}
|
||
|
||
// Zero retourne le nombre 0.
|
||
func Zero(base ...uint) Number { return IntOf(0, base...) }
|
||
|
||
// One retourne le nombre 1.
|
||
func One(base ...uint) Number { return IntOf(1, base...) }
|
||
|
||
// Two retourne le nombre 2.
|
||
func Two(base ...uint) Number { return IntOf(2, base...) }
|
||
|
||
// DecOf0 retourne un nombre décimal à partir d’un rationnel.
|
||
func DecOf0(f *big.Rat, base ...uint) Number {
|
||
if f == nil {
|
||
return Nan()
|
||
}
|
||
|
||
n := Number{
|
||
number: option.Some(new(big.Rat).Set(f)),
|
||
sign: 1,
|
||
base: formatBase(base...),
|
||
tpe: Decimal,
|
||
}
|
||
|
||
return n.format()
|
||
}
|
||
|
||
// DecOf retourne un nombre décimal à partir d’un flottant.
|
||
func DecOf[N integer | float](f float64, base ...uint) Number {
|
||
return DecOf0(new(big.Rat).SetFloat64(float64(f)))
|
||
}
|
||
|
||
// FracOf0 retourne une fraction à partir d’un numérateur et d’un dénominateur entiers.
|
||
func FracOf0(num, denom *big.Int, base ...uint) Number {
|
||
if num == nil || denom == nil {
|
||
return Nan()
|
||
}
|
||
|
||
n := Number{
|
||
number: option.Some(new(big.Rat).SetFrac(num, denom)),
|
||
sign: 1,
|
||
base: formatBase(base...),
|
||
tpe: Fraction,
|
||
}
|
||
|
||
return n.format()
|
||
}
|
||
|
||
// FracOf retourne une fraction à partir d’un numérateur et d’un dénominateur entiers.
|
||
func FracOf[N integer | float](num, denom N, base ...uint) Number {
|
||
return FracOf0(new(big.Int).SetInt64(int64(num)), new(big.Int).SetInt64(int64(denom)), base...)
|
||
}
|
||
|
||
func parseBaseN(str string) (next string, base option.Option[uint]) {
|
||
begin := strings.Index(str, "(")
|
||
end := strings.Index(str, ")")
|
||
|
||
next = str[end+1:]
|
||
if b, err := strconv.ParseUint(str[begin+1:end], 10, 64); err == nil && isBaseValid(b) {
|
||
base = option.Some(uint(b))
|
||
}
|
||
return
|
||
}
|
||
|
||
func parseBaseB(str string) (next string, base uint) {
|
||
next = str[1:]
|
||
switch str[0] {
|
||
case 'B', 'b':
|
||
base = 2
|
||
case 'O', 'o':
|
||
base = 8
|
||
default:
|
||
base = 16
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseSignN(str string) (next string, sign int) {
|
||
switch str[0] {
|
||
case '-':
|
||
next, sign = str[1:], -1
|
||
case '+':
|
||
next, sign = str[1:], 1
|
||
default:
|
||
next, sign = str, 1
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseSignB(str string) (next string, sign int) {
|
||
next, sign = str[1:], 1
|
||
if str[0] == '1' {
|
||
sign = -1
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseNumber(str string, base uint) (n option.Option[*big.Int]) {
|
||
if e, ok := new(big.Int).SetString(str, int(base)); ok {
|
||
n = option.Some(e)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func setInt(str string, base uint, sign int) (n option.Option[Number]) {
|
||
if nb, ok := parseNumber(str, base).Get(); ok {
|
||
if sign < 0 {
|
||
nb.Neg(nb)
|
||
}
|
||
n = option.Some(IntOf0(nb, base))
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseInt10(str string) (n option.Option[Number]) {
|
||
next, sign := parseSignN(str)
|
||
|
||
return setInt(next, 10, sign)
|
||
}
|
||
|
||
func parseIntB(str string) (n option.Option[Number]) {
|
||
next, sign := parseSignB(str)
|
||
next, base := parseBaseB(next)
|
||
|
||
return setInt(next, base, sign)
|
||
}
|
||
|
||
func parseIntN(str string) (n option.Option[Number]) {
|
||
next, base := parseBaseN(str)
|
||
if b, ok := base.Get(); ok {
|
||
next, sign := parseSignN(next)
|
||
n = setInt(next, b, sign)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// Parse retourne un nombre à partir d’une chaîne de caractères.
|
||
func Parse(str string) (out option.Option[Number]) {
|
||
switch str {
|
||
case "+∞", "<inf>", "<+inf>":
|
||
return option.Some(Inf())
|
||
case "-∞", "<-inf>":
|
||
return option.Some(NegInf())
|
||
case "NaN", "<NaN>":
|
||
return option.Some(Nan())
|
||
}
|
||
|
||
switch {
|
||
case regInt10.MatchString(str):
|
||
return parseInt10(str)
|
||
case regIntB.MatchString(str):
|
||
return parseIntB(str)
|
||
case regIntN.MatchString(str):
|
||
return parseIntN(str)
|
||
case regDec.MatchString(str):
|
||
l := len(str)
|
||
i := strings.Index(str, ".")
|
||
dot := l - 1 - i
|
||
str = fmt.Sprintf("%s%s", str[:i], str[i+1:])
|
||
if n, ok := Parse(str).Get(); ok {
|
||
out = option.Some(n.Div(pow(n.Base(), dot)).ToType(Decimal))
|
||
}
|
||
case regExp10.MatchString(str):
|
||
i := strings.Index(strings.ToLower(str), "e")
|
||
if exponent, err := strconv.Atoi(str[i+1:]); err == nil {
|
||
if n, ok := Parse(str[:i]).Get(); ok {
|
||
out = option.Some(n.Mul(pow(10, exponent)).ToType(Scientific))
|
||
}
|
||
}
|
||
case regExpN.MatchString(str):
|
||
i, j := strings.Index(str, "×"), strings.Index(str, "^")
|
||
if expBase, err := strconv.ParseUint(str[i+2:j], 10, 64); err == nil && isBaseValid(expBase) {
|
||
if exponent, err := strconv.Atoi(str[j+1:]); err == nil {
|
||
if n, ok := Parse(str[:i]).Get(); ok {
|
||
out = option.Some(n.Mul(pow(expBase, exponent)).ToType(Scientific))
|
||
}
|
||
}
|
||
}
|
||
case regFrac.MatchString(str):
|
||
i := strings.Index(str, "/")
|
||
num, denom := Parse(str[:i]), Parse(str[i+1:])
|
||
if n, ok := num.Get(); ok {
|
||
if d, ok := denom.Get(); ok {
|
||
out = option.Some(n.Div(d).ToType(Fraction))
|
||
}
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// ParseBool retourne 1 si vrai, et 0 sinon.
|
||
func ParseBool(b bool) Number {
|
||
if b {
|
||
return One()
|
||
}
|
||
return Zero()
|
||
}
|
||
|
||
// ToBool retourne vrai si le nombre n’est ni NaN ni 0.
|
||
func ToBool(n Number) bool { return n.Sign() != 0 }
|