From c3c91577a80541f7cfb3f9a98863c024117927c2 Mon Sep 17 00:00:00 2001 From: shcabin Date: Tue, 7 Oct 2025 23:02:01 +0800 Subject: [PATCH 1/2] convert using continued fractions fundamental recurrence formulas --- lib.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ numfmt.go | 21 +-------------------- numfmt_test.go | 3 +-- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/lib.go b/lib.go index e12965d0f1..f15efa108c 100644 --- a/lib.go +++ b/lib.go @@ -880,6 +880,54 @@ func continuedFraction(n float64, i int64, limit int64, prec float64) *big.Rat { return res } +func floatToFraction(x float64, numeratorPlaceHolder, denominatorPlaceHolder int) string { + if denominatorPlaceHolder <= 0 { + return "" + } + var rat string + num, den := floatToFracUseContinuedFraction(x, int64(math.Pow10(denominatorPlaceHolder))) + if num == 0 { + rat = strings.Repeat(" ", numeratorPlaceHolder+denominatorPlaceHolder+1) + } else { + numStr := strconv.FormatInt(num, 10) + denStr := strconv.FormatInt(den, 10) + numeratorPlaceHolder = max(numeratorPlaceHolder-len(numStr), 0) + denominatorPlaceHolder = max(denominatorPlaceHolder-len(denStr), 0) + rat = fmt.Sprintf("%s%s/%s%s", strings.Repeat(" ", numeratorPlaceHolder), numStr, denStr, strings.Repeat(" ", denominatorPlaceHolder)) + } + return rat +} + +// floatToFracUseContinuedFraction implement convert a floating-point decimal +// to a fraction using continued fractions and recurrence relations. +func floatToFracUseContinuedFraction(x float64, denominatorLimit int64) (num, den int64) { + p_1 := int64(1) + q_1 := int64(0) + p_2 := int64(0) + q_2 := int64(1) + var lasta, lastb int64 + var curra, currb int64 + for i := 0; i < 100; i++ { + a := int64(math.Floor(x)) + 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 := x - float64(a) + if q_1 >= denominatorLimit { + return lasta, lastb //big.NewRat(lasta, lastb) + } + if math.Abs(frac) < 1e-12 { + return curra, currb //big.NewRat(curra, currb) + } + + lasta, lastb = curra, currb + x = 1.0 / frac + } + return lasta, lastb //big.NewRat(lasta, lastb) +} + // assignFieldValue assigns the value from an immutable reflect.Value to a // mutable reflect.Value based on the type of the immutable value. func assignFieldValue(field string, immutable, mutable reflect.Value) { diff --git a/numfmt.go b/numfmt.go index d65942924a..905db1c64d 100644 --- a/numfmt.go +++ b/numfmt.go @@ -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 { diff --git a/numfmt_test.go b/numfmt_test.go index 065fc58df6..0a3cd8a53b 100644 --- a/numfmt_test.go +++ b/numfmt_test.go @@ -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) } } } From 232d35665084cb1696c8d554958beafd60bf6731 Mon Sep 17 00:00:00 2001 From: shcabin Date: Thu, 9 Oct 2025 22:27:59 +0800 Subject: [PATCH 2/2] convert using continued fractions fundamental recurrence formulas --- lib_test.go | 37 +++++++++++++++++++++++++++++++++++++ numfmt.go | 5 +++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib_test.go b/lib_test.go index fe5eed0530..b16c083b20 100644 --- a/lib_test.go +++ b/lib_test.go @@ -6,6 +6,7 @@ import ( "encoding/xml" "fmt" "io" + "math" "os" "strconv" "strings" @@ -412,3 +413,39 @@ func TestUnzipToTemp(t *testing.T) { _, err = f.unzipToTemp(z.File[0]) assert.EqualError(t, err, "EOF") } + +func oldFractionHandler(frac float64, fracPlaceHolder int) string { + var rat string + for i := 0; i < 5000; i++ { + if r := newRat(frac, int64(i), 0); len(r.Denom().String()) <= fracPlaceHolder { + if rat = r.String(); strings.HasPrefix(rat, "0/") { + rat = strings.Repeat(" ", 3) + } + continue + } + break + } + return rat +} + +func TestFloat2Frac(t *testing.T) { + valueList := []float64{0.19, 0.54, 0.9, 0.99, 0.999, 0.999} + for _, v := range valueList { + for idx := 0; idx <= 10; idx++ { + res1 := strings.Trim(oldFractionHandler(v, idx), " ") + res2 := strings.Trim(floatToFraction(v, idx, idx), " ") + assert.Equal(t, res1, res2, "value %f, fracPlaceHolder %d", v, idx) + } + } + for k := range 10 { + val := math.Pi / math.Pow(10, float64(k)) + for idx := 1; idx <= 5; idx++ { + res1 := strings.Trim(oldFractionHandler(val, idx), " ") + res2 := strings.Trim(floatToFraction(val, idx, idx), " ") + assert.Equal(t, res1, res2, "value %f, fracPlaceHolder %d", val, idx) + if res1 != "" { + t.Log(idx, val, res1, res2) + } + } + } +} diff --git a/numfmt.go b/numfmt.go index 905db1c64d..c72bbabcc6 100644 --- a/numfmt.go +++ b/numfmt.go @@ -5520,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)) } }