diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5f674c3aeb..b9841144cb 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -31,7 +31,7 @@ jobs: run: env GO111MODULE=on go test -v -timeout 30m -race ./... -coverprofile=coverage.txt -covermode=atomic - name: Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: file: coverage.txt flags: unittests diff --git a/calc.go b/calc.go index 1c6edca055..a4ecb66749 100644 --- a/calc.go +++ b/calc.go @@ -55,13 +55,6 @@ const ( criteriaG criteriaErr criteriaRegexp - // Numeric precision correct numeric values as legacy Excel application - // https://en.wikipedia.org/wiki/Numeric_precision_in_Microsoft_Excel In the - // top figure the fraction 1/9000 in Excel is displayed. Although this number - // has a decimal representation that is an infinite string of ones, Excel - // displays only the leading 15 figures. In the second line, the number one - // is added to the fraction, and again Excel displays only 15 figures. - numericPrecision = 1000000000000000 maxFinancialIterations = 128 financialPercision = 1.0e-08 // Date and time format regular expressions @@ -511,6 +504,7 @@ type formulaFuncs struct { // WEIBULL.DIST // XOR // YEAR +// YEARFRAC // Z.TEST // ZTEST // @@ -533,7 +527,7 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) { result = token.TValue isNum, precision := isNumeric(result) if isNum && precision > 15 { - num, _ := roundPrecision(result) + num := roundPrecision(result, -1) result = strings.ToUpper(num) } return @@ -6689,6 +6683,157 @@ func (fn *formulaFuncs) YEAR(argsList *list.List) formulaArg { return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Year())) } +// yearFracBasisCond is an implementation of the yearFracBasis1. +func yearFracBasisCond(sy, sm, sd, ey, em, ed int) bool { + return (isLeapYear(sy) && (sm < 2 || (sm == 2 && sd <= 29))) || (isLeapYear(ey) && (em > 2 || (em == 2 && ed == 29))) +} + +// yearFracBasis0 function returns the fraction of a year that between two +// supplied dates in US (NASD) 30/360 type of day. +func yearFracBasis0(startDate, endDate float64) (dayDiff, daysInYear float64) { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + sy, smM, sd := startTime.Date() + ey, emM, ed := endTime.Date() + sm, em := int(smM), int(emM) + if sd == 31 { + sd-- + } + if sd == 30 && ed == 31 { + ed-- + } else if leap := isLeapYear(sy); sm == 2 && ((leap && sd == 29) || (!leap && sd == 28)) { + sd = 30 + if leap := isLeapYear(ey); em == 2 && ((leap && ed == 29) || (!leap && ed == 28)) { + ed = 30 + } + } + dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd)) + daysInYear = 360 + return +} + +// yearFracBasis1 function returns the fraction of a year that between two +// supplied dates in actual type of day. +func yearFracBasis1(startDate, endDate float64) (dayDiff, daysInYear float64) { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + sy, smM, sd := startTime.Date() + ey, emM, ed := endTime.Date() + sm, em := int(smM), int(emM) + dayDiff = endDate - startDate + isYearDifferent := sy != ey + if isYearDifferent && (ey != sy+1 || sm < em || (sm == em && sd < ed)) { + dayCount := 0 + for y := sy; y <= ey; y++ { + dayCount += getYearDays(y, 1) + } + daysInYear = float64(dayCount) / float64(ey-sy+1) + } else { + if !isYearDifferent && isLeapYear(sy) { + daysInYear = 366 + } else { + if isYearDifferent && yearFracBasisCond(sy, sm, sd, ey, em, ed) { + daysInYear = 366 + } else { + daysInYear = 365 + } + } + } + return +} + +// yearFracBasis4 function returns the fraction of a year that between two +// supplied dates in European 30/360 type of day. +func yearFracBasis4(startDate, endDate float64) (dayDiff, daysInYear float64) { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + sy, smM, sd := startTime.Date() + ey, emM, ed := endTime.Date() + sm, em := int(smM), int(emM) + if sd == 31 { + sd-- + } + if ed == 31 { + ed-- + } + dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd)) + daysInYear = 360 + return +} + +// yearFrac is an implementation of the formula function YEARFRAC. +func yearFrac(startDate, endDate float64, basis int) formulaArg { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + if startTime == endTime { + return newNumberFormulaArg(0) + } + var dayDiff, daysInYear float64 + switch basis { + case 0: + dayDiff, daysInYear = yearFracBasis0(startDate, endDate) + case 1: + dayDiff, daysInYear = yearFracBasis1(startDate, endDate) + case 2: + dayDiff = endDate - startDate + daysInYear = 360 + case 3: + dayDiff = endDate - startDate + daysInYear = 365 + case 4: + dayDiff, daysInYear = yearFracBasis4(startDate, endDate) + default: + return newErrorFormulaArg(formulaErrorNUM, "invalid basis") + } + return newNumberFormulaArg(dayDiff / daysInYear) +} + +// getYearDays return days of the year with specifying the type of day count +// basis to be used. +func getYearDays(year, basis int) int { + switch basis { + case 1: + if isLeapYear(year) { + return 366 + } + return 365 + case 3: + return 365 + default: + return 360 + } +} + +// YEARFRAC function returns the fraction of a year that is represented by the +// number of whole days between two supplied dates. The syntax of the +// function is: +// +// YEARFRAC(start_date,end_date,[basis]) +// +func (fn *formulaFuncs) YEARFRAC(argsList *list.List) formulaArg { + if argsList.Len() != 2 && argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "YEARFRAC requires 3 or 4 arguments") + } + var basisArg formulaArg + startArg, endArg := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber() + args := list.New().Init() + if startArg.Type != ArgNumber { + args.PushBack(argsList.Front().Value.(formulaArg)) + if startArg = fn.DATEVALUE(args); startArg.Type != ArgNumber { + return startArg + } + } + if endArg.Type != ArgNumber { + args.Init() + args.PushBack(argsList.Front().Next().Value.(formulaArg)) + if endArg = fn.DATEVALUE(args); endArg.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + if argsList.Len() == 3 { + if basisArg = argsList.Back().Value.(formulaArg).ToNumber(); basisArg.Type != ArgNumber { + return basisArg + } + } + return yearFrac(startArg.Number, endArg.Number, int(basisArg.Number)) +} + // NOW function returns the current date and time. The function receives no // arguments and therefore. The syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index a391a19551..de34723ef4 100644 --- a/calc_test.go +++ b/calc_test.go @@ -57,12 +57,12 @@ func TestCalcCellValue(t *testing.T) { // BESSELJ "=BESSELJ(1.9,2)": "0.329925727692387", // BESSELK - "=BESSELK(0.05,0)": "3.114234034289662", + "=BESSELK(0.05,0)": "3.11423403428966", "=BESSELK(0.05,1)": "19.90967432724863", "=BESSELK(0.05,2)": "799.501207124235", - "=BESSELK(3,2)": "0.061510458561912", + "=BESSELK(3,2)": "0.0615104585619118", // BESSELY - "=BESSELY(0.05,0)": "-1.979311006841528", + "=BESSELY(0.05,0)": "-1.97931100684153", "=BESSELY(0.05,1)": "-12.789855163794034", "=BESSELY(0.05,2)": "-509.61489554491976", "=BESSELY(9,2)": "-0.229082087487741", @@ -169,7 +169,7 @@ func TestCalcCellValue(t *testing.T) { "=IMCOS(0.5)": "0.877582561890373", "=IMCOS(\"3+0.5i\")": "-1.1163412445261518-0.0735369737112366i", // IMCOSH - "=IMCOSH(0.5)": "1.127625965206381", + "=IMCOSH(0.5)": "1.12762596520638", "=IMCOSH(\"3+0.5i\")": "8.835204606500994+4.802825082743033i", "=IMCOSH(\"2-i\")": "2.0327230070196656-3.0518977991518i", "=IMCOSH(COMPLEX(1,-1))": "0.8337300251311491-0.9888977057628651i", @@ -188,7 +188,7 @@ func TestCalcCellValue(t *testing.T) { "=IMDIV(COMPLEX(5,2),COMPLEX(0,1))": "2-5i", // IMEXP "=IMEXP(0)": "1", - "=IMEXP(0.5)": "1.648721270700128", + "=IMEXP(0.5)": "1.64872127070013", "=IMEXP(\"1-2i\")": "-1.1312043837568135-2.4717266720048183i", "=IMEXP(COMPLEX(1,-1))": "1.4686939399158851-2.2873552871788423i", // IMLN @@ -243,7 +243,7 @@ func TestCalcCellValue(t *testing.T) { "=IMSUM(COMPLEX(5,2),COMPLEX(0,1))": "5+3i", // IMTAN "=IMTAN(-0)": "0", - "=IMTAN(0.5)": "0.546302489843791", + "=IMTAN(0.5)": "0.54630248984379", "=IMTAN(\"3+0.5i\")": "-0.11162105077158344+0.46946999342588536i", "=IMTAN(\"2-i\")": "-0.24345820118572523-1.16673625724092i", "=IMTAN(COMPLEX(1,-1))": "0.2717525853195117-1.0839233273386948i", @@ -275,21 +275,21 @@ func TestCalcCellValue(t *testing.T) { "=ABS(ABS(-1))": "1", // ACOS "=ACOS(-1)": "3.141592653589793", - "=ACOS(0)": "1.570796326794897", - "=ACOS(ABS(0))": "1.570796326794897", + "=ACOS(0)": "1.5707963267949", + "=ACOS(ABS(0))": "1.5707963267949", // ACOSH "=ACOSH(1)": "0", "=ACOSH(2.5)": "1.566799236972411", - "=ACOSH(5)": "2.292431669561178", - "=ACOSH(ACOSH(5))": "1.471383321536679", + "=ACOSH(5)": "2.29243166956118", + "=ACOSH(ACOSH(5))": "1.47138332153668", // ACOT "=_xlfn.ACOT(1)": "0.785398163397448", "=_xlfn.ACOT(-2)": "2.677945044588987", - "=_xlfn.ACOT(0)": "1.570796326794897", + "=_xlfn.ACOT(0)": "1.5707963267949", "=_xlfn.ACOT(_xlfn.ACOT(0))": "0.566911504941009", // ACOTH "=_xlfn.ACOTH(-5)": "-0.202732554054082", - "=_xlfn.ACOTH(1.1)": "1.522261218861711", + "=_xlfn.ACOTH(1.1)": "1.52226121886171", "=_xlfn.ACOTH(2)": "0.549306144334055", "=_xlfn.ACOTH(ABS(-2))": "0.549306144334055", // ARABIC @@ -299,12 +299,12 @@ func TestCalcCellValue(t *testing.T) { "=_xlfn.ARABIC(\"\")": "0", "=_xlfn.ARABIC(\" ll lc \")": "-50", // ASIN - "=ASIN(-1)": "-1.570796326794897", + "=ASIN(-1)": "-1.5707963267949", "=ASIN(0)": "0", "=ASIN(ASIN(0))": "0", // ASINH "=ASINH(0)": "0", - "=ASINH(-0.5)": "-0.481211825059604", + "=ASINH(-0.5)": "-0.481211825059603", "=ASINH(2)": "1.44363547517881", "=ASINH(ASINH(0))": "0", // ATAN @@ -383,22 +383,22 @@ func TestCalcCellValue(t *testing.T) { "=COS(COS(0))": "0.54030230586814", // COSH "=COSH(0)": "1", - "=COSH(0.5)": "1.127625965206381", - "=COSH(-2)": "3.762195691083632", - "=COSH(COSH(0))": "1.543080634815244", + "=COSH(0.5)": "1.12762596520638", + "=COSH(-2)": "3.76219569108363", + "=COSH(COSH(0))": "1.54308063481524", // _xlfn.COT - "=_xlfn.COT(0.785398163397448)": "1.000000000000001", + "=_xlfn.COT(0.785398163397448)": "1", "=_xlfn.COT(_xlfn.COT(0.45))": "-0.545473116787229", // _xlfn.COTH - "=_xlfn.COTH(-3.14159265358979)": "-1.003741873197322", - "=_xlfn.COTH(_xlfn.COTH(1))": "1.156014018113954", + "=_xlfn.COTH(-3.14159265358979)": "-1.00374187319732", + "=_xlfn.COTH(_xlfn.COTH(1))": "1.15601401811395", // _xlfn.CSC - "=_xlfn.CSC(-6)": "3.578899547254406", + "=_xlfn.CSC(-6)": "3.57889954725441", "=_xlfn.CSC(1.5707963267949)": "1", - "=_xlfn.CSC(_xlfn.CSC(1))": "1.077851840310882", + "=_xlfn.CSC(_xlfn.CSC(1))": "1.07785184031088", // _xlfn.CSCH - "=_xlfn.CSCH(-3.14159265358979)": "-0.086589537530047", - "=_xlfn.CSCH(_xlfn.CSCH(1))": "1.044510103955183", + "=_xlfn.CSCH(-3.14159265358979)": "-0.0865895375300472", + "=_xlfn.CSCH(_xlfn.CSCH(1))": "1.04451010395518", // _xlfn.DECIMAL `=_xlfn.DECIMAL("1100",2)`: "12", `=_xlfn.DECIMAL("186A0",16)`: "100000", @@ -419,9 +419,9 @@ func TestCalcCellValue(t *testing.T) { "=EVEN((0))": "0", // EXP "=EXP(100)": "2.6881171418161356E+43", - "=EXP(0.1)": "1.105170918075648", + "=EXP(0.1)": "1.10517091807565", "=EXP(0)": "1", - "=EXP(-5)": "0.006737946999085", + "=EXP(-5)": "0.00673794699908547", "=EXP(EXP(0))": "2.718281828459045", // FACT "=FACT(3)": "6", @@ -502,18 +502,18 @@ func TestCalcCellValue(t *testing.T) { "=LN(1)": "0", "=LN(100)": "4.605170185988092", "=LN(0.5)": "-0.693147180559945", - "=LN(LN(100))": "1.527179625807901", + "=LN(LN(100))": "1.5271796258079", // LOG "=LOG(64,2)": "6", "=LOG(100)": "2", "=LOG(4,0.5)": "-2", - "=LOG(500)": "2.698970004336019", + "=LOG(500)": "2.69897000433602", "=LOG(LOG(100))": "0.301029995663981", // LOG10 "=LOG10(100)": "2", "=LOG10(1000)": "3", "=LOG10(0.001)": "-3", - "=LOG10(25)": "1.397940008672038", + "=LOG10(25)": "1.39794000867204", "=LOG10(LOG10(100))": "0.301029995663981", // IMLOG2 "=IMLOG2(\"5+2i\")": "2.4289904975637864+0.5489546632866347i", @@ -626,9 +626,9 @@ func TestCalcCellValue(t *testing.T) { "=_xlfn.SEC(0)": "1", "=_xlfn.SEC(_xlfn.SEC(0))": "0.54030230586814", // SECH - "=_xlfn.SECH(-3.14159265358979)": "0.086266738334055", + "=_xlfn.SECH(-3.14159265358979)": "0.0862667383340547", "=_xlfn.SECH(0)": "1", - "=_xlfn.SECH(_xlfn.SECH(0))": "0.648054273663886", + "=_xlfn.SECH(_xlfn.SECH(0))": "0.648054273663885", // SIGN "=SIGN(9.5)": "1", "=SIGN(-9.5)": "-1", @@ -665,10 +665,10 @@ func TestCalcCellValue(t *testing.T) { "=STDEVA(MUNIT(2))": "0.577350269189626", "=STDEVA(0,INT(0))": "0", // POISSON.DIST - "=POISSON.DIST(20,25,FALSE)": "0.051917468608491", + "=POISSON.DIST(20,25,FALSE)": "0.0519174686084913", "=POISSON.DIST(35,40,TRUE)": "0.242414197690103", // POISSON - "=POISSON(20,25,FALSE)": "0.051917468608491", + "=POISSON(20,25,FALSE)": "0.0519174686084913", "=POISSON(35,40,TRUE)": "0.242414197690103", // SUM "=SUM(1,2)": "3", @@ -760,15 +760,15 @@ func TestCalcCellValue(t *testing.T) { "=GAMMA(1.5)": "0.886226925452758", "=GAMMA(5.5)": "52.34277778455352", // GAMMALN - "=GAMMALN(4.5)": "2.453736570842443", + "=GAMMALN(4.5)": "2.45373657084244", "=GAMMALN(INT(1))": "0", // HARMEAN - "=HARMEAN(2.5,3,0.5,1,3)": "1.229508196721312", - "=HARMEAN(\"2.5\",3,0.5,1,INT(3),\"\")": "1.229508196721312", + "=HARMEAN(2.5,3,0.5,1,3)": "1.22950819672131", + "=HARMEAN(\"2.5\",3,0.5,1,INT(3),\"\")": "1.22950819672131", // KURT - "=KURT(F1:F9)": "-1.033503502551368", - "=KURT(F1,F2:F9)": "-1.033503502551368", - "=KURT(INT(1),MUNIT(2))": "-3.333333333333336", + "=KURT(F1:F9)": "-1.03350350255137", + "=KURT(F1,F2:F9)": "-1.03350350255137", + "=KURT(INT(1),MUNIT(2))": "-3.33333333333334", // NORM.DIST "=NORM.DIST(0.8,1,0.3,TRUE)": "0.252492537546923", "=NORM.DIST(50,40,20,FALSE)": "0.017603266338215", @@ -993,6 +993,29 @@ func TestCalcCellValue(t *testing.T) { "=YEAR(42171)": "2015", "=YEAR(\"29-May-2015\")": "2015", "=YEAR(\"05/03/1984\")": "1984", + // YEARFRAC + "=YEARFRAC(42005,42005)": "0", + "=YEARFRAC(42005,42094)": "0.25", + "=YEARFRAC(42005,42094,0)": "0.25", + "=YEARFRAC(42005,42094,1)": "0.243835616438356", + "=YEARFRAC(42005,42094,2)": "0.247222222222222", + "=YEARFRAC(42005,42094,3)": "0.243835616438356", + "=YEARFRAC(42005,42094,4)": "0.247222222222222", + "=YEARFRAC(\"01/01/2015\",\"03/31/2015\")": "0.25", + "=YEARFRAC(\"01/01/2015\",\"03/31/2015\",0)": "0.25", + "=YEARFRAC(\"01/01/2015\",\"03/31/2015\",1)": "0.243835616438356", + "=YEARFRAC(\"01/01/2015\",\"03/31/2015\",2)": "0.247222222222222", + "=YEARFRAC(\"01/01/2015\",\"03/31/2015\",3)": "0.243835616438356", + "=YEARFRAC(\"01/01/2015\",\"03/31/2015\",4)": "0.247222222222222", + "=YEARFRAC(\"01/01/2015\",42094)": "0.25", + "=YEARFRAC(42005,\"03/31/2015\",0)": "0.25", + "=YEARFRAC(\"01/31/2015\",\"03/31/2015\")": "0.166666666666667", + "=YEARFRAC(\"01/30/2015\",\"03/31/2015\")": "0.166666666666667", + "=YEARFRAC(\"02/29/2000\", \"02/29/2008\")": "8", + "=YEARFRAC(\"02/29/2000\", \"02/29/2008\",1)": "7.998175182481752", + "=YEARFRAC(\"02/29/2000\", \"01/29/2001\",1)": "0.915300546448087", + "=YEARFRAC(\"02/29/2000\", \"03/29/2000\",1)": "0.0792349726775956", + "=YEARFRAC(\"01/31/2000\", \"03/29/2000\",4)": "0.163888888888889", // Text Functions // CHAR "=CHAR(65)": "A", @@ -1246,7 +1269,7 @@ func TestCalcCellValue(t *testing.T) { "=ISPMT(0.05/12,2,60,50000)": "-201.38888888888886", "=ISPMT(0.05/12,2,1,50000)": "208.33333333333334", // NOMINAL - "=NOMINAL(0.025,12)": "0.024718035238113", + "=NOMINAL(0.025,12)": "0.0247180352381129", // NPER "=NPER(0.04,-6000,50000)": "10.338035071507665", "=NPER(0,-6000,50000)": "8.333333333333334", @@ -2062,6 +2085,12 @@ func TestCalcCellValue(t *testing.T) { "=YEAR(-1)": "YEAR only accepts positive argument", "=YEAR(\"text\")": "#VALUE!", "=YEAR(\"January 25, 100\")": "#VALUE!", + // YEARFRAC + "=YEARFRAC()": "YEARFRAC requires 3 or 4 arguments", + "=YEARFRAC(42005,42094,5)": "invalid basis", + "=YEARFRAC(\"\",42094,5)": "#VALUE!", + "=YEARFRAC(42005,\"\",5)": "#VALUE!", + "=YEARFRAC(42005,42094,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", // NOW "=NOW(A1)": "NOW accepts no arguments", // TODAY @@ -2412,8 +2441,8 @@ func TestCalcCellValue(t *testing.T) { "=(-2-SUM(-4+A2))*5": "0", "=SUM(Sheet1!A1:Sheet1!A1:A2,A2)": "5", "=SUM(A1,A2,A3)*SUM(2,3)": "30", - "=1+SUM(SUM(A1+A2/A3)*(2-3),2)": "1.333333333333334", - "=A1/A2/SUM(A1:A2:B1)": "0.041666666666667", + "=1+SUM(SUM(A1+A2/A3)*(2-3),2)": "1.33333333333333", + "=A1/A2/SUM(A1:A2:B1)": "0.0416666666666667", "=A1/A2/SUM(A1:A2:B1)*A3": "0.125", "=SUM(B1:D1)": "4", "=SUM(\"X\")": "0", @@ -2760,7 +2789,7 @@ func TestCalcMIRR(t *testing.T) { cellData := [][]interface{}{{-100}, {18}, {22.5}, {28}, {35.5}, {45}} f := prepareCalcData(cellData) formulaList := map[string]string{ - "=MIRR(A1:A5,0.055,0.05)": "0.025376365108071", + "=MIRR(A1:A5,0.055,0.05)": "0.0253763651080707", "=MIRR(A1:A6,0.055,0.05)": "0.1000268752662", } for formula, expected := range formulaList { @@ -2848,3 +2877,9 @@ func TestStrToDate(t *testing.T) { _, _, _, _, err := strToDate("") assert.Equal(t, formulaErrorVALUE, err.Error) } + +func TestGetYearDays(t *testing.T) { + for _, data := range [][]int{{2021, 0, 360}, {2000, 1, 366}, {2021, 1, 365}, {2000, 3, 365}} { + assert.Equal(t, data[2], getYearDays(data[0], data[1])) + } +} diff --git a/cell.go b/cell.go index b95db9c3fb..41a9517886 100644 --- a/cell.go +++ b/cell.go @@ -70,15 +70,6 @@ func (f *File) GetCellValue(sheet, axis string, opts ...Options) (string, error) // GetCellType provides a function to get the cell's data type by given // worksheet name and axis in spreadsheet file. func (f *File) GetCellType(sheet, axis string) (CellType, error) { - cellTypes := map[string]CellType{ - "b": CellTypeBool, - "d": CellTypeDate, - "n": CellTypeNumber, - "e": CellTypeError, - "s": CellTypeString, - "str": CellTypeString, - "inlineStr": CellTypeString, - } var ( err error cellTypeStr string @@ -1046,9 +1037,16 @@ func (f *File) formattedValue(s int, v string, raw bool) string { precise := v isNum, precision := isNumeric(v) if isNum && precision > 10 { - precise, _ = roundPrecision(v) + precise = roundPrecision(v, -1) + } + if raw { + return v + } + if !isNum { + v = roundPrecision(v, 15) + precise = v } - if s == 0 || raw { + if s == 0 { return precise } styleSheet := f.stylesReader() @@ -1063,7 +1061,7 @@ func (f *File) formattedValue(s int, v string, raw bool) string { ok := builtInNumFmtFunc[numFmtID] if ok != nil { - return ok(v, builtInNumFmt[numFmtID]) + return ok(precise, builtInNumFmt[numFmtID]) } if styleSheet == nil || styleSheet.NumFmts == nil { return precise diff --git a/cell_test.go b/cell_test.go index 126e5614f3..0395ef4c34 100644 --- a/cell_test.go +++ b/cell_test.go @@ -245,10 +245,66 @@ func TestGetCellValue(t *testing.T) { assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") - f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `2422.30000000000022422.300000000000212.49641101.5999999999999275.3999999999999868.90000000000000644385.2083333333365.09999999999999965.11000000000000035.09999999999999965.11099999999999985.11110000000000042422.0123456782422.012345678912.0123456789019641101.5999999999999275.3999999999999868.900000000000006`))) + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, ` + 2422.3000000000002 + 2422.3000000000002 + 12.4 + 964 + 1101.5999999999999 + 275.39999999999998 + 68.900000000000006 + 44385.208333333336 + 5.0999999999999996 + 5.1100000000000003 + 5.0999999999999996 + 5.1109999999999998 + 5.1111000000000004 + 2422.012345678 + 2422.0123456789 + 12.012345678901 + 964 + 1101.5999999999999 + 275.39999999999998 + 68.900000000000006 + 8.8880000000000001E-2 + 4.0000000000000003E-5 + 2422.3000000000002 + 1101.5999999999999 + 275.39999999999998 + 68.900000000000006 + 1.1000000000000001 +`))) f.checked = nil rows, err = f.GetRows("Sheet1") - assert.Equal(t, [][]string{{"2422.3", "2422.3", "12.4", "964", "1101.6", "275.4", "68.9", "44385.20833333333", "5.1", "5.11", "5.1", "5.111", "5.1111", "2422.012345678", "2422.0123456789", "12.012345678901", "964", "1101.6", "275.4", "68.9"}}, rows) + assert.Equal(t, [][]string{{ + "2422.3", + "2422.3", + "12.4", + "964", + "1101.6", + "275.4", + "68.9", + "44385.2083333333", + "5.1", + "5.11", + "5.1", + "5.111", + "5.1111", + "2422.012345678", + "2422.0123456789", + "12.012345678901", + "964", + "1101.6", + "275.4", + "68.9", + "0.08888", + "0.00004", + "2422.3", + "1101.6", + "275.4", + "68.9", + "1.1", + }}, rows) assert.NoError(t, err) } diff --git a/rows.go b/rows.go index 3d8c247f3c..3171ab1491 100644 --- a/rows.go +++ b/rows.go @@ -18,6 +18,7 @@ import ( "io" "log" "math" + "math/big" "os" "strconv" @@ -421,14 +422,19 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { } } -// roundPrecision round precision for numeric. -func roundPrecision(value string) (result string, err error) { - var num float64 - if num, err = strconv.ParseFloat(value, 64); err != nil { - return +// roundPrecision provides a function to format floating-point number text +// with precision, if the given text couldn't be parsed to float, this will +// return the original string. +func roundPrecision(text string, prec int) string { + decimal := big.Float{} + if _, ok := decimal.SetString(text); ok { + flt, _ := decimal.Float64() + if prec == -1 { + return decimal.Text('G', 15) + } + return strconv.FormatFloat(flt, 'f', -1, 64) } - result = fmt.Sprintf("%g", math.Round(num*numericPrecision)/numericPrecision) - return + return text } // SetRowVisible provides a function to set visible of a single row by given diff --git a/rows_test.go b/rows_test.go index 0ebe59d47e..19ed866b60 100644 --- a/rows_test.go +++ b/rows_test.go @@ -884,11 +884,6 @@ func TestGetValueFromNumber(t *testing.T) { } } -func TestRoundPrecision(t *testing.T) { - _, err := roundPrecision("") - assert.EqualError(t, err, "strconv.ParseFloat: parsing \"\": invalid syntax") -} - func TestErrSheetNotExistError(t *testing.T) { err := ErrSheetNotExist{SheetName: "Sheet1"} assert.EqualValues(t, err.Error(), "sheet Sheet1 is not exist")