gob/number/convert.go

266 lines
6.0 KiB
Go
Raw Permalink 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"
"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 dun 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 dun 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 dun 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 dun flottant.
func DecOf[N integer | float](f float64, base ...uint) Number {
return DecOf0(new(big.Rat).SetFloat64(float64(f)))
}
// FracOf0 retourne une fraction à partir dun numérateur et dun 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 dun numérateur et dun 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 dune 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 nest ni NaN ni 0.
func ToBool(n Number) bool { return n.Sign() != 0 }