Skip to content
Open
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
52 changes: 52 additions & 0 deletions lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,58 @@ 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.
// Formula reference: https://en.wikipedia.org/wiki/Continued_fraction#Fundamental_recurrence_formulas
// or https://oi-wiki.org/math/number-theory/continued-fraction/#%E9%80%92%E6%8E%A8%E5%85%B3%E7%B3%BB
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
// mutable reflect.Value based on the type of the immutable value.
func assignFieldValue(field string, immutable, mutable reflect.Value) {
Expand Down
38 changes: 38 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,40 @@ 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.9999}
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)
}
}
}
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