|
12 | 12 | import java.io.StringReader; |
13 | 13 | import java.io.Writer; |
14 | 14 | import java.math.BigDecimal; |
15 | | -import java.math.BigInteger; |
16 | 15 | import java.net.URL; |
17 | 16 | import java.nio.charset.StandardCharsets; |
18 | 17 | import java.sql.Array; |
@@ -178,271 +177,12 @@ public String getString(int columnIndex) throws SQLException { |
178 | 177 | return switch (val) { |
179 | 178 | case Value.Null n -> null; |
180 | 179 | case Value.Integer i -> Long.toString(i.value()); |
181 | | - case Value.Float f -> sqliteRealText(f.value()); |
| 180 | + case Value.Float f -> SqliteRealText.format(f.value()); |
182 | 181 | case Value.Text t -> t.value(); |
183 | 182 | case Value.Blob b -> new String(b.bytes(), java.nio.charset.StandardCharsets.UTF_8); |
184 | 183 | }; |
185 | 184 | } |
186 | 185 |
|
187 | | - // Port of the fixed-point conversion SQLite 3.53.4 uses for printf("%!.17g"), which is the |
188 | | - // text sqlite-jdbc 3.53.4.0 returns for REAL. The power-of-ten tables come from the same |
189 | | - // source (util.c and tool/mkfptab.c). |
190 | | - private static final long[] FP_POWERS_OF_TEN = { |
191 | | - 0x8000000000000000L, 0xa000000000000000L, 0xc800000000000000L, 0xfa00000000000000L, |
192 | | - 0x9c40000000000000L, 0xc350000000000000L, 0xf424000000000000L, 0x9896800000000000L, |
193 | | - 0xbebc200000000000L, 0xee6b280000000000L, 0x9502f90000000000L, 0xba43b74000000000L, |
194 | | - 0xe8d4a51000000000L, 0x9184e72a00000000L, 0xb5e620f480000000L, 0xe35fa931a0000000L, |
195 | | - 0x8e1bc9bf04000000L, 0xb1a2bc2ec5000000L, 0xde0b6b3a76400000L, 0x8ac7230489e80000L, |
196 | | - 0xad78ebc5ac620000L, 0xd8d726b7177a8000L, 0x878678326eac9000L, 0xa968163f0a57b400L, |
197 | | - 0xd3c21bcecceda100L, 0x84595161401484a0L, 0xa56fa5b99019a5c8L, |
198 | | - }; |
199 | | - |
200 | | - private static final long[] FP_POWER_SCALES = { |
201 | | - 0x8049a4ac0c5811aeL, 0xcf42894a5dce35eaL, 0xa76c582338ed2621L, 0x873e4f75e2224e68L, |
202 | | - 0xda7f5bf590966848L, 0xb080392cc4349decL, 0x8e938662882af53eL, 0xe65829b3046b0afaL, |
203 | | - 0xba121a4650e4ddebL, 0x964e858c91ba2655L, 0xf2d56790ab41c2a2L, 0xc428d05aa4751e4cL, |
204 | | - 0x9e74d1b791e07e48L, 0xccccccccccccccccL, 0xcecb8f27f4200f3aL, 0xa70c3c40a64e6c51L, |
205 | | - 0x86f0ac99b4e8dafdL, 0xda01ee641a708de9L, 0xb01ae745b101e9e4L, 0x8e41ade9fbebc27dL, |
206 | | - 0xe5d3ef282a242e81L, 0xb9a74a0637ce2ee1L, 0x95f83d0a1fb69cd9L, 0xf24a01a73cf2dccfL, |
207 | | - 0xc3b8358109e84f07L, 0x9e19db92b4e31ba9L, |
208 | | - }; |
209 | | - |
210 | | - private static final int[] FP_POWER_SCALE_LOW = { |
211 | | - 0x205b896d, 0x52064cad, 0xaf2af2b8, 0x5a7744a7, 0xaf39a475, 0xbd8d794e, |
212 | | - 0x547eb47b, 0x0cb4a5a3, 0x92f34d62, 0x3a6a07f9, 0xfae27299, 0xaa97e14c, |
213 | | - 0x775ea265, 0xcccccccc, 0x00000000, 0x999090b6, 0x69a028bb, 0xe80e6f48, |
214 | | - 0x5ec05dd0, 0x14588f14, 0x8f1668c9, 0x6d953e2c, 0x4abdaf10, 0xbc633b39, |
215 | | - 0x0a862f81, 0x6c07a2c2, |
216 | | - }; |
217 | | - |
218 | | - private static String sqliteRealText(double value) { |
219 | | - if (Double.isNaN(value)) { |
220 | | - return "NaN"; |
221 | | - } |
222 | | - if (Double.isInfinite(value)) { |
223 | | - return value < 0 ? "-Inf" : "Inf"; |
224 | | - } |
225 | | - boolean negative = value < 0.0; |
226 | | - double magnitude = negative ? -value : value; |
227 | | - if (magnitude == 0.0) { |
228 | | - return "0.0"; |
229 | | - } |
230 | | - |
231 | | - long bits = Double.doubleToRawLongBits(magnitude); |
232 | | - int exponent = (int) ((bits >>> 52) & 0x7ff); |
233 | | - long mantissa = bits & 0x000fffffffffffffL; |
234 | | - if (exponent == 0) { |
235 | | - int shift = Long.numberOfLeadingZeros(mantissa); |
236 | | - mantissa <<= shift; |
237 | | - exponent = -1074 - shift; |
238 | | - } else { |
239 | | - mantissa = (mantissa << 11) | (1L << 63); |
240 | | - exponent -= 1086; |
241 | | - } |
242 | | - |
243 | | - int power = 17 - fpPowerOfTwoToTen(exponent + 63); |
244 | | - long powerOfTen = fpPowerOfTen(power); |
245 | | - long high = fpMultiplyHigh(mantissa, powerOfTen); |
246 | | - int shift = -(exponent + fpPowerOfTenToTwo(power) + 2); |
247 | | - high >>>= shift; |
248 | | - long digitsValue = (high + ((high << 1) & 2)) >>> 1; |
249 | | - int powerOfTenExponent = -power; |
250 | | - |
251 | | - String digits = Long.toUnsignedString(digitsValue); |
252 | | - int digitCount = digits.length(); |
253 | | - int decimalPoint = digitCount + powerOfTenExponent; |
254 | | - |
255 | | - if (digitCount > 17) { |
256 | | - int roundAt = 17; |
257 | | - if (digits.charAt(15) == '9' && digits.charAt(14) == '9') { |
258 | | - int lastKept = 14; |
259 | | - while (lastKept > 0 && digits.charAt(lastKept - 1) == '9') { |
260 | | - lastKept--; |
261 | | - } |
262 | | - BigInteger candidate = lastKept == 0 |
263 | | - ? BigInteger.ONE |
264 | | - : new BigInteger(digits.substring(0, lastKept)).add(BigInteger.ONE); |
265 | | - if (roundTripsTo(magnitude, candidate, powerOfTenExponent + digitCount - lastKept)) { |
266 | | - roundAt = lastKept + 1; |
267 | | - } |
268 | | - } else if (decimalPoint >= digitCount |
269 | | - || (digits.charAt(15) == '0' |
270 | | - && digits.charAt(14) == '0' |
271 | | - && digits.charAt(13) == '0')) { |
272 | | - int lastKept = 13; |
273 | | - while (lastKept > 0 && digits.charAt(lastKept - 1) == '0') { |
274 | | - lastKept--; |
275 | | - } |
276 | | - BigInteger candidate = new BigInteger(digits.substring(0, lastKept)); |
277 | | - if (roundTripsTo(magnitude, candidate, powerOfTenExponent + digitCount - lastKept)) { |
278 | | - roundAt = lastKept + 1; |
279 | | - } |
280 | | - } |
281 | | - |
282 | | - char[] rounded = new char[digitCount + 1]; |
283 | | - digits.getChars(0, digitCount, rounded, 1); |
284 | | - int offset = 1; |
285 | | - int count = roundAt; |
286 | | - if (rounded[offset + roundAt] >= '5') { |
287 | | - int index = offset + roundAt - 1; |
288 | | - while (true) { |
289 | | - rounded[index]++; |
290 | | - if (rounded[index] <= '9') { |
291 | | - break; |
292 | | - } |
293 | | - rounded[index] = '0'; |
294 | | - if (index == offset) { |
295 | | - offset--; |
296 | | - rounded[offset] = '1'; |
297 | | - count++; |
298 | | - decimalPoint++; |
299 | | - break; |
300 | | - } |
301 | | - index--; |
302 | | - } |
303 | | - } |
304 | | - digits = new String(rounded, offset, count); |
305 | | - } |
306 | | - |
307 | | - int end = digits.length(); |
308 | | - while (end > 1 && digits.charAt(end - 1) == '0') { |
309 | | - end--; |
310 | | - } |
311 | | - digits = digits.substring(0, end); |
312 | | - |
313 | | - int decimalExponent = decimalPoint - 1; |
314 | | - StringBuilder out = new StringBuilder(); |
315 | | - if (negative) { |
316 | | - out.append('-'); |
317 | | - } |
318 | | - if (decimalExponent < -4 || decimalExponent > 16) { |
319 | | - out.append(digits.charAt(0)).append('.'); |
320 | | - int fractionEnd = digits.length(); |
321 | | - while (fractionEnd > 1 && digits.charAt(fractionEnd - 1) == '0') { |
322 | | - fractionEnd--; |
323 | | - } |
324 | | - if (fractionEnd > 1) { |
325 | | - out.append(digits, 1, fractionEnd); |
326 | | - } else { |
327 | | - out.append('0'); |
328 | | - } |
329 | | - out.append('e'); |
330 | | - out.append(decimalExponent < 0 ? '-' : '+'); |
331 | | - int absoluteExponent = Math.abs(decimalExponent); |
332 | | - if (absoluteExponent >= 100) { |
333 | | - out.append((char) ('0' + absoluteExponent / 100)); |
334 | | - } |
335 | | - out.append((char) ('0' + (absoluteExponent / 10) % 10)); |
336 | | - out.append((char) ('0' + absoluteExponent % 10)); |
337 | | - } else { |
338 | | - int integerPlaces = decimalPoint - 1; |
339 | | - int fractionPrecision = 16 - decimalExponent; |
340 | | - StringBuilder fraction = new StringBuilder(); |
341 | | - if (integerPlaces < 0) { |
342 | | - out.append('0'); |
343 | | - int leadingZeros = Math.min(-1 - integerPlaces, fractionPrecision); |
344 | | - fraction.append("0".repeat(leadingZeros)); |
345 | | - fractionPrecision -= leadingZeros; |
346 | | - fraction.append(digits, 0, Math.min(digits.length(), fractionPrecision)); |
347 | | - } else { |
348 | | - int integerDigits = Math.min(integerPlaces + 1, digits.length()); |
349 | | - out.append(digits, 0, integerDigits); |
350 | | - out.append("0".repeat(Math.max(0, integerPlaces + 1 - integerDigits))); |
351 | | - int significantDigits = Math.min(digits.length() - integerDigits, fractionPrecision); |
352 | | - if (significantDigits > 0) { |
353 | | - fraction.append(digits, integerDigits, integerDigits + significantDigits); |
354 | | - } |
355 | | - } |
356 | | - int fractionEnd = fraction.length(); |
357 | | - while (fractionEnd > 0 && fraction.charAt(fractionEnd - 1) == '0') { |
358 | | - fractionEnd--; |
359 | | - } |
360 | | - out.append('.'); |
361 | | - if (fractionEnd == 0) { |
362 | | - out.append('0'); |
363 | | - } else { |
364 | | - out.append(fraction, 0, fractionEnd); |
365 | | - } |
366 | | - } |
367 | | - return out.toString(); |
368 | | - } |
369 | | - |
370 | | - private static boolean roundTripsTo(double magnitude, BigInteger digits, int powerOfTen) { |
371 | | - double candidate = new BigDecimal(digits).scaleByPowerOfTen(powerOfTen).doubleValue(); |
372 | | - return magnitude == candidate; |
373 | | - } |
374 | | - |
375 | | - private static int fpPowerOfTenToTwo(int power) { |
376 | | - return (power * 108853) >> 15; |
377 | | - } |
378 | | - |
379 | | - private static int fpPowerOfTwoToTen(int power) { |
380 | | - return (power * 78913) >> 18; |
381 | | - } |
382 | | - |
383 | | - private static long fpPowerOfTen(int power) { |
384 | | - int[] nextBits = new int[1]; |
385 | | - if (power < 0) { |
386 | | - if (power == -1) { |
387 | | - nextBits[0] = FP_POWER_SCALE_LOW[13]; |
388 | | - return FP_POWER_SCALES[13]; |
389 | | - } |
390 | | - int group = power / 27; |
391 | | - int index = power % 27; |
392 | | - if (index != 0) { |
393 | | - group--; |
394 | | - index += 27; |
395 | | - } |
396 | | - long scale = FP_POWER_SCALES[group + 13]; |
397 | | - if (index == 0) { |
398 | | - nextBits[0] = FP_POWER_SCALE_LOW[group + 13]; |
399 | | - return scale; |
400 | | - } |
401 | | - return fpNormalizeHigh( |
402 | | - fpMultiplyWide( |
403 | | - scale, FP_POWER_SCALE_LOW[group + 13], FP_POWERS_OF_TEN[index], nextBits), |
404 | | - nextBits); |
405 | | - } |
406 | | - if (power < 27) { |
407 | | - nextBits[0] = 0; |
408 | | - return FP_POWERS_OF_TEN[power]; |
409 | | - } |
410 | | - int group = power / 27; |
411 | | - int index = power % 27; |
412 | | - long scale = FP_POWER_SCALES[group + 13]; |
413 | | - if (index == 0) { |
414 | | - nextBits[0] = FP_POWER_SCALE_LOW[group + 13]; |
415 | | - return scale; |
416 | | - } |
417 | | - return fpNormalizeHigh( |
418 | | - fpMultiplyWide(scale, FP_POWER_SCALE_LOW[group + 13], FP_POWERS_OF_TEN[index], nextBits), |
419 | | - nextBits); |
420 | | - } |
421 | | - |
422 | | - private static long fpNormalizeHigh(long high, int[] nextBits) { |
423 | | - if (high >= 0) { |
424 | | - high = (high << 1) | ((nextBits[0] >>> 31) & 1); |
425 | | - nextBits[0] = (nextBits[0] << 1) | 1; |
426 | | - } |
427 | | - return high; |
428 | | - } |
429 | | - |
430 | | - private static long fpMultiplyWide(long high, int low, long multiplier, int[] resultLow) { |
431 | | - BigInteger left = fpUnsigned(high).shiftLeft(32).or(BigInteger.valueOf(low & 0xffffffffL)); |
432 | | - BigInteger product = left.multiply(fpUnsigned(multiplier)); |
433 | | - resultLow[0] = product.shiftRight(64).intValue(); |
434 | | - return product.shiftRight(96).longValue(); |
435 | | - } |
436 | | - |
437 | | - private static long fpMultiplyHigh(long left, long right) { |
438 | | - return fpUnsigned(left).multiply(fpUnsigned(right)).shiftRight(64).longValue(); |
439 | | - } |
440 | | - |
441 | | - private static BigInteger fpUnsigned(long value) { |
442 | | - BigInteger unsigned = BigInteger.valueOf(value); |
443 | | - return value < 0 ? unsigned.add(BigInteger.ONE.shiftLeft(64)) : unsigned; |
444 | | - } |
445 | | - |
446 | 186 | /** |
447 | 187 | * Converts every storage class through {@link #getInt(int)}, matching sqlite-jdbc. NULL and values |
448 | 188 | * whose integer coercion is zero are false: non-numeric text such as {@code "true"}, REAL values |
@@ -638,7 +378,7 @@ public byte[] getBytes(int columnIndex) throws SQLException { |
638 | 378 | case Value.Blob b -> b.bytes(); |
639 | 379 | case Value.Text t -> t.value().getBytes(java.nio.charset.StandardCharsets.UTF_8); |
640 | 380 | case Value.Integer i -> Long.toString(i.value()).getBytes(java.nio.charset.StandardCharsets.UTF_8); |
641 | | - case Value.Float f -> sqliteRealText(f.value()).getBytes(java.nio.charset.StandardCharsets.UTF_8); |
| 381 | + case Value.Float f -> SqliteRealText.format(f.value()).getBytes(java.nio.charset.StandardCharsets.UTF_8); |
642 | 382 | }; |
643 | 383 | } |
644 | 384 |
|
|
0 commit comments