1 /******************************************************************************* 2 3 A set of functions for converting between string and floating- 4 point values. 5 6 Applying the D "import alias" mechanism to this module is highly 7 recommended, in order to limit namespace pollution: 8 --- 9 import Float = ocean.text.convert.Float; 10 11 auto f = Float.parse ("3.14159"); 12 --- 13 14 Copyright: 15 Copyright (c) 2004 Kris Bell. 16 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 17 All rights reserved. 18 19 License: 20 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 21 See LICENSE_TANGO.txt for details. 22 23 Version: 24 Nov 2005: Initial release 25 Jan 2010: added internal ecvt() 26 27 Authors: Kris 28 29 ********************************************************************************/ 30 31 module ocean.text.convert.Float; 32 33 import ocean.core.ExceptionDefinitions; 34 import ocean.core.Verify; 35 import ocean.math.IEEE; 36 import ocean.meta.types.Qualifiers; 37 static import Integer = ocean.text.convert.Integer_tango; 38 39 static import tsm = core.stdc.math; 40 41 private alias real NumType; 42 43 /****************************************************************************** 44 45 Constants 46 47 ******************************************************************************/ 48 49 private enum 50 { 51 Pad = 0, // default trailing decimal zero 52 Dec = 2, // default decimal places 53 Exp = 10, // default switch to scientific notation 54 } 55 56 /****************************************************************************** 57 58 Convert a formatted string of digits to a floating-point 59 number. Throws an exception where the input text is not 60 parsable in its entirety. 61 62 ******************************************************************************/ 63 64 NumType toFloat(T) (T[] src) 65 { 66 uint len; 67 68 auto x = parse (src, &len); 69 if (len < src.length || len == 0) 70 throw new IllegalArgumentException ("Float.toFloat :: invalid number"); 71 return x; 72 } 73 74 /****************************************************************************** 75 76 Template wrapper to make life simpler. Returns a text version 77 of the provided value. 78 79 See format() for details 80 81 ******************************************************************************/ 82 83 char[] toString (NumType d, uint decimals=Dec, int e=Exp) 84 { 85 char[64] tmp = void; 86 87 return format (tmp, d, decimals, e).dup; 88 } 89 90 /****************************************************************************** 91 92 Truncate trailing '0' and '.' from a string, such that 200.000 93 becomes 200, and 20.10 becomes 20.1 94 95 Returns a potentially shorter slice of what you give it. 96 97 ******************************************************************************/ 98 99 T[] truncate(T) (T[] s) 100 { 101 auto tmp = s; 102 int i = tmp.length; 103 foreach (int idx, T c; tmp) 104 { 105 if (c is '.') 106 { 107 while (--i >= idx) 108 { 109 if (tmp[i] != '0') 110 { 111 if (tmp[i] is '.') 112 --i; 113 s = tmp [0 .. i+1]; 114 while (--i >= idx) 115 if (tmp[i] is 'e') 116 return tmp; 117 break; 118 } 119 } 120 } 121 } 122 return s; 123 } 124 125 /****************************************************************************** 126 127 Extract a sign-bit 128 129 ******************************************************************************/ 130 131 private bool negative (NumType x) 132 { 133 static if (NumType.sizeof is 4) 134 return ((*cast(uint *)&x) & 0x8000_0000) != 0; 135 else 136 static if (NumType.sizeof is 8) 137 return ((*cast(ulong *)&x) & 0x8000_0000_0000_0000) != 0; 138 else 139 { 140 auto pe = cast(ubyte *)&x; 141 return (pe[9] & 0x80) != 0; 142 } 143 } 144 145 146 /******************************************************************************* 147 148 Format a floating-point value according to a format string 149 150 Defaults to 2 decimal places and 10 exponent, as the other format overload 151 does. 152 153 Format specifiers (additive unless stated otherwise): 154 '.' = Do not pad 155 'e' or 'E' = Display exponential notation 156 Any number = Set the decimal precision 157 158 Params: 159 T = character type 160 V = Floating point type 161 output = Where to write the string to - expected to be large enough 162 v = Number to format 163 fmt = Format string, see this function's description 164 165 Returns: 166 A const reference to `output` 167 168 *******************************************************************************/ 169 170 public const(T)[] format (T, V) (T[] output, V v, in T[] fmt) 171 { 172 static assert(is(V : const(real)), 173 "Float.format only support floating point types or types that" 174 ~ "implicitly convert to them"); 175 176 int dec = Dec; 177 int exp = Exp; 178 bool pad = true; 179 180 for (auto p = fmt.ptr, e = p + fmt.length; p < e; ++p) 181 switch (*p) 182 { 183 case '.': 184 pad = false; 185 break; 186 case 'e': 187 case 'E': 188 exp = 0; 189 break; 190 default: 191 Unqual!(T) c = *p; 192 if (c >= '0' && c <= '9') 193 { 194 dec = c - '0', c = p[1]; 195 if (c >= '0' && c <= '9' && ++p < e) 196 dec = dec * 10 + c - '0'; 197 } 198 break; 199 } 200 201 return format!(T)(output, v, dec, exp, pad); 202 } 203 204 /****************************************************************************** 205 206 Convert a floating-point number to a string. 207 208 The e parameter controls the number of exponent places emitted, 209 and can thus control where the output switches to the scientific 210 notation. For example, setting e=2 for 0.01 or 10.0 would result 211 in normal output. Whereas setting e=1 would result in both those 212 values being rendered in scientific notation instead. Setting e 213 to 0 forces that notation on for everything. Parameter pad will 214 append trailing '0' decimals when set ~ otherwise trailing '0's 215 will be elided 216 217 ******************************************************************************/ 218 219 T[] format(T) (T[] dst, NumType x, int decimals=Dec, int e=Exp, bool pad=Pad) 220 { 221 const(char)* end, str; 222 int exp, 223 sign, 224 mode=5; 225 char[32] buf = void; 226 227 // test exponent to determine mode 228 exp = (x == 0) ? 1 : cast(int) tsm.log10l(x < 0 ? -x : x); 229 if (exp <= -e || exp >= e) 230 mode = 2, ++decimals; 231 232 str = convertl (buf.ptr, x, decimals, &exp, &sign, mode is 5); 233 234 auto p = dst.ptr; 235 if (sign) 236 *p++ = '-'; 237 238 if (exp is 9999) 239 while (*str) 240 *p++ = *str++; 241 else 242 { 243 if (mode is 2) 244 { 245 --exp; 246 *p++ = *str++; 247 if (*str || pad) 248 { 249 auto d = p; 250 *p++ = '.'; 251 while (*str) 252 *p++ = *str++; 253 if (pad) 254 while (p-d < decimals) 255 *p++ = '0'; 256 } 257 *p++ = 'e'; 258 if (exp < 0) 259 *p++ = '-', exp = -exp; 260 else 261 *p++ = '+'; 262 if (exp >= 1000) 263 { 264 *p++ = cast(T)((exp/1000) + '0'); 265 exp %= 1000; 266 } 267 if (exp >= 100) 268 { 269 *p++ = cast(char) (exp / 100 + '0'); 270 exp %= 100; 271 } 272 *p++ = cast(char) (exp / 10 + '0'); 273 *p++ = cast(char) (exp % 10 + '0'); 274 } 275 else 276 { 277 if (exp <= 0) 278 *p++ = '0'; 279 else 280 for (; exp > 0; --exp) 281 *p++ = (*str) ? *str++ : '0'; 282 if (*str || pad) 283 { 284 *p++ = '.'; 285 auto d = p; 286 for (; exp < 0; ++exp) 287 *p++ = '0'; 288 while (*str) 289 *p++ = *str++; 290 if (pad) 291 while (p-d < decimals) 292 *p++ = '0'; 293 } 294 } 295 } 296 297 // stuff a C terminator in there too ... 298 *p = 0; 299 return dst[0..(p - dst.ptr)]; 300 } 301 302 303 /****************************************************************************** 304 305 ecvt() and fcvt() for 80bit FP, which DMD does not include. Based 306 upon the following: 307 308 Copyright (c) 2009 Ian Piumarta 309 310 All rights reserved. 311 312 Permission is hereby granted, free of charge, to any person 313 obtaining a copy of this software and associated documentation 314 files (the 'Software'), to deal in the Software without restriction, 315 including without limitation the rights to use, copy, modify, merge, 316 publish, distribute, and/or sell copies of the Software, and to permit 317 persons to whom the Software is furnished to do so, provided that the 318 above copyright notice(s) and this permission notice appear in all 319 copies of the Software. 320 321 ******************************************************************************/ 322 323 private const(char)* convertl (char* buf, real value, int ndigit, 324 int *decpt, int *sign, int fflag) 325 { 326 if ((*sign = negative(value)) != 0) 327 value = -value; 328 329 *decpt = 9999; 330 if (tsm.isnan(value)) 331 return "nan\0".ptr; 332 333 if (isInfinity(value)) 334 return "inf\0".ptr; 335 336 int exp10 = (value == 0) ? !fflag : cast(int) tsm.ceill(tsm.log10l(value)); 337 if (exp10 < -4931) 338 exp10 = -4931; 339 value *= tsm.powl(10.0, -exp10); 340 if (value) 341 { 342 while (value < 0.1) { value *= 10; --exp10; } 343 while (value >= 1.0) { value /= 10; ++exp10; } 344 } 345 verify(isZero(value) || (0.1 <= value && value < 1.0)); 346 //auto zero = pad ? int.max : 1; 347 auto zero = 1; 348 if (fflag) 349 { 350 // if (! pad) 351 zero = exp10; 352 if (ndigit + exp10 < 0) 353 { 354 *decpt= -ndigit; 355 return "\0".ptr; 356 } 357 ndigit += exp10; 358 } 359 *decpt = exp10; 360 int ptr = 1; 361 362 if (ndigit > real.dig) 363 ndigit = real.dig; 364 //printf ("< flag %d, digits %d, exp10 %d, decpt %d\n", fflag, ndigit, exp10, *decpt); 365 while (ptr <= ndigit) 366 { 367 real i = void; 368 value = tsm.modfl(value * 10, &i); 369 buf [ptr++]= cast(char) ('0' + cast(int) i); 370 } 371 372 if (value >= 0.5) 373 while (--ptr && ++buf[ptr] > '9') 374 buf[ptr] = (ptr > zero) ? '\0' : '0'; 375 else 376 for (auto i=ptr; i && --i > zero && buf[i] is '0';) 377 buf[i] = '\0'; 378 379 if (ptr) 380 { 381 buf [ndigit + 1] = '\0'; 382 return buf + 1; 383 } 384 if (fflag) 385 { 386 ++ndigit; 387 } 388 buf[0]= '1'; 389 ++*decpt; 390 buf[ndigit]= '\0'; 391 return buf; 392 } 393 394 395 /****************************************************************************** 396 397 Convert a formatted string of digits to a floating-point number. 398 Good for general use, but use David Gay's dtoa package if serious 399 rounding adjustments should be applied. 400 401 ******************************************************************************/ 402 403 NumType parse(T) (in T[] src, uint* ate=null) 404 { 405 T c; 406 const(T)* p; 407 int exp; 408 bool sign; 409 uint radix; 410 NumType value = 0.0; 411 412 static bool match (const(T)* aa, in T[] bb) 413 { 414 foreach (b; bb) 415 { 416 T a = *aa++; 417 if (a >= 'A' && a <= 'Z') 418 a += 'a' - 'A'; 419 if (a != b) 420 return false; 421 } 422 return true; 423 } 424 425 // remove leading space, and sign 426 p = src.ptr + Integer.trim (src, sign, radix); 427 428 // bail out if the string is empty 429 if (src.length == 0 || p > &src[$-1]) 430 return NumType.nan; 431 c = *p; 432 433 // handle non-decimal representations 434 if (radix != 10) 435 { 436 long v = Integer.parse (src, radix, ate); 437 return cast(NumType) v; 438 } 439 440 // set begin and end checks 441 auto begin = p; 442 auto end = src.ptr + src.length; 443 444 // read leading digits; note that leading 445 // zeros are simply multiplied away 446 while (c >= '0' && c <= '9' && p < end) 447 { 448 value = value * 10 + (c - '0'); 449 c = *++p; 450 } 451 452 // gobble up the point 453 if (c is '.' && p < end) 454 c = *++p; 455 456 // read fractional digits; note that we accumulate 457 // all digits ... very long numbers impact accuracy 458 // to a degree, but perhaps not as much as one might 459 // expect. A prior version limited the digit count, 460 // but did not show marked improvement. For maximum 461 // accuracy when reading and writing, use David Gay's 462 // dtoa package instead 463 while (c >= '0' && c <= '9' && p < end) 464 { 465 value = value * 10 + (c - '0'); 466 c = *++p; 467 --exp; 468 } 469 470 // did we get something? 471 if (p > begin) 472 { 473 // parse base10 exponent? 474 if ((c is 'e' || c is 'E') && p < end ) 475 { 476 uint eaten; 477 exp += Integer.parse (src[(++p-src.ptr) .. $], 0, &eaten); 478 p += eaten; 479 } 480 481 // adjust mantissa; note that the exponent has 482 // already been adjusted for fractional digits 483 if (exp < 0) 484 value /= pow10 (-exp); 485 else 486 value *= pow10 (exp); 487 } 488 else 489 { 490 if (end - p >= 3) 491 { 492 switch (*p) 493 { 494 case 'I': case 'i': 495 if (match (p+1, "nf")) 496 { 497 value = value.infinity; 498 p += 3; 499 if (end - p >= 5 && match (p, "inity")) 500 p += 5; 501 } 502 break; 503 504 case 'N': case 'n': 505 if (match (p+1, "an")) 506 { 507 value = value.nan; 508 p += 3; 509 } 510 break; 511 default: 512 break; 513 } 514 } 515 } 516 517 // set parse length, and return value 518 if (ate) 519 { 520 ptrdiff_t diff = p - src.ptr; 521 verify (diff >= 0 && diff <= uint.max); 522 *ate = cast(uint) diff; 523 } 524 525 if (sign) 526 value = -value; 527 return value; 528 } 529 530 /****************************************************************************** 531 532 Internal function to convert an exponent specifier to a floating 533 point value. 534 535 ******************************************************************************/ 536 537 private NumType pow10 (uint exp) 538 { 539 static NumType[] Powers = [ 540 1.0e1L, 541 1.0e2L, 542 1.0e4L, 543 1.0e8L, 544 1.0e16L, 545 1.0e32L, 546 1.0e64L, 547 1.0e128L, 548 1.0e256L, 549 1.0e512L, 550 1.0e1024L, 551 1.0e2048L, 552 1.0e4096L, 553 1.0e8192L, 554 ]; 555 556 if (exp >= 16384) 557 throw new IllegalArgumentException ("Float.pow10 :: exponent too large"); 558 559 NumType mult = 1.0; 560 foreach (NumType power; Powers) 561 { 562 if (exp & 1) 563 mult *= power; 564 if ((exp >>= 1) == 0) 565 break; 566 } 567 return mult; 568 } 569 570 /****************************************************************************** 571 572 ******************************************************************************/ 573 574 debug (Float) 575 { 576 import ocean.io.Console; 577 578 void main() 579 { 580 char[500] tmp; 581 /+ 582 Cout (format(tmp, NumType.max)).newline; 583 Cout (format(tmp, -NumType.nan)).newline; 584 Cout (format(tmp, -NumType.infinity)).newline; 585 Cout (format(tmp, toFloat("nan"w))).newline; 586 Cout (format(tmp, toFloat("-nan"d))).newline; 587 Cout (format(tmp, toFloat("inf"))).newline; 588 Cout (format(tmp, toFloat("-inf"))).newline; 589 +/ 590 Cout (format(tmp, toFloat ("0.000000e+00"))).newline; 591 Cout (format(tmp, toFloat("0x8000000000000000"))).newline; 592 Cout (format(tmp, 1)).newline; 593 Cout (format(tmp, -0)).newline; 594 Cout (format(tmp, 0.000001)).newline.newline; 595 596 Cout (format(tmp, 3.14159, 6, 0)).newline; 597 Cout (format(tmp, 3.0e10, 6, 3)).newline; 598 Cout (format(tmp, 314159, 6)).newline; 599 Cout (format(tmp, 314159123213, 6, 15)).newline; 600 Cout (format(tmp, 3.14159, 6, 2)).newline; 601 Cout (format(tmp, 3.14159, 3, 2)).newline; 602 Cout (format(tmp, 0.00003333, 6, 2)).newline; 603 Cout (format(tmp, 0.00333333, 6, 3)).newline; 604 Cout (format(tmp, 0.03333333, 6, 2)).newline; 605 Cout.newline; 606 607 Cout (format(tmp, -3.14159, 6, 0)).newline; 608 Cout (format(tmp, -3e100, 6, 3)).newline; 609 Cout (format(tmp, -314159, 6)).newline; 610 Cout (format(tmp, -314159123213, 6, 15)).newline; 611 Cout (format(tmp, -3.14159, 6, 2)).newline; 612 Cout (format(tmp, -3.14159, 2, 2)).newline; 613 Cout (format(tmp, -0.00003333, 6, 2)).newline; 614 Cout (format(tmp, -0.00333333, 6, 3)).newline; 615 Cout (format(tmp, -0.03333333, 6, 2)).newline; 616 Cout.newline; 617 618 Cout (format(tmp, -0.9999999, 7, 3)).newline; 619 Cout (format(tmp, -3.0e100, 6, 3)).newline; 620 Cout ((format(tmp, 1.0, 6))).newline; 621 Cout ((format(tmp, 30, 6))).newline; 622 Cout ((format(tmp, 3.14159, 6, 0))).newline; 623 Cout ((format(tmp, 3e100, 6, 3))).newline; 624 Cout ((format(tmp, 314159, 6))).newline; 625 Cout ((format(tmp, 314159123213.0, 3, 15))).newline; 626 Cout ((format(tmp, 3.14159, 6, 2))).newline; 627 Cout ((format(tmp, 3.14159, 4, 2))).newline; 628 Cout ((format(tmp, 0.00003333, 6, 2))).newline; 629 Cout ((format(tmp, 0.00333333, 6, 3))).newline; 630 Cout ((format(tmp, 0.03333333, 6, 2))).newline; 631 Cout (format(tmp, NumType.min, 6)).newline; 632 Cout (format(tmp, -1)).newline; 633 Cout (format(tmp, toFloat(format(tmp, -1)))).newline; 634 Cout.newline; 635 } 636 }