Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 47 additions & 22 deletions lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,28 +856,53 @@ func bstrMarshal(s string) (result string) {
return result
}

// newRat converts decimals to rational fractions with the required precision.
func newRat(n float64, iterations int64, prec float64) *big.Rat {
x := int64(math.Floor(n))
y := n - float64(x)
rat := continuedFraction(y, 1, iterations, prec)
return rat.Add(rat, new(big.Rat).SetInt64(x))
}

// continuedFraction returns rational from decimal with the continued fraction
// algorithm.
func continuedFraction(n float64, i int64, limit int64, prec float64) *big.Rat {
if i >= limit || n <= prec {
return big.NewRat(0, 1)
}
inverted := 1 / n
y := int64(math.Floor(inverted))
x := inverted - float64(y)
ratY := new(big.Rat).SetInt64(y)
ratNext := continuedFraction(x, i+1, limit, prec)
res := ratY.Add(ratY, ratNext)
res = res.Inv(res)
return res
// floatToFraction converts a float number to a fraction string representation
// with specified placeholder widths for numerator and denominator.
func floatToFraction(x float64, numeratorPlaceHolder, denominatorPlaceHolder int) string {
if denominatorPlaceHolder <= 0 {
return ""
}
num, den := floatToFracUseContinuedFraction(x, int64(math.Pow10(denominatorPlaceHolder)))
if num == 0 {
return strings.Repeat(" ", numeratorPlaceHolder+denominatorPlaceHolder+1)
}
numStr := strconv.FormatInt(num, 10)
denStr := strconv.FormatInt(den, 10)
numeratorPlaceHolder = max(numeratorPlaceHolder-len(numStr), 0)
denominatorPlaceHolder = max(denominatorPlaceHolder-len(denStr), 0)
return fmt.Sprintf("%s%s/%s%s", strings.Repeat(" ", numeratorPlaceHolder), numStr, denStr, strings.Repeat(" ", denominatorPlaceHolder))
}

// floatToFracUseContinuedFraction implement convert a floating-point decimal
// to a fraction using continued fractions and recurrence relations.
func floatToFracUseContinuedFraction(r float64, denominatorLimit int64) (num, den int64) {
p_1 := int64(1) // LaTex: p_{-1}
q_1 := int64(0) // LaTex: q_{-1}
p_2 := int64(0) // LaTex: p_{-2}
q_2 := int64(1) // LaTex: q_{-2}
var lasta, lastb int64
var curra, currb int64
for k := 0; ; k++ {
// a_{k} = \lfloor r_{k} \rfloor
a := int64(math.Floor(r))
// Fundamental recurrence formulas: p_{k} = a_{k} \cdot p_{k-1} + p_{k-2}
curra, currb = a*p_1+p_2, a*q_1+q_2
p_2 = p_1
q_2 = q_1
p_1 = curra
q_1 = currb
frac := r - float64(a)
if q_1 >= denominatorLimit {
return lasta, lastb
}
if math.Abs(frac) < 1e-12 {
// use safe floating-point number comparison. If the input(r) is a real number, here is 0.
return curra, currb
}
lasta, lastb = curra, currb
// r_{k+1} = \frac{1}{r_{k} - a_{k}}
r = 1.0 / frac
}
}

// assignFieldValue assigns the value from an immutable reflect.Value to a
Expand Down
8 changes: 8 additions & 0 deletions lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/xml"
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -412,3 +413,10 @@ func TestUnzipToTemp(t *testing.T) {
_, err = f.unzipToTemp(z.File[0])
assert.EqualError(t, err, "EOF")
}

func TestFloat2Frac(t *testing.T) {
assert.Empty(t, floatToFraction(0.19, 0, 0))
assert.Equal(t, "1/5", floatToFraction(0.19, 1, 1))
assert.Equal(t, "9999/10000", strings.Trim(floatToFraction(0.9999, 10, 10), " "))
assert.Equal(t, "954888175898973913/351283728530932463", floatToFraction(math.E, 1, 18))
}
26 changes: 4 additions & 22 deletions numfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5439,27 +5439,8 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
// negative numeric.
func (nf *numberFormat) fractionHandler(frac float64, token nfp.Token, numeratorPlaceHolder int) string {
var rat, result string
var lastRat *big.Rat
if token.TType == nfp.TokenTypeDigitalPlaceHolder {
denominatorPlaceHolder := len(token.TValue)
for i := range 5000 {
if r := newRat(frac, int64(i), 0); len(r.Denom().String()) <= denominatorPlaceHolder {
lastRat = r // record the last valid ratio, and delay conversion to string
continue
}
break
}
if lastRat != nil {
if lastRat.Num().Int64() == 0 {
rat = strings.Repeat(" ", numeratorPlaceHolder+denominatorPlaceHolder+1)
} else {
num := lastRat.Num().String()
den := lastRat.Denom().String()
numeratorPlaceHolder = max(numeratorPlaceHolder-len(num), 0)
denominatorPlaceHolder = max(denominatorPlaceHolder-len(den), 0)
rat = fmt.Sprintf("%s%s/%s%s", strings.Repeat(" ", numeratorPlaceHolder), num, den, strings.Repeat(" ", denominatorPlaceHolder))
}
}
rat = floatToFraction(frac, numeratorPlaceHolder, len(token.TValue))
result += rat
}
if token.TType == nfp.TokenTypeDenominator {
Expand Down Expand Up @@ -5539,8 +5520,9 @@ func (nf *numberFormat) numberHandler() string {
intLen, fracLen = nf.getNumberPartLen()
result string
)
if isNum, precision, decimal := isNumeric(nf.value); isNum {
if precision > 15 && intLen+fracLen > 15 && !nf.useScientificNotation {
if intLen+fracLen > 15 && !nf.useScientificNotation {
isNum, precision, decimal := isNumeric(nf.value)
if isNum && precision > 15 {
return nf.printNumberLiteral(nf.printBigNumber(decimal, fracLen))
}
}
Expand Down
3 changes: 1 addition & 2 deletions numfmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4350,8 +4350,7 @@ func BenchmarkNumFmtPlaceHolder(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, item := range items {
result := format(item[0], item[1], false, CellTypeNumber, nil)
_ = result
_ = format(item[0], item[1], false, CellTypeNumber, nil)
}
}
}
Loading