487 lines
9.4 KiB
Go
487 lines
9.4 KiB
Go
package number
|
||
|
||
import (
|
||
"fmt"
|
||
"math/big"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"gitea.zaclys.com/bvaudour/gob/option"
|
||
)
|
||
|
||
func undefinedString(sign int) string {
|
||
switch sign {
|
||
case -1:
|
||
return "-∞"
|
||
case +1:
|
||
return "+∞"
|
||
default:
|
||
return "NaN"
|
||
}
|
||
}
|
||
|
||
func integerString(n *big.Int, base uint, sign int) string {
|
||
out := n.Text(int(base))
|
||
|
||
if sign < 0 {
|
||
out = fmt.Sprintf("-%s", out)
|
||
}
|
||
|
||
if base != 10 {
|
||
out = fmt.Sprintf("(%d)%s", base, out)
|
||
}
|
||
|
||
return out
|
||
}
|
||
|
||
func fractionString(n, d *big.Int, base uint, sign int) string {
|
||
out := fmt.Sprintf("%s/%s", n.Text(int(base)), d.Text(int(base)))
|
||
|
||
if sign < 0 {
|
||
out = fmt.Sprintf("-%s", out)
|
||
}
|
||
|
||
if base != 10 {
|
||
out = fmt.Sprintf("(%d)%s", base, out)
|
||
}
|
||
|
||
return out
|
||
}
|
||
|
||
func decimalString(n Number) string {
|
||
base := n.Base()
|
||
num, denom := n.num(), n.denom()
|
||
p := pow(base, FloatingPrecision)
|
||
num = num.mul(p)
|
||
q := num.quo(denom)
|
||
|
||
out := "0"
|
||
if num.cmp(denom) >= 0 {
|
||
if qn, ok := q.get(); ok {
|
||
out = qn.Num().Text(int(base))
|
||
} else {
|
||
return ""
|
||
}
|
||
}
|
||
|
||
precision := int(FloatingPrecision)
|
||
if len(out) < precision {
|
||
out = fmt.Sprintf("%s%s", strings.Repeat("0", precision), out)
|
||
}
|
||
|
||
deltaPrecision := len(out) - precision
|
||
out = fmt.Sprintf("%s.%s", out[:deltaPrecision], out[deltaPrecision:])
|
||
|
||
if !FixedPrecision {
|
||
l := len(out) - 1
|
||
for out[l] == '0' {
|
||
out, l = out[:l], l-1
|
||
}
|
||
if out == "." {
|
||
out = "0."
|
||
}
|
||
}
|
||
|
||
if n.IsNeg() {
|
||
out = fmt.Sprintf("-%s", out)
|
||
} else if q.IsZero() && !n.IsZero() {
|
||
out = fmt.Sprintf("+%s", out)
|
||
}
|
||
|
||
if base != 10 {
|
||
out = fmt.Sprintf("(%d)%s", base, out)
|
||
}
|
||
|
||
return out
|
||
}
|
||
|
||
func scientificStsring(n Number) string {
|
||
base := n.Base()
|
||
var exponent int
|
||
tmp := n
|
||
if n.IsZero() {
|
||
num, denom := n.Abs().NumDenom()
|
||
nl, dl := num.Len(), denom.Len()
|
||
exponent := nl - dl
|
||
if exponent > 0 {
|
||
denom = denom.Mul(Number{
|
||
base: base,
|
||
tpe: Integer,
|
||
atom: pow(base, exponent),
|
||
})
|
||
} else if exponent < 0 {
|
||
num = num.Mul(Number{
|
||
base: base,
|
||
tpe: Integer,
|
||
atom: pow(base, -exponent),
|
||
})
|
||
}
|
||
|
||
if num.Lt(denom) {
|
||
exponent--
|
||
num.Mul(Int(base, base))
|
||
}
|
||
tmp = num.Div(denom)
|
||
}
|
||
|
||
out := decimalString(tmp)
|
||
|
||
signExponent := ""
|
||
if exponent > 0 {
|
||
signExponent = "+"
|
||
} else if exponent < 0 {
|
||
signExponent = "-"
|
||
}
|
||
|
||
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 nb, ok := n.get(); ok {
|
||
switch n.Type() {
|
||
case Integer:
|
||
return integerString(nb.Num(), n.Base(), n.Sign())
|
||
case Fraction:
|
||
return fractionString(nb.Num(), nb.Denom(), n.base, n.Sign())
|
||
case Scientific:
|
||
return scientificStsring(n)
|
||
default:
|
||
return decimalString(n)
|
||
}
|
||
}
|
||
|
||
return undefinedString(n.Sign())
|
||
}
|
||
|
||
func parseBase(str string) (next string, base option.Option[uint]) {
|
||
begin := strings.Index(str, "(")
|
||
end := strings.Index(str, ")")
|
||
if begin > end || begin < 0 || end < 0 {
|
||
return
|
||
}
|
||
|
||
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 parseBaseN(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 parseSign(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 parseSignN(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 {
|
||
nn := Number{
|
||
base: base,
|
||
tpe: Integer,
|
||
atom: entire0(nb, sign),
|
||
}
|
||
n = option.Some(nn.format())
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseInt(str string) (n option.Option[Number]) {
|
||
next, base := parseBase(str)
|
||
|
||
if b, ok := base.Get(); ok {
|
||
next, sign := parseSign(next)
|
||
n = setInt(next, b, sign)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseInt10(str string) (n option.Option[Number]) {
|
||
next, sign := parseSign(str)
|
||
|
||
return setInt(next, 10, sign)
|
||
}
|
||
|
||
func parseIntN(str string) (n option.Option[Number]) {
|
||
next, sign := parseSignN(str)
|
||
next, base := parseBaseN(next)
|
||
|
||
return setInt(next, base, sign)
|
||
}
|
||
|
||
func setFrac(str string, base uint, sign int) (n option.Option[Number]) {
|
||
i := strings.Index(str, "/")
|
||
if i < 0 {
|
||
return
|
||
}
|
||
|
||
sNum, sDenom := str[:i], str[i:]
|
||
if num, ok := parseNumber(sNum, base).Get(); ok {
|
||
if denom, ok := parseNumber(sDenom, base).Get(); ok {
|
||
nn := Number{
|
||
base: base,
|
||
tpe: Fraction,
|
||
atom: frac0(num, denom, sign),
|
||
}
|
||
n = option.Some(nn.format())
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseFrac(str string) (n option.Option[Number]) {
|
||
next, base := parseBase(str)
|
||
|
||
if b, ok := base.Get(); ok {
|
||
next, sign := parseSign(next)
|
||
n = setFrac(next, b, sign)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseFrac10(str string) (n option.Option[Number]) {
|
||
next, sign := parseSign(str)
|
||
|
||
return setFrac(next, 10, sign)
|
||
}
|
||
|
||
func parseDot(str string) (next string, dot int) {
|
||
i := strings.Index(str, ".")
|
||
if i >= 0 {
|
||
next = str[:i] + str[:i]
|
||
dot = len(next) - i
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func setDec(str string, base uint, sign int) (n option.Option[Number]) {
|
||
next, dot := parseDot(str)
|
||
|
||
if num, ok := parseNumber(next, base).Get(); ok {
|
||
denom := pow(base, dot)
|
||
nn := Number{
|
||
base: base,
|
||
tpe: Decimal,
|
||
atom: entire0(num, sign).div(denom),
|
||
}
|
||
n = option.Some(nn.format())
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseDec(str string) (n option.Option[Number]) {
|
||
next, base := parseBase(str)
|
||
|
||
if b, ok := base.Get(); ok {
|
||
next, sign := parseSign(next)
|
||
n = setDec(next, b, sign)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseDec10(str string) (n option.Option[Number]) {
|
||
next, sign := parseSign(str)
|
||
|
||
return setDec(next, 10, sign)
|
||
}
|
||
|
||
func parseDecN(str string) (n option.Option[Number]) {
|
||
next, sign := parseSignN(str)
|
||
next, base := parseBaseN(next)
|
||
|
||
return setDec(next, base, sign)
|
||
}
|
||
|
||
func parseExp(str string) (next string, baseExponent option.Option[int], exponent option.Option[int]) {
|
||
begin := strings.Index(str, "x")
|
||
end := strings.Index(str, "^")
|
||
if begin > end || begin < 0 || end < 0 {
|
||
return
|
||
}
|
||
|
||
sBase, sExponent := str[begin+1:end], str[end+1:]
|
||
|
||
if b, err := strconv.Atoi(sBase); err == nil && isBaseValid(b) {
|
||
if e, err := strconv.Atoi(sExponent); err == nil {
|
||
next, baseExponent, exponent = str[:begin], option.Some(b), option.Some(e)
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseExp10(str string) (next string, exponent option.Option[int]) {
|
||
i := strings.Index(strings.ToLower(str), "e")
|
||
if i < 0 {
|
||
return
|
||
}
|
||
|
||
sExponent := str[i+1:]
|
||
|
||
if e, err := strconv.Atoi(sExponent); err == nil {
|
||
next, exponent = str[:i], option.Some(e)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func setSci(str string, base uint, sign, baseExponent, exponent int) (n option.Option[Number]) {
|
||
next, dot := parseDot(str)
|
||
|
||
if num, ok := parseNumber(next, base).Get(); ok {
|
||
denom := pow(base, dot)
|
||
nn := Number{
|
||
base: base,
|
||
tpe: Scientific,
|
||
atom: entire0(num, sign).div(denom),
|
||
}
|
||
if exponent > 0 {
|
||
nn.atom = nn.atom.mul(pow(baseExponent, exponent))
|
||
} else if exponent < 0 {
|
||
nn.atom = nn.atom.div(pow(baseExponent, -exponent))
|
||
}
|
||
|
||
n = option.Some(nn.format())
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseSci(str string) (n option.Option[Number]) {
|
||
next, base := parseBase(str)
|
||
|
||
if b, ok := base.Get(); ok {
|
||
next, sign := parseSign(next)
|
||
next, be, e := parseExp(next)
|
||
if baseExponent, ok := be.Get(); ok {
|
||
if exponent, ok := e.Get(); ok {
|
||
n = setSci(next, b, sign, baseExponent, exponent)
|
||
}
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseSci10(str string) (n option.Option[Number]) {
|
||
next, sign := parseSign(str)
|
||
next, be, e := parseExp(next)
|
||
|
||
if baseExponent, ok := be.Get(); ok {
|
||
if exponent, ok := e.Get(); ok {
|
||
n = setSci(next, 10, sign, baseExponent, exponent)
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func parseSci10Simple(str string) (n option.Option[Number]) {
|
||
next, sign := parseSign(str)
|
||
next, e := parseExp10(next)
|
||
|
||
if exponent, ok := e.Get(); ok {
|
||
n = setSci(next, 10, sign, 10, exponent)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// Parse retourne un nombre à partir d’une chaîne de caractères.
|
||
func Parse(str string) 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 regInt.MatchString(str):
|
||
return parseInt(str)
|
||
case regInt10.MatchString(str):
|
||
return parseInt10(str)
|
||
case regInt2.MatchString(str), regInt8.MatchString(str), regInt16.MatchString(str):
|
||
return parseIntN(str)
|
||
case regFrac.MatchString(str):
|
||
return parseFrac(str)
|
||
case regFrac10.MatchString(str):
|
||
return parseFrac10(str)
|
||
case regDec.MatchString(str):
|
||
return parseInt(str)
|
||
case regDec10.MatchString(str):
|
||
return parseInt10(str)
|
||
case regDec2.MatchString(str), regDec8.MatchString(str), regDec16.MatchString(str):
|
||
return parseIntN(str)
|
||
case regSci.MatchString(str):
|
||
return parseSci(str)
|
||
case regSci10.MatchString(str):
|
||
return parseSci10(str)
|
||
case regSci10Simple.MatchString(str):
|
||
return parseSci10Simple(str)
|
||
}
|
||
|
||
return option.None[Number]()
|
||
}
|
||
|
||
func ParseBool(b bool) Number {
|
||
if b {
|
||
return One()
|
||
}
|
||
return Zero()
|
||
}
|
||
|
||
func ToBool(n Number) bool {
|
||
return !n.IsZero() && !n.IsNan()
|
||
}
|