Skip to content

Commit 3d23585

Browse files
authored
optimize: convert using continued fractions fundamental recurrence formulas (qax-os#2220)
1 parent d97f1a3 commit 3d23585

File tree

4 files changed

+60
-46
lines changed

4 files changed

+60
-46
lines changed

lib.go

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -856,28 +856,53 @@ func bstrMarshal(s string) (result string) {
856856
return result
857857
}
858858

859-
// newRat converts decimals to rational fractions with the required precision.
860-
func newRat(n float64, iterations int64, prec float64) *big.Rat {
861-
x := int64(math.Floor(n))
862-
y := n - float64(x)
863-
rat := continuedFraction(y, 1, iterations, prec)
864-
return rat.Add(rat, new(big.Rat).SetInt64(x))
865-
}
866-
867-
// continuedFraction returns rational from decimal with the continued fraction
868-
// algorithm.
869-
func continuedFraction(n float64, i int64, limit int64, prec float64) *big.Rat {
870-
if i >= limit || n <= prec {
871-
return big.NewRat(0, 1)
872-
}
873-
inverted := 1 / n
874-
y := int64(math.Floor(inverted))
875-
x := inverted - float64(y)
876-
ratY := new(big.Rat).SetInt64(y)
877-
ratNext := continuedFraction(x, i+1, limit, prec)
878-
res := ratY.Add(ratY, ratNext)
879-
res = res.Inv(res)
880-
return res
859+
// floatToFraction converts a float number to a fraction string representation
860+
// with specified placeholder widths for numerator and denominator.
861+
func floatToFraction(x float64, numeratorPlaceHolder, denominatorPlaceHolder int) string {
862+
if denominatorPlaceHolder <= 0 {
863+
return ""
864+
}
865+
num, den := floatToFracUseContinuedFraction(x, int64(math.Pow10(denominatorPlaceHolder)))
866+
if num == 0 {
867+
return strings.Repeat(" ", numeratorPlaceHolder+denominatorPlaceHolder+1)
868+
}
869+
numStr := strconv.FormatInt(num, 10)
870+
denStr := strconv.FormatInt(den, 10)
871+
numeratorPlaceHolder = max(numeratorPlaceHolder-len(numStr), 0)
872+
denominatorPlaceHolder = max(denominatorPlaceHolder-len(denStr), 0)
873+
return fmt.Sprintf("%s%s/%s%s", strings.Repeat(" ", numeratorPlaceHolder), numStr, denStr, strings.Repeat(" ", denominatorPlaceHolder))
874+
}
875+
876+
// floatToFracUseContinuedFraction implement convert a floating-point decimal
877+
// to a fraction using continued fractions and recurrence relations.
878+
func floatToFracUseContinuedFraction(r float64, denominatorLimit int64) (num, den int64) {
879+
p_1 := int64(1) // LaTex: p_{-1}
880+
q_1 := int64(0) // LaTex: q_{-1}
881+
p_2 := int64(0) // LaTex: p_{-2}
882+
q_2 := int64(1) // LaTex: q_{-2}
883+
var lasta, lastb int64
884+
var curra, currb int64
885+
for k := 0; ; k++ {
886+
// a_{k} = \lfloor r_{k} \rfloor
887+
a := int64(math.Floor(r))
888+
// Fundamental recurrence formulas: p_{k} = a_{k} \cdot p_{k-1} + p_{k-2}
889+
curra, currb = a*p_1+p_2, a*q_1+q_2
890+
p_2 = p_1
891+
q_2 = q_1
892+
p_1 = curra
893+
q_1 = currb
894+
frac := r - float64(a)
895+
if q_1 >= denominatorLimit {
896+
return lasta, lastb
897+
}
898+
if math.Abs(frac) < 1e-12 {
899+
// use safe floating-point number comparison. If the input(r) is a real number, here is 0.
900+
return curra, currb
901+
}
902+
lasta, lastb = curra, currb
903+
// r_{k+1} = \frac{1}{r_{k} - a_{k}}
904+
r = 1.0 / frac
905+
}
881906
}
882907

883908
// assignFieldValue assigns the value from an immutable reflect.Value to a

lib_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/xml"
77
"fmt"
88
"io"
9+
"math"
910
"os"
1011
"strconv"
1112
"strings"
@@ -412,3 +413,10 @@ func TestUnzipToTemp(t *testing.T) {
412413
_, err = f.unzipToTemp(z.File[0])
413414
assert.EqualError(t, err, "EOF")
414415
}
416+
417+
func TestFloat2Frac(t *testing.T) {
418+
assert.Empty(t, floatToFraction(0.19, 0, 0))
419+
assert.Equal(t, "1/5", floatToFraction(0.19, 1, 1))
420+
assert.Equal(t, "9999/10000", strings.Trim(floatToFraction(0.9999, 10, 10), " "))
421+
assert.Equal(t, "954888175898973913/351283728530932463", floatToFraction(math.E, 1, 18))
422+
}

numfmt.go

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5439,27 +5439,8 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
54395439
// negative numeric.
54405440
func (nf *numberFormat) fractionHandler(frac float64, token nfp.Token, numeratorPlaceHolder int) string {
54415441
var rat, result string
5442-
var lastRat *big.Rat
54435442
if token.TType == nfp.TokenTypeDigitalPlaceHolder {
5444-
denominatorPlaceHolder := len(token.TValue)
5445-
for i := range 5000 {
5446-
if r := newRat(frac, int64(i), 0); len(r.Denom().String()) <= denominatorPlaceHolder {
5447-
lastRat = r // record the last valid ratio, and delay conversion to string
5448-
continue
5449-
}
5450-
break
5451-
}
5452-
if lastRat != nil {
5453-
if lastRat.Num().Int64() == 0 {
5454-
rat = strings.Repeat(" ", numeratorPlaceHolder+denominatorPlaceHolder+1)
5455-
} else {
5456-
num := lastRat.Num().String()
5457-
den := lastRat.Denom().String()
5458-
numeratorPlaceHolder = max(numeratorPlaceHolder-len(num), 0)
5459-
denominatorPlaceHolder = max(denominatorPlaceHolder-len(den), 0)
5460-
rat = fmt.Sprintf("%s%s/%s%s", strings.Repeat(" ", numeratorPlaceHolder), num, den, strings.Repeat(" ", denominatorPlaceHolder))
5461-
}
5462-
}
5443+
rat = floatToFraction(frac, numeratorPlaceHolder, len(token.TValue))
54635444
result += rat
54645445
}
54655446
if token.TType == nfp.TokenTypeDenominator {
@@ -5539,8 +5520,9 @@ func (nf *numberFormat) numberHandler() string {
55395520
intLen, fracLen = nf.getNumberPartLen()
55405521
result string
55415522
)
5542-
if isNum, precision, decimal := isNumeric(nf.value); isNum {
5543-
if precision > 15 && intLen+fracLen > 15 && !nf.useScientificNotation {
5523+
if intLen+fracLen > 15 && !nf.useScientificNotation {
5524+
isNum, precision, decimal := isNumeric(nf.value)
5525+
if isNum && precision > 15 {
55445526
return nf.printNumberLiteral(nf.printBigNumber(decimal, fracLen))
55455527
}
55465528
}

numfmt_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4350,8 +4350,7 @@ func BenchmarkNumFmtPlaceHolder(b *testing.B) {
43504350
b.ReportAllocs()
43514351
for i := 0; i < b.N; i++ {
43524352
for _, item := range items {
4353-
result := format(item[0], item[1], false, CellTypeNumber, nil)
4354-
_ = result
4353+
_ = format(item[0], item[1], false, CellTypeNumber, nil)
43554354
}
43564355
}
43574356
}

0 commit comments

Comments
 (0)