Artifact 71f972a2363740e7bc1ff7a7fa0187b82d0530ae9d55507812b8b3e2efd56ce4:
- File
internal/fsltype/sqliteatof.go
— part of check-in
[342474c212]
at
2026-10-18 22:01:38
on branch feat/submanifest-reader
— fix(fsltype): read text Julian days back as fossil's SQLite does
fossil writes event, plink, attachment and forumpost times as 16-digit SQL text, and its bundled SQLite 3.52.0 reads that text with sqlite3AtoF, which multiplies by a 64-bit power-of-ten approximation and is not correctly rounded. TextJulianDayFromMillis parsed the text with strconv.ParseFloat, so about one time in 3,000 came out one unit in the last place away from fossil's: 13 rows of fossil's own repository. sqliteAtoF ports SQLite 3.52.0's conversion.
julianday() itself already matched; a sweep of 21,000 card times checks JulianDayFromMillis against the linked SQLite's julianday(), and a fossil-binary sweep rebuilds 4,000 control artifacts and requires every event time fossil stores. (user: [email protected], size: 4168) [annotate] [blame] [check-ins using] [more...]
package fsltype import ( "math" "math/bits" ) // sqliteAtoF reads a decimal Julian day the way SQLite 3.52.0, the SQLite // fossil 2.28 bundles, reads a real literal in SQL text: sqlite3AtoF and // sqlite3Fp10Convert2 in its src/util.c. That conversion is not correctly // rounded -- it multiplies the decimal significand by a 64-bit // approximation of the power of ten -- so for some 16-digit Julian days it // returns a double one unit in the last place away from the one // strconv.ParseFloat returns, and fossil stores SQLite's. // // s is the text fossilPrintf17g writes: digits, a point, digits. func sqliteAtoF(s string) float64 { const largestInt64 = math.MaxInt64 var sig uint64 // significand exp := 0 // value is sig * 10^exp i := 0 for ; i < len(s) && isDigit(s[i]); i++ { sig = sig*10 + uint64(s[i]-'0') if sig >= (largestInt64-9)/10 { for i+1 < len(s) && isDigit(s[i+1]) { i++ exp++ } } } if i < len(s) && s[i] == '.' { for i++; i < len(s) && isDigit(s[i]); i++ { if sig < (largestInt64-9)/10 { sig = sig*10 + uint64(s[i]-'0') exp-- } } } if i != len(s) || sig == 0 { panic("fsltype.sqliteAtoF: not a positive decimal: " + s) } return sqliteFp10Convert2(sig, exp) } func isDigit(c byte) bool { return c >= '0' && c <= '9' } // sqliteFp10Convert2 is SQLite 3.52.0's sqlite3Fp10Convert2: the double // nearest, by its arithmetic, to d * 10^p. func sqliteFp10Convert2(d uint64, p int) float64 { if d&(1<<63) != 0 || d == 0 { panic("fsltype.sqliteFp10Convert2: significand out of range") } if p < powersOf10First { return 0 } if p > powersOf10Last { return math.Inf(1) } lz := bits.LeadingZeros64(d) lp := pwr10to2(p) e1 := lz - (lp + 11) if e1 > 1074 { if e1 >= 1130 { return 0 } e1 = 1074 } h, _ := bits.Mul64(d<<lz, powerOfTen(p)) x := lz - (e1 + lp + 3) if x < 0 || x > 63 { panic("fsltype.sqliteFp10Convert2: shift out of range") } out := h >> x if out >= 1<<55-2 { out >>= 1 e1-- } if e1 <= -972 { return math.Inf(1) } out = (out + 2) >> 2 if out&(1<<52) != 0 { out = out&^(1<<52) | uint64(1075-e1)<<52 } return math.Float64frombits(out) } const ( powersOf10First = -348 powersOf10Last = 347 ) // pwr10to2 is SQLite's floor(log2(10^p)) approximation. func pwr10to2(p int) int { return (p * 108853) >> 15 } // powerOfTen is SQLite 3.52.0's powerOfTen: the most significant 64 bits // of 10^p, for p in [-348, 347]. func powerOfTen(p int) uint64 { if p < powersOf10First || p > powersOf10Last { panic("fsltype.powerOfTen: p out of range") } var g, n int switch { case p < 0: g, n = p/27, p%27 if n != 0 { g-- n += 27 } case p < 27: return pow10Base[p] default: g, n = p/27, p%27 } y := pow10Scale[g+13] if n == 0 { return y } x, _ := bits.Mul64(pow10Base[n], y) if x&(1<<63) == 0 { x = x<<1 | 1 } return x } // pow10Base holds 10^0 through 10^26, each shifted to fill 64 bits. var pow10Base = [27]uint64{ 0x8000000000000000, 0xa000000000000000, 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, 0xa56fa5b99019a5c8, } // pow10Scale holds 10^(27*(i-13)) for i in 0..25, each shifted to fill 64 // bits. var pow10Scale = [26]uint64{ 0x8049a4ac0c5811ae, 0xcf42894a5dce35ea, 0xa76c582338ed2622, 0x873e4f75e2224e68, 0xda7f5bf590966849, 0xb080392cc4349ded, 0x8e938662882af53e, 0xe65829b3046b0afa, 0xba121a4650e4ddec, 0x964e858c91ba2655, 0xf2d56790ab41c2a3, 0xc428d05aa4751e4d, 0x9e74d1b791e07e48, 0x8000000000000000, 0xcecb8f27f4200f3a, 0xa70c3c40a64e6c52, 0x86f0ac99b4e8dafd, 0xda01ee641a708dea, 0xb01ae745b101e9e4, 0x8e41ade9fbebc27d, 0xe5d3ef282a242e82, 0xb9a74a0637ce2ee1, 0x95f83d0a1fb69cd9, 0xf24a01a73cf2dcd0, 0xc3b8358109e84f07, 0x9e19db92b4e31ba9, }