├── .gitignore ├── C ├── pure_parse_float │ ├── pure_parse_float.c │ ├── pure_parse_float.h │ └── readme.md └── test │ ├── main.c │ ├── test.dev │ └── test_dll │ ├── dll.h │ ├── dllmain.c │ ├── pure_parse_float_c_32.dev │ └── pure_parse_float_c_64.dev ├── LICENSE ├── Pascal ├── PureParseFloat │ ├── PureParseFloat.inc │ ├── PureParseFloat.pas │ └── readme.md └── Test │ ├── IeeeConvert32.dll │ ├── IeeeConvert64.dll │ ├── MainUnit.pas │ ├── Test.dpr │ ├── Test.dproj │ ├── Test.lpi │ ├── Test.lpr │ ├── pure_parse_float_c_32.dll │ ├── pure_parse_float_c_64.dll │ └── readme.md ├── Ports ├── CSharp │ ├── CSharpFloatParser.cs │ ├── CSharpSafe.csproj │ └── Readme.md ├── CSharpUnsafe │ ├── CSharpFloatParserUnsafe.cs │ ├── CSharpUnsafe.csproj │ └── Readme.md ├── Rust │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── readme.md │ └── src │ │ └── lib.rs └── RustUnsafe │ ├── Cargo.lock │ ├── Cargo.toml │ ├── readme.md │ └── src │ └── lib.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.bpl 32 | *.bpi 33 | *.dcp 34 | *.so 35 | *.apk 36 | *.drc 37 | *.map 38 | *.dres 39 | *.rsm 40 | *.tds 41 | *.dcu 42 | *.lib 43 | *.a 44 | *.o 45 | *.ocx 46 | 47 | # Delphi autogenerated files (duplicated info) 48 | *Resource.rc 49 | 50 | # Delphi local files (user-specific info) 51 | *.local 52 | *.identcache 53 | *.projdata 54 | *.tvsconfig 55 | *.dsk 56 | 57 | # Delphi history and backups 58 | __history/ 59 | __recovery/ 60 | *.~* 61 | 62 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 63 | *.stat 64 | 65 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 66 | modules/ 67 | -------------------------------------------------------------------------------- /C/pure_parse_float/pure_parse_float.c: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // ______ __ _ 3 | // /_ __/_ _______/ /_ ____ _____(_)_ ______ ___ 4 | // / / / / / / ___/ __ \/ __ \/ ___/ / / / / __ `__ \ 5 | // / / / /_/ / / / /_/ / /_/ / / / / /_/ / / / / / / 6 | // /_/ \__,_/_/ /_.___/\____/_/ /_/\__,_/_/ /_/ /_/ 7 | // 8 | // ____ ____ ______ __ 9 | // / __ \__ __________ / __ \____ ______________ / __/ /___ ____ _/ /_ 10 | // / /_/ / / / / ___/ _ \ / /_/ / __ `/ ___/ ___/ _ \ / /_/ / __ \/ __ `/ __/ 11 | // / ____/ /_/ / / / __/ / ____/ /_/ / / (__ ) __/ / __/ / /_/ / /_/ / /_ 12 | // /_/ \__,_/_/ \___/ /_/ \__,_/_/ /____/\___/ /_/ /_/\____/\__,_/\__/ 13 | // 14 | // ------------------------------------------------------------------------------------------------- 15 | // 16 | // Pure Parse Float - This is a simple and clear, "clean" algorithm and implementation of a function 17 | // for converting a string to a double number, with OK accuracy. 18 | // The main feature of the algorithm that underlies this implementation is the trade-off between 19 | // simplicity, accuracy, and speed. 20 | // 21 | // Original place: https://github.com/turborium/PureParseFloat 22 | // 23 | // ------------------------------------------------------------------------------------------------- 24 | // 25 | // Pure Parse Float algorithm repeats the usual Simple Parse Float algorithm, 26 | // but uses Double-Double arithmetic for correct rounding. 27 | // 28 | // Double-Double arithmetic is a technique to implement nearly quadruple precision using 29 | // pairs of Double values. Using two IEEE Double values with 53-bit mantissa, 30 | // Double-Double arithmetic provides operations on numbers with mantissa of least 2*53 = 106 bit. 31 | // But the range(exponent) of a Double-Double remains same as the Double format. 32 | // Double-Double number have a guaranteed precision of 31 decimal digits, with the exception of 33 | // exponents less than -291(exp^2 >= -968), due denormalized numbers, where the precision of a 34 | // Double-Double gradually decreases to that of a regular Double. 35 | // An small overview of the Double-Double is available here: 36 | // https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format#Double-double_arithmetic 37 | // More information about Double-Double is provided in the list below in this file. 38 | // 39 | // The key feature of Double-Double is that it is Hi + Lo, where Hi is the properly rounded 40 | // "Big" number, and Lo is some kind of "Remainder" that we simply ignore when converting 41 | // Double-Double to Double. 42 | // For example: 123456789123456789123456789 presented as 1.2345678912345679e+26 + -2.214306027e+9. 43 | // 44 | // ------------------------------------------------------------------------------------------------- 45 | // 46 | // Licensed under an unmodified MIT license. 47 | // 48 | // Copyright (c) 2023-2023 Turborium 49 | // 50 | // ------------------------------------------------------------------------------------------------- 51 | 52 | #include "pure_parse_float.h" 53 | 54 | // ------------------------------------------------------------------------------------------------- 55 | // Double-Double arithmetic routines 56 | // 57 | // [1] 58 | // Mioara Joldes, Jean-Michel Muller, Valentina Popescu. 59 | // Tight and rigourous error bounds for basic building blocks of double-word arithmetic, 2017. 60 | // [https://hal.science/hal-01351529v3/document] 61 | // 62 | // [2] 63 | // T. J. Dekker, A Floating-Point Technique for Extending the Available Precision, 1971 64 | // [https://csclub.uwaterloo.ca/~pbarfuss/dekker1971.pdf] 65 | // 66 | // [3] 67 | // Yozo Hida, Xiaoye Li, David Bailey. Library for Double-Double and Quad-Double Arithmetic, 2000. 68 | // [http://web.mit.edu/tabbott/Public/quaddouble-debian/qd-2.3.4-old/docs/qd.pdf] 69 | // 70 | // [4] 71 | // Laurent Thevenoux, Philippe Langlois, Matthieu Martel. 72 | // Automatic Source-to-Source Error Compensation of Floating-Point Programs 73 | // [https://hal.science/hal-01158399/document] 74 | // 75 | // [5] 76 | // Jonathan Richard Shewchuk 77 | // Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates, 1997 78 | // [https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf] 79 | // 80 | // https://learn.microsoft.com/en-us/cpp/porting/visual-cpp-change-history-2003-2015?view=msvc-170 81 | // https://stackoverflow.com/questions/21349847/positive-vs-negative-nans 82 | 83 | typedef struct { 84 | double hi, lo; // 31 digits garantee, with (exp^10 >= -291) or (exp^2 >= -968) 85 | } doubledouble; 86 | 87 | // Make doubledouble from double 88 | static doubledouble double_to_doubledouble(double value) 89 | { 90 | doubledouble result; 91 | 92 | result.hi = value; 93 | result.lo = 0; 94 | return result; 95 | } 96 | 97 | // Make double from doubledouble 98 | static double doubledouble_to_double(const doubledouble value) 99 | { 100 | return value.hi; 101 | } 102 | 103 | // Check value is +Inf or -Inf 104 | static int is_infinity(double value) 105 | { 106 | return (value == 1.0 / 0.0) || (value == -1.0 / 0.0); 107 | } 108 | 109 | // Add two Double1 value, condition: |A| >= |B| 110 | // The "Fast2Sum" algorithm (Dekker 1971) [1] 111 | static doubledouble doubledouble_fast_add11(double a, double b) 112 | { 113 | doubledouble result; 114 | 115 | result.hi = a + b; 116 | 117 | // infinity check 118 | if (is_infinity(result.hi)) { 119 | return double_to_doubledouble(result.hi); 120 | } 121 | 122 | result.lo = (b - (result.hi - a)); 123 | return result; 124 | } 125 | 126 | // The "2Sum" algorithm [1] 127 | static doubledouble doubledouble_add11(double a, double b) 128 | { 129 | doubledouble result; 130 | double ah, bh; 131 | 132 | result.hi = a + b; 133 | 134 | // infinity check 135 | if (is_infinity(result.hi)) { 136 | return double_to_doubledouble(result.hi); 137 | } 138 | 139 | ah = result.hi - b; 140 | bh = result.hi - ah; 141 | 142 | result.lo = (a - ah) + (b - bh); 143 | return result; 144 | } 145 | 146 | // The "Veltkamp Split" algorithm [2] [3] [4] 147 | // See "Splitting into Halflength Numbers" and ALGOL procedure "mul12" in Appendix in [2] 148 | static doubledouble doubledouble_split1(double a) 149 | { 150 | // The Splitter should be chosen equal to 2^trunc(t - t / 2) + 1, 151 | // where t is the number of binary digits in the mantissa. 152 | const double splitter = 134217729.0;// = 2^(53 - 53 div 2) + 1 = 2^27 + 1 153 | // Just make sure we don't have an overflow for Splitter, 154 | // InfinitySplit is 2^(e - (t - t div 2)) 155 | // where e is max exponent, t is number of binary digits. 156 | const double infinity_split = 6.69692879491417e+299;// = 2^(1023 - (53 - 53 div 2)) = 2^996 157 | // just multiply by the next lower power of two to get rid of the overflow 158 | // 2^(+/-)27 + 1 = 2^(+/-)28 159 | const double infinity_down = 3.7252902984619140625e-09;// = 2^-(27 + 1) = 2^-28 160 | const double infinity_up = 268435456.0;// = 2^(27 + 1) = 2^28 161 | 162 | doubledouble result; 163 | double temp; 164 | 165 | if ((a > infinity_split) || (a < -infinity_split)) { 166 | // down 167 | a = a * infinity_down; 168 | // mul 169 | temp = splitter * a; 170 | result.hi = temp + (a - temp); 171 | result.lo = a - result.hi; 172 | // up 173 | result.hi = result.hi * infinity_up; 174 | result.lo = result.lo * infinity_up; 175 | } else { 176 | temp = splitter * a; 177 | result.hi = temp + (a - temp); 178 | result.lo = a - result.hi; 179 | } 180 | return result; 181 | } 182 | 183 | // Multiplication two Double1 value 184 | // The "TWO-PRODUCT" algorithm [5] 185 | static doubledouble doubledouble_mul11(double a, double b) 186 | { 187 | doubledouble result; 188 | doubledouble a2, b2; 189 | double err1, err2, err3; 190 | 191 | result.hi = a * b; 192 | 193 | // infinity check 194 | if (is_infinity(result.hi)) { 195 | return double_to_doubledouble(result.hi); 196 | } 197 | 198 | a2 = doubledouble_split1(a); 199 | b2 = doubledouble_split1(b); 200 | 201 | err1 = result.hi - (a2.hi * b2.hi); 202 | err2 = err1 - (a2.lo * b2.hi); 203 | err3 = err2 - (a2.hi * b2.lo); 204 | 205 | result.lo = (a2.lo * b2.lo) - err3; 206 | //result.hi = temp; 207 | //result.lo = ((a2.hi * b2.hi - temp) + a2.hi * b2.lo + a2.lo * b2.hi) + a2.lo * b2.lo; 208 | return result; 209 | } 210 | 211 | // Multiplication Double2 by Double1 212 | // The "DWTimesFP1" algorithm [1] 213 | static doubledouble doubledouble_mul21(const doubledouble a, double b) 214 | { 215 | doubledouble result; 216 | doubledouble c; 217 | 218 | c = doubledouble_mul11(a.hi, b); 219 | 220 | // infinity check 221 | if (is_infinity(c.hi)) { 222 | return double_to_doubledouble(c.hi); 223 | } 224 | 225 | result = doubledouble_fast_add11(c.hi, a.lo * b); 226 | result = doubledouble_fast_add11(result.hi, result.lo + c.lo); 227 | return result; 228 | } 229 | 230 | // Division Double2 by Double1 231 | // The "DWDivFP2" algorithm [1] 232 | static doubledouble doubledouble_div21(const doubledouble a, double b) 233 | { 234 | doubledouble result; 235 | doubledouble p, d; 236 | 237 | result.hi = a.hi / b; 238 | 239 | // infinity check 240 | if (is_infinity(result.hi)) { 241 | return double_to_doubledouble(result.hi); 242 | } 243 | 244 | p = doubledouble_mul11(result.hi, b); 245 | 246 | d.hi = a.hi - p.hi; 247 | d.lo = d.hi - p.lo; 248 | 249 | result.lo = (d.lo + a.lo) / b; 250 | 251 | result = doubledouble_fast_add11(result.hi, result.lo); 252 | return result; 253 | } 254 | 255 | // Addition Double2 and Double1 256 | // The DWPlusFP algorithm [1] 257 | static doubledouble doubledouble_add21(const doubledouble a, double b) 258 | { 259 | doubledouble result; 260 | 261 | result = doubledouble_add11(a.hi, b); 262 | 263 | // infinity check 264 | if (is_infinity(result.hi)) { 265 | return double_to_doubledouble(result.hi); 266 | } 267 | 268 | result.lo = result.lo + a.lo; 269 | result = doubledouble_fast_add11(result.hi, result.lo); 270 | return result; 271 | } 272 | 273 | // --- 274 | 275 | typedef struct 276 | { 277 | int count; 278 | long int exponent; 279 | int is_negative; 280 | unsigned char digits[17 * 2];// Max digits in Double value * 2 281 | } fixed_decimal; 282 | 283 | static int text_prefix_length(const char *text, const char *prefix) 284 | { 285 | int i; 286 | 287 | i = 0; 288 | while ((text[i] == prefix[i]) || 289 | ((text[i] >= 'A') && (text[i] <= 'Z') && ((text[i] + 32) == prefix[i]))) { 290 | 291 | if (text[i] == '\0') { 292 | break; 293 | } 294 | i++; 295 | } 296 | 297 | return i; 298 | } 299 | 300 | static int read_special(double *number, const char *text, char **text_end) 301 | { 302 | char *p; 303 | int is_negative; 304 | int len; 305 | 306 | // clean 307 | is_negative = 0; 308 | 309 | // read from start 310 | p = (char*)text; 311 | 312 | // read sign 313 | switch (*p) { 314 | case '+': 315 | p++; 316 | break; 317 | case '-': 318 | is_negative = 1; 319 | p++; 320 | break; 321 | } 322 | 323 | // special 324 | switch (*p) { 325 | case 'I': 326 | case 'i': 327 | len = text_prefix_length(p, "infinity"); 328 | if ((len == 3) || (len == 8)) { 329 | *number = 1.0 / 0.0; 330 | if (is_negative) { 331 | *number = -*number; 332 | } 333 | p = p + len; 334 | *text_end = p; 335 | return 1; 336 | } 337 | break; 338 | case 'N': 339 | case 'n': 340 | len = text_prefix_length(p, "nan"); 341 | if (len == 3) { 342 | *number = -(0.0 / 0.0); 343 | if (is_negative) { 344 | *number = -*number; 345 | } 346 | p = p + len; 347 | *text_end = p; 348 | return 1; 349 | } 350 | break; 351 | } 352 | 353 | // fail 354 | *text_end = (char*)text; 355 | return 0; 356 | } 357 | 358 | int read_text_to_fixed_decimal(fixed_decimal *decimal, const char *text, char **text_end) 359 | { 360 | const long int clip_exponent = 1000000; 361 | 362 | char *p, *p_start_exponent; 363 | int has_point, has_digit; 364 | int exponent_sign; 365 | long int exponent; 366 | 367 | // clean 368 | decimal->count = 0; 369 | decimal->is_negative = 0; 370 | decimal->exponent = -1; 371 | 372 | // read from start 373 | p = (char*)text; 374 | 375 | // read sign 376 | switch (*p) { 377 | case '+': 378 | p++; 379 | break; 380 | case '-': 381 | decimal->is_negative = 1; 382 | p++; 383 | break; 384 | } 385 | 386 | // read mantissa 387 | has_digit = 0;// has read any digit (0..9) 388 | has_point = 0;// has read decimal point 389 | while (*p != '\0') { 390 | switch (*p) { 391 | case '0': 392 | case '1': 393 | case '2': 394 | case '3': 395 | case '4': 396 | case '5': 397 | case '6': 398 | case '7': 399 | case '8': 400 | case '9': 401 | if ((decimal->count != 0) || (*p != '0')) { 402 | // save digit 403 | if (decimal->count < (sizeof(decimal->digits) / sizeof(decimal->digits[0]))) { 404 | decimal->digits[decimal->count] = *p - '0'; 405 | decimal->count++; 406 | } 407 | // inc exponenta 408 | if ((!has_point) && (decimal->exponent < clip_exponent)) { 409 | decimal->exponent++; 410 | } 411 | } else { 412 | // skip zero (dec exponenta) 413 | if (has_point && (decimal->exponent > -clip_exponent)) { 414 | decimal->exponent--; 415 | } 416 | } 417 | has_digit = 1; 418 | break; 419 | case '.': 420 | if (has_point) { 421 | *text_end = p; 422 | return 1;// make 423 | } 424 | has_point = 1; 425 | break; 426 | default: 427 | goto end_read_mantissa; 428 | } 429 | p++; 430 | } 431 | 432 | end_read_mantissa: 433 | 434 | if (!has_digit) { 435 | *text_end = (char*)text; 436 | return 0;// fail 437 | } 438 | 439 | // read exponenta 440 | if ((*p == 'e') || (*p == 'E')) { 441 | p_start_exponent = p; 442 | p++; 443 | 444 | exponent = 0; 445 | exponent_sign = 1; 446 | 447 | // check sign 448 | switch (*p) { 449 | case '+': 450 | p++; 451 | break; 452 | case '-': 453 | exponent_sign = -1; 454 | p++; 455 | break; 456 | } 457 | 458 | // read 459 | if ((*p >= '0') && (*p <= '9')) { 460 | while ((*p >= '0') && (*p <= '9')) { 461 | exponent = exponent * 10 + (*p - '0'); 462 | if (exponent > clip_exponent) { 463 | exponent = clip_exponent; 464 | } 465 | p++; 466 | } 467 | } else { 468 | *text_end = p_start_exponent;// revert 469 | return 1;// Make 470 | } 471 | 472 | // fix 473 | decimal->exponent = decimal->exponent + exponent_sign * exponent; 474 | } 475 | 476 | *text_end = p; 477 | return 1;// Make 478 | } 479 | 480 | double fixed_decimal_to_double(fixed_decimal *decimal) 481 | { 482 | const int last_accuracy_exponent_10 = 22;// for Double 483 | const double last_accuracy_power_10 = 1e22;// for Double 484 | const long long int max_safe_int = 9007199254740991;// (2^53−1) for Double 485 | const long long max_safe_hi = (max_safe_int - 9) / 10;// for X * 10 + 9 486 | const double power_of_10[] = { 487 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 488 | 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 489 | }; 490 | 491 | double result; 492 | int i; 493 | doubledouble number; 494 | long int exponent; 495 | 496 | number = double_to_doubledouble(0.0); 497 | 498 | // set mantissa 499 | for (i = 0; i < decimal->count; i++) { 500 | if (number.hi <= max_safe_hi) { 501 | number.hi = number.hi * 10;// * 10 502 | number.hi = number.hi + decimal->digits[i];// + Digit 503 | } else { 504 | number = doubledouble_mul21(number, 10.0);// * 10 505 | number = doubledouble_add21(number, decimal->digits[i]);// + Digit 506 | } 507 | } 508 | 509 | // set exponent 510 | exponent = decimal->exponent - decimal->count + 1; 511 | 512 | // positive exponent 513 | while (exponent > 0) { 514 | if (exponent > last_accuracy_exponent_10) { 515 | // * e22 516 | number = doubledouble_mul21(number, last_accuracy_power_10); 517 | // overflow break 518 | if (is_infinity(doubledouble_to_double(number))) { 519 | break; 520 | } 521 | exponent = exponent - last_accuracy_exponent_10; 522 | } else { 523 | // * eX 524 | number = doubledouble_mul21(number, power_of_10[exponent]); 525 | break; 526 | } 527 | } 528 | 529 | // negative exponent 530 | while (exponent < 0) { 531 | if (exponent < -last_accuracy_exponent_10) { 532 | // / e22 533 | number = doubledouble_div21(number, last_accuracy_power_10); 534 | // underflow break 535 | if (doubledouble_to_double(number) == 0.0) { 536 | break; 537 | } 538 | exponent = exponent + last_accuracy_exponent_10; 539 | } else { 540 | // / eX 541 | number = doubledouble_div21(number, power_of_10[-exponent]); 542 | break; 543 | } 544 | } 545 | 546 | // make result 547 | result = doubledouble_to_double(number); 548 | 549 | // fix sign 550 | if (decimal->is_negative) { 551 | result = -result; 552 | } 553 | return result; 554 | } 555 | 556 | // --- 557 | 558 | // ------------------------------------------------------------------------------------------------- 559 | // ParseFloat parse chars with float point pattern to Double and stored to Value param. 560 | // If no chars match the pattern then Value is unmodified, else the chars convert to 561 | // float point value which will be is stored in Value. 562 | // On success, EndPtr points at the first char not matching the float point pattern. 563 | // If there is no pattern match, EndPtr equals with TextPtr. 564 | // 565 | // If successful function return 1 else 0. 566 | // Remember: on failure, the Value will not be changed. 567 | // 568 | // The pattern is a regular float number, with an optional exponent (E/e) and optional (+/-) sign. 569 | // The pattern allows the values Inf/Infinity and NaN, with any register and optional sign. 570 | // Leading spaces are not allowed in the pattern. A dot is always used as separator. 571 | // ------------------------------------------------------------------------------------------------- 572 | // 573 | // Examples: 574 | // 575 | // "1984\0" -- just read to terminal 576 | // ^ ^------- 577 | // Text, TextEnd, Value = 1984, Result = True 578 | // 579 | // 580 | // "+123.45e-22 abc\0" -- read to end of float number 581 | // ^ ^ 582 | // Text, TextEnd, Value = 123.45e-22, Result = True 583 | // 584 | // 585 | // "aboba\0" -- invalid float point 586 | // ^---------- 587 | // Text, TextEnd, Value = dont change, Result = False 588 | // 589 | // 590 | // "AAA.99\0" -- read with leading dot notation 591 | // ---^ ^----- 592 | // Text, TextEnd, Value = 0.99, Result = True 593 | // 594 | // 595 | // "500e\0" -- read correct part of input 596 | // ^ ^-------- 597 | // Text, TextEnd, Value = 500, Result = True 598 | // 599 | // ------------------------------------------------------------------------------------------------- 600 | 601 | int parse_float(const char *text, double *value, char **text_end) 602 | { 603 | fixed_decimal decimal; 604 | char *dummy; 605 | 606 | if (!text_end) { 607 | text_end = &dummy; 608 | } 609 | 610 | // Try read inf/nan 611 | if (read_special(value, text, text_end)) { 612 | // Read done 613 | return 1; 614 | } 615 | 616 | // Try read number 617 | if (read_text_to_fixed_decimal(&decimal, text, text_end)) { 618 | // Convert Decimal to Double 619 | *value = fixed_decimal_to_double(&decimal); 620 | 621 | // Read done 622 | return 1; 623 | } 624 | 625 | // Fail 626 | return 0; 627 | } -------------------------------------------------------------------------------- /C/pure_parse_float/pure_parse_float.h: -------------------------------------------------------------------------------- 1 | #ifndef pure_parse_float_h 2 | #define pure_parse_float_h 3 | 4 | int parse_float(const char *text, double *value, char **text_end); 5 | 6 | #endif -------------------------------------------------------------------------------- /C/pure_parse_float/readme.md: -------------------------------------------------------------------------------- 1 | Before using the parser, make sure to set the FPU precision to "double" and the rounding mode to "nearest". 2 | When using GCC on x86, make sure to use the -ffloat-store option, otherwise GCC will generate incorrect code: https://gcc.gnu.org/wiki/FloatingPointMath 3 | -------------------------------------------------------------------------------- /C/test/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "..\pure_parse_float\pure_parse_float.h" 5 | 6 | uint32_t test_rand_seed = 0; 7 | 8 | void set_test_seed(const uint32_t seed) 9 | { 10 | test_rand_seed = seed; 11 | } 12 | 13 | int32_t test_random(const int32_t max) 14 | { 15 | int32_t result; 16 | result = (int32_t)test_rand_seed * 0x08088405 + 1; 17 | test_rand_seed = result; 18 | result = ((uint64_t)((uint32_t)max) * (uint64_t)((uint32_t)result)) >> 32; 19 | } 20 | 21 | typedef union { 22 | double value; 23 | uint16_t array[4]; 24 | int64_t bin; 25 | } number; 26 | 27 | int main(int argc, char *argv[]) 28 | { 29 | const int test_count = 1000000; 30 | int i; 31 | int one_ulp_error_count; 32 | int fatal_error_count; 33 | number source, a, b; 34 | char *a_end, *b_end; 35 | char buffer[200]; 36 | 37 | printf("Testing, please wait...\n"); 38 | 39 | set_test_seed(404); 40 | one_ulp_error_count = 0; 41 | fatal_error_count = 0; 42 | for (i = 0; i < test_count; i++) { 43 | // make 44 | source.array[0] = test_random(0xFFFF + 1); 45 | source.array[1] = test_random(0xFFFF + 1); 46 | source.array[2] = test_random(0xFFFF + 1); 47 | source.array[3] = test_random(0xFFFF + 1); 48 | if (test_random(2) == 0) { 49 | sprintf(buffer, "%.15lg\n", source.value); 50 | } else { 51 | sprintf(buffer, "%lg\n", source.value); 52 | } 53 | 54 | // convert 55 | a.value = 0.0; 56 | parse_float(buffer, &(a.value), &a_end); 57 | b.value = strtod(buffer, &b_end); 58 | 59 | // reading count 60 | if (a_end != b_end) { 61 | fatal_error_count++; 62 | continue; 63 | } 64 | 65 | // ulp fail 66 | if (a.bin != b.bin) { 67 | if ((a.bin + 1 != b.bin) && (a.bin - 1 != b.bin)) { 68 | fatal_error_count++; 69 | continue; 70 | } 71 | one_ulp_error_count++; 72 | } 73 | } 74 | 75 | if (fatal_error_count == 0) { 76 | printf("Tests OK\n"); 77 | } else { 78 | printf("Tests FAIL\n"); 79 | } 80 | 81 | printf("Test count: %i\n", test_count); 82 | printf("Fatal error count: %i\n", fatal_error_count); 83 | printf("One ulp error count: %i (%.3f%%)\n", one_ulp_error_count, 100.0 * ((double)one_ulp_error_count / (double)test_count)); 84 | 85 | return 0; 86 | } -------------------------------------------------------------------------------- /C/test/test.dev: -------------------------------------------------------------------------------- 1 | [Project] 2 | filename=test.dev 3 | name=test 4 | Type=1 5 | Ver=2 6 | ObjFiles= 7 | Includes= 8 | Libs= 9 | PrivateResource= 10 | ResourceIncludes= 11 | MakeIncludes= 12 | Compiler=-ffloat-store_@@_ 13 | CppCompiler= 14 | Linker= 15 | IsCpp=0 16 | Icon= 17 | ExeOutput= 18 | ObjectOutput= 19 | LogOutput= 20 | LogOutputEnabled=0 21 | OverrideOutput=0 22 | OverrideOutputName=test.exe 23 | HostApplication= 24 | UseCustomMakefile=0 25 | CustomMakefile= 26 | CommandLine= 27 | Folders= 28 | IncludeVersionInfo=0 29 | SupportXPThemes=0 30 | CompilerSet=3 31 | CompilerSettings=0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;8;0;0;0 32 | UnitCount=3 33 | 34 | [VersionInfo] 35 | Major=1 36 | Minor=0 37 | Release=0 38 | Build=0 39 | LanguageID=1033 40 | CharsetID=1252 41 | CompanyName= 42 | FileVersion=1.0.0.0 43 | FileDescription=Developed using the Dev-C++ IDE 44 | InternalName= 45 | LegalCopyright= 46 | LegalTrademarks= 47 | OriginalFilename= 48 | ProductName= 49 | ProductVersion=1.0.0.0 50 | AutoIncBuildNr=0 51 | SyncProduct=1 52 | 53 | [Unit1] 54 | FileName=main.c 55 | CompileCpp=0 56 | Folder= 57 | Compile=1 58 | Link=1 59 | Priority=1000 60 | OverrideBuildCmd=0 61 | BuildCmd= 62 | 63 | [Unit2] 64 | FileName=..\pure_parse_float\pure_parse_float.c 65 | CompileCpp=0 66 | Folder= 67 | Compile=1 68 | Link=1 69 | Priority=1000 70 | OverrideBuildCmd=0 71 | BuildCmd= 72 | 73 | [Unit3] 74 | FileName=..\pure_parse_float\pure_parse_float.h 75 | CompileCpp=0 76 | Folder= 77 | Compile=1 78 | Link=1 79 | Priority=1000 80 | OverrideBuildCmd=0 81 | BuildCmd= 82 | 83 | -------------------------------------------------------------------------------- /C/test/test_dll/dll.h: -------------------------------------------------------------------------------- 1 | #ifndef _DLL_H_ 2 | #define _DLL_H_ 3 | 4 | #include 5 | 6 | #if BUILDING_DLL 7 | #define DLLIMPORT __declspec(dllexport) 8 | #else 9 | #define DLLIMPORT __declspec(dllimport) 10 | #endif 11 | 12 | DLLIMPORT bool pure_parse_float(const char *text, double *value, char **text_end); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /C/test/test_dll/dllmain.c: -------------------------------------------------------------------------------- 1 | /* Replace "dll.h" with the name of your header */ 2 | #include "dll.h" 3 | #include 4 | #include "..\..\pure_parse_float\pure_parse_float.h" 5 | 6 | DLLIMPORT bool pure_parse_float(const char *text, double *value, char **text_end) 7 | { 8 | return parse_float(text, value, text_end); 9 | } 10 | 11 | BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) 12 | { 13 | /* Return TRUE on success, FALSE on failure */ 14 | return TRUE; 15 | } 16 | -------------------------------------------------------------------------------- /C/test/test_dll/pure_parse_float_c_32.dev: -------------------------------------------------------------------------------- 1 | [Project] 2 | filename=pure_parse_float_c_32.dev 3 | name=pure_parse_float_c_32 4 | Type=3 5 | Ver=2 6 | ObjFiles= 7 | Includes= 8 | Libs= 9 | PrivateResource= 10 | ResourceIncludes= 11 | MakeIncludes= 12 | Compiler=-DBUILDING_DLL=1_@@_-ffloat-store_@@_ 13 | CppCompiler=-DBUILDING_DLL=1_@@_ 14 | Linker= 15 | IsCpp=0 16 | Icon= 17 | ExeOutput= 18 | ObjectOutput=win32 19 | LogOutput= 20 | LogOutputEnabled=0 21 | OverrideOutput=0 22 | OverrideOutputName=pure_parse_float_c_32.dll 23 | HostApplication= 24 | UseCustomMakefile=0 25 | CustomMakefile= 26 | CommandLine= 27 | Folders= 28 | IncludeVersionInfo=0 29 | SupportXPThemes=0 30 | CompilerSet=3 31 | CompilerSettings=0;0;0;0;0;0;3;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;8;0;0;0 32 | UnitCount=4 33 | 34 | [VersionInfo] 35 | Major=1 36 | Minor=0 37 | Release=0 38 | Build=0 39 | LanguageID=1033 40 | CharsetID=1252 41 | CompanyName= 42 | FileVersion=1.0.0.0 43 | FileDescription=Developed using the Dev-C++ IDE 44 | InternalName= 45 | LegalCopyright= 46 | LegalTrademarks= 47 | OriginalFilename= 48 | ProductName= 49 | ProductVersion=1.0.0.0 50 | AutoIncBuildNr=0 51 | SyncProduct=1 52 | 53 | [Unit1] 54 | FileName=dllmain.c 55 | CompileCpp=0 56 | Folder= 57 | Compile=1 58 | Link=1 59 | Priority=1000 60 | OverrideBuildCmd=0 61 | BuildCmd= 62 | 63 | [Unit2] 64 | FileName=dll.h 65 | CompileCpp=0 66 | Folder= 67 | Compile=1 68 | Link=1 69 | Priority=1000 70 | OverrideBuildCmd=0 71 | BuildCmd= 72 | 73 | [Unit3] 74 | FileName=..\..\pure_parse_float\pure_parse_float.c 75 | CompileCpp=0 76 | Folder= 77 | Compile=1 78 | Link=1 79 | Priority=1000 80 | OverrideBuildCmd=0 81 | BuildCmd= 82 | 83 | [Unit4] 84 | FileName=..\..\pure_parse_float\pure_parse_float.h 85 | CompileCpp=0 86 | Folder= 87 | Compile=1 88 | Link=1 89 | Priority=1000 90 | OverrideBuildCmd=0 91 | BuildCmd= 92 | 93 | -------------------------------------------------------------------------------- /C/test/test_dll/pure_parse_float_c_64.dev: -------------------------------------------------------------------------------- 1 | [Project] 2 | filename=pure_parse_float_c_64.dev 3 | name=pure_parse_float_c_64 4 | Type=3 5 | Ver=2 6 | ObjFiles= 7 | Includes= 8 | Libs= 9 | PrivateResource= 10 | ResourceIncludes= 11 | MakeIncludes= 12 | Compiler=-DBUILDING_DLL=1_@@_ 13 | CppCompiler=-DBUILDING_DLL=1_@@_ 14 | Linker= 15 | IsCpp=0 16 | Icon= 17 | ExeOutput= 18 | ObjectOutput=win64 19 | LogOutput= 20 | LogOutputEnabled=0 21 | OverrideOutput=0 22 | OverrideOutputName=pure_parse_float_c_64.dll 23 | HostApplication= 24 | UseCustomMakefile=0 25 | CustomMakefile= 26 | CommandLine= 27 | Folders= 28 | IncludeVersionInfo=0 29 | SupportXPThemes=0 30 | CompilerSet=0 31 | CompilerSettings=0;0;0;0;0;7;3;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;8;0;0;0 32 | UnitCount=4 33 | 34 | [VersionInfo] 35 | Major=1 36 | Minor=0 37 | Release=0 38 | Build=0 39 | LanguageID=1033 40 | CharsetID=1252 41 | CompanyName= 42 | FileVersion=1.0.0.0 43 | FileDescription=Developed using the Dev-C++ IDE 44 | InternalName= 45 | LegalCopyright= 46 | LegalTrademarks= 47 | OriginalFilename= 48 | ProductName= 49 | ProductVersion=1.0.0.0 50 | AutoIncBuildNr=0 51 | SyncProduct=1 52 | 53 | [Unit1] 54 | FileName=dllmain.c 55 | CompileCpp=0 56 | Folder= 57 | Compile=1 58 | Link=1 59 | Priority=1000 60 | OverrideBuildCmd=0 61 | BuildCmd= 62 | 63 | [Unit2] 64 | FileName=dll.h 65 | CompileCpp=0 66 | Folder= 67 | Compile=1 68 | Link=1 69 | Priority=1000 70 | OverrideBuildCmd=0 71 | BuildCmd= 72 | 73 | [Unit3] 74 | FileName=..\..\pure_parse_float\pure_parse_float.c 75 | CompileCpp=0 76 | Folder= 77 | Compile=1 78 | Link=1 79 | Priority=1000 80 | OverrideBuildCmd=0 81 | BuildCmd= 82 | 83 | [Unit4] 84 | FileName=..\..\pure_parse_float\pure_parse_float.h 85 | CompileCpp=0 86 | Folder= 87 | Compile=1 88 | Link=1 89 | Priority=1000 90 | OverrideBuildCmd=0 91 | BuildCmd= 92 | 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 turborium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pascal/PureParseFloat/PureParseFloat.inc: -------------------------------------------------------------------------------- 1 | // Can be enabled if your code itself sets normal FPU settings 2 | {.$DEFINE PURE_PARSE_FLOAT_DISABLE_SET_NORMAL_FPU_SETTINGS} 3 | -------------------------------------------------------------------------------- /Pascal/PureParseFloat/PureParseFloat.pas: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // ______ __ _ 3 | // /_ __/_ _______/ /_ ____ _____(_)_ ______ ___ 4 | // / / / / / / ___/ __ \/ __ \/ ___/ / / / / __ `__ \ 5 | // / / / /_/ / / / /_/ / /_/ / / / / /_/ / / / / / / 6 | // /_/ \__,_/_/ /_.___/\____/_/ /_/\__,_/_/ /_/ /_/ 7 | // 8 | // ____ ____ ______ __ 9 | // / __ \__ __________ / __ \____ ______________ / __/ /___ ____ _/ /_ 10 | // / /_/ / / / / ___/ _ \ / /_/ / __ `/ ___/ ___/ _ \ / /_/ / __ \/ __ `/ __/ 11 | // / ____/ /_/ / / / __/ / ____/ /_/ / / (__ ) __/ / __/ / /_/ / /_/ / /_ 12 | // /_/ \__,_/_/ \___/ /_/ \__,_/_/ /____/\___/ /_/ /_/\____/\__,_/\__/ 13 | // 14 | // ------------------------------------------------------------------------------------------------- 15 | // 16 | // Pure Parse Float - This is a simple and clear, "clean" algorithm and implementation of a function 17 | // for converting a string to a double number, with OK accuracy. 18 | // The main feature of the algorithm that underlies this implementation is the trade-off between 19 | // simplicity, accuracy, and speed. 20 | // 21 | // Original place: https://github.com/turborium/PureParseFloat 22 | // 23 | // ------------------------------------------------------------------------------------------------- 24 | // 25 | // Pure Parse Float algorithm repeats the usual Simple Parse Float algorithm, 26 | // but uses Double-Double arithmetic for correct rounding. 27 | // 28 | // Double-Double arithmetic is a technique to implement nearly quadruple precision using 29 | // pairs of Double values. Using two IEEE Double values with 53-bit mantissa, 30 | // Double-Double arithmetic provides operations on numbers with mantissa of least 2*53 = 106 bit. 31 | // But the range(exponent) of a Double-Double remains same as the Double format. 32 | // Double-Double number have a guaranteed precision of 31 decimal digits, with the exception of 33 | // exponents less than -291(exp^2 >= -968), due denormalized numbers, where the precision of a 34 | // Double-Double gradually decreases to that of a regular Double. 35 | // An small overview of the Double-Double is available here: 36 | // https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format#Double-double_arithmetic 37 | // More information about Double-Double is provided in the list below in this file. 38 | // 39 | // The key feature of Double-Double is that it is Hi + Lo, where Hi is the properly rounded 40 | // "Big" number, and Lo is some kind of "Remainder" that we simply ignore when converting 41 | // Double-Double to Double. 42 | // For example: 123456789123456789123456789 presented as 1.2345678912345679e+26 + -2.214306027e+9. 43 | // 44 | // ------------------------------------------------------------------------------------------------- 45 | // 46 | // Licensed under an unmodified MIT license. 47 | // 48 | // Copyright (c) 2023-2023 Turborium 49 | // 50 | // ------------------------------------------------------------------------------------------------- 51 | unit PureParseFloat; 52 | 53 | // User Defined Options 54 | {$INCLUDE PureParseFloat.inc} 55 | 56 | // Only for FreePascal 57 | {$IFDEF FPC} 58 | {$WARN 5059 OFF} 59 | {$MODE DELPHIUNICODE} 60 | {$IFDEF CPUI386} 61 | {$DEFINE CPUX86} 62 | {$ENDIF} 63 | {$ENDIF} 64 | 65 | // Compiler Options 66 | {$WARN SYMBOL_PLATFORM OFF} 67 | {$OPTIMIZATION ON} 68 | {$OVERFLOWCHECKS OFF} 69 | {$RANGECHECKS OFF} 70 | {$IOCHECKS OFF} 71 | {$INLINE ON} 72 | 73 | interface 74 | 75 | // ------------------------------------------------------------------------------------------------- 76 | // ParseFloat parse chars with float point pattern to Double and stored to Value param. 77 | // If no chars match the pattern then Value is unmodified, else the chars convert to 78 | // float point value which will be is stored in Value. 79 | // On success, EndPtr points at the first char not matching the float point pattern. 80 | // If there is no pattern match, EndPtr equals with TextPtr. 81 | // 82 | // If successful function return True else False. 83 | // Remember: on failure, the Value will not be changed. 84 | // 85 | // The pattern is a regular float number, with an optional exponent (E/e) and optional (+/-) sign. 86 | // The pattern allows the values Inf/Infinity and NaN, with any register and optional sign. 87 | // Leading spaces are not allowed in the pattern. A dot is always used as separator. 88 | // ------------------------------------------------------------------------------------------------- 89 | // 90 | // Examples: 91 | // 92 | // "1984\0" -- just read to terminal 93 | // ^ ^------- 94 | // TextPtr, EndPtr, Value = 1984, Result = True 95 | // 96 | // 97 | // "+123.45e-22 abc\0" -- read to end of float number 98 | // ^ ^ 99 | // TextPtr, EndPtr, Value = 123.45e-22, Result = True 100 | // 101 | // 102 | // "aboba\0" -- invalid float point 103 | // ^---------- 104 | // TextPtr, EndPtr, Value = dont change, Result = False 105 | // 106 | // 107 | // "AAA.99\0" -- read with leading dot notation 108 | // ---^ ^----- 109 | // TextPtr, EndPtr, Value = 0.99, Result = True 110 | // 111 | // 112 | // "500e\0" -- read correct part of input 113 | // ^ ^-------- 114 | // TextPtr, EndPtr, Value = 500, Result = True 115 | // 116 | // ------------------------------------------------------------------------------------------------- 117 | 118 | function ParseFloat(const TextPtr: PWideChar; var Value: Double; out EndPtr: PWideChar): Boolean; overload; 119 | function ParseFloat(const TextPtr: PWideChar; var Value: Double): Boolean; overload; 120 | 121 | function TryCharsToFloat(const TextPtr: PWideChar; out Value: Double): Boolean; 122 | function CharsToFloat(const TextPtr: PWideChar; const Default: Double = 0.0): Double; 123 | 124 | function TryStringToFloat(const Str: UnicodeString; out Value: Double): Boolean; 125 | function StringToFloat(const Str: UnicodeString; const Default: Double = 0.0): Double; 126 | 127 | implementation 128 | 129 | {$IFNDEF PURE_PARSE_FLOAT_DISABLE_SET_NORMAL_FPU_SETTINGS} 130 | {$IF NOT(DEFINED(CPUX86) OR DEFINED(CPUX64))} 131 | uses 132 | Math; 133 | {$ENDIF} 134 | {$ENDIF} 135 | 136 | // ------------------------------------------------------------------------------------------------- 137 | // Double-Double arithmetic routines 138 | // 139 | // [1] 140 | // Mioara Joldes, Jean-Michel Muller, Valentina Popescu. 141 | // Tight and rigourous error bounds for basic building blocks of double-word arithmetic, 2017. 142 | // [https://hal.science/hal-01351529v3/document] 143 | // 144 | // [2] 145 | // T. J. Dekker, A Floating-Point Technique for Extending the Available Precision, 1971 146 | // [https://csclub.uwaterloo.ca/~pbarfuss/dekker1971.pdf] 147 | // 148 | // [3] 149 | // Yozo Hida, Xiaoye Li, David Bailey. Library for Double-Double and Quad-Double Arithmetic, 2000. 150 | // [http://web.mit.edu/tabbott/Public/quaddouble-debian/qd-2.3.4-old/docs/qd.pdf] 151 | // 152 | // [4] 153 | // Laurent Thevenoux, Philippe Langlois, Matthieu Martel. 154 | // Automatic Source-to-Source Error Compensation of Floating-Point Programs 155 | // [https://hal.science/hal-01158399/document] 156 | // 157 | // [5] 158 | // Jonathan Richard Shewchuk 159 | // Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates, 1997 160 | // [https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf] 161 | 162 | type 163 | TDoubleDouble = record 164 | Hi, Lo: Double; // 31 digits garantee, with (exp^10 >= -291) or (exp^2 >= -968) 165 | end; 166 | 167 | // Make DoubleDouble from Double 168 | function DoubleToDoubleDouble(Value: Double): TDoubleDouble; inline; 169 | begin 170 | Result.Hi := Value; 171 | Result.Lo := 0; 172 | end; 173 | 174 | // Make Double from DoubleDouble 175 | function DoubleDoubleToDouble(const Value: TDoubleDouble): Double; inline; 176 | begin 177 | Result := Value.Hi; 178 | end; 179 | 180 | // Check value is +Inf or -Inf 181 | function IsInfinity(Value: Double): Boolean; inline; 182 | begin 183 | Result := (Value = 1.0 / 0.0) or (Value = -1.0 / 0.0); 184 | end; 185 | 186 | // Add two Double1 value, condition: |A| >= |B| 187 | // The "Fast2Sum" algorithm (Dekker 1971) [1] 188 | function DoubleDoubleFastAdd11(A, B: Double): TDoubleDouble; inline; 189 | begin 190 | Result.Hi := A + B; 191 | 192 | // infinity check 193 | if IsInfinity(Result.Hi) then 194 | begin 195 | exit(DoubleToDoubleDouble(Result.Hi)); 196 | end; 197 | 198 | Result.Lo := (B - (Result.Hi - A)); 199 | end; 200 | 201 | // The "2Sum" algorithm [1] 202 | function DoubleDoubleAdd11(A, B: Double): TDoubleDouble; inline; 203 | var 204 | AH, BH: Double; 205 | begin 206 | Result.Hi := A + B; 207 | 208 | // infinity check 209 | if IsInfinity(Result.Hi) then 210 | begin 211 | exit(DoubleToDoubleDouble(Result.Hi)); 212 | end; 213 | 214 | AH := Result.Hi - B; 215 | BH := Result.Hi - AH; 216 | 217 | Result.Lo := (A - AH) + (B - BH); 218 | end; 219 | 220 | // The "Veltkamp Split" algorithm [2] [3] [4] 221 | // See "Splitting into Halflength Numbers" and ALGOL procedure "mul12" in Appendix in [2] 222 | function DoubleDoubleSplit1(A: Double): TDoubleDouble; inline; 223 | const 224 | // The Splitter should be chosen equal to 2^trunc(t - t / 2) + 1, 225 | // where t is the number of binary digits in the mantissa. 226 | Splitter: Double = 134217729.0;// = 2^(53 - 53 div 2) + 1 = 2^27 + 1 227 | // Just make sure we don't have an overflow for Splitter, 228 | // InfinitySplit is 2^(e - (t - t div 2)) 229 | // where e is max exponent, t is number of binary digits. 230 | InfinitySplit: Double = 6.69692879491417e+299;// = 2^(1023 - (53 - 53 div 2)) = 2^996 231 | // just multiply by the next lower power of two to get rid of the overflow 232 | // 2^(+/-)27 + 1 = 2^(+/-)28 233 | InfinityDown: Double = 3.7252902984619140625e-09;// = 2^-(27 + 1) = 2^-28 234 | InfinityUp: Double = 268435456.0;// = 2^(27 + 1) = 2^28 235 | var 236 | Temp: Double; 237 | begin 238 | if (A > InfinitySplit) or (A < -InfinitySplit) then 239 | begin 240 | // down 241 | A := A * InfinityDown; 242 | // mul 243 | Temp := Splitter * A; 244 | Result.Hi := Temp + (A - Temp); 245 | Result.Lo := A - Result.Hi; 246 | // up 247 | Result.Hi := Result.Hi * InfinityUp; 248 | Result.Lo := Result.Lo * InfinityUp; 249 | end else 250 | begin 251 | Temp := Splitter * A; 252 | Result.Hi := Temp + (A - Temp); 253 | Result.Lo := A - Result.Hi; 254 | end; 255 | end; 256 | 257 | // Multiplication two Double1 value 258 | // The "TWO-PRODUCT" algorithm [5] 259 | function DoubleDoubleMul11(A, B: Double): TDoubleDouble;// inline; 260 | var 261 | A2, B2: TDoubleDouble; 262 | Err1, Err2, Err3: Double; 263 | begin 264 | Result.Hi := A * B; 265 | 266 | // infinity check 267 | if IsInfinity(Result.Hi) then 268 | begin 269 | exit(DoubleToDoubleDouble(Result.Hi)); 270 | end; 271 | 272 | A2 := DoubleDoubleSplit1(A); 273 | B2 := DoubleDoubleSplit1(B); 274 | 275 | Err1 := Result.Hi - (A2.Hi * B2.Hi); 276 | Err2 := Err1 - (A2.Lo * B2.Hi); 277 | Err3 := Err2 - (A2.Hi * B2.Lo); 278 | 279 | Result.Lo := (A2.Lo * B2.Lo) - Err3; 280 | end; 281 | 282 | // Multiplication Double2 by Double1 283 | // The "DWTimesFP1" algorithm [1] 284 | function DoubleDoubleMul21(const A: TDoubleDouble; B: Double): TDoubleDouble; inline; 285 | var 286 | C: TDoubleDouble; 287 | begin 288 | C := DoubleDoubleMul11(A.Hi, B); 289 | 290 | // infinity check 291 | if IsInfinity(C.Hi) then 292 | begin 293 | exit(DoubleToDoubleDouble(C.Hi)); 294 | end; 295 | 296 | Result := DoubleDoubleFastAdd11(C.Hi, A.Lo * B); 297 | Result := DoubleDoubleFastAdd11(Result.Hi, Result.Lo + C.Lo); 298 | end; 299 | 300 | // Division Double2 by Double1 301 | // The "DWDivFP2" algorithm [1] 302 | function DoubleDoubleDiv21(const A: TDoubleDouble; B: Double): TDoubleDouble; inline; 303 | var 304 | P, D: TDoubleDouble; 305 | begin 306 | Result.Hi := A.Hi / B; 307 | 308 | // infinity check 309 | if IsInfinity(Result.Hi) then 310 | begin 311 | exit(DoubleToDoubleDouble(Result.Hi)); 312 | end; 313 | 314 | P := DoubleDoubleMul11(Result.Hi, B); 315 | 316 | D.Hi := A.Hi - P.Hi; 317 | D.Lo := D.Hi - P.Lo; 318 | 319 | Result.Lo := (D.Lo + A.Lo) / B; 320 | 321 | Result := DoubleDoubleFastAdd11(Result.Hi, Result.Lo); 322 | end; 323 | 324 | // Addition Double2 and Double1 325 | // The DWPlusFP algorithm [1] 326 | function DoubleDoubleAdd21(const A: TDoubleDouble; B: Double): TDoubleDouble; inline; 327 | begin 328 | Result := DoubleDoubleAdd11(A.Hi, B); 329 | 330 | // infinity check 331 | if IsInfinity(Result.Hi) then 332 | begin 333 | exit(DoubleToDoubleDouble(Result.Hi)); 334 | end; 335 | 336 | Result.Lo := Result.Lo + A.Lo; 337 | Result := DoubleDoubleFastAdd11(Result.Hi, Result.Lo); 338 | end; 339 | 340 | // ------------------------------------------------------------------------------------------------- 341 | 342 | const 343 | QNAN = -(0.0 / 0.0);// +NAN 344 | 345 | type 346 | TDigitsArray = array [0 .. 17 * 2 - 1] of Byte;// Max digits in Double value * 2 347 | 348 | TFixedDecimal = record 349 | Count: Integer; 350 | Exponent: Integer; 351 | IsNegative: Boolean; 352 | Digits: TDigitsArray; 353 | end; 354 | 355 | function TextPrefixLength(const Text: PWideChar; const Prefix: PWideChar): Integer; 356 | var 357 | I: Integer; 358 | begin 359 | I := 0; 360 | while (Text[I] = Prefix[I]) or 361 | ((Text[I] >= 'A') and (Text[I] <= 'Z') and (WideChar(Ord(Text[I]) + 32) = Prefix[I])) do 362 | begin 363 | if Text[I] = #0 then 364 | begin 365 | break; 366 | end; 367 | Inc(I); 368 | end; 369 | 370 | exit(I); 371 | end; 372 | 373 | function ReadSpecial(var Number: Double; const Text: PWideChar; out TextEnd: PWideChar): Boolean; 374 | var 375 | P: PWideChar; 376 | IsNegative: Boolean; 377 | Len: Integer; 378 | begin 379 | // clean 380 | IsNegative := False; 381 | 382 | // read from start 383 | P := Text; 384 | 385 | // read sign 386 | case P^ of 387 | '+': 388 | begin 389 | Inc(P); 390 | end; 391 | '-': 392 | begin 393 | IsNegative := True; 394 | Inc(P); 395 | end; 396 | end; 397 | 398 | // special 399 | case P^ of 400 | 'I', 'i': 401 | begin 402 | Len := TextPrefixLength(P, 'infinity'); 403 | if (Len = 3) or (Len = 8) then 404 | begin 405 | Number := 1.0 / 0.0; 406 | if IsNegative then 407 | begin 408 | Number := -Number; 409 | end; 410 | P := P + Len; 411 | TextEnd := P; 412 | exit(True); 413 | end; 414 | end; 415 | 'N', 'n': 416 | begin 417 | Len := TextPrefixLength(P, 'nan'); 418 | if Len = 3 then 419 | begin 420 | Number := QNAN; 421 | if IsNegative then 422 | begin 423 | Number := -Number; 424 | end; 425 | P := P + Len; 426 | TextEnd := P; 427 | exit(True); 428 | end; 429 | end; 430 | end; 431 | 432 | // fail 433 | TextEnd := Text; 434 | exit(False); 435 | end; 436 | 437 | function ReadTextToFixedDecimal(out Decimal: TFixedDecimal; const Text: PWideChar; out EndText: PWideChar): Boolean; 438 | const 439 | ClipExponent = 1000000; 440 | var 441 | P, PStartExponent: PWideChar; 442 | HasPoint, HasDigit: Boolean; 443 | ExponentSign: Integer; 444 | Exponent: Integer; 445 | begin 446 | // clean 447 | Decimal.Count := 0; 448 | Decimal.IsNegative := False; 449 | Decimal.Exponent := -1; 450 | 451 | // read from start 452 | P := Text; 453 | 454 | // read sign 455 | case P^ of 456 | '+': 457 | begin 458 | Inc(P); 459 | end; 460 | '-': 461 | begin 462 | Decimal.IsNegative := True; 463 | Inc(P); 464 | end; 465 | end; 466 | 467 | // read mantissa 468 | HasDigit := False;// has read any digit (0..9) 469 | HasPoint := False;// has read decimal point 470 | while True do 471 | begin 472 | case P^ of 473 | '0'..'9': 474 | begin 475 | if (Decimal.Count <> 0) or (P^ <> '0') then 476 | begin 477 | // save digit 478 | if Decimal.Count < Length(Decimal.Digits) then 479 | begin 480 | Decimal.Digits[Decimal.Count] := Ord(P^) - Ord('0'); 481 | Inc(Decimal.Count); 482 | end; 483 | // inc exponenta 484 | if (not HasPoint) and (Decimal.Exponent < ClipExponent) then 485 | begin 486 | Inc(Decimal.Exponent); 487 | end; 488 | end else 489 | begin 490 | // skip zero (dec exponenta) 491 | if HasPoint and (Decimal.Exponent > -ClipExponent) then 492 | begin 493 | Dec(Decimal.Exponent); 494 | end; 495 | end; 496 | HasDigit := True; 497 | end; 498 | '.': 499 | begin 500 | if HasPoint then 501 | begin 502 | EndText := P; 503 | exit(True);// make 504 | end; 505 | HasPoint := True; 506 | end; 507 | else 508 | break; 509 | end; 510 | Inc(P); 511 | end; 512 | 513 | if not HasDigit then 514 | begin 515 | EndText := Text; 516 | exit(False);// fail 517 | end; 518 | 519 | // read exponenta 520 | if (P^ = 'e') or (P^ = 'E') then 521 | begin 522 | PStartExponent := P; 523 | Inc(P); 524 | 525 | Exponent := 0; 526 | ExponentSign := 1; 527 | 528 | // check sign 529 | case P^ of 530 | '+': 531 | begin 532 | Inc(P); 533 | end; 534 | '-': 535 | begin 536 | ExponentSign := -1; 537 | Inc(P); 538 | end; 539 | end; 540 | 541 | // read 542 | if (P^ >= '0') and (P^ <= '9') then 543 | begin 544 | while (P^ >= '0') and (P^ <= '9') do 545 | begin 546 | Exponent := Exponent * 10 + (Ord(P^) - Ord('0')); 547 | if Exponent > ClipExponent then 548 | begin 549 | Exponent := ClipExponent; 550 | end; 551 | Inc(P); 552 | end; 553 | end else 554 | begin 555 | EndText := PStartExponent;// revert 556 | exit(True);// make 557 | end; 558 | 559 | // fix 560 | Decimal.Exponent := Decimal.Exponent + ExponentSign * Exponent; 561 | end; 562 | 563 | EndText := P; 564 | exit(True);// make 565 | end; 566 | 567 | function FixedDecimalToDouble(var Decimal: TFixedDecimal): Double; 568 | const 569 | LastAccuracyExponent10 = 22;// for Double 570 | LastAccuracyPower10 = 1e22;// for Double 571 | PowerOf10: array [0..LastAccuracyExponent10] of Double = ( 572 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 573 | 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 574 | ); 575 | MaxSafeInt = 9007199254740991;// (2^53−1) for Double 576 | MaxSafeHi = (MaxSafeInt - 9) div 10;// for X * 10 + 9 577 | var 578 | I: Integer; 579 | Number: TDoubleDouble; 580 | Exponent: Integer; 581 | begin 582 | Number := DoubleToDoubleDouble(0.0); 583 | 584 | // set mantissa 585 | for I := 0 to Decimal.Count - 1 do 586 | begin 587 | if Number.Hi <= MaxSafeHi then 588 | begin 589 | Number.Hi := Number.Hi * 10;// * 10 590 | Number.Hi := Number.Hi + Decimal.Digits[I];// + Digit 591 | end else 592 | begin 593 | Number := DoubleDoubleMul21(Number, 10.0);// * 10 594 | Number := DoubleDoubleAdd21(Number, Decimal.Digits[I]);// + Digit 595 | end; 596 | end; 597 | 598 | // set exponent 599 | Exponent := Decimal.Exponent - Decimal.Count + 1; 600 | 601 | // positive exponent 602 | while Exponent > 0 do 603 | begin 604 | if Exponent > LastAccuracyExponent10 then 605 | begin 606 | // * e22 607 | Number := DoubleDoubleMul21(Number, LastAccuracyPower10); 608 | // overflow break 609 | if IsInfinity(DoubleDoubleToDouble(Number)) then 610 | begin 611 | break; 612 | end; 613 | Exponent := Exponent - LastAccuracyExponent10; 614 | end else 615 | begin 616 | // * eX 617 | Number := DoubleDoubleMul21(Number, PowerOf10[Exponent]); 618 | break; 619 | end; 620 | end; 621 | 622 | // negative exponent 623 | while Exponent < 0 do 624 | begin 625 | if Exponent < -LastAccuracyExponent10 then 626 | begin 627 | // / e22 628 | Number := DoubleDoubleDiv21(Number, LastAccuracyPower10); 629 | // underflow break 630 | if DoubleDoubleToDouble(Number) = 0.0 then 631 | begin 632 | break; 633 | end; 634 | Exponent := Exponent + LastAccuracyExponent10; 635 | end else 636 | begin 637 | // / eX 638 | Number := DoubleDoubleDiv21(Number, PowerOf10[-Exponent]); 639 | break; 640 | end; 641 | end; 642 | 643 | // make result 644 | Result := DoubleDoubleToDouble(Number); 645 | 646 | // fix sign 647 | if Decimal.IsNegative then 648 | begin 649 | Result := -Result; 650 | end; 651 | end; 652 | 653 | // ------------------------------------------------------------------------------------------------- 654 | 655 | {$IFNDEF PURE_PARSE_FLOAT_DISABLE_SET_NORMAL_FPU_SETTINGS} 656 | type 657 | TFPUSettings = record 658 | {$IF DEFINED(CPUX86)} 659 | CW: UInt16; 660 | {$ELSEIF DEFINED(CPUX64)} 661 | MXCSR: UInt32; 662 | {$ELSE} 663 | ExceptionMask: TFPUExceptionMask; 664 | RoundingMode: TFPURoundingMode; 665 | PrecisionMode: TFPUPrecisionMode; 666 | {$ENDIF} 667 | end; 668 | 669 | procedure SetNormalDoubleFPUSettings(out FPUSettings: TFPUSettings); inline; 670 | begin 671 | {$IF DEFINED(CPUX86)} 672 | FPUSettings.CW := Get8087CW(); 673 | Set8087CW($123F);// Mask all exceptions, Double (https://www.club155.ru/x86internalreg-fpucw) 674 | {$ELSEIF DEFINED(CPUX64)} 675 | FPUSettings.MXCSR := GetMXCSR(); 676 | SetMXCSR($1F80);// Mask all exceptions (https://www.club155.ru/x86internalreg-simd) 677 | {$ELSE} 678 | FPUSettings.ExceptionMask := SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]); 679 | FPUSettings.RoundingMode := SetRoundMode(rmNearest); 680 | FPUSettings.PrecisionMode := SetPrecisionMode(pmDouble); 681 | {$ENDIF} 682 | end; 683 | 684 | procedure RestoreFPUSettings(const FPUSettings: TFPUSettings); inline; 685 | begin 686 | {$IF DEFINED(CPUX86)} 687 | Set8087CW(FPUSettings.CW); 688 | {$ELSEIF DEFINED(CPUX64)} 689 | SetMXCSR(FPUSettings.MXCSR); 690 | {$ELSE} 691 | SetExceptionMask(FPUSettings.ExceptionMask); 692 | SetRoundMode(FPUSettings.RoundingMode); 693 | SetPrecisionMode(FPUSettings.PrecisionMode); 694 | {$ENDIF} 695 | end; 696 | {$ENDIF} 697 | 698 | // ------------------------------------------------------------------------------------------------- 699 | 700 | function ParseFloat(const TextPtr: PWideChar; var Value: Double; out EndPtr: PWideChar): Boolean; 701 | var 702 | Decimal: TFixedDecimal; 703 | {$IFNDEF PURE_PARSE_FLOAT_DISABLE_SET_NORMAL_FPU_SETTINGS} 704 | FPUSettings: TFPUSettings; 705 | {$ENDIF} 706 | begin 707 | // try read inf/nan 708 | if ReadSpecial(Value, TextPtr, EndPtr) then 709 | begin 710 | // read done 711 | exit(True); 712 | end; 713 | 714 | // try read number 715 | if ReadTextToFixedDecimal(Decimal, TextPtr, EndPtr) then 716 | begin 717 | {$IFNDEF PURE_PARSE_FLOAT_DISABLE_SET_NORMAL_FPU_SETTINGS} 718 | // Change FPU settings to Double Precision, Banker Rounding, Mask FPU Exceptions 719 | SetNormalDoubleFPUSettings(FPUSettings); 720 | try 721 | // Convert Decimal to Double 722 | Value := FixedDecimalToDouble(Decimal); 723 | finally 724 | // Restore FPU settings 725 | RestoreFPUSettings(FPUSettings); 726 | end; 727 | {$ELSE} 728 | // convert Decimal to Double 729 | Value := FixedDecimalToDouble(Decimal); 730 | {$ENDIF} 731 | 732 | // read done 733 | exit(True); 734 | end; 735 | 736 | // fail 737 | exit(False); 738 | end; 739 | 740 | function ParseFloat(const TextPtr: PWideChar; var Value: Double): Boolean; 741 | var 742 | DeadBeef: PWideChar; 743 | begin 744 | Result := ParseFloat(TextPtr, Value, DeadBeef); 745 | end; 746 | 747 | // ------------------------------------------------------------------------------------------------- 748 | 749 | procedure SkipSpaces(var TextPtr: PWideChar); inline; 750 | begin 751 | while (TextPtr^ = ' ') or (TextPtr^ = #11) do 752 | begin 753 | Inc(TextPtr); 754 | end; 755 | end; 756 | 757 | function TryCharsToFloat(const TextPtr: PWideChar; out Value: Double): Boolean; 758 | var 759 | P: PWideChar; 760 | begin 761 | Value := 0.0; 762 | 763 | // read from start 764 | P := TextPtr; 765 | 766 | // skip leading spaces 767 | SkipSpaces(P); 768 | 769 | // try parse 770 | if not ParseFloat(P, Value, P) then 771 | begin 772 | Exit(False); 773 | end; 774 | 775 | // skip trailing spaces 776 | SkipSpaces(P); 777 | 778 | // check normal end 779 | if P^ <> #0 then 780 | begin 781 | exit(False); 782 | end; 783 | 784 | // ok 785 | exit(True); 786 | end; 787 | 788 | function CharsToFloat(const TextPtr: PWideChar; const Default: Double = 0.0): Double; 789 | begin 790 | if not TryCharsToFloat(TextPtr, Result) then 791 | begin 792 | Result := Default; 793 | end; 794 | end; 795 | 796 | function TryStringToFloat(const Str: UnicodeString; out Value: Double): Boolean; 797 | begin 798 | Result := TryCharsToFloat(PWideChar(Str), Value); 799 | end; 800 | 801 | function StringToFloat(const Str: UnicodeString; const Default: Double = 0.0): Double; 802 | begin 803 | Result := CharsToFloat(PWideChar(Str), Default); 804 | end; 805 | 806 | end. 807 | -------------------------------------------------------------------------------- /Pascal/PureParseFloat/readme.md: -------------------------------------------------------------------------------- 1 | ### Reference implementation of the Pure Parse Float algorithm 2 | 3 | PureParseFloat.pas - main pas file 4 | PureParseFloat.inc - config file 5 | -------------------------------------------------------------------------------- /Pascal/Test/IeeeConvert32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turborium/PureParseFloat/2b9782179e3a5e81d4258522081b221ba52fc05a/Pascal/Test/IeeeConvert32.dll -------------------------------------------------------------------------------- /Pascal/Test/IeeeConvert64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turborium/PureParseFloat/2b9782179e3a5e81d4258522081b221ba52fc05a/Pascal/Test/IeeeConvert64.dll -------------------------------------------------------------------------------- /Pascal/Test/MainUnit.pas: -------------------------------------------------------------------------------- 1 | // Pure Parse Float Test Unit 2 | unit MainUnit; 3 | 4 | {$IFDEF FPC} 5 | {$MODE DELPHIUNICODE} 6 | {$WARN 5036 off} 7 | {$WARN 4104 off} 8 | {$WARN 6018 off} 9 | {$ENDIF} 10 | 11 | {$ASSERTIONS ON} 12 | {$WARN SYMBOL_PLATFORM OFF} 13 | 14 | interface 15 | 16 | uses 17 | Math, StrUtils, SysUtils, Classes, PureParseFloat, Windows; 18 | 19 | procedure RunTests(); 20 | 21 | implementation 22 | 23 | const 24 | DigitCount = 31; 25 | SizeLarge = 10000000; 26 | SizeMedium = 1000000; 27 | SizeSmall = 100000; 28 | 29 | var 30 | TestCount: Integer = SizeMedium; 31 | Mode: (ModePure, ModeDelphi, ModeMicrosoft) = ModePure; 32 | TestCVersion: Boolean = False; 33 | 34 | function IeeeCharsToDouble(Str: PAnsiChar; PtrEnd: PPAnsiChar): Double; cdecl; 35 | {$IFDEF CPUX64} 36 | external 'IeeeConvert64.dll' name 'IeeeCharsToDouble' 37 | {$ELSE} 38 | external 'IeeeConvert32.dll' name 'IeeeCharsToDouble' 39 | {$ENDIF}; 40 | 41 | function IeeeDoubleToChars(Buf32: PAnsiChar; Value: Double): PAnsiChar; cdecl; 42 | {$IFDEF CPUX64} 43 | external 'IeeeConvert64.dll' name 'IeeeDoubleToChars' 44 | {$ELSE} 45 | external 'IeeeConvert32.dll' name 'IeeeDoubleToChars' 46 | {$ENDIF}; 47 | 48 | function MsvcrtStrtod(Str: PAnsiChar; PtrEnd: PPAnsiChar): Double; cdecl; external 'MSVCRT.DLL' name 'strtod'; 49 | function MsvcrtWcstod(Str: PWideChar; PtrEnd: PPWideChar): Double; cdecl; external 'MSVCRT.DLL' name 'wcstod'; 50 | // function UcrtbaseStrtod(Str: PAnsiChar; PtrEnd: PPAnsiChar): Double; cdecl; external 'UCRTBASE.DLL' name 'strtod'; 51 | 52 | // c version here: 53 | var 54 | pure_parse_float_library: HMODULE; 55 | pure_parse_float: function(Str: PAnsiChar; Double: PDouble; PtrEnd: PPAnsiChar): Double; cdecl; 56 | dll_pure_parse_float_library: HMODULE; 57 | dll_pure_parse_float: function(Str: PAnsiChar; Double: PDouble; PtrEnd: PPAnsiChar): Double; cdecl; 58 | 59 | // from ieee_convert32.dll/ieee_convert64.dll 60 | function IeeeStringToDouble(Str: UnicodeString; out Value: Double): Integer; 61 | var 62 | AnsiStr: AnsiString; 63 | PEnd: PAnsiChar; 64 | begin 65 | AnsiStr := AnsiString(Str);// unicode -> ansi 66 | 67 | Value := IeeeCharsToDouble(PAnsiChar(AnsiStr), @PEnd);// convert 68 | 69 | Result := PEnd - PAnsiChar(AnsiStr);// calc length 70 | end; 71 | 72 | // from ieee_convert32.dll/ieee_convert64.dll 73 | function IeeeDoubleToString(Value: Double): UnicodeString; 74 | var 75 | Buf32: array [0..31] of AnsiChar; 76 | begin 77 | IeeeDoubleToChars(Buf32, Value); 78 | 79 | Result := UnicodeString(Buf32);// ansi -> unicode 80 | end; 81 | 82 | // from PureParseFloat.pas 83 | function PureStringToDouble(Str: UnicodeString; out Value: Double): Integer; 84 | var 85 | PEnd: PWideChar; 86 | begin 87 | Value := 0.0; 88 | 89 | ParseFloat(PWideChar(Str), Value, PEnd); 90 | 91 | Result := PEnd - PWideChar(Str); 92 | end; 93 | 94 | // from pure_parse_float.dll 95 | function PureStringToDoubleC(Str: UnicodeString; out Value: Double): Integer; 96 | var 97 | AnsiStr: AnsiString; 98 | PEnd: PAnsiChar; 99 | begin 100 | Value := 0.0; 101 | AnsiStr := AnsiString(Str);// unicode -> ansi 102 | 103 | pure_parse_float(PAnsiChar(AnsiStr), @Value, @PEnd); 104 | 105 | Result := PEnd - PAnsiChar(AnsiStr); 106 | end; 107 | 108 | // from user dll 109 | function PureStringToDoubleUserDll(Str: UnicodeString; out Value: Double): Integer; 110 | var 111 | AnsiStr: AnsiString; 112 | PEnd: PAnsiChar; 113 | begin 114 | Value := 0.0; 115 | AnsiStr := AnsiString(Str);// unicode -> ansi 116 | 117 | dll_pure_parse_float(PAnsiChar(AnsiStr), @Value, @PEnd); 118 | 119 | Result := PEnd - PAnsiChar(AnsiStr); 120 | end; 121 | 122 | // form MSVCRT.dll 123 | function MsvcrtStringToDouble(Str: UnicodeString; out Value: Double): Integer; 124 | var 125 | AnsiStr: AnsiString; 126 | PEnd: PAnsiChar; 127 | begin 128 | AnsiStr := AnsiString(Str);// unicode -> ansi 129 | 130 | Value := MsvcrtStrtod(PAnsiChar(AnsiStr), @PEnd); 131 | 132 | Result := PEnd - PAnsiChar(AnsiStr);// calc length 133 | end; 134 | 135 | // form UCRTBASE.dll 136 | (*function UcrtbaseStringToDouble(Str: UnicodeString; out Value: Double): Integer; 137 | var 138 | AnsiStr: AnsiString; 139 | PEnd: PAnsiChar; 140 | begin 141 | AnsiStr := AnsiString(Str);// unicode -> ansi 142 | 143 | Value := UcrtbaseStrtod(PAnsiChar(AnsiStr), @PEnd); 144 | 145 | Result := PEnd - PAnsiChar(AnsiStr);// calc length 146 | end;*) 147 | 148 | // random 149 | 150 | var 151 | TestRandSeed: UInt32; 152 | 153 | procedure SetTestSeed(const Seed: Integer); 154 | begin 155 | TestRandSeed := Seed; 156 | end; 157 | 158 | {$R-} {$Q-} 159 | function TestRandom(const Max: Integer): Integer; 160 | begin 161 | Result := Int32(TestRandSeed) * $08088405 + 1; 162 | TestRandSeed := Result; 163 | 164 | Result := (UInt64(UInt32(Max)) * UInt64(UInt32(Result))) shr 32; 165 | Assert(Result >= 0); 166 | end; 167 | {$R+} {$Q+} 168 | 169 | procedure AssertEqual(S: UnicodeString; WarnPrint: Boolean = False); 170 | var 171 | A, B: Double; 172 | ABin: UInt64 absolute A; 173 | BBin: UInt64 absolute B; 174 | CountA, CountB: Integer; 175 | begin 176 | CountA := IeeeStringToDouble(S, A); 177 | 178 | if @dll_pure_parse_float <> nil then 179 | begin 180 | CountB := PureStringToDoubleUserDll(S, B); 181 | if WarnPrint then 182 | begin 183 | if CountA <> CountB then 184 | begin 185 | Writeln(' Fail Length!'); 186 | Writeln(' String: "', S, '"'); 187 | Writeln(' Expected: ', CountA, ', Actual: ', CountB); 188 | end; 189 | end; 190 | Assert(CountA = CountB); 191 | if WarnPrint then 192 | begin 193 | if ABin <> BBin then 194 | begin 195 | Writeln(' Fail Bin!'); 196 | Writeln(' String: "', S, '"'); 197 | Writeln(' Expected: 0x', IntToHex(ABin, 8), ', Actual: 0x', IntToHex(BBin, 8)); 198 | end; 199 | end; 200 | Assert(ABin = BBin); 201 | exit; 202 | end; 203 | 204 | case Mode of 205 | ModePure: 206 | begin 207 | if not TestCVersion then 208 | CountB := PureStringToDouble(S, B)// pascal 209 | else 210 | CountB := PureStringToDoubleC(S, B);// c 211 | Assert(CountA = CountB); 212 | end; 213 | ModeDelphi: 214 | begin 215 | {$IFNDEF FPC} 216 | SysUtils.TextToFloat(PWideChar(S), B); 217 | {$ELSE} 218 | SysUtils.TextToFloat(PAnsiChar(AnsiString(S)), B); 219 | {$ENDIF} 220 | if IsNan(A) and IsNan(B) then 221 | exit; 222 | end; 223 | else 224 | begin 225 | CountB := MsvcrtStringToDouble(S, B); 226 | if IsNan(A) and IsNan(B) then 227 | exit; 228 | Assert(CountA = CountB); 229 | end; 230 | end; 231 | 232 | // check equal numbers 233 | Assert(ABin = BBin); 234 | end; 235 | 236 | procedure AssertEqualWarn(S: UnicodeString); 237 | begin 238 | AssertEqual(S, True); 239 | end; 240 | 241 | type 242 | TUlpDiffType = (udtSame, udtOne, udtMoreThanOne); 243 | {$R-} {$Q-} 244 | // True - 0 or 1, False - more than 1 245 | function UlpDiff(S: UnicodeString): TUlpDiffType; 246 | var 247 | A, B: Double; 248 | ABin: Int64 absolute A; 249 | BBin: Int64 absolute B; 250 | begin 251 | IeeeStringToDouble(S, A); 252 | 253 | if @dll_pure_parse_float <> nil then 254 | begin 255 | PureStringToDoubleUserDll(S, B); 256 | if ABin = BBin then 257 | begin 258 | exit(udtSame); 259 | end; 260 | if (ABin - 1 = BBin) or (ABin + 1 = BBin) then 261 | begin 262 | exit(udtOne); 263 | end; 264 | exit(udtMoreThanOne); 265 | end; 266 | 267 | case Mode of 268 | ModePure: 269 | begin 270 | if not TestCVersion then 271 | PureStringToDouble(S, B)// pascal 272 | else 273 | PureStringToDoubleC(S, B);// c 274 | end; 275 | ModeDelphi: 276 | begin 277 | {$IFNDEF FPC} 278 | SysUtils.TextToFloat(PWideChar(S), B); 279 | {$ELSE} 280 | SysUtils.TextToFloat(PAnsiChar(AnsiString(S)), B); 281 | {$ENDIF} 282 | end; 283 | else 284 | begin 285 | MsvcrtStringToDouble(S, B); 286 | end; 287 | end; 288 | 289 | if ABin = BBin then 290 | begin 291 | exit(udtSame); 292 | end; 293 | 294 | if (ABin - 1 = BBin) or (ABin + 1 = BBin) then 295 | begin 296 | exit(udtOne); 297 | end; 298 | 299 | exit(udtMoreThanOne); 300 | end; 301 | {$R+} {$Q+} 302 | 303 | type 304 | TTest = record 305 | Name: UnicodeString; 306 | Proc: TProcedure; 307 | end; 308 | 309 | function MakeTest(Name: UnicodeString; Proc: TProcedure): TTest; 310 | begin 311 | Result.Name := Name; 312 | Result.Proc := Proc; 313 | end; 314 | 315 | procedure SimpleIntegerTest(); 316 | begin 317 | AssertEqualWarn('0'); 318 | AssertEqualWarn('666999'); 319 | AssertEqualWarn('+12345'); 320 | AssertEqualWarn('100000000000000001'); 321 | AssertEqualWarn('-329'); 322 | AssertEqualWarn('00000000000004'); 323 | AssertEqualWarn('-00000000000009'); 324 | AssertEqualWarn('+00000000000009'); 325 | end; 326 | 327 | procedure SimpleFloatTest(); 328 | begin 329 | AssertEqualWarn('0.0'); 330 | AssertEqualWarn('10.2'); 331 | AssertEqualWarn('-1.2'); 332 | AssertEqualWarn('0.2'); 333 | end; 334 | 335 | procedure SimpleExponentTest(); 336 | begin 337 | AssertEqualWarn('0.0e0'); 338 | AssertEqualWarn('10.2e10'); 339 | AssertEqualWarn('-1.2e99'); 340 | AssertEqualWarn('0.2e-12'); 341 | end; 342 | 343 | procedure SpecialStringTest(); 344 | begin 345 | AssertEqualWarn('Nan'); 346 | AssertEqualWarn('Inf'); 347 | AssertEqualWarn('+Inf'); 348 | AssertEqualWarn('-Inf'); 349 | AssertEqualWarn('+Infinity'); 350 | AssertEqualWarn('-Infinity'); 351 | AssertEqualWarn('+Nan'); 352 | AssertEqualWarn('-Nan'); 353 | end; 354 | 355 | procedure HardParseTest(); 356 | begin 357 | AssertEqualWarn('.e1');// not a number 358 | AssertEqualWarn('1.e1'); 359 | AssertEqualWarn('.1'); 360 | AssertEqualWarn('.1e000000000010'); 361 | AssertEqualWarn('-.1e-000000000010'); 362 | AssertEqualWarn('1.'); 363 | AssertEqualWarn('2e.10'); 364 | AssertEqualWarn('-0.00e-214'); 365 | AssertEqualWarn('0e320'); 366 | AssertEqualWarn('.123e10'); 367 | AssertEqualWarn('123' + DupeString('0', 10000) + 'e-10000');// 123.0 368 | AssertEqualWarn('1234567891234567891234567891234' + DupeString('0', 10000) + 'e-10000');// 1234567891234567891234567891234.0 369 | AssertEqualWarn('-0.' + DupeString('0', 10000) + '9e10000');// -0.9 370 | // special values 371 | AssertEqualWarn('6.69692879491417e+299'); 372 | AssertEqualWarn('3.7252902984619140625e-09'); 373 | AssertEqualWarn('1.1754943508222875080e-38'); 374 | AssertEqualWarn('1.4012984643248170709e-45'); 375 | AssertEqualWarn('340282346638528859811704183484516925440.0'); 376 | AssertEqualWarn('2.2250738585072013831e-308'); 377 | AssertEqualWarn('4.9406564584124654418e-324'); 378 | AssertEqualWarn('1.7976931348623157081e+308'); 379 | AssertEqualWarn('1.8145860519450699870567321328132e-5'); 380 | AssertEqualWarn('0.34657359027997265470861606072909'); 381 | // inf 382 | AssertEqualWarn('1000000000000e307');// +inf 383 | AssertEqualWarn('-1000000000000e307');// -inf 384 | // demo values 385 | AssertEqualWarn('18014398509481993'); 386 | end; 387 | 388 | procedure RandomIntegerTest(); 389 | var 390 | I, J: Integer; 391 | S: UnicodeString; 392 | begin 393 | SetTestSeed(29); 394 | 395 | for I := 0 to TestCount - 1 do 396 | begin 397 | S := ''; 398 | // make sign 399 | if TestRandom(2) = 0 then 400 | S := S + '-' 401 | else 402 | S := S + '+'; 403 | // make digits 404 | for J := 0 to DigitCount - 1 do 405 | begin 406 | S := S + Char(Ord('0') + TestRandom(10)); 407 | end; 408 | // check 409 | AssertEqual(S); 410 | end; 411 | end; 412 | 413 | procedure RandomDoubleTest(); 414 | var 415 | I, J: Integer; 416 | S: UnicodeString; 417 | begin 418 | SetTestSeed(777); 419 | // 420 | for I := 0 to TestCount - 1 do 421 | begin 422 | S := ''; 423 | // make sign 424 | if TestRandom(2) = 0 then 425 | S := S + '-' 426 | else 427 | S := S + '+'; 428 | // make digits 429 | for J := 0 to DigitCount - 1 do 430 | begin 431 | S := S + Char(Ord('0') + TestRandom(10)); 432 | end; 433 | // dot 434 | Insert('.', S, 2 + TestRandom(Length(S) - 2 + 1)); 435 | // check 436 | AssertEqual(S); 437 | end; 438 | end; 439 | 440 | procedure RoundTest(); 441 | var 442 | I, J: Integer; 443 | S: UnicodeString; 444 | begin 445 | SetTestSeed(1337); 446 | for I := 0 to TestCount - 1 do 447 | begin 448 | S := '1'; 449 | for J := 0 to DigitCount - 1 - 1 do 450 | begin 451 | case TestRandom(30) of 452 | 1: S := S + '1'; 453 | 5: S := S + '5'; 454 | 6: S := S + '6'; 455 | else 456 | S := S + '0'; 457 | end; 458 | end; 459 | S := S + 'e' + IntToStr(291 - TestRandom(291 * 2 + 1)); 460 | AssertEqual(S); 461 | end; 462 | end; 463 | 464 | procedure RegularDoubleTest(); 465 | var 466 | I, J: Integer; 467 | S: UnicodeString; 468 | begin 469 | SetTestSeed(57005); 470 | for I := 0 to TestCount - 1 do 471 | begin 472 | S := ''; 473 | // make sign 474 | if TestRandom(2) = 0 then 475 | S := S + '-' 476 | else 477 | S := S + '+'; 478 | // make digits 479 | for J := 0 to TestRandom(DigitCount - 1) + 1 do 480 | begin 481 | S := S + Char(Ord('0') + TestRandom(10)); 482 | end; 483 | // dot 484 | Insert('.', S, 2 + TestRandom(Length(S) - 2 + 1)); 485 | // expo 486 | S := S + 'e' + IntToStr(291 - TestRandom(291 * 2 + 1)); 487 | // test 488 | AssertEqual(S); 489 | end; 490 | end; 491 | 492 | procedure BigDoubleTest(); 493 | var 494 | I, J: Integer; 495 | S: UnicodeString; 496 | begin 497 | SetTestSeed(57005); 498 | for I := 0 to TestCount - 1 do 499 | begin 500 | S := ''; 501 | // make sign 502 | if TestRandom(2) = 0 then 503 | S := S + '-' 504 | else 505 | S := S + '+'; 506 | // make digits 507 | for J := 0 to TestRandom(DigitCount - 1) + 1 do 508 | begin 509 | S := S + Char(Ord('0') + TestRandom(10)); 510 | end; 511 | // dot 512 | Insert('.', S, 2 + TestRandom(Length(S) - 2 + 1)); 513 | // expo 514 | S := S + 'e' + IntToStr(280 + TestRandom(30)); 515 | // test 516 | AssertEqual(S); 517 | end; 518 | end; 519 | 520 | procedure HardOneUlpDiffTest(); 521 | var 522 | I, J: Integer; 523 | S: UnicodeString; 524 | begin 525 | SetTestSeed(47806); 526 | for I := 0 to TestCount - 1 do 527 | begin 528 | S := ''; 529 | // make sign 530 | if TestRandom(2) = 0 then 531 | S := S + '-' 532 | else 533 | S := S + '+'; 534 | // make digits 535 | for J := 0 to TestRandom(200) + 1 do 536 | begin 537 | S := S + Char(Ord('0') + TestRandom(10)); 538 | end; 539 | // expo 540 | S := S + 'e' + IntToStr(-290 - TestRandom(100)); 541 | // test 542 | Assert(UlpDiff(S) <> udtMoreThanOne); 543 | end; 544 | end; 545 | 546 | procedure HardRoundOneUlpDiffTest(); 547 | var 548 | I, J: Integer; 549 | S: UnicodeString; 550 | begin 551 | SetTestSeed(2077); 552 | for I := 0 to TestCount - 1 do 553 | begin 554 | S := '1'; 555 | for J := 0 to 10 + TestRandom(50) do 556 | begin 557 | case TestRandom(30) of 558 | 1: S := S + '1'; 559 | 5: S := S + '5'; 560 | 6: S := S + '6'; 561 | else 562 | S := S + '0'; 563 | end; 564 | end; 565 | S := S + 'e' + IntToStr(-290 - TestRandom(100)); 566 | Assert(UlpDiff(S) <> udtMoreThanOne); 567 | end; 568 | end; 569 | 570 | procedure RegularOneUlpDiffTest(); 571 | var 572 | I, J: Integer; 573 | S: UnicodeString; 574 | Diff: TUlpDiffType; 575 | OneUlpErrorCount: Integer; 576 | MoreUlpErrorCount: Integer; 577 | begin 578 | SetTestSeed(237); 579 | 580 | OneUlpErrorCount := 0; 581 | MoreUlpErrorCount := 0; 582 | for I := 0 to TestCount - 1 do 583 | begin 584 | S := ''; 585 | // make sign 586 | if TestRandom(2) = 0 then 587 | S := S + '-' 588 | else 589 | S := S + '+'; 590 | // make digits 591 | for J := 0 to TestRandom(30) + 1 do 592 | begin 593 | S := S + Char(Ord('0') + TestRandom(10)); 594 | end; 595 | // dot 596 | Insert('.', S, 2 + TestRandom(Length(S) - 2 + 1)); 597 | // expo 598 | S := S + 'e' + IntToStr(308 - TestRandom(308 + 324 + 10 + 1)); 599 | // test 600 | Diff := UlpDiff(S); 601 | if Diff = udtOne then 602 | OneUlpErrorCount := OneUlpErrorCount + 1 603 | else if Diff = udtMoreThanOne then 604 | MoreUlpErrorCount := MoreUlpErrorCount + 1; 605 | end; 606 | 607 | // display 608 | Writeln(' Number Count: ', TestCount); 609 | Writeln(' One ULP Error Count: ', OneUlpErrorCount); 610 | case TestCount of 611 | 100000: Writeln(' Expected: ', 39); 612 | 1000000: Writeln(' Expected: ', 328); 613 | 10000000: Writeln(' Expected: ', 3386); 614 | end; 615 | if MoreUlpErrorCount <> 0 then 616 | Writeln(' More Than One ULP Error Count (Fail Count): ', MoreUlpErrorCount); 617 | Writeln(' Percent Error: ', ((OneUlpErrorCount + MoreUlpErrorCount) / TestCount) * 100:0:3, '%'); 618 | if MoreUlpErrorCount > 0 then 619 | begin 620 | Writeln(' One ULP Error: ', (OneUlpErrorCount / TestCount) * 100:0:3, '%'); 621 | Writeln(' More ULP Error: ', (MoreUlpErrorCount / TestCount) * 100:0:3, '%'); 622 | end; 623 | 624 | // final check 625 | Assert(MoreUlpErrorCount = 0); 626 | end; 627 | 628 | procedure LongNumberOneUlpDiffTest(); 629 | var 630 | I, J: Integer; 631 | S: UnicodeString; 632 | Diff: TUlpDiffType; 633 | OneUlpErrorCount: Integer; 634 | MoreUlpErrorCount: Integer; 635 | begin 636 | SetTestSeed(420); 637 | 638 | OneUlpErrorCount := 0; 639 | MoreUlpErrorCount := 0; 640 | for I := 0 to TestCount - 1 do 641 | begin 642 | S := ''; 643 | // make sign 644 | if TestRandom(2) = 0 then 645 | S := S + '-' 646 | else 647 | S := S + '+'; 648 | // make digits 649 | for J := 0 to TestRandom(200) + 1 do 650 | begin 651 | S := S + Char(Ord('0') + TestRandom(10)); 652 | end; 653 | // dot 654 | Insert('.', S, 2 + TestRandom(Length(S) - 2 + 1)); 655 | // expo 656 | S := S + 'e' + IntToStr(308 - TestRandom(308 + 324 + 10 + 1)); 657 | // test 658 | Diff := UlpDiff(S); 659 | if Diff = udtOne then 660 | OneUlpErrorCount := OneUlpErrorCount + 1 661 | else if Diff = udtMoreThanOne then 662 | MoreUlpErrorCount := MoreUlpErrorCount + 1; 663 | end; 664 | 665 | // display 666 | Writeln(' Number Count: ', TestCount); 667 | Writeln(' One ULP Error Count: ', OneUlpErrorCount); 668 | case TestCount of 669 | 100000: Writeln(' Expected: ', 11); 670 | 1000000: Writeln(' Expected: ', 128); 671 | 10000000: Writeln(' Expected: ', 1379); 672 | end; 673 | if MoreUlpErrorCount <> 0 then 674 | Writeln(' More Than One ULP Error Count (Fail Count): ', MoreUlpErrorCount); 675 | Writeln(' Percent Error: ', ((OneUlpErrorCount + MoreUlpErrorCount) / TestCount) * 100:0:3, '%'); 676 | if MoreUlpErrorCount > 0 then 677 | begin 678 | Writeln(' One ULP Error: ', (OneUlpErrorCount / TestCount) * 100:0:3, '%'); 679 | Writeln(' More ULP Error: ', (MoreUlpErrorCount / TestCount) * 100:0:3, '%'); 680 | end; 681 | 682 | // final check 683 | Assert(MoreUlpErrorCount = 0); 684 | end; 685 | 686 | procedure ReadWriteOneUlpDiffTest(); 687 | var 688 | I: Integer; 689 | S: UnicodeString; 690 | Diff: TUlpDiffType; 691 | OneUlpErrorCount: Integer; 692 | MoreUlpErrorCount: Integer; 693 | D: Double; 694 | W: array [0..3] of UInt16 absolute D; 695 | begin 696 | SetTestSeed(1591); 697 | 698 | OneUlpErrorCount := 0; 699 | MoreUlpErrorCount := 0; 700 | for I := 0 to TestCount - 1 do 701 | begin 702 | // make bit/double 703 | repeat 704 | W[0] := TestRandom($FFFF + 1); 705 | W[1] := TestRandom($FFFF + 1); 706 | W[2] := TestRandom($FFFF + 1); 707 | W[3] := TestRandom($FFFF + 1); 708 | until not (IsInfinite(D) or IsNan(D)); 709 | // https://stackoverflow.com/questions/6988192/cant-get-a-nan-from-the-msvcrt-strtod-sscanf-atof-functions 710 | // gen string 711 | S := IeeeDoubleToString(D); 712 | // test 713 | Diff := UlpDiff(S); 714 | if Diff = udtOne then 715 | OneUlpErrorCount := OneUlpErrorCount + 1 716 | else if Diff = udtMoreThanOne then 717 | MoreUlpErrorCount := MoreUlpErrorCount + 1; 718 | end; 719 | 720 | // display 721 | Writeln(' Number Count: ', TestCount); 722 | Writeln(' One ULP Error Count: ', OneUlpErrorCount); 723 | case TestCount of 724 | 100000: Writeln(' Expected: ', 16); 725 | 1000000: Writeln(' Expected: ', 157); 726 | 10000000: Writeln(' Expected: ', 1624); 727 | end; 728 | if MoreUlpErrorCount <> 0 then 729 | Writeln(' More Than One ULP Error Count (Fail Count): ', MoreUlpErrorCount); 730 | Writeln(' Percent Error: ', ((OneUlpErrorCount + MoreUlpErrorCount) / TestCount) * 100:0:3, '%'); 731 | if MoreUlpErrorCount > 0 then 732 | begin 733 | Writeln(' One ULP Error: ', (OneUlpErrorCount / TestCount) * 100:0:3, '%'); 734 | Writeln(' More ULP Error: ', (MoreUlpErrorCount / TestCount) * 100:0:3, '%'); 735 | end; 736 | 737 | // final check 738 | Assert(MoreUlpErrorCount = 0); 739 | end; 740 | 741 | procedure Benchmark(); 742 | var 743 | UnicodeTestStrings: array of UnicodeString; 744 | AnsiTestStrings: array of AnsiString; 745 | D: Double; 746 | W: array [0..3] of UInt16 absolute D; 747 | E: Extended; 748 | S: UnicodeString; 749 | I: Integer; 750 | PAnsiEnd: PAnsiChar; 751 | Time: UInt64; 752 | Times: array of Int64; 753 | N: Integer; 754 | begin 755 | Writeln('Benchmark, less time is better.'); 756 | Writeln; 757 | 758 | // make 759 | SetTestSeed(404); 760 | UnicodeTestStrings := []; 761 | AnsiTestStrings := []; 762 | for I := 0 to 10000 do 763 | begin 764 | // make bit/double 765 | repeat 766 | W[0] := TestRandom($FFFF + 1); 767 | W[1] := TestRandom($FFFF + 1); 768 | W[2] := TestRandom($FFFF + 1); 769 | W[3] := TestRandom($FFFF + 1); 770 | until not (IsInfinite(D) or IsNan(D)); 771 | // https://stackoverflow.com/questions/6988192/cant-get-a-nan-from-the-msvcrt-strtod-sscanf-atof-functions 772 | // gen string 773 | S := IeeeDoubleToString(D); 774 | UnicodeTestStrings := UnicodeTestStrings + [S]; 775 | AnsiTestStrings := AnsiTestStrings + [AnsiString(S)]; 776 | end; 777 | 778 | Times := [0, 0, 0, 0, 0, 0]; 779 | for N := 0 to 400 - 1 do 780 | begin 781 | // netlib/David M. Gay 782 | Time := TThread.GetTickCount64(); 783 | for I := 0 to High(AnsiTestStrings) do 784 | begin 785 | IeeeCharsToDouble(PAnsiChar(AnsiTestStrings[I]), @PAnsiEnd); 786 | end; 787 | Times[0] := Times[0] + Int64(TThread.GetTickCount64() - Time); 788 | 789 | // delphi TextToFloat 790 | Time := TThread.GetTickCount64(); 791 | for I := 0 to High(AnsiTestStrings) do 792 | begin 793 | {$IFDEF FPC} 794 | SysUtils.TextToFloat(PAnsiChar(AnsiTestStrings[I]), E, fvExtended); 795 | {$ELSE} 796 | SysUtils.TextToFloat(PWideChar(UnicodeTestStrings[I]), E, fvExtended); 797 | {$ENDIF} 798 | end; 799 | Times[1] := Times[1] + Int64(TThread.GetTickCount64() - Time); 800 | 801 | // microsoft strtod 802 | Time := TThread.GetTickCount64(); 803 | for I := 0 to High(AnsiTestStrings) do 804 | begin 805 | MsvcrtStrtod(PAnsiChar(AnsiTestStrings[I]), @PAnsiEnd); 806 | end; 807 | Times[2] := Times[2] + Int64(TThread.GetTickCount64() - Time); 808 | 809 | // ParseFloat 810 | Time := TThread.GetTickCount64(); 811 | for I := 0 to High(AnsiTestStrings) do 812 | begin 813 | ParseFloat(PWideChar(UnicodeTestStrings[I]), D); 814 | end; 815 | Times[3] := Times[3] + Int64(TThread.GetTickCount64() - Time); 816 | 817 | // ParseFloat C 818 | if TestCVersion then 819 | begin 820 | Time := TThread.GetTickCount64(); 821 | for I := 0 to High(AnsiTestStrings) do 822 | begin 823 | pure_parse_float(PAnsiChar(AnsiTestStrings[I]), @D, nil); 824 | end; 825 | Times[4] := Times[4] + Int64(TThread.GetTickCount64() - Time); 826 | end; 827 | 828 | // ParseFloat dll 829 | if @dll_pure_parse_float <> nil then 830 | begin 831 | Time := TThread.GetTickCount64(); 832 | for I := 0 to High(AnsiTestStrings) do 833 | begin 834 | dll_pure_parse_float(PAnsiChar(AnsiTestStrings[I]), @D, @PAnsiEnd); 835 | end; 836 | Times[5] := Times[5] + Int64(TThread.GetTickCount64() - Time); 837 | end; 838 | end; 839 | 840 | Writeln('Netlib strtod: ', Times[0], 'ms'); 841 | Writeln('Delphi TextToFloat: ', Times[1], 'ms'); 842 | Writeln('Microsoft strtod: ', Times[2], 'ms'); 843 | Writeln('ParseFloat: ', Times[3], 'ms'); 844 | if TestCVersion then 845 | begin 846 | Writeln('ParseFloat C Version: ', Times[4], 'ms'); 847 | if Times[3] >= Times[4] then 848 | begin 849 | Writeln(' ', (Times[3] / Times[4]):0:2, 'x faser'); 850 | end else 851 | begin 852 | Writeln(' ', (Times[4] / Times[3]):0:2, 'x slower'); 853 | end; 854 | end; 855 | if @dll_pure_parse_float <> nil then 856 | begin 857 | Writeln('User DLL ParseFloat: ', Times[5], 'ms'); 858 | end; 859 | end; 860 | 861 | procedure DoRunTests(); 862 | var 863 | Tests: array of TTest; 864 | I: Integer; 865 | SuccessCount: Integer; 866 | CmdSize, CmdMode: UnicodeString; 867 | DllName, DllFunctionName: UnicodeString; 868 | begin 869 | Writeln('=== PureFloatParser Test ==='); 870 | 871 | if FindCmdLineSwitch('dll') then 872 | begin 873 | if not FindCmdLineSwitch('function') then 874 | begin 875 | Writeln('Please use -function with -dll option!'); 876 | exit; 877 | end; 878 | // load dll 879 | {$IFDEF FPC} 880 | DllName := GetCmdLineArg('dll', ['-']); 881 | {$ELSE} 882 | DllName := ''; 883 | FindCmdLineSwitch('dll', DllName, True, [clstValueNextParam]); 884 | {$ENDIF} 885 | if DllName = '' then 886 | begin 887 | Writeln('Bad -dll param!'); 888 | exit; 889 | end; 890 | {$IFDEF FPC} 891 | dll_pure_parse_float_library := LoadLibrary(PAnsiChar(AnsiString(DllName))); 892 | {$ELSE} 893 | dll_pure_parse_float_library := LoadLibrary(PWideChar(DllName)); 894 | {$ENDIF} 895 | if pure_parse_float_library = 0 then 896 | begin 897 | Writeln('Can''t open "' + DllName + '" dll!'); 898 | exit; 899 | end; 900 | // load func 901 | {$IFDEF FPC} 902 | DllFunctionName := GetCmdLineArg('function', ['-']); 903 | {$ELSE} 904 | DllFunctionName := ''; 905 | FindCmdLineSwitch('function', DllFunctionName, True, [clstValueNextParam]); 906 | {$ENDIF} 907 | if DllFunctionName = '' then 908 | begin 909 | Writeln('Bad -function param!'); 910 | exit; 911 | end; 912 | {$IFDEF FPC} 913 | @dll_pure_parse_float := GetProcAddress(dll_pure_parse_float_library, PAnsiChar(AnsiString(DllFunctionName))); 914 | {$ELSE} 915 | @dll_pure_parse_float := GetProcAddress(dll_pure_parse_float_library, PWideChar(DllFunctionName)); 916 | {$ENDIF} 917 | if @dll_pure_parse_float = nil then 918 | begin 919 | Writeln('Can''t load function "' + DllFunctionName + '" from dll!'); 920 | exit; 921 | end; 922 | // print info 923 | Writeln('*** Parser function "' + DllFunctionName + '", from user DLL "', DllName, '" ***'); 924 | end; 925 | 926 | if FindCmdLineSwitch('c') then 927 | begin 928 | if @pure_parse_float = nil then 929 | begin 930 | Writeln('Please place pure_float_parser_c_32.dll/pure_float_parser_c_64.dll for test C version!'); 931 | exit; 932 | end; 933 | TestCVersion := True; 934 | end; 935 | 936 | if FindCmdLineSwitch('benchmark') then 937 | begin 938 | Benchmark(); 939 | exit; 940 | end; 941 | 942 | {$IFDEF FPC} 943 | CmdSize := GetCmdLineArg('size', ['-']); 944 | {$ENDIF} 945 | if {$IFDEF FPC}CmdSize <> ''{$ELSE}FindCmdLineSwitch('size', CmdSize, True, [clstValueNextParam]){$ENDIF} then 946 | begin 947 | if UpperCase(CmdSize) = 'SMALL' then 948 | begin 949 | TestCount := SizeSmall; 950 | end 951 | else if UpperCase(CmdSize) = 'MEDIUM' then 952 | begin 953 | TestCount := SizeMedium; 954 | end 955 | else if UpperCase(CmdSize) = 'LARGE' then 956 | begin 957 | TestCount := SizeLarge; 958 | end else 959 | begin 960 | Writeln('Bad param size!'); 961 | exit; 962 | end; 963 | end else 964 | begin 965 | Writeln('Use "-size small/medium/large" for change sub test count.'); 966 | Writeln(' Size=Small by default.'); 967 | Writeln; 968 | end; 969 | 970 | {$IFDEF FPC} 971 | CmdMode := GetCmdLineArg('parser', ['-']); 972 | {$ENDIF} 973 | if {$IFDEF FPC}CmdMode <> ''{$ELSE}FindCmdLineSwitch('parser', CmdMode, True, [clstValueNextParam]){$ENDIF} then 974 | begin 975 | if UpperCase(CmdMode) = 'PURE' then 976 | begin 977 | Mode := ModePure; 978 | end 979 | else if UpperCase(CmdMode) = 'DELPHI' then 980 | begin 981 | Mode := ModeDelphi; 982 | end 983 | else if UpperCase(CmdMode) = 'MICROSOFT' then 984 | begin 985 | Mode := ModeMicrosoft; 986 | end else 987 | begin 988 | Writeln('Bad param parser!'); 989 | exit; 990 | end; 991 | end 992 | else if @dll_pure_parse_float = nil then 993 | begin 994 | Writeln('Use "-parser pure/delphi/microsoft" for change a parser under test.'); 995 | Writeln(' Parser=Pure by default.'); 996 | Writeln; 997 | end; 998 | 999 | if High(NativeInt) = High(Int32) then 1000 | Writeln(' 32 bit cpu') 1001 | else 1002 | Writeln(' 64 bit cpu'); 1003 | 1004 | if @dll_pure_parse_float = nil then 1005 | begin 1006 | case Mode of 1007 | ModePure: Writeln(' Parser=Pure'); 1008 | ModeDelphi: Writeln(' Parser=Delphi'); 1009 | ModeMicrosoft: Writeln(' Parser=Microsoft'); 1010 | end; 1011 | if TestCVersion then 1012 | begin 1013 | Writeln(' Test C Version'); 1014 | end; 1015 | end else 1016 | Writeln(' Parser=User DLL'); 1017 | 1018 | case TestCount of 1019 | SizeSmall: Writeln(' Size=Small'); 1020 | SizeMedium: Writeln(' Size=Medium'); 1021 | SizeLarge: Writeln(' Size=Large'); 1022 | end; 1023 | 1024 | Writeln; 1025 | 1026 | // make tests 1027 | Tests := [ 1028 | MakeTest('Simple Integer Test', SimpleIntegerTest), 1029 | MakeTest('Simple Float Test', SimpleFloatTest), 1030 | MakeTest('Simple Exponent Test', SimpleExponentTest), 1031 | MakeTest('Special String Test', SpecialStringTest), 1032 | MakeTest('Hard Parse Test', HardParseTest), 1033 | MakeTest('Random Integer Test', RandomIntegerTest), 1034 | MakeTest('Random Double Test', RandomDoubleTest), 1035 | MakeTest('Round Test', RoundTest), 1036 | MakeTest('Regular Double Test', RegularDoubleTest), 1037 | MakeTest('Big Double Test', BigDoubleTest), 1038 | MakeTest('Hard One Ulp Diff Test', HardOneUlpDiffTest), 1039 | MakeTest('Hard Round One Ulp Diff Test', HardRoundOneUlpDiffTest), 1040 | MakeTest('Regular One Ulp Diff Test', RegularOneUlpDiffTest), 1041 | MakeTest('Long Number One Ulp Diff Test', LongNumberOneUlpDiffTest), 1042 | MakeTest('Read Write One Ulp Diff Test', ReadWriteOneUlpDiffTest) 1043 | ]; 1044 | 1045 | // run tests 1046 | SuccessCount := 0; 1047 | for I := 0 to High(Tests) do 1048 | begin 1049 | Writeln('Start "', Tests[I].Name, '", ', (I + 1), '/', Length(Tests)); 1050 | try 1051 | Tests[I].Proc(); 1052 | SuccessCount := SuccessCount + 1; 1053 | Writeln(' Passed!'); 1054 | except 1055 | Writeln(' Failed!'); 1056 | end; 1057 | end; 1058 | 1059 | // show result 1060 | Writeln; 1061 | if SuccessCount = Length(Tests) then 1062 | begin 1063 | Writeln('All Tests Passed!'); 1064 | Writeln(' Count: ', Length(Tests)); 1065 | end else 1066 | begin 1067 | Writeln('Tests Failed!'); 1068 | Writeln(' Count: ', Length(Tests)); 1069 | Writeln(' Passed: ', SuccessCount); 1070 | Writeln(' Faild: ', Length(Tests) - SuccessCount); 1071 | end; 1072 | end; 1073 | 1074 | procedure RunTests(); 1075 | var 1076 | Time: UInt64; 1077 | begin 1078 | FormatSettings.DecimalSeparator := '.'; 1079 | SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]); 1080 | SetRoundMode(rmNearest); 1081 | SetPrecisionMode(pmDouble); 1082 | 1083 | Time := TThread.GetTickCount64(); 1084 | try 1085 | DoRunTests(); 1086 | except 1087 | on E: Exception do 1088 | Writeln(E.ClassName, ': ', E.Message); 1089 | end; 1090 | Writeln; 1091 | Writeln('Executing Time: ', (TThread.GetTickCount64() - Time) div 1000, 's'); 1092 | Writeln; 1093 | 1094 | Writeln('Press Enter to exit...'); 1095 | Readln; 1096 | end; 1097 | 1098 | initialization 1099 | {$IFDEF CPUX64} 1100 | pure_parse_float_library := LoadLibrary('pure_parse_float_c_64.dll'); 1101 | {$ELSE} 1102 | pure_parse_float_library := LoadLibrary('pure_parse_float_c_32.dll'); 1103 | {$ENDIF} 1104 | if pure_parse_float_library <> 0 then 1105 | begin 1106 | @pure_parse_float := GetProcAddress(pure_parse_float_library, 'pure_parse_float'); 1107 | end; 1108 | 1109 | finalization 1110 | FreeLibrary(pure_parse_float_library); 1111 | FreeLibrary(dll_pure_parse_float_library); 1112 | 1113 | end. 1114 | 1115 | -------------------------------------------------------------------------------- /Pascal/Test/Test.dpr: -------------------------------------------------------------------------------- 1 | program Test; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | MainUnit in 'MainUnit.pas', 9 | PureParseFloat in '..\PureParseFloat\PureParseFloat.pas'; 10 | 11 | begin 12 | RunTests(); 13 | end. 14 | -------------------------------------------------------------------------------- /Pascal/Test/Test.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | True 4 | Console 5 | Release 6 | None 7 | Test.dpr 8 | Win64 9 | {6F34EDEE-19C0-4B6F-8834-1F9ED440DAE2} 10 | 19.5 11 | 3 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Cfg_1 40 | true 41 | true 42 | 43 | 44 | true 45 | Base 46 | true 47 | 48 | 49 | true 50 | Base 51 | true 52 | 53 | 54 | true 55 | Cfg_3 56 | true 57 | true 58 | 59 | 60 | Test 61 | .\$(Platform)\$(Config) 62 | PURE_PARSE_FLOAT_DISABLE_SET_NORMAL_FPU_SETTINGS;$(DCC_Define) 63 | .\$(Platform)\$(Config) 64 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 65 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 66 | 1049 67 | 68 | 69 | none 70 | Debug 71 | true 72 | .\ 73 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 74 | vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;ibmonitor;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;vcldb;ibxbindings;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ibxpress;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;vclib;fmxobj;bindcompvclsmp;FMXTee;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 75 | -size medium -parser pure 76 | (None) 77 | 32.exe 78 | 1033 79 | 80 | 81 | none 82 | Debug 83 | true 84 | .\ 85 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) 86 | vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;ibmonitor;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;vcldb;ibxbindings;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ibxpress;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;vclib;fmxobj;bindcompvclsmp;FMXTee;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 87 | -size medium -parser pure 88 | (None) 89 | 64.exe 90 | 1033 91 | 92 | 93 | true 94 | true 95 | DEBUG;$(DCC_Define) 96 | true 97 | true 98 | false 99 | true 100 | true 101 | 102 | 103 | false 104 | 105 | 106 | -size medium -parser pure 107 | 108 | 109 | 0 110 | RELEASE;$(DCC_Define) 111 | false 112 | 0 113 | 114 | 115 | -benchmark -c 116 | 117 | 118 | 119 | MainSource 120 | 121 | 122 | 123 | 124 | 125 | Base 126 | 127 | 128 | Cfg_1 129 | Base 130 | 131 | 132 | Cfg_2 133 | Base 134 | 135 | 136 | Cfg_3 137 | Base 138 | 139 | 140 | 141 | Delphi.Personality.12 142 | Application 143 | 144 | 145 | 146 | Test.dpr 147 | 148 | 149 | 150 | 151 | False 152 | False 153 | True 154 | True 155 | 156 | 157 | 12 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /Pascal/Test/Test.lpi: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <UseAppBundle Value="False"/> 15 | <ResourceType Value="res"/> 16 | </General> 17 | <BuildModes> 18 | <Item Name="Default" Default="True"/> 19 | <Item Name="Debug"> 20 | <CompilerOptions> 21 | <Version Value="11"/> 22 | <PathDelim Value="\"/> 23 | <Target> 24 | <Filename Value="Test"/> 25 | </Target> 26 | <SearchPaths> 27 | <IncludeFiles Value="$(ProjOutDir)"/> 28 | <OtherUnitFiles Value="..\PureParseFloat"/> 29 | <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> 30 | </SearchPaths> 31 | <CodeGeneration> 32 | <Optimizations> 33 | <OptimizationLevel Value="3"/> 34 | </Optimizations> 35 | </CodeGeneration> 36 | <Linking> 37 | <Debugging> 38 | <DebugInfoType Value="dsDwarf3"/> 39 | <UseExternalDbgSyms Value="True"/> 40 | </Debugging> 41 | </Linking> 42 | </CompilerOptions> 43 | </Item> 44 | <Item Name="Release"> 45 | <CompilerOptions> 46 | <Version Value="11"/> 47 | <PathDelim Value="\"/> 48 | <Target> 49 | <Filename Value="Test"/> 50 | </Target> 51 | <SearchPaths> 52 | <IncludeFiles Value="$(ProjOutDir)"/> 53 | <OtherUnitFiles Value="..\PureParseFloat"/> 54 | <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> 55 | </SearchPaths> 56 | <CodeGeneration> 57 | <SmartLinkUnit Value="True"/> 58 | <Optimizations> 59 | <OptimizationLevel Value="3"/> 60 | </Optimizations> 61 | </CodeGeneration> 62 | <Linking> 63 | <Debugging> 64 | <GenerateDebugInfo Value="False"/> 65 | <DebugInfoType Value="dsDwarf2Set"/> 66 | </Debugging> 67 | <LinkSmart Value="True"/> 68 | </Linking> 69 | <Other> 70 | <Verbosity> 71 | <ShowAll Value="True"/> 72 | <ShowDebugInfo Value="True"/> 73 | </Verbosity> 74 | <CustomOptions Value="-CfSSE3 75 | -dDISABLE_SET_NORMAL_FPU_SETTINGS"/> 76 | </Other> 77 | </CompilerOptions> 78 | </Item> 79 | </BuildModes> 80 | <PublishOptions> 81 | <Version Value="2"/> 82 | <UseFileFilters Value="True"/> 83 | </PublishOptions> 84 | <RunParams> 85 | <FormatVersion Value="2"/> 86 | </RunParams> 87 | <Units> 88 | <Unit> 89 | <Filename Value="Test.lpr"/> 90 | <IsPartOfProject Value="True"/> 91 | </Unit> 92 | <Unit> 93 | <Filename Value="MainUnit.pas"/> 94 | <IsPartOfProject Value="True"/> 95 | </Unit> 96 | <Unit> 97 | <Filename Value="..\PureParseFloat\PureParseFloat.pas"/> 98 | <IsPartOfProject Value="True"/> 99 | </Unit> 100 | </Units> 101 | </ProjectOptions> 102 | <CompilerOptions> 103 | <Version Value="11"/> 104 | <PathDelim Value="\"/> 105 | <Target> 106 | <Filename Value="Test"/> 107 | </Target> 108 | <SearchPaths> 109 | <IncludeFiles Value="$(ProjOutDir)"/> 110 | <OtherUnitFiles Value="..\PureParseFloat"/> 111 | <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> 112 | </SearchPaths> 113 | <Linking> 114 | <Debugging> 115 | <DebugInfoType Value="dsDwarf2Set"/> 116 | </Debugging> 117 | </Linking> 118 | </CompilerOptions> 119 | <Debugging> 120 | <Exceptions> 121 | <Item> 122 | <Name Value="EAbort"/> 123 | </Item> 124 | <Item> 125 | <Name Value="ECodetoolError"/> 126 | </Item> 127 | <Item> 128 | <Name Value="EFOpenError"/> 129 | </Item> 130 | </Exceptions> 131 | </Debugging> 132 | </CONFIG> 133 | -------------------------------------------------------------------------------- /Pascal/Test/Test.lpr: -------------------------------------------------------------------------------- 1 | program Test; 2 | 3 | uses 4 | MainUnit; 5 | 6 | begin 7 | RunTests(); 8 | end. 9 | 10 | -------------------------------------------------------------------------------- /Pascal/Test/pure_parse_float_c_32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turborium/PureParseFloat/2b9782179e3a5e81d4258522081b221ba52fc05a/Pascal/Test/pure_parse_float_c_32.dll -------------------------------------------------------------------------------- /Pascal/Test/pure_parse_float_c_64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turborium/PureParseFloat/2b9782179e3a5e81d4258522081b221ba52fc05a/Pascal/Test/pure_parse_float_c_64.dll -------------------------------------------------------------------------------- /Pascal/Test/readme.md: -------------------------------------------------------------------------------- 1 | ### Pure Parse Float Test App 2 | 3 | **Params:** 4 | ```-c``` - Enable "C" version of "pure" parser in pure_parse_float_c_32/64.dll, disabled by default 5 | ```-parser <pure|delphi|microsoft>``` - Select parser for test, ```pure``` by default 6 | ```-size <small|medium|large>``` - Select count of test cases, ```small``` by default 7 | ```-benchmark``` - Run benchmark, you can add bench "C" version with use ```-c``` param 8 | 9 | ```-dll <dll name>``` - Test your DLL 10 | ```-function <function name>``` - Chose ParseFloat function from DLL 11 | (The function MUST corresponds to standart DLL Pascal/C FFI interface - see C version) 12 | 13 | For this application to work, two DLLs IeeeConvert64.dll and IeeeConvert32.dll are required, they provide reference values. 14 | This DLLs are already included in this repository, but you can build them yourself: https://github.com/turborium/IeeeConvertDynamicLibrary 15 | 16 | To check the "C" version of the parser you need to build pure_parse_float_c_32.dll and pure_parse_float_c_64.dll. 17 | 18 | **For example:** 19 | Check:```Test.32.exe -size medium -parser pure``` 20 | Benchmark:```Test.32.exe -benchmark``` 21 | Benchmark with C version:```Test.32.exe -benchmark -c``` 22 | 23 | To check your DLL place DLL and set ```-dll``` and ```-function``` params. 24 | **For example:** 25 | Check *rust32.dll*: ```Test.32.exe -size medium -dll rust32.dll -function parse_float``` 26 | Check *rust64.dll*: ```Test.64.exe -size medium -dll rust64.dll -function parse_float``` 27 | Benchmark *rust32.dll*: ```Test.32.exe -benchmark -dll rust32.dll -function parse_float``` 28 | Benchmark *rust64.dll*: ```Test.64.exe -benchmark -dll rust64.dll -function parse_float``` 29 | 30 | -------------------------------------------------------------------------------- /Ports/CSharp/CSharpFloatParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace CSharpSafe 6 | { 7 | public class CSharpFloatParser 8 | { 9 | private struct DoubleDouble 10 | { 11 | public double Hi; 12 | public double Lo; 13 | } 14 | 15 | private struct FixedDecimal 16 | { 17 | public int Count; 18 | public long Exponent; 19 | public int IsNegative; 20 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17 * 2)] 21 | public byte[] Digits = new byte[17 * 2]; 22 | 23 | public FixedDecimal() { } 24 | } 25 | 26 | 27 | private static DoubleDouble DoubleToDoubleDouble(double value) 28 | { 29 | DoubleDouble result; 30 | 31 | result.Hi = value; 32 | result.Lo = 0; 33 | 34 | return result; 35 | } 36 | 37 | private static double DoubleDoubleToDouble(DoubleDouble value) => value.Hi; 38 | 39 | // Alternative is return double.IsInfinity(value); 40 | private static bool IsInfinity(double value) => (value == 1.0 / 0.0) || (value == -1.0 / 0.0); 41 | 42 | private static DoubleDouble DoubleDoubleFastAdd11(double a, double b) 43 | { 44 | DoubleDouble result; 45 | 46 | result.Hi = a + b; 47 | 48 | if (IsInfinity(result.Hi)) 49 | return DoubleToDoubleDouble(result.Hi); 50 | 51 | result.Lo = (b - (result.Hi - a)); 52 | return result; 53 | } 54 | 55 | private static DoubleDouble DoubleDoubleAdd11(double a, double b) 56 | { 57 | DoubleDouble result; 58 | double ah, bh; 59 | 60 | result.Hi = a + b; 61 | 62 | if (IsInfinity(result.Hi)) 63 | return DoubleToDoubleDouble(result.Hi); 64 | 65 | ah = result.Hi - b; 66 | bh = result.Hi - ah; 67 | 68 | result.Lo = (a - ah) + (b - bh); 69 | return result; 70 | } 71 | 72 | private static DoubleDouble DoubleDoubleSplit1(double a) 73 | { 74 | const double splitter = 134217729.0; 75 | const double infinitySplit = 6.69692879491417e+299; 76 | const double infinityDown = 3.7252902984619140625e-09; 77 | const double infinityUp = 268435456.0; 78 | 79 | DoubleDouble result; 80 | double temp; 81 | 82 | if ((a > infinitySplit) || (a < -infinitySplit)) 83 | { 84 | a *= infinityDown; 85 | temp = splitter * a; 86 | result.Hi = temp + (a - temp); 87 | result.Lo = a - result.Hi; 88 | result.Hi *= infinityUp; 89 | result.Lo *= infinityUp; 90 | } 91 | else 92 | { 93 | temp = splitter * a; 94 | result.Hi = temp + (a - temp); 95 | result.Lo = a - result.Hi; 96 | } 97 | 98 | return result; 99 | } 100 | 101 | private static DoubleDouble DoubleDoubleMul11(double a, double b) 102 | { 103 | DoubleDouble result; 104 | DoubleDouble a2, b2; 105 | double err1, err2, err3; 106 | 107 | result.Hi = a * b; 108 | 109 | if (IsInfinity(result.Hi)) 110 | return DoubleToDoubleDouble(result.Hi); 111 | 112 | a2 = DoubleDoubleSplit1(a); 113 | b2 = DoubleDoubleSplit1(b); 114 | 115 | err1 = result.Hi - (a2.Hi * b2.Hi); 116 | err2 = err1 - (a2.Lo * b2.Hi); 117 | err3 = err2 - (a2.Hi * b2.Lo); 118 | 119 | result.Lo = (a2.Lo * b2.Lo) - err3; 120 | 121 | return result; 122 | } 123 | 124 | private static DoubleDouble DoubleDoubleMul21(DoubleDouble a, double b) 125 | { 126 | DoubleDouble result; 127 | DoubleDouble c; 128 | 129 | c = DoubleDoubleMul11(a.Hi, b); 130 | 131 | if (IsInfinity(c.Hi)) 132 | return DoubleToDoubleDouble(c.Hi); 133 | 134 | result = DoubleDoubleFastAdd11(c.Hi, a.Lo * b); 135 | return DoubleDoubleFastAdd11(result.Hi, result.Lo + c.Lo); 136 | } 137 | 138 | private static DoubleDouble DoubleDoubleDiv21(DoubleDouble a, double b) 139 | { 140 | DoubleDouble result; 141 | DoubleDouble p, d; 142 | 143 | result.Hi = a.Hi / b; 144 | 145 | if (IsInfinity(result.Hi)) 146 | return DoubleToDoubleDouble(result.Hi); 147 | 148 | p = DoubleDoubleMul11(result.Hi, b); 149 | 150 | d.Hi = a.Hi - p.Hi; 151 | d.Lo = d.Hi - p.Lo; 152 | 153 | result.Lo = (d.Lo + a.Lo) / b; 154 | 155 | return DoubleDoubleFastAdd11(result.Hi, result.Lo); 156 | } 157 | 158 | private static DoubleDouble DoubleDoubleAdd21(DoubleDouble a, double b) 159 | { 160 | DoubleDouble result; 161 | 162 | result = DoubleDoubleAdd11(a.Hi, b); 163 | 164 | if (IsInfinity(result.Hi)) 165 | return DoubleToDoubleDouble(result.Hi); 166 | 167 | result.Lo = result.Lo + a.Lo; 168 | return DoubleDoubleFastAdd11(result.Hi, result.Lo); 169 | } 170 | 171 | private static int TextPrefixLength(string text, string prefix) 172 | { 173 | int i = 0; 174 | 175 | while (i < text.Length && i < prefix.Length && (char.ToLowerInvariant(text[i]) == char.ToLowerInvariant(prefix[i]))) 176 | { 177 | if (text[i] == '\0') break; 178 | i++; 179 | } 180 | 181 | return i; 182 | } 183 | 184 | static int ReadSpecial(ref double number, string text, ref int index) 185 | { 186 | int isNegative = 0; 187 | int len; 188 | 189 | isNegative = 0; 190 | 191 | index = 0; 192 | 193 | switch (text[index]) 194 | { 195 | case '+': 196 | index++; 197 | break; 198 | case '-': 199 | isNegative = 1; 200 | index++; 201 | break; 202 | } 203 | 204 | switch (Char.ToLower(text[index])) 205 | { 206 | case 'i': 207 | len = TextPrefixLength(text.Substring(index), "infinity"); 208 | if (len == 3 || len == 8) 209 | { 210 | number = Double.PositiveInfinity; 211 | 212 | if (isNegative == 1) 213 | number = -number; 214 | 215 | index += len; 216 | 217 | return 1; 218 | } 219 | break; 220 | case 'n': 221 | len = TextPrefixLength(text.Substring(index), "nan"); 222 | if (len == 3) 223 | { 224 | number = Double.CopySign(Double.NaN, +0.0); 225 | 226 | if (isNegative == 1) 227 | number = -number; 228 | 229 | index += len; 230 | 231 | return 1; 232 | } 233 | break; 234 | } 235 | 236 | number = 0; 237 | return 0; 238 | } 239 | 240 | private static int ReadTextToFixedDecimal(ref FixedDecimal result, string text, ref int index) 241 | { 242 | const long clipExponent = 1000000; 243 | 244 | result.Count = 0; 245 | result.IsNegative = 0; 246 | result.Exponent = -1; 247 | result.Digits = new byte[17 * 2]; 248 | 249 | index = 0; 250 | 251 | if (text[index] == '-') 252 | { 253 | result.IsNegative = 1; 254 | index++; 255 | } 256 | else if (text[index] == '+') index++; 257 | 258 | int hasPoint = 0; 259 | int hasDigit = 0; 260 | 261 | while (index < text.Length) 262 | { 263 | switch (text[index]) 264 | { 265 | case '0': 266 | case '1': 267 | case '2': 268 | case '3': 269 | case '4': 270 | case '5': 271 | case '6': 272 | case '7': 273 | case '8': 274 | case '9': 275 | if (result.Count != 0 || text[index] != '0') 276 | { 277 | if (result.Count < result.Digits.Length) 278 | { 279 | result.Digits[result.Count] = (byte)(text[index] - '0'); 280 | result.Count++; 281 | } 282 | 283 | if (hasPoint == 0 && result.Exponent < clipExponent) 284 | result.Exponent++; 285 | } 286 | else 287 | { 288 | if (hasPoint == 1 && result.Exponent > -clipExponent) 289 | result.Exponent--; 290 | } 291 | hasDigit = 1; 292 | break; 293 | case '.': 294 | if (hasPoint == 1) return 1; 295 | 296 | hasPoint = 1; 297 | break; 298 | default: 299 | goto EndReadMantissa; 300 | } 301 | 302 | index++; 303 | } 304 | 305 | EndReadMantissa: 306 | 307 | if (hasDigit == 0) 308 | { 309 | index = 0; 310 | return 0; 311 | } 312 | 313 | if ((index < text.Length) && (text[index] == 'e' || text[index] == 'E')) 314 | { 315 | int startIndexExponent = index; 316 | index++; 317 | 318 | long exponent = 0; 319 | int exponentSign = 1; 320 | 321 | switch (text[index]) 322 | { 323 | case '+': 324 | index++; 325 | break; 326 | case '-': 327 | exponentSign = -1; 328 | index++; 329 | break; 330 | } 331 | 332 | if ((text[index] >= '0' && text[index] <= '9')) 333 | { 334 | while ((index < text.Length) && (text[index] >= '0' && text[index] <= '9')) 335 | { 336 | exponent = exponent * 10 + (text[index] - '0'); 337 | 338 | if (exponent > clipExponent) 339 | exponent = clipExponent; 340 | 341 | index++; 342 | } 343 | } 344 | else 345 | { 346 | index = startIndexExponent; 347 | return 1; 348 | } 349 | 350 | result.Exponent += exponentSign * exponent; 351 | } 352 | 353 | return 1; 354 | } 355 | 356 | private static double FixedDecimalToDouble(FixedDecimal decimalValue) 357 | { 358 | const int lastAccuracyExponent10 = 22; 359 | const double lastAccuracyPower10 = 1e22; 360 | const long maxSafeInt = 9007199254740991; 361 | const long maxSafeHi = (maxSafeInt - 9) / 10; 362 | double[] powerOf10 = 363 | { 364 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 365 | 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 366 | }; 367 | 368 | DoubleDouble number = DoubleToDoubleDouble(0.0); 369 | 370 | for (int i = 0; i < decimalValue.Count; i++) 371 | { 372 | if (number.Hi <= maxSafeHi) 373 | { 374 | number.Hi = number.Hi * 10; 375 | number.Hi = number.Hi + decimalValue.Digits[i]; 376 | } 377 | else 378 | { 379 | number = DoubleDoubleMul21(number, 10.0); 380 | number = DoubleDoubleAdd21(number, decimalValue.Digits[i]); 381 | } 382 | } 383 | 384 | long exponent = decimalValue.Exponent - decimalValue.Count + 1; 385 | 386 | while (exponent > 0) 387 | { 388 | if (exponent > lastAccuracyExponent10) 389 | { 390 | number = DoubleDoubleMul21(number, lastAccuracyPower10); 391 | 392 | if (IsInfinity(DoubleDoubleToDouble(number))) break; 393 | 394 | exponent -= lastAccuracyExponent10; 395 | } 396 | else 397 | { 398 | number = DoubleDoubleMul21(number, powerOf10[exponent]); 399 | break; 400 | } 401 | } 402 | 403 | while (exponent < 0) 404 | { 405 | if (exponent < -lastAccuracyExponent10) 406 | { 407 | number = DoubleDoubleDiv21(number, lastAccuracyPower10); 408 | 409 | if (DoubleDoubleToDouble(number) == 0.0) break; 410 | 411 | exponent += lastAccuracyExponent10; 412 | } 413 | else 414 | { 415 | number = DoubleDoubleDiv21(number, powerOf10[-exponent]); 416 | break; 417 | } 418 | } 419 | 420 | double result = DoubleDoubleToDouble(number); 421 | 422 | if (decimalValue.IsNegative != 0) 423 | result = -result; 424 | 425 | return result; 426 | } 427 | 428 | 429 | private static int ParseFloatSafe(string text, ref double value, ref int index) 430 | { 431 | FixedDecimal decimalResult = new(); 432 | 433 | if (ReadSpecial(ref value, text, ref index) == 1) return 1; 434 | 435 | if (ReadTextToFixedDecimal(ref decimalResult, text, ref index) == 1) 436 | { 437 | value = FixedDecimalToDouble(decimalResult); 438 | 439 | return 1; 440 | } 441 | 442 | return 0; 443 | } 444 | 445 | [UnmanagedCallersOnly(EntryPoint = "ParseFloat")] 446 | public static unsafe int ParseFloat(byte* text, double* value, byte** textEnd) 447 | { 448 | double doubleValue = 0.0; 449 | int index = 0; 450 | 451 | int length = 0; 452 | while (text[length] != 0) 453 | length++; 454 | 455 | 456 | byte[] byteArray = new byte[length]; 457 | for (int i = 0; i < length; i++) 458 | byteArray[i] = text[i]; 459 | 460 | 461 | string textString = Encoding.ASCII.GetString(byteArray); 462 | 463 | int result = ParseFloatSafe(textString, ref doubleValue, ref index); 464 | 465 | *value = doubleValue; 466 | 467 | *textEnd = text + index; 468 | 469 | return result; 470 | } 471 | } 472 | } -------------------------------------------------------------------------------- /Ports/CSharp/CSharpSafe.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | 3 | <PropertyGroup> 4 | <TargetFramework>net7.0</TargetFramework> 5 | <ImplicitUsings>enable</ImplicitUsings> 6 | <Nullable>enable</Nullable> 7 | <AllowUnsafeBlocks>True</AllowUnsafeBlocks> 8 | <PublishAot>true</PublishAot> 9 | <IlcDisableReflection>true</IlcDisableReflection> 10 | <RootAllApplicationAssemblies>false</RootAllApplicationAssemblies> 11 | <IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata> 12 | <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData> 13 | <IlcDisableUnhandledExceptionExperience>true</IlcDisableUnhandledExceptionExperience> 14 | <IlcOptimizationPreference>Speed</IlcOptimizationPreference> 15 | </PropertyGroup> 16 | 17 | </Project> 18 | -------------------------------------------------------------------------------- /Ports/CSharp/Readme.md: -------------------------------------------------------------------------------- 1 | # Float Parser on .NET with NativeAOT Safe Version 2 | 3 | - Install **.NET 7** or **8** [from here](https://dotnet.microsoft.com/en-us/download) 4 | 5 | - Build project 6 | 7 | ```bash 8 | dotnet publish -f net7.0 -c Release -r win-x64 -p:PublishAot=true -p:NativeLib=Shared -p:SelfContained=true 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /Ports/CSharpUnsafe/CSharpFloatParserUnsafe.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | #pragma warning disable CS8500 6 | 7 | namespace CSharpUnsafe 8 | { 9 | public class CSharpFloatParserUnsafe 10 | { 11 | private struct DoubleDouble 12 | { 13 | public double Hi; 14 | public double Lo; 15 | } 16 | 17 | private struct FixedDecimal 18 | { 19 | public int Count; 20 | public long Exponent; 21 | public int IsNegative; 22 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17 * 2)] 23 | public byte[] Digits = new byte[17 * 2]; 24 | 25 | public FixedDecimal(){} 26 | } 27 | 28 | 29 | private static DoubleDouble DoubleToDoubleDouble(double value) 30 | { 31 | DoubleDouble result; 32 | 33 | result.Hi = value; 34 | result.Lo = 0; 35 | 36 | return result; 37 | } 38 | 39 | private static double DoubleDoubleToDouble(DoubleDouble value) => value.Hi; 40 | 41 | // Alternative is return double.IsInfinity(value); 42 | private static bool IsInfinity(double value) => (value == 1.0 / 0.0) || (value == -1.0 / 0.0); 43 | 44 | private static DoubleDouble DoubleDoubleFastAdd11(double a, double b) 45 | { 46 | DoubleDouble result; 47 | 48 | result.Hi = a + b; 49 | 50 | if (IsInfinity(result.Hi)) 51 | return DoubleToDoubleDouble(result.Hi); 52 | 53 | result.Lo = (b - (result.Hi - a)); 54 | return result; 55 | } 56 | 57 | private static DoubleDouble DoubleDoubleAdd11(double a, double b) 58 | { 59 | DoubleDouble result; 60 | double ah, bh; 61 | 62 | result.Hi = a + b; 63 | 64 | if (IsInfinity(result.Hi)) 65 | return DoubleToDoubleDouble(result.Hi); 66 | 67 | ah = result.Hi - b; 68 | bh = result.Hi - ah; 69 | 70 | result.Lo = (a - ah) + (b - bh); 71 | return result; 72 | } 73 | 74 | private static DoubleDouble DoubleDoubleSplit1(double a) 75 | { 76 | const double splitter = 134217729.0; 77 | const double infinitySplit = 6.69692879491417e+299; 78 | const double infinityDown = 3.7252902984619140625e-09; 79 | const double infinityUp = 268435456.0; 80 | 81 | DoubleDouble result; 82 | double temp; 83 | 84 | if ((a > infinitySplit) || (a < -infinitySplit)) 85 | { 86 | a *= infinityDown; 87 | temp = splitter * a; 88 | result.Hi = temp + (a - temp); 89 | result.Lo = a - result.Hi; 90 | result.Hi *= infinityUp; 91 | result.Lo *= infinityUp; 92 | } 93 | else 94 | { 95 | temp = splitter * a; 96 | result.Hi = temp + (a - temp); 97 | result.Lo = a - result.Hi; 98 | } 99 | 100 | return result; 101 | } 102 | 103 | private static DoubleDouble DoubleDoubleMul11(double a, double b) 104 | { 105 | DoubleDouble result; 106 | DoubleDouble a2, b2; 107 | double err1, err2, err3; 108 | 109 | result.Hi = a * b; 110 | 111 | if (IsInfinity(result.Hi)) 112 | return DoubleToDoubleDouble(result.Hi); 113 | 114 | a2 = DoubleDoubleSplit1(a); 115 | b2 = DoubleDoubleSplit1(b); 116 | 117 | err1 = result.Hi - (a2.Hi * b2.Hi); 118 | err2 = err1 - (a2.Lo * b2.Hi); 119 | err3 = err2 - (a2.Hi * b2.Lo); 120 | 121 | result.Lo = (a2.Lo * b2.Lo) - err3; 122 | 123 | return result; 124 | } 125 | 126 | private static DoubleDouble DoubleDoubleMul21(DoubleDouble a, double b) 127 | { 128 | DoubleDouble result; 129 | DoubleDouble c; 130 | 131 | c = DoubleDoubleMul11(a.Hi, b); 132 | 133 | if (IsInfinity(c.Hi)) 134 | return DoubleToDoubleDouble(c.Hi); 135 | 136 | result = DoubleDoubleFastAdd11(c.Hi, a.Lo * b); 137 | return DoubleDoubleFastAdd11(result.Hi, result.Lo + c.Lo); 138 | } 139 | 140 | private static DoubleDouble DoubleDoubleDiv21(DoubleDouble a, double b) 141 | { 142 | DoubleDouble result; 143 | DoubleDouble p, d; 144 | 145 | result.Hi = a.Hi / b; 146 | 147 | if (IsInfinity(result.Hi)) 148 | return DoubleToDoubleDouble(result.Hi); 149 | 150 | p = DoubleDoubleMul11(result.Hi, b); 151 | 152 | d.Hi = a.Hi - p.Hi; 153 | d.Lo = d.Hi - p.Lo; 154 | 155 | result.Lo = (d.Lo + a.Lo) / b; 156 | 157 | return DoubleDoubleFastAdd11(result.Hi, result.Lo); 158 | } 159 | 160 | private static DoubleDouble DoubleDoubleAdd21(DoubleDouble a, double b) 161 | { 162 | DoubleDouble result; 163 | 164 | result = DoubleDoubleAdd11(a.Hi, b); 165 | 166 | if (IsInfinity(result.Hi)) 167 | return DoubleToDoubleDouble(result.Hi); 168 | 169 | result.Lo = result.Lo + a.Lo; 170 | return DoubleDoubleFastAdd11(result.Hi, result.Lo); 171 | } 172 | 173 | private static unsafe int TextPrefixLength(byte* text, byte* prefix) 174 | { 175 | int i = 0; 176 | 177 | while ((text[i] == prefix[i]) || 178 | ((text[i] >= 'A') && 179 | (text[i] <= 'Z') && 180 | ((text[i] + 32) == prefix[i]))) 181 | { 182 | if (text[i] == '\0') break; 183 | i++; 184 | } 185 | return i; 186 | } 187 | 188 | private static unsafe int ReadSpecial(double* number, byte* text, byte** textEnd) 189 | { 190 | byte* p; 191 | int isNegative = 0; 192 | int len; 193 | 194 | p = (byte*)text; 195 | 196 | switch ((char)*p) 197 | { 198 | case '+': 199 | p++; 200 | break; 201 | case '-': 202 | isNegative = 1; 203 | p++; 204 | break; 205 | } 206 | 207 | switch ((char)*p) 208 | { 209 | case 'I': 210 | case 'i': 211 | fixed (byte* infinityPtr = Encoding.ASCII.GetBytes("infinity")) 212 | len = TextPrefixLength(p, infinityPtr); 213 | 214 | if ((len == 3) || (len == 8)) 215 | { 216 | *number = Double.PositiveInfinity; 217 | 218 | if (isNegative == 1) 219 | *number = -*number; 220 | 221 | p = p + len; 222 | *textEnd = p; 223 | 224 | return 1; 225 | } 226 | break; 227 | case 'N': 228 | case 'n': 229 | fixed (byte* nanPtr = Encoding.ASCII.GetBytes("nan")) 230 | len = TextPrefixLength(p, nanPtr); 231 | 232 | if (len == 3) 233 | { 234 | *number = Double.CopySign(Double.NaN, +0.0); 235 | 236 | if (isNegative == 1) 237 | *number = -*number; 238 | 239 | p = p + len; 240 | *textEnd = p; 241 | 242 | return 1; 243 | } 244 | break; 245 | } 246 | *textEnd = (byte*)text; 247 | return 0; 248 | } 249 | 250 | private static unsafe int ReadTextToFixedDecimal(FixedDecimal* decimalValue, byte* text, byte** textEnd) 251 | { 252 | const long clipExponent = 1000000; 253 | 254 | byte* p, pStartExponent; 255 | int hasPoint, hasDigit; 256 | int exponentSign; 257 | long exponent; 258 | 259 | decimalValue->Count = 0; 260 | decimalValue->IsNegative = 0; 261 | decimalValue->Exponent = -1; 262 | 263 | p = (byte*)text; 264 | 265 | switch (*p) 266 | { 267 | case (byte)'+': 268 | p++; 269 | break; 270 | case (byte)'-': 271 | decimalValue->IsNegative = 1; 272 | p++; 273 | break; 274 | } 275 | 276 | hasDigit = 0; 277 | hasPoint = 0; 278 | while (*p != '\0') 279 | { 280 | switch (*p) 281 | { 282 | case (byte)'0': 283 | case (byte)'1': 284 | case (byte)'2': 285 | case (byte)'3': 286 | case (byte)'4': 287 | case (byte)'5': 288 | case (byte)'6': 289 | case (byte)'7': 290 | case (byte)'8': 291 | case (byte)'9': 292 | if ((decimalValue->Count != 0) || (*p != '0')) 293 | { 294 | if (decimalValue->Count < decimalValue->Digits.Length) 295 | { 296 | decimalValue->Digits[decimalValue->Count] = (byte)(*p - '0'); 297 | decimalValue->Count++; 298 | } 299 | 300 | if ((hasPoint == 0) && (decimalValue->Exponent < clipExponent)) 301 | decimalValue->Exponent++; 302 | } 303 | else 304 | { 305 | if (hasPoint == 1 && (decimalValue->Exponent > -clipExponent)) 306 | decimalValue->Exponent--; 307 | } 308 | hasDigit = 1; 309 | break; 310 | case (byte)'.': 311 | if (hasPoint == 1) 312 | { 313 | *textEnd = p; 314 | return 1; 315 | } 316 | hasPoint = 1; 317 | break; 318 | default: 319 | goto endReadMantissa; 320 | } 321 | p++; 322 | } 323 | 324 | endReadMantissa: 325 | 326 | if (hasDigit == 0) 327 | { 328 | *textEnd = (byte*)text; 329 | return 0; 330 | } 331 | 332 | if ((*p == 'e') || (*p == 'E')) 333 | { 334 | pStartExponent = p; 335 | p++; 336 | 337 | exponent = 0; 338 | exponentSign = 1; 339 | 340 | switch ((char)*p) 341 | { 342 | case '+': 343 | p++; 344 | break; 345 | case '-': 346 | exponentSign = -1; 347 | p++; 348 | break; 349 | } 350 | 351 | if ((*p >= '0') && (*p <= '9')) 352 | { 353 | while ((*p >= '0') && (*p <= '9')) 354 | { 355 | exponent = exponent * 10 + (*p - '0'); 356 | 357 | if (exponent > clipExponent) 358 | exponent = clipExponent; 359 | 360 | p++; 361 | } 362 | } 363 | else 364 | { 365 | *textEnd = pStartExponent; 366 | return 1; 367 | } 368 | 369 | decimalValue->Exponent = decimalValue->Exponent + exponentSign * exponent; 370 | } 371 | 372 | *textEnd = p; 373 | return 1; 374 | } 375 | 376 | private static unsafe double FixedDecimalToDouble(FixedDecimal* decimalValue) 377 | { 378 | const int lastAccuracyExponent10 = 22; 379 | const double lastAccuracyPower10 = 1e22; 380 | const long maxSafeInt = 9007199254740991; 381 | const long maxSafeHi = (maxSafeInt - 9) / 10; 382 | double[] powerOf10 = 383 | { 384 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 385 | 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 386 | }; 387 | 388 | DoubleDouble number = DoubleToDoubleDouble(0.0); 389 | 390 | for (int i = 0; i < decimalValue->Count; i++) 391 | { 392 | if (number.Hi <= maxSafeHi) 393 | { 394 | number.Hi = number.Hi * 10; 395 | number.Hi = number.Hi + decimalValue->Digits[i]; 396 | } 397 | else 398 | { 399 | number = DoubleDoubleMul21(number, 10.0); 400 | number = DoubleDoubleAdd21(number, decimalValue->Digits[i]); 401 | } 402 | } 403 | 404 | long exponent = decimalValue->Exponent - decimalValue->Count + 1; 405 | 406 | while (exponent > 0) 407 | { 408 | if (exponent > lastAccuracyExponent10) 409 | { 410 | number = DoubleDoubleMul21(number, lastAccuracyPower10); 411 | 412 | if (IsInfinity(DoubleDoubleToDouble(number))) break; 413 | 414 | exponent -= lastAccuracyExponent10; 415 | } 416 | else 417 | { 418 | number = DoubleDoubleMul21(number, powerOf10[exponent]); 419 | break; 420 | } 421 | } 422 | 423 | while (exponent < 0) 424 | { 425 | if (exponent < -lastAccuracyExponent10) 426 | { 427 | number = DoubleDoubleDiv21(number, lastAccuracyPower10); 428 | 429 | if (DoubleDoubleToDouble(number) == 0.0) break; 430 | 431 | exponent += lastAccuracyExponent10; 432 | } 433 | else 434 | { 435 | number = DoubleDoubleDiv21(number, powerOf10[-exponent]); 436 | break; 437 | } 438 | } 439 | 440 | double result = DoubleDoubleToDouble(number); 441 | 442 | if (decimalValue->IsNegative != 0) 443 | result = -result; 444 | 445 | return result; 446 | } 447 | 448 | [UnmanagedCallersOnly(EntryPoint = "ParseFloat")] 449 | public static unsafe int ParseFloat(byte* text, double* value, byte** textEnd) 450 | { 451 | FixedDecimal decimalValue = new FixedDecimal(); 452 | 453 | byte[] bytes = new byte[200]; 454 | byte* dummy = (byte*)bytes[0]; 455 | 456 | if (textEnd == null) 457 | textEnd = &dummy; 458 | 459 | if (ReadSpecial(value, text, textEnd) == 1) return 1; 460 | 461 | if (ReadTextToFixedDecimal(&decimalValue, text, textEnd) == 1) 462 | { 463 | *value = FixedDecimalToDouble(&decimalValue); 464 | 465 | return 1; 466 | } 467 | return 0; 468 | } 469 | } 470 | } -------------------------------------------------------------------------------- /Ports/CSharpUnsafe/CSharpUnsafe.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | 3 | <PropertyGroup> 4 | <TargetFramework>net7.0</TargetFramework> 5 | <ImplicitUsings>enable</ImplicitUsings> 6 | <Nullable>enable</Nullable> 7 | <AllowUnsafeBlocks>True</AllowUnsafeBlocks> 8 | <PublishAot>true</PublishAot> 9 | <IlcDisableReflection>true</IlcDisableReflection> 10 | <RootAllApplicationAssemblies>false</RootAllApplicationAssemblies> 11 | <IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata> 12 | <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData> 13 | <IlcDisableUnhandledExceptionExperience>true</IlcDisableUnhandledExceptionExperience> 14 | <IlcOptimizationPreference>Speed</IlcOptimizationPreference> 15 | </PropertyGroup> 16 | 17 | </Project> 18 | -------------------------------------------------------------------------------- /Ports/CSharpUnsafe/Readme.md: -------------------------------------------------------------------------------- 1 | # Float Parser on .NET with NativeAOT Unsafe Version 2 | 3 | - Install **.NET 7** or **8** [from here](https://dotnet.microsoft.com/en-us/download) 4 | 5 | - Build project 6 | 7 | ```bash 8 | dotnet publish -f net7.0 -c Release -r win-x64 -p:PublishAot=true -p:NativeLib=Shared -p:SelfContained=true 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /Ports/Rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /Ports/Rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "hello" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /Ports/Rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /Ports/Rust/readme.md: -------------------------------------------------------------------------------- 1 | Build: 2 | ```sh 3 | cargo build --release 4 | ``` 5 | 6 | Dinamically link library will be in `target/release/deps/libhello.so`. 7 | -------------------------------------------------------------------------------- /Ports/Rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_int, c_char, c_double}; 2 | 3 | // Safe wrapper for null terminated string 4 | #[derive(Clone)] 5 | struct Reader(*const u8, usize); 6 | 7 | impl Reader { 8 | fn from_raw_ptr(text: *const u8) -> Self { 9 | Reader(text, 0) 10 | } 11 | 12 | #[allow(dead_code)] 13 | fn from_str(text: &str) -> Self { 14 | Reader(text.as_ptr(), 0) 15 | } 16 | 17 | fn get(&self) -> u8 { 18 | unsafe { *self.0.add(self.1) } 19 | } 20 | 21 | // before using, make sure Reader is not ended 22 | fn advance(&mut self) { 23 | self.1 += 1; 24 | } 25 | 26 | fn ended(&self) -> bool { 27 | self.get() == 0 28 | } 29 | } 30 | 31 | // 31 digits garantee, with (exp^10 >= -291) or (exp^2 >= -968) 32 | struct DoubleDouble { 33 | hi: f64, 34 | lo: f64, 35 | } 36 | 37 | impl Into<DoubleDouble> for f64 { 38 | fn into(self) -> DoubleDouble { 39 | DoubleDouble{ 40 | hi: self, 41 | lo: 0.0, 42 | } 43 | } 44 | } 45 | 46 | impl std::ops::Add<f64> for DoubleDouble { 47 | type Output = Self; 48 | 49 | // The DWPlusFP algorithm [1] 50 | fn add(self, rhs: f64) -> Self::Output { 51 | let result = Self::add(self.hi, rhs); 52 | Self::fast_add(result.hi, result.lo + self.lo) 53 | } 54 | } 55 | 56 | impl std::ops::Mul<f64> for &DoubleDouble { 57 | type Output = DoubleDouble; 58 | 59 | // The "DWTimesFP1" algorithm [1] 60 | fn mul(self, rhs: f64) -> Self::Output { 61 | let c = DoubleDouble::mul(self.hi, rhs); 62 | 63 | let result = DoubleDouble::fast_add(c.hi, self.lo * rhs); 64 | DoubleDouble::fast_add(result.hi, result.lo + c.lo) 65 | } 66 | } 67 | 68 | impl std::ops::MulAssign<f64> for DoubleDouble { 69 | fn mul_assign(&mut self, rhs: f64) { 70 | *self = &*self * rhs; 71 | } 72 | } 73 | 74 | impl std::ops::Div<f64> for &DoubleDouble { 75 | type Output = DoubleDouble; 76 | 77 | // The "DWDivFP2" algorithm [1] 78 | fn div(self, rhs: f64) -> Self::Output { 79 | let hi = self.hi / rhs; 80 | 81 | let p = DoubleDouble::mul(hi, rhs); 82 | 83 | let dhi = self.hi - p.hi; 84 | let d = DoubleDouble{ 85 | hi: dhi, 86 | lo: dhi - p.lo, 87 | }; 88 | 89 | let result = DoubleDouble{ 90 | hi, 91 | lo: (d.lo + self.lo) / rhs, 92 | }; 93 | 94 | return DoubleDouble::fast_add(result.hi, result.lo); 95 | } 96 | } 97 | 98 | impl std::ops::DivAssign<f64> for DoubleDouble { 99 | fn div_assign(&mut self, rhs: f64) { 100 | *self = &*self / rhs; 101 | } 102 | } 103 | 104 | impl DoubleDouble { 105 | // Add two f64 values, condition: |A| >= |B| 106 | // The "Fast2Sum" algorithm (Dekker 1971) [1] 107 | fn fast_add(a: f64, b: f64) -> Self { 108 | let hi = a + b; 109 | 110 | Self{ 111 | hi, 112 | lo: b - (hi - a), 113 | } 114 | } 115 | 116 | // The "2Sum" algorithm [1] 117 | fn add(a: f64, b: f64) -> Self { 118 | let hi = a + b; 119 | 120 | let ah = hi - b; 121 | let bh = hi - ah; 122 | 123 | Self{ 124 | hi, 125 | lo: (a - ah) + (b - bh), 126 | } 127 | } 128 | 129 | // Multiply two f64 values 130 | // The "TWO-PRODUCT" algorithm [5] 131 | fn mul(a: f64, b: f64) -> Self { 132 | let hi = a * b; 133 | 134 | Self{ 135 | hi, 136 | lo: a.mul_add(b, -hi), 137 | } 138 | } 139 | } 140 | 141 | // --- 142 | 143 | const FIXED_DECIMAL_DIGITS: usize = 17 * 2; 144 | struct FixedDecimal { 145 | count: isize, 146 | exponent: isize, 147 | is_negative: bool, 148 | digits: [u8; FIXED_DECIMAL_DIGITS], // Max digits in Double value * 2 149 | } 150 | 151 | impl Into<f64> for &FixedDecimal { 152 | fn into(self) -> f64 { 153 | const LAST_ACCURACY_EXPONENT_10: isize = 22; // for Double 154 | const LAST_ACCURACY_POWER_10: f64 = 1e22; // for Double 155 | const MAX_SAFE_INT: f64 = 9007199254740991.0; // (2^53−1) for Double 156 | const MAX_SAFE_HI: f64 = (MAX_SAFE_INT - 9.) / 10.; // for X * 10 + 9 157 | const POWER_OF_10: [f64; 1+LAST_ACCURACY_EXPONENT_10 as usize] = [ 158 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 159 | 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 160 | ]; 161 | 162 | let mut number: DoubleDouble = 0.0.into(); 163 | // set mantissa 164 | for &digit in &self.digits[..self.count as usize] { 165 | if number.hi <= MAX_SAFE_HI { 166 | number.hi = number.hi * 10.0 + digit as f64; 167 | } else { 168 | number = &number * 10.0 + digit as f64; 169 | } 170 | }; 171 | 172 | let mut exponent = self.exponent - self.count + 1; 173 | match exponent { 174 | _ if exponent > 0 => { 175 | while exponent > LAST_ACCURACY_EXPONENT_10 { 176 | number *= LAST_ACCURACY_POWER_10; // * e22 177 | // overflow break 178 | if number.hi.is_infinite() { 179 | break; 180 | } 181 | exponent -= LAST_ACCURACY_EXPONENT_10; 182 | } 183 | number *= POWER_OF_10[exponent as usize]; // * eX 184 | } 185 | _ if exponent < 0 => { 186 | while exponent < -LAST_ACCURACY_EXPONENT_10 { 187 | number /= LAST_ACCURACY_POWER_10; // / e22 188 | // underflow break 189 | if number.hi == 0.0 { 190 | break; 191 | } 192 | exponent += LAST_ACCURACY_EXPONENT_10; 193 | } 194 | number /= POWER_OF_10[-exponent as usize]; // / eX 195 | } 196 | _ => {} 197 | } 198 | 199 | if self.is_negative { -number.hi } else { number.hi } 200 | } 201 | } 202 | 203 | fn read_inf_or_nan(mut p: Reader) -> Option<(f64, usize)> { 204 | fn common_prefix_length(mut text: Reader, prefix: &[u8]) -> usize { 205 | let mut i: usize = 0; 206 | for &c in prefix { 207 | if text.ended() || text.get().to_ascii_lowercase() != c { 208 | break; 209 | } 210 | i += 1; 211 | text.advance(); 212 | } 213 | return i; 214 | } 215 | 216 | let is_negative = match p.get() { 217 | b'+' => { p.advance(); false } 218 | b'-' => { p.advance(); true } 219 | _ => false 220 | }; 221 | 222 | match p.get() { 223 | b'I' | b'i' => { 224 | let len = common_prefix_length(p.clone(), "infinity".as_bytes()); 225 | if len == 3 || len == 8 { 226 | let res = if is_negative { f64::NEG_INFINITY } else { f64::INFINITY }; 227 | return Some((res, p.1 + len)); 228 | } 229 | } 230 | b'N' | b'n' => { 231 | let len = common_prefix_length(p.clone(), "nan".as_bytes()); 232 | if len == 3 { 233 | let res = if is_negative { -f64::NAN } else { f64::NAN }; 234 | return Some((res, p.1 + len )); 235 | } 236 | } 237 | _ => {}, 238 | } 239 | 240 | None 241 | } 242 | 243 | fn read_fixed_decimal(mut p: Reader) -> Option<(FixedDecimal, usize)> { 244 | const CLIP_EXPONENT: isize = 1000000; 245 | 246 | // read sign 247 | let is_negative = match p.get() { 248 | b'+' => { p.advance(); false } 249 | b'-' => { p.advance(); true }, 250 | _ => false, 251 | }; 252 | 253 | let mut decimal = FixedDecimal{ 254 | count: 0, 255 | digits: [0; FIXED_DECIMAL_DIGITS], 256 | exponent: -1, 257 | is_negative, 258 | }; 259 | 260 | // read mantissa 261 | let mut has_digit = false; // has read any digit (0..9) 262 | let mut has_point = false; // has read decimal point 263 | 'read_mantissa_loop: while !p.ended() { 264 | match p.get() { 265 | b'0'..=b'9' => { 266 | if decimal.count != 0 || p.get() != b'0' { 267 | // save digit 268 | if decimal.count < FIXED_DECIMAL_DIGITS as isize { 269 | decimal.digits[decimal.count as usize] = p.get() - b'0'; 270 | decimal.count += 1; 271 | } 272 | // inc exponenta 273 | if !has_point && decimal.exponent < CLIP_EXPONENT { 274 | decimal.exponent += 1; 275 | } 276 | } else { 277 | // skip zero (dec exponenta) 278 | if has_point && (decimal.exponent > -CLIP_EXPONENT) { 279 | decimal.exponent -= 1; 280 | } 281 | } 282 | has_digit = true; 283 | }, 284 | b'.' => { 285 | if has_point { 286 | return Some((decimal, p.1)); 287 | } 288 | has_point = true; 289 | }, 290 | _ => { 291 | break 'read_mantissa_loop; 292 | }, 293 | } 294 | p.advance(); 295 | } 296 | 297 | if !has_digit { 298 | return None; 299 | } 300 | 301 | // read exponenta 302 | if matches!(p.get(), b'e' | b'E') { 303 | let p_start_exponent = p.1; 304 | p.advance(); 305 | 306 | let mut exponent: isize = 0; 307 | let exponent_sign: isize = match p.get() { 308 | b'+' => { p.advance(); 1 }, 309 | b'-' => { p.advance(); -1 }, 310 | _ => 1, 311 | }; 312 | 313 | // read 314 | if matches!(p.get(), b'0'..=b'9') { 315 | while matches!(p.get(), b'0'..=b'9') { 316 | exponent = (exponent * 10 + (p.get() - b'0') as isize).min(CLIP_EXPONENT); 317 | p.advance(); 318 | } 319 | } else { 320 | return Some((decimal, p_start_exponent)); 321 | } 322 | 323 | // fix 324 | decimal.exponent = decimal.exponent + exponent_sign * exponent; 325 | } 326 | 327 | Some((decimal, p.1)) 328 | } 329 | 330 | // ------------------------------------------------------------------------------------------------- 331 | // ParseFloat parse chars with float point pattern to Double and stored to Value param. 332 | // If no chars match the pattern then Value is unmodified, else the chars convert to 333 | // float point value which will be is stored in Value. 334 | // On success, EndPtr points at the first char not matching the float point pattern. 335 | // If there is no pattern match, EndPtr equals with TextPtr. 336 | // 337 | // If successful function return 1 else 0. 338 | // Remember: on failure, the Value will not be changed. 339 | // 340 | // The pattern is a regular float number, with an optional exponent (E/e) and optional (+/-) sign. 341 | // The pattern allows the values Inf/Infinity and NaN, with any register and optional sign. 342 | // Leading spaces are not allowed in the pattern. A dot is always used as separator. 343 | // ------------------------------------------------------------------------------------------------- 344 | // 345 | // Examples: 346 | // 347 | // "1984\0" -- just read to terminal 348 | // ^ ^------- 349 | // Text, TextEnd, Value = 1984, Result = True 350 | // 351 | // 352 | // "+123.45e-22 abc\0" -- read to end of float number 353 | // ^ ^ 354 | // Text, TextEnd, Value = 123.45e-22, Result = True 355 | // 356 | // 357 | // "aboba\0" -- invalid float point 358 | // ^---------- 359 | // Text, TextEnd, Value = dont change, Result = False 360 | // 361 | // 362 | // "AAA.99\0" -- read with leading dot notation 363 | // ---^ ^----- 364 | // Text, TextEnd, Value = 0.99, Result = True 365 | // 366 | // 367 | // "500e\0" -- read correct part of input 368 | // ^ ^-------- 369 | // Text, TextEnd, Value = 500, Result = True 370 | // 371 | // ------------------------------------------------------------------------------------------------- 372 | fn parse_float_impl(text: Reader) -> Option<(f64, usize)> { 373 | if let res@Some(_) = read_inf_or_nan(text.clone()) { 374 | return res 375 | } else if let Some((decimal, count)) = read_fixed_decimal(text) { 376 | Some(((&decimal).into(), count)) 377 | } else { 378 | None 379 | } 380 | } 381 | 382 | #[no_mangle] 383 | unsafe extern fn parse_float(text: *const c_char, value: *mut c_double, text_end: *mut *const c_char) -> c_int { 384 | match parse_float_impl(Reader::from_raw_ptr(text as *const u8)) { 385 | Some((res, end)) => { 386 | *value = res; 387 | *text_end = text.add(end); 388 | 1 389 | } 390 | None => 0 391 | } 392 | } 393 | 394 | #[cfg(test)] 395 | mod tests { 396 | use crate::{parse_float_impl, Reader}; 397 | 398 | #[test] 399 | fn pi() { 400 | let (result, _) = parse_float_impl(Reader::from_str("3.14159265")).unwrap(); 401 | assert_eq!(result, 3.14159265); 402 | } 403 | 404 | #[test] 405 | fn exponent() { 406 | let (result, _) = parse_float_impl(Reader::from_str("-1234e10")).unwrap(); 407 | assert_eq!(result, -1234e10); 408 | } 409 | 410 | #[test] 411 | fn nan() { 412 | let (result, _) = parse_float_impl(Reader::from_str("nan")).unwrap(); 413 | assert!(f64::is_nan(result)); 414 | } 415 | 416 | #[test] 417 | fn minus_nan() { 418 | let (result, _) = parse_float_impl(Reader::from_str("-nan")).unwrap(); 419 | assert!(f64::is_nan(result)); 420 | } 421 | 422 | #[test] 423 | fn minus_nan_uppercase() { 424 | let (result, _) = parse_float_impl(Reader::from_str("-NaN")).unwrap(); 425 | assert!(f64::is_nan(result)); 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /Ports/RustUnsafe/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "hello" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /Ports/RustUnsafe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /Ports/RustUnsafe/readme.md: -------------------------------------------------------------------------------- 1 | Build: 2 | ```sh 3 | cargo build --release 4 | ``` 5 | 6 | Dinamically link library will be in `target/release/deps/libhello.so`. 7 | -------------------------------------------------------------------------------- /Ports/RustUnsafe/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_int, c_char, c_double}; 2 | 3 | // Check value is +Inf or -Inf 4 | fn is_infinity(value: f64) -> bool { 5 | value == f64::INFINITY || value == f64::NEG_INFINITY 6 | } 7 | 8 | // 31 digits garantee, with (exp^10 >= -291) or (exp^2 >= -968) 9 | #[derive(Clone, Copy)] 10 | struct DoubleDouble { 11 | hi: f64, 12 | lo: f64, 13 | } 14 | 15 | impl Into<DoubleDouble> for f64 { 16 | fn into(self) -> DoubleDouble { 17 | DoubleDouble{ 18 | hi: self, 19 | lo: 0.0, 20 | } 21 | } 22 | } 23 | 24 | impl From<DoubleDouble> for f64 { 25 | fn from(value: DoubleDouble) -> Self { 26 | value.hi 27 | } 28 | } 29 | 30 | impl DoubleDouble { 31 | // Add two Double1 value, condition: |A| >= |B| 32 | // The "Fast2Sum" algorithm (Dekker 1971) [1] 33 | fn fast_add11(a: f64, b: f64) -> Self { 34 | let hi = a + b; 35 | 36 | // infinity check 37 | if is_infinity(hi) { 38 | return hi.into(); 39 | } 40 | 41 | Self{ 42 | hi, 43 | lo: b - (hi - a), 44 | } 45 | } 46 | 47 | // The "2Sum" algorithm [1] 48 | fn add11(a: f64, b: f64) -> Self { 49 | let hi = a + b; 50 | 51 | // infinity check 52 | if is_infinity(hi) { 53 | return hi.into(); 54 | } 55 | 56 | let ah = hi - b; 57 | let bh = hi - ah; 58 | 59 | Self{ 60 | hi: hi, 61 | lo: (a - ah) + (b - bh), 62 | } 63 | } 64 | 65 | // The "Veltkamp Split" algorithm [2] [3] [4] 66 | // See "Splitting into Halflength Numbers" and ALGOL procedure "mul12" in Appendix in [2] 67 | fn split1(a: f64) -> Self { 68 | // The Splitter should be chosen equal to 2^trunc(t - t / 2) + 1, 69 | // where t is the number of binary digits in the mantissa. 70 | const SPLITTER: f64 = 134217729.0;// = 2^(53 - 53 div 2) + 1 = 2^27 + 1 71 | // Just make sure we don't have an overflow for Splitter, 72 | // InfinitySplit is 2^(e - (t - t div 2)) 73 | // where e is max exponent, t is number of binary digits. 74 | const INFINITY_SPLIT: f64 = 6.69692879491417e+299;// = 2^(1023 - (53 - 53 div 2)) = 2^996 75 | // just multiply by the next lower power of two to get rid of the overflow 76 | // 2^(+/-)27 + 1 = 2^(+/-)28 77 | const INFINITY_DOWN: f64 = 3.7252902984619140625e-09;// = 2^-(27 + 1) = 2^-28 78 | const INFINITY_UP: f64 = 268435456.0;// = 2^(27 + 1) = 2^28 79 | 80 | if a > INFINITY_SPLIT || a < -INFINITY_SPLIT { 81 | // down 82 | let a = a * INFINITY_DOWN; 83 | // mul 84 | let temp = SPLITTER * a; 85 | let hi = temp + (a - temp); 86 | let lo = a - hi; 87 | // up 88 | return Self{ 89 | hi: hi * INFINITY_UP, 90 | lo: lo * INFINITY_UP, 91 | }; 92 | } 93 | 94 | let temp = SPLITTER * a; 95 | let hi = temp + (a - temp); 96 | Self{ 97 | hi: hi, 98 | lo: a - hi, 99 | } 100 | } 101 | 102 | // Multiplication two Double1 value 103 | // The "TWO-PRODUCT" algorithm [5] 104 | fn mul11(a: f64, b: f64) -> Self { 105 | let hi = a * b; 106 | 107 | // infinity check 108 | if is_infinity(hi) { 109 | return hi.into(); 110 | } 111 | 112 | let a2 = Self::split1(a); 113 | let b2 = Self::split1(b); 114 | 115 | let err1 = hi - a2.hi * b2.hi; 116 | let err2 = err1 - a2.lo * b2.hi; 117 | let err3 = err2 - a2.hi * b2.lo; 118 | 119 | Self{ 120 | hi: hi, 121 | lo: a2.lo * b2.lo - err3, 122 | } 123 | } 124 | 125 | // Multiplication Double2 by Double1 126 | // The "DWTimesFP1" algorithm [1] 127 | fn mul21(a: Self, b: f64) -> Self { 128 | let c = Self::mul11(a.hi, b); 129 | 130 | // infinity check 131 | if is_infinity(c.hi) { 132 | return c.hi.into(); 133 | } 134 | 135 | let result = Self::fast_add11(c.hi, a.lo * b); 136 | Self::fast_add11(result.hi, result.lo + c.lo) 137 | } 138 | 139 | // Division Double2 by Double1 140 | // The "DWDivFP2" algorithm [1] 141 | fn div21(a: Self, b: f64) -> Self { 142 | let hi = a.hi / b; 143 | 144 | // infinity check 145 | if is_infinity(hi) { 146 | return hi.into(); 147 | } 148 | 149 | let p = Self::mul11(hi, b); 150 | 151 | let dhi = a.hi - p.hi; 152 | let d = Self{ 153 | hi: dhi, 154 | lo: dhi - p.lo, 155 | }; 156 | 157 | let result = Self{ 158 | hi: hi, 159 | lo: (d.lo + a.lo) / b, 160 | }; 161 | 162 | return Self::fast_add11(result.hi, result.lo); 163 | } 164 | 165 | // Addition Double2 and Double1 166 | // The DWPlusFP algorithm [1] 167 | fn add21(a: Self, b: f64) -> Self { 168 | let mut result = Self::add11(a.hi, b); 169 | 170 | // infinity check 171 | if is_infinity(result.hi) { 172 | return result.hi.into(); 173 | } 174 | 175 | result.lo = result.lo + a.lo; 176 | Self::fast_add11(result.hi, result.lo) 177 | } 178 | } 179 | 180 | 181 | // --- 182 | 183 | const FIXED_DECIMAL_DIGITS: usize = 17 * 2; 184 | struct FixedDecimal { 185 | count: isize, 186 | exponent: isize, 187 | is_negative: bool, 188 | digits: [u8; FIXED_DECIMAL_DIGITS], // Max digits in Double value * 2 189 | } 190 | 191 | unsafe fn text_prefix_length(text: *const u8, prefix: *const u8) -> isize { 192 | let mut i: isize = 0; 193 | while { 194 | let texti = *text.offset(i); 195 | let prefixi = *prefix.offset(i); 196 | texti == prefixi || texti >= b'A' && texti <= b'Z' && texti + 32 == prefixi 197 | } { 198 | if *text.offset(i) == b'\0' { 199 | break; 200 | } 201 | i += 1; 202 | } 203 | return i; 204 | } 205 | 206 | unsafe fn read_special(text: *const u8) -> Result<(f64, *const u8), ()> { 207 | // clean 208 | let mut is_negative: bool = false; 209 | 210 | // read from start 211 | let mut p: *const u8 = text; 212 | 213 | // read sign 214 | match *p { 215 | b'+' => p = p.offset(1), 216 | b'-' => { 217 | is_negative = true; 218 | p = p.offset(1); 219 | }, 220 | _ => {}, 221 | } 222 | 223 | // special 224 | match *p { 225 | b'I' | b'i' => { 226 | let len = text_prefix_length(p, "infinity".as_ptr()); 227 | if len == 3 || len == 8 { 228 | let mut res = f64::INFINITY; 229 | if is_negative { 230 | res = -res; 231 | } 232 | return Ok((res, p.offset(len))); 233 | } 234 | } 235 | b'N' | b'n' => { 236 | let len = text_prefix_length(p, "nan".as_ptr()); 237 | if len == 3 { 238 | let mut res = f64::NAN; 239 | if is_negative { 240 | res = -res; 241 | } 242 | return Ok((res, p.offset(len))); 243 | } 244 | } 245 | _ => {}, 246 | } 247 | 248 | Err(()) 249 | } 250 | 251 | unsafe fn read_text_to_fixed_decimal(text: *const u8) -> Result<(FixedDecimal, *const u8), ()> { 252 | const CLIP_EXPONENT: isize = 1000000; 253 | 254 | // clean 255 | let mut decimal = FixedDecimal{ 256 | count: 0, 257 | digits: [0; FIXED_DECIMAL_DIGITS], 258 | exponent: -1, 259 | is_negative: false, 260 | }; 261 | 262 | // read from start 263 | let mut p: *const u8 = text; 264 | 265 | // read sign 266 | match *p { 267 | b'+' => p = p.offset(1), 268 | b'-' => { 269 | decimal.is_negative = true; 270 | p = p.offset(1); 271 | }, 272 | _ => {}, 273 | } 274 | 275 | // read mantissa 276 | let mut has_digit = false; // has read any digit (0..9) 277 | let mut has_point = false; // has read decimal point 278 | 'end_read_mantissa: while *p != b'\0' { 279 | match *p { 280 | b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' => { 281 | if decimal.count != 0 || *p != b'0' { 282 | // save digit 283 | if decimal.count < FIXED_DECIMAL_DIGITS as isize { 284 | decimal.digits[decimal.count as usize] = *p - b'0'; 285 | decimal.count += 1; 286 | } 287 | // inc exponenta 288 | if !has_point && decimal.exponent < CLIP_EXPONENT { 289 | decimal.exponent += 1; 290 | } 291 | } else { 292 | // skip zero (dec exponenta) 293 | if has_point && (decimal.exponent > -CLIP_EXPONENT) { 294 | decimal.exponent -= 1; 295 | } 296 | } 297 | has_digit = true; 298 | }, 299 | b'.' => { 300 | if has_point { 301 | return Ok((decimal, p)); // make 302 | } 303 | has_point = true; 304 | }, 305 | _ => { 306 | break 'end_read_mantissa; 307 | }, 308 | } 309 | p = p.offset(1); 310 | } 311 | 312 | if !has_digit { 313 | return Err(()); 314 | } 315 | 316 | // read exponenta 317 | if *p == b'e' || *p == b'E' { 318 | let p_start_exponent = p; 319 | p = p.offset(1); 320 | 321 | let mut exponent: isize = 0; 322 | let mut exponent_sign: isize = 1; 323 | 324 | // check sign 325 | match *p { 326 | b'+' => p = p.offset(1), 327 | b'-' => { 328 | exponent_sign = -1; 329 | p = p.offset(1); 330 | }, 331 | _ => {}, 332 | } 333 | 334 | // read 335 | if *p >= b'0' && *p <= b'9' { 336 | while *p >= b'0' && *p <= b'9' { 337 | exponent = exponent * 10 + (*p - b'0') as isize; 338 | if exponent > CLIP_EXPONENT { 339 | exponent = CLIP_EXPONENT; 340 | } 341 | p = p.offset(1); 342 | } 343 | } else { 344 | return Ok((decimal, p_start_exponent)); // Make 345 | } 346 | 347 | // fix 348 | decimal.exponent = decimal.exponent + exponent_sign * exponent; 349 | } 350 | 351 | Ok((decimal, p)) // Make 352 | } 353 | 354 | fn fixed_decimal_to_double(decimal: &FixedDecimal) -> f64 { 355 | const LAST_ACCURACY_EXPONENT_10: isize = 22; // for Double 356 | const LAST_ACCURACY_POWER_10: f64 = 1e22; // for Double 357 | const MAX_SAFE_INT: f64 = 9007199254740991.0; // (2^53−1) for Double 358 | const MAX_SAFE_HI: f64 = (MAX_SAFE_INT - 9.) / 10.; // for X * 10 + 9 359 | const POWER_OF_10: [f64; 1+LAST_ACCURACY_EXPONENT_10 as usize] = [ 360 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 361 | 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 362 | ]; 363 | 364 | let mut result: f64; 365 | let mut number: DoubleDouble; 366 | 367 | number = 0.0.into(); 368 | 369 | // set mantissa 370 | for i in 0..decimal.count as usize { 371 | if number.hi <= MAX_SAFE_HI { 372 | number.hi = number.hi * 10.0; 373 | number.hi = number.hi + decimal.digits[i] as f64; // + Digit 374 | } else { 375 | number = DoubleDouble::mul21(number, 10.0); // * 10 376 | number = DoubleDouble::add21(number, decimal.digits[i] as f64); // + Digit 377 | } 378 | }; 379 | 380 | // set exponent 381 | let mut exponent = decimal.exponent - decimal.count + 1; 382 | 383 | // positive exponent 384 | while exponent > 0 { 385 | if exponent > LAST_ACCURACY_EXPONENT_10 { 386 | // * e22 387 | number = DoubleDouble::mul21(number, LAST_ACCURACY_POWER_10); 388 | // overflow break 389 | if is_infinity(number.into()) { 390 | break; 391 | } 392 | exponent = exponent - LAST_ACCURACY_EXPONENT_10; 393 | } else { 394 | // * eX 395 | number = DoubleDouble::mul21(number, POWER_OF_10[exponent as usize]); 396 | break; 397 | } 398 | } 399 | 400 | // negative exponent 401 | while exponent < 0 { 402 | if exponent < -LAST_ACCURACY_EXPONENT_10 { 403 | // / e22 404 | number = DoubleDouble::div21(number, LAST_ACCURACY_POWER_10); 405 | // underflow break 406 | if <DoubleDouble as Into<f64>>::into(number) == 0.0 { 407 | break; 408 | } 409 | exponent = exponent + LAST_ACCURACY_EXPONENT_10; 410 | } else { 411 | // / eX 412 | number = DoubleDouble::div21(number, POWER_OF_10[-exponent as usize]); 413 | break; 414 | } 415 | } 416 | 417 | // make result 418 | result = number.into(); 419 | 420 | // fix sign 421 | if decimal.is_negative { 422 | result = -result; 423 | } 424 | return result; 425 | } 426 | 427 | 428 | // ------------------------------------------------------------------------------------------------- 429 | // ParseFloat parse chars with float point pattern to Double and stored to Value param. 430 | // If no chars match the pattern then Value is unmodified, else the chars convert to 431 | // float point value which will be is stored in Value. 432 | // On success, EndPtr points at the first char not matching the float point pattern. 433 | // If there is no pattern match, EndPtr equals with TextPtr. 434 | // 435 | // If successful function return 1 else 0. 436 | // Remember: on failure, the Value will not be changed. 437 | // 438 | // The pattern is a regular float number, with an optional exponent (E/e) and optional (+/-) sign. 439 | // The pattern allows the values Inf/Infinity and NaN, with any register and optional sign. 440 | // Leading spaces are not allowed in the pattern. A dot is always used as separator. 441 | // ------------------------------------------------------------------------------------------------- 442 | // 443 | // Examples: 444 | // 445 | // "1984\0" -- just read to terminal 446 | // ^ ^------- 447 | // Text, TextEnd, Value = 1984, Result = True 448 | // 449 | // 450 | // "+123.45e-22 abc\0" -- read to end of float number 451 | // ^ ^ 452 | // Text, TextEnd, Value = 123.45e-22, Result = True 453 | // 454 | // 455 | // "aboba\0" -- invalid float point 456 | // ^---------- 457 | // Text, TextEnd, Value = dont change, Result = False 458 | // 459 | // 460 | // "AAA.99\0" -- read with leading dot notation 461 | // ---^ ^----- 462 | // Text, TextEnd, Value = 0.99, Result = True 463 | // 464 | // 465 | // "500e\0" -- read correct part of input 466 | // ^ ^-------- 467 | // Text, TextEnd, Value = 500, Result = True 468 | // 469 | // ------------------------------------------------------------------------------------------------- 470 | unsafe fn parse_float_impl(text: *const u8) -> Result<(f64, *const u8), ()> { 471 | // Try read inf/nan 472 | read_special(text) 473 | .or_else(|_| { 474 | // Try read number 475 | read_text_to_fixed_decimal(text) 476 | // Convert Decimal to Double 477 | .map(|(decimal, text_end)| 478 | (fixed_decimal_to_double(&decimal), text_end)) 479 | }) 480 | } 481 | 482 | #[no_mangle] 483 | unsafe extern fn parse_float(text: *const c_char, value: *mut c_double, text_end: *mut *const c_char) -> c_int { 484 | match parse_float_impl(text as *const u8) { 485 | Ok((res, end)) => { 486 | *value = res; 487 | *text_end = end as *const c_char; 488 | 1 489 | }, 490 | Err(_) => { 491 | 0 492 | }, 493 | } 494 | } 495 | 496 | #[cfg(test)] 497 | mod tests { 498 | use crate::parse_float_impl; 499 | 500 | #[test] 501 | fn pi() { 502 | let (result, _) = unsafe { parse_float_impl("3.14159265".as_ptr()).unwrap() }; 503 | assert_eq!(result, 3.14159265); 504 | } 505 | 506 | #[test] 507 | fn exponent() { 508 | let (result, _) = unsafe { parse_float_impl("-1234e10".as_ptr()).unwrap() }; 509 | assert_eq!(result, -1234e10); 510 | } 511 | 512 | #[test] 513 | fn nan() { 514 | let (result, _) = unsafe { parse_float_impl("nan".as_ptr()).unwrap() }; 515 | assert!(f64::is_nan(result)); 516 | } 517 | 518 | #[test] 519 | fn minus_nan() { 520 | let (result, _) = unsafe { parse_float_impl("-nan".as_ptr()).unwrap() }; 521 | assert!(f64::is_nan(result)); 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PureParseFloat [en] 2 | 3 | **PureParseFloat - This is a simple and clear, "clean" algorithm and implementation of a function for converting a string to a double number, with OK accuracy.** 4 | **The main feature of the algorithm that underlies this implementation is the trade-off between simplicity, accuracy, and speed.** 5 | 6 | **PureParseFloat** provides accurate conversion of numbers with a mantissa up to 31 digits, in the exponent range from -291 to +308, otherwise the conversion can have a maximum error of 1 ULP, which means that for most numbers encountered in practice the conversion will be absolutely accurate. 7 | In general, this implementation adheres to the idea of a "fairly good and at the same time simple floating point parser." 8 | 9 | There are two "reference" implementations available in this repository, for Pascal and C. Both implementations are covered with tests on millions of values, however the "main" implementation is the Pascal version. 10 | 11 | User ports in [```Ports/```](https://github.com/turborium/PureParseFloat/tree/main/Ports) dir. 12 | 13 | Pascal version was tested on Delphi and FreePascal, C version was tested on TDM-GCC. 14 | 15 | Warning: the algorithm needs double aritmetic exactly matching the IEEE standard, so before using the ParseFloat function, you must set the FPU precision to Double/64bit and rounding to the closest. In ObjectPascal there are standard functions for this, which are used by the Pascal reference implementation, but for C you need to do this yourself. 16 | 17 | # PureParseFloat [ru] 18 | 19 | **PureParseFloat - Это простая и понятная, "чистая", реализация функции для преобразования строки в число формата double, с достаточно хорошей точностью.** 20 | **Главная особенность алгоритма, который лежит в основе этой реализации, это компромисс между простотой, точносью, и скоростью.** 21 | 22 | **PureParseFloat** обеспечивает точное преобразование чисел с мантиссой до 31 разряда, в диапазоне экспоненты от -291 до +308, в противном случае преобразование может иметь максимальную ошибку в 1 ULP, это означает, что для большинства встречающихся на практике чисел перобразование будет абсолютно точным. 23 | В целом эта реализация придерживается идеи "достаточно хороший и при этом простой парсер чисел с плавающей запятой". 24 | 25 | В этом репозитории доступны две "референсные" реализации, для Pascal и C. Обе реализации покрыты тестами на миллионы значений, однако "главной" реализацией являеться Pascal версия. 26 | 27 | Пользовательские порты в папке [```Ports/```](https://github.com/turborium/PureParseFloat/tree/main/Ports). 28 | 29 | Pascal версия протестирована в Delphi и FreePascal, С версия протистирована в TDM-GCC. 30 | 31 | Внимание: алгоритм полагается на точное соответсвие double стандарту ieee, поэтому перед использованием функции ParseFloat необходимо установить точность математического сопроцессора в Double/64bit, а округление в "ближайшее"/Nearest. В ObjectPascal для этого есть стандартные средства, котрые использует референсная Pascal реализация, для C же необходимо это сделать самостоятельно. 32 | 33 | ## Проблематика 34 | Преобразование строки в число формата double, на удивление, является довольно [сложной](https://www.exploringbinary.com/topics/#correctly-rounded-decimal-to-floating-point) и комплексной задачей. 35 | Из-за ограниченой точности чисел формата Double, простые алгоритмы преобразования, часто, дают ошибку в несколько ULP (+/- несколько единиц двоичной мантиссы). Описание простого алгорита преобразования есть ниже, а также в [статье](https://www.exploringbinary.com/quick-and-dirty-decimal-to-floating-point-conversion/). 36 | 37 | В стандартной библиотеке многих компиляторов и интерпритаторов есть функции преобразования строки в число подверженные некоторой неточности, а стандарты языков программирования как правило не уточняют ничего о точности преобразования. 38 | 39 | **Примеры проблем:** 40 | - **Visual Studio C++** более 20 лет [включала неточную версию функции **strtod**](https://www.exploringbinary.com/incorrectly-rounded-conversions-in-visual-c-plus-plus/), а исправление потребовало [множество итераций](https://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/). Классический **С RTL** - **'MSVCRT.DLL'**, включенный в **Windows**, все еще имеет "неточную" версию **strtod**. 41 | - Исправление ошибок округления в **GNU GCC** [потребовало множество лет и итераций](https://www.exploringbinary.com/incorrectly-rounded-conversions-in-gcc-and-glibc/), и в конце концов [увенчалось успехом](https://www.exploringbinary.com/real-c-rounding-is-perfect-gcc-now-converts-correctly/), благодаря переходу на использование сторонней, "тяжелой", библиотеки [**MPFR**](https://www.mpfr.org/), которая использует "тяжелую" библиотеку [**GMPLIB**](https://gmplib.org/) для длинных вычислений. Однако в **GCC**, все еще, есть [некоторые проблемы](https://lemire.me/blog/2020/06/26/gcc-not-nearest/) с точностью. 42 | - В **Delphi**, все еще, используется простой алгоритм подверженный ошибкам, особенно в **x64**, т.к. в **x64** используется **double**, а не более точный **extended** для преобразования. 43 | 44 | Несмотря на то, что для большинства приложений, ошибка в несколько **ULP** не являеться реальной проблемой, в определеных задачах хотелось бы иметь достаточно точное и стабильное преобразование, не зависящее от разрядности, компилятора, языка программирования и т.д.. 45 | 46 | Одно из решений для преобразования строки в double это алгоритм авторства **David M. Gay** описанный в [**"Correctly Rounded Binary-Decimal 47 | and Decimal-Binary Conversions"**](https://www.ampl.com/archive/first-website/REFS/rounding.pdf), реализованный автором алгоритма в виде библиотеки [**netlib dtoa.c**](https://portal.ampl.com/~dmg/netlib/fp/dtoa.c). 48 | Стоит отметить, что именно **David M. Gay** один из первых поднял "проблематику" неточных преобразований строки в число, и предложил свое решение. 49 | Однако, несмотря на то, что даная реализация часто используется как "референсная", они имеет некотрые проблемы, такие как сложность и подверженность фатальным ошибкам. Файл **dtoa.c** имеет более **6000** строк кода, имеет очень большую цикломатическую сложность, большую вложенность, множество магических чисел, операторов goto. 50 | 51 | **Некоторые примеры проблем которые вызвало использование netlib dtoa.c:** 52 | - [Переполнение буффера](https://bugzilla.mozilla.org/show_bug.cgi?id=516396) при специально подготовленном длином числе в строке (уязвимость была в **FireFox**, **Safari**, **Chrome**, **Ruby**) 53 | - [Фатальное зацикливание](https://news.ycombinator.com/item?id=2164863) при преобразовании строки **"2.2250738585072011e-308"**, которое приводило к зависанию [**PHP**](https://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/) и [**Java**](https://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/) 54 | - [Фатальная неточность](https://patrakov.blogspot.com/2009/03/dont-use-old-dtoac.html) в преобразовании: "1.1" -> 11.0 (получение полностью неверного числа для корректной строки) 55 | - [Неточное преобразование](https://www.exploringbinary.com/incorrect-directed-conversions-in-david-gays-strtod/) 56 | - [Ошибка на 1 ULP](https://www.exploringbinary.com/a-bug-in-the-bigcomp-function-of-david-gays-strtod/) 57 | 58 | Несмотря на то, что на данный момент, все перечисленные баги были [исправлены](https://portal.ampl.com/~dmg/netlib/fp/changes), нельзя быть на 100% увереным в отсутствии других "фатальных" багов. Кроме того, реализция [**netlib dtoa.c**](https://portal.ampl.com/~dmg/netlib/fp/dtoa.c) настолько сложна, что переписать ее на другой язык является крайне сложной задачей, с очень большой вероятностью внести, при переписывании, дополнительные ошибки. 59 | А если учитывать, что переписаная на [**Java версия имела баг, с зависанием,**](https://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/), от референсной **C** реализации, можно сделать вывод, что понимание того как в действительности работает [**netlib dtoa.c**](https://portal.ampl.com/~dmg/netlib/fp/dtoa.c) имеет только один человек - автор данного алгоритма и реализации, что, на взгляд автора данной статьи, являеться просто "безумным", учитывя "базовость", концепции преобразования строчки в число. 60 | 61 | В 2020 году появился новый алгоритм преобразования [**Daniel Lemire fast_double_parser**](https://github.com/lemire/fast_double_parser), а также его улучшенная реализация [**fast_float**](https://github.com/fastfloat/fast_float). 62 | Главным плюсом данного алгоритма являеться наличие понятного, хотябы в общих чертах, описания, а также наличие [нескольких реализаций](https://github.com/lemire/fast_double_parser#ports-and-users) на [нескольких языках](https://github.com/fastfloat/fast_float#other-programming-languages). Существует также ["вводное" описание алгоритма](https://nigeltao.github.io/blog/2020/eisel-lemire.html). Главным же плюсаом, на взгляд автора статьи, является намного меньшая сложность в сравнении с **netlib dtoa.c**. 63 | 64 | **Некоторые минусы алгоритма Daniel Lemire:** 65 | - Местами меньшая скорость чем у **netlib dtoa.c** 66 | - Все еще высокая сложность, что делает его понимание практически невозможным для большинства программистов (несколько тысяч строк при полной реализации на **С++**) 67 | - Наличие "тяжелых" таблиц (не подходит для слабых микроконтроллеров с малым количеством памяти) 68 | - Использование "дорогой" длиной арифметики для некоторых "сложных" чисел, что делает время преобразования сильно недетерминированным (время преобразования моежт возрастать на порядок из-за небольшого изменения входных данных) 69 | 70 | Исходя из выше описанных плюсов и минусов, можно сделать вывод, что относительно новый алгоритм **Daniel Lemire** может быть хорошим выбором во многих случаях. Однако и он сликом сложен для понимания большинства программистов. Кроме того, несмотря на наличие нескольких описаний, конкретные реализации имеют некоторые "магические" числа, кочующиее из одной реализации в другую, предположительно методом "копипаста". В текущих реализациях "резервного алгоритма"(с использованием длинной арифметики) используется буффер цифр фиксированного размера, с коменариями ["должно быть достаточно"](https://github.com/google/wuffs/blob/ba3818cb6b473a2ed0b38ecfc07dbbd3a97e8ae7/internal/cgen/base/floatconv-submodule-code.c#L160C77-L160C77), что не дает быть уверенным, что мы всегда получим "безупречную" точность. 71 | 72 | Изучив плюсы и минусы выше пречисленных алгоритмов был разработан новый "достаточно хороший" алгоритм **Pure Parse Float** для преобразования строчки в число **double**. 73 | Новый алгоритм имеет среднюю скорость, достаточную точность, стабильность, и максимальную простоту. 74 | Таким образом, если вам важна понятность, небольшой размер (в том числе в бинарном виде), отсутствие "скрытых" критических багов, которые могут привести к зависанию, переполнению буффера, или фатальной неточности, из-за слишком большой сложности, но при этом вы готовы к небольшому количеству отклонений в **1 ULP** на граничных случаях, то алгоритм **Pure Parse Float** является оптимальным выбором. (В самом худшем случае, если в алгоритме есть ошибки - при преобразовании выйдет ошибка в небольшом количестве **ULP**) 75 | 76 | ## Алгоритм 77 | 78 | Так как алгоритм **Pure Parse Float** основан на простом алгоритме преобразования, то сначала рассмотрим простой алгоритм преобразования строчки в double. 79 | 80 | ### Простой алгоритм преобразования строки в double 81 | 1) Необходимо сконвертировать строчку в структуру FixedDecimal следующего вида: 82 | ``` 83 | TDigitsArray = array [0 .. 16] of Byte;// Массив цифр, размер массива равен максимальному количеству десятичных цифр в Double (17 цифр) 84 | TFixedDecimal = record 85 | Count: Integer;// Количество цифр 86 | Exponent: Integer;// Экспонента числа 87 | IsNegative: Boolean;// Негативное число 88 | Digits: TDigitsArray;// Цифры (мантисса) 89 | end; 90 | ``` 91 | *Пример для строчки "123.45":* 92 | ``` 93 | Count = 5; 94 | Exponent = 2; 95 | IsNegative = False; 96 | Digits = [1, 2, 3, 4, 5]; 97 | ``` 98 | 2) Зануляем результат: 99 | ``` 100 | Result := 0.0; 101 | ``` 102 | 3) Записываем цифры числа(мантиссу) в Result, умножая Result на 10 и добавляя очередную цифру, пока не запишем все цифры: 103 | ``` 104 | for I := 0 to Decimal.Count - 1 do 105 | begin 106 | Result := Result * 10;// * 10 107 | Result := Result + Decimal.Digits[I];// + Digit 108 | end; 109 | ``` 110 | 4) Корректируем экспоненту числа: 111 | ``` 112 | Exponent := Decimal.Exponent - Decimal.Count + 1; // Разница между необходимой и текущей экспонентой 113 | Result := Result * Power(10.0, Exponent); // фиксим степень 114 | ``` 115 | 5) Корректируем знак числа: 116 | ``` 117 | if Decimal.IsNegative then 118 | begin 119 | Result := -Result; 120 | end; 121 | ``` 122 | 123 | Этот алгоритм для входных строк в диапазоне **double** в ~60% будет выдавать правильно округленный результат, в ~30% отклонение будет в **1 ULP**, и в ~10% отклонение будет больше чем **1 ULP**. 124 | Стоит отметить, что "неточности" складываються. Например, если некоторое число было неверно округлено при формировании мантиссы, например, из-за того что в нем было больше 15 цифр, то это даст ошибку в **1 ULP**, а если при установке порядка произошло еще одно неверное округление, то выйдет ошибка в еще несколько **ULP**, таким образом общая ошибка может быть больше **1 ULP**. 125 | В целом, совсем "фатально" неверных результатов преобразования получить нельзя, однако "чучуть" неточных результатов слишком много. В худшем случае до +/-20 ULP. 126 | 127 | Основной причиной неточности "простого" алгоритма является преждевременное округление результата, из-за физического ограничения количества битов хранящих число формата double. В double может поместиться 15-18 десятичных цифр, реально же гарантируется 15. 128 | Например, для вполне валидной(для double) строки "18014398509481993" с 17 цифрами простой алгоритм выдаст результат с ошбикой в **1 ULP**, из-за того, что double гарантировано хранит лишь 15-17 десятичных цифр, и при попытке "внести" вполне корректное число с 17 цифрами, мы превышаем всегда гарантированые 15 цифр, что, на конкретно этом числе, приводит к неверному округлению. Для строки же "18014398509481993e71" уже неверно внесенные 17 цифр еще и умножаются на степень 10, что еще дополнительно увеличивает ошибку. Кроме того функция возведения числа в степень сама может иметь некоторую неточность(в большинстве реализаций), что может еще сильнее увеличить ошибку. 129 | Интересно здесь то, что буквально 1 бита мантиссы хватило бы для верного округления. 130 | 131 | Теперь рассмотрим алгоритм Pure Parse Float. 132 | 133 | ### Алгоритм Pure Parse Float 134 | 135 | Алгоритм Pure Parse Float в общих чертах аналогичен простому алгоритму, но для промежуточных вычислений использует числа формата double-double. 136 | 137 | Арифметика double-double - это описанный в статье T. J. Dekker ["A Floating-Point Technique for Extending the Available Precision"](https://csclub.uwaterloo.ca/~pbarfuss/dekker1971.pdf) метод реализации float-point, почти четырехкратной точности, с использованием пары значений double. Используя два значения IEEE double с 53-битной мантиссой, арифметика double-double обеспечивает операции с числами, мантисса которых не меньше 2 * 53 = 106 бит. 138 | Однако диапазон(экспонента) double-double остается таким же, как и у формата double. 139 | Числа double-double имеют гарантированную точность в 31 десятичную цифру, за исключением показателей степени меньше -291 (exp^2 >= -968), из-за денормализованных чисел, где точность double-double постепенно уменьшается до обычного double. 140 | Небольшой обзор дабл-дабла доступен здесь в этой [статье](https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format#Double-double_arithmetic). 141 | Дополнительная информация о double-double представлена в референсной Pascal реализации. 142 | Ключевой особенностью double-double является то, что число double-double представлено в виде Hi + Lo, где Hi - это правильно округленное значение «главного» числа, а Lo — это некий «Остаток». 143 | Например: '123456789123456789123456789' представлено в виде '1.2345678912345679e+26' + '-2.214306027e+9'. 144 | Существует простое объяснение double-double в виде [статьи](https://habr.com/ru/articles/423469/) и в виде [ролика](https://www.youtube.com/watch?v=6OuqnaHHUG8). 145 | 146 | Итак, ключевой идеей Pure Parse Float является то, что double-double арифметика позволяет проводить вычисления с большей точностью, получая, в Hi части double-double числа, правильно округленное значение результата преобразования строчки в число. 147 | 148 | Таким образом, для чисел длиной до 31 цифры, и с экспонентой от -291 до +308, ошибка будет в **0 ULP**. 149 | Для денормализованных чисел в диапазоне экспоненты меньше -291 ошибка будет всего в 1 ULP, т.к. ошибка происходит единожды, при установке степени. 150 | 151 | Референсная Pascal реализация имеет доработанные методы работы с double-double арифметикой, с дополнениями позволяющими правильно обрабатывать такие числа как NaN/Infinity. 152 | Все методы работы с double-double имеют необходимые ссылки на их математическое доказательство, а все константы подробно объяснены. 153 | Также, для упрощения, функция возведения в степень не реализована, используется простой табличный метод и цикл. (Имеет смысл провести исследование на тему возведения в степень чисел формата double-double, возможно можно избавиться и от ошибки в 1 ULP, в области денормализованных чисел) 154 | 155 | Внимание: алгоритм полагается на точное соответсвие double стандарту ieee, поэтому перед использованием функции ParseFloat необходимо установить точность сопроцессора в Double/64bit, а округление в "ближайшее"/Nearest. В ObjectPascal для этого есть стандартные средства, котрые использует референсная Pascal реализация, для C же необходимо это сделать самостоятельно. 156 | 157 | Референсная реализация имеет минимальную "ручную" оптимизацию исходного текста для максимальной читабельности. 158 | 159 | ## Тесты 160 | 161 | Корректность работы Pure Parse Float была проверена на нескольких десятках миллионах входных строк, для чего была написана специальная тестовая программа. 162 | Тестовая программа также вклчает в себя бенчмарк, позволяющий сравнить производительность Pure Parse Float в сравнении с другими алгоритмами. 163 | 164 | Результат сравнения точности на всем диапазоне чисел: 165 | | Parser | Regular Numbers Fails | Long Numbers Fails | Read/Write Double Fails | Result | 166 | |-------------------------------------|--------------------------|---------------------------|--------------------------|--------| 167 | | PureParseFloat (x86/x64) | 0.033% | 0.013% | 0.016% | OK | 168 | | Delphi StrToFloat (x86) | 0.033% | 0.102% | 0.010% | OK | 169 | | Delphi StrToFloat (x64) | 39.106% / (7.872% >1ULP) | 71.897% / (47.451% >1ULP) | 31.434% / (0.934% >1ULP) | FAIL | 170 | | FreePascal StrToFloat (x64) | 0.067% | 0.062% | 0.021% | OK | 171 | | Microsoft strtod (MSVCRT.DLL) (x86) | 0.987% / (0.407% >1ULP) | 0.326% / (0.111% >1ULP) | 0.018% | FAIL | 172 | | Microsoft strtod (MSVCRT.DLL) (x64) | 0.987% / (0.407% >1ULP) | 0.326% / (0.111% >1ULP) | 0.018% | FAIL | 173 | 174 | В качестве референса взяты результаты преобразования **netlib dtoa.c**, в которой, предположительно, уже "отловлены" большинство ошибок. 175 | Как видно из таблицы - x64 Delphi StrToFloat работает намного хуже, чем x86 версия, из-за упомянутого выше "округления" double, в x86 же вычисления присходят в более точном extended. 176 | Реализация от Microsoft имеет множество ошибок больших чем 1ULP. 177 | Реализация в FreePascal довольно хороша в точности. 178 | Однако, как видно из таблицы, Pure Parse Float обеспечивает самую лучшую точность, при всей своей простоте. 179 | 180 | Резуьтат сравнения скорости (меньше - лучше): 181 | 182 | | Parser | Time (x86) | Time (x64) | 183 | |-------------------------------|------------|------------| 184 | | PureParseFloat (Delphi) | 7058ms | 2929ms | 185 | | PureParseFloat (FreePascal) | | 2996ms | 186 | | PureParseFloat (C, TDM-GCC) | 15866ms(!) | 1992ms | 187 | | Netlib strtod | 1656ms | 1226ms | 188 | | Delphi TextToFloat | 692ms | 1842ms | 189 | | FreePascal TextToFloat | | 1685ms | 190 | | Microsoft strtod | 5488ms | 3808ms | 191 | 192 | Как можно видеть из таблицы - лидера по скорости нет, в зависимости от компилятора и разрядности получаются разные результаты. 193 | В целом ParseFloat имеет среднюю скорость, кроме аномально низкой скорости C версии, на x86, с компилятором GCC, вероятно причиной этому является активированая опция ```-ffloat-store```, без которй GCC [порождает неверный код](https://lemire.me/blog/2020/06/26/gcc-not-nearest/). 194 | В x64 версии - видны, те самые, ~20% разницы производительности между C и Pascal. 195 | Что интересно - скорость функции ParseFloat скомпилированной FreePascal и Delphi - примерно одинаковая, хотя известно что в FPC намного более "слабый" оптимизатор. 196 | 197 | Если вам интересно увидеть свою реализацию, для языка отсутсвующего в этой таблице, то создайте ее в соответсвующей директории, и приложите проект, создающий DLL для тестирования, аналогичный тому, что создан для C версии. 198 | --------------------------------------------------------------------------------