diff --git a/std/format.d b/std/format.d index c4a28bb6193..38e38e791b9 100644 --- a/std/format.d +++ b/std/format.d @@ -2654,7 +2654,7 @@ if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) size_t len; char[] buf; - if (fs.spec=='a' || fs.spec=='A') + if (fs.spec=='a' || fs.spec=='A' || fs.spec=='e' || fs.spec=='E') { static if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) { @@ -7065,12 +7065,6 @@ private auto printFloat(T, Char)(T val, FormatSpec!Char f, RoundingMode rm = RoundingMode.toNearestTiesToEven) if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) { - import std.conv : to; - import std.algorithm.comparison : max; - - enum byte[16] alpha = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']; - enum byte[16] Alpha = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']; - union FloatBits { T floatValue; @@ -7096,19 +7090,20 @@ if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double. ulong mnt = ival & ((1L << (T.mant_dig - 1)) - 1); int exp = (ival >> (T.mant_dig - 1)) & ((1L << (log2_max_exp + 1)) - 1); - int maxexp = 2 * T.max_exp - 1; + enum maxexp = 2 * T.max_exp - 1; string sgn = (ival >> (T.mant_dig + log2_max_exp)) & 1 ? "-" : ""; - enum long bias = T.max_exp - 1; if (sgn == "" && f.flPlus) sgn = "+"; if (sgn == "" && f.flSpace) sgn = " "; - assert(f.spec == 'a' || f.spec == 'A'); - bool is_upper = f.spec == 'A'; + assert(f.spec == 'a' || f.spec == 'A' || f.spec == 'e' || f.spec == 'E'); + bool is_upper = f.spec == 'A' || f.spec == 'E'; // special treatment for nan and inf if (exp == maxexp) { + import std.algorithm.comparison : max; + char[] result; result.length = max(f.width, sgn.length + 3); result[] = ' '; @@ -7126,6 +7121,26 @@ if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double. return result.idup; } + final switch (f.spec) + { + case 'a': case 'A': + return printFloatA(val, f, rm, sgn, exp, mnt, is_upper); + case 'e': case 'E': + return printFloatE(val, f, rm, sgn, exp, mnt, is_upper); + } +} + +private auto printFloatA(T, Char)(T val, FormatSpec!Char f, RoundingMode rm, + string sgn, int exp, ulong mnt, bool is_upper) +if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) +{ + import std.algorithm.comparison : max; + + enum byte[16] alpha = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']; + enum byte[16] Alpha = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']; + + enum int bias = T.max_exp - 1; + static if (is(T == float)) { mnt <<= 1; // make mnt dividable by 4 @@ -7579,3 +7594,854 @@ if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double. assert(printFloat(0x1.19f81p0, f) == "0X1.1A0P+0"); assert(printFloat(0x1.19f01p0, f) == "0X1.19FP+0"); } + +private auto printFloatE(T, Char)(T val, FormatSpec!Char f, RoundingMode rm, + string sgn, int exp, ulong mnt, bool is_upper) +if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) +{ + import std.conv : to; + import std.algorithm.comparison : max; + + enum int bias = T.max_exp - 1; + + if (f.precision == f.UNSPECIFIED) + f.precision = 6; + + // special treatment for 0.0 + if (exp == 0 && mnt == 0) + { + char[] result; + result.length = max(f.width, f.precision + ((f.precision == 0 && !f.flHash) ? 1 : 2) + sgn.length + 4); + result[] = '0'; + + if (f.flDash) + { + if (sgn != "") + result[0] = sgn[0]; + + int dot_pos = cast(int) (sgn.length + 1); + if (f.precision > 0 || f.flHash) + result[dot_pos] = '.'; + + auto exp_start = dot_pos + ((f.precision > 0 || f.flHash) ? 1 : 0) + f.precision; + if (exp_start + 4 < result.length) + result[exp_start + 4 .. $] = ' '; + + result[exp_start] = is_upper?'E':'e'; + result[exp_start + 1] = '+'; + } + else + { + int sign_pos = cast(int) (result.length - 6); + if (f.precision > 0 || f.flHash) + { + int dot_pos = cast(int) (result.length - f.precision - 5); + result[dot_pos] = '.'; + sign_pos = dot_pos - 2; + } + + if (f.flZero) + sign_pos = 0; + else if (sign_pos > 0) + result[0 .. sign_pos + (sgn.length == 0 ? 1 : 0)] = ' '; + + if (sgn != "") + result[sign_pos] = sgn[0]; + + result[$ - 3] = '+'; + result[$ - 4] = is_upper ? 'E' : 'e'; + } + + return result.idup; + } + + // add leading 1 for normalized values or correct exponent for denormalied values + if (exp != 0) + mnt |= 1L << (T.mant_dig - 1); + else + exp = 1; + exp -= bias; + + // estimate the number of bytes needed left and right of the decimal point + // the speed of the algorithm depends on being as accurate as possible with + // this estimate + + // Default for the right side is the number of digits given by f.precision plus one for the dot + // plus 6 more for e+... + auto max_right = f.precision + 7; + + // If the exponent is <= 0 there is only the sign and one digit left of the dot else + // we have to estimate the number of digits. The factor between exp, which is the number of + // digits in binary system and the searched number is log_2(10). We round this down to 3.32 to + // get a conservative estimate. We need to add 3, because of the sign, the fact, that the + // logarithm is one to small and because we need to round up instead of down, which to!int does. + // And then we might need one more digit in case of a rounding overflow. + auto max_left = exp>0 ? to!int(exp / 3.32) + 4 : 3; + + // If the result is not left justified, we may need to add more digits here for getting the + // correct width. + if (!f.flDash) + max_left = max(max_left, f.width - max_right + max_left + 1); + + // If the result is left justified, we may need to add more digits to the right. This strongly + // depends, on the exponent, see above. This time, we need to be conservative in the other direction + // for not missing a digit; therefore we round log_2(10) up to 3.33. + if (exp > 0 && f.flDash) + max_right = max(max_right, f.width - to!int(exp / 3.33) - 2); + else if (f.flDash) + max_right = max(max_right, f.width); + + + byte[] buffer = new byte[max_left + max_right]; + size_t start = max_left; + size_t left = max_left; + size_t right = max_left; + + int final_exp = 0; + + enum roundType { ZERO, LOWER, FIVE, UPPER } + roundType next; + + // Depending on exp, we will use one of three algorithms: + // + // Algorithm A: For large exponents (exp >= T.mant_dig) + // Algorithm B: For small exponents (exp < T.mant_dig - 61) + // Algorithm C: For exponents close to 0. + // + // Algorithm A: + // The number to print looks like this: mantissa followed by several zeros. + // + // We know, that there is no fractional part, so we can just use integer division, + // consecutivly dividing by 10 and writing down the remainder from right to left. + // Unfortunately the integer is too large to fit in an ulong, so we use something + // like BigInt: An array of ulongs. We only use 60 bits of that ulongs, because + // this simplifies (and speeds up) the division to come. + // + // For the division we use integer division with reminder for each ulong and put + // the reminder of each step in the first 4 bits of ulong of the next step (think of + // long division for the rationale behind this). The final reminder is the next + // digit (from right to left). + // + // This results in the output we would have for the %f specifier. We now adjust this + // for %e: First we calculate the place, where the exponent should be printed, filling + // up with zeros if needed and second we move the leftmost digit one to the left + // and inserting a dot. + // + // After that we decide on the rounding type, using the digits right of the position, + // where the exponent will be printed (currently they are still there, but will be + // overwritten later). + // + // Algorithm B: + // The number to print looks like this: zero dot several zeros followed by the mantissa + // + // We know, that the number has no integer part. The algorithm consecutivly multiplies + // by 10. The integer part (rounded down) after the multiplication is the next digit + // (from left to right). This integer part is removed after each step. + // Again, the number is represented as an array of ulongs, with only 60 bits used of + // every ulong. + // + // For the multiplication we use normal integer multiplication, which can result in digits + // in the uppermost 4 bits. These 4 digits are the carry which is added to the result + // of the next multiplication and finally the last carry is the next digit. + // + // Other than for the %f specifier, this multiplication is splitted into two almost + // identical parts. The first part lasts as long as we find zeros. We need to do this + // to calculate the correct exponent. + // + // The second part will stop, when only zeros remain or when we've got enough digits + // for the requested precision. In the second case, we have to find out, which rounding + // we have. Aside from special cases we do this by calculating one more digit. + // + // Algorithm C: + // This time, we know, that the integral part and the fractional part each fit into a + // ulong. The mantissa might be partially in both parts or completely in the fractional + // part. + // + // We first calculate the integral part by consecutive division by 10. Depending on the + // precision this might result in more digits, than we need. In that case we calculate + // the position of the exponent and the rounding type. + // + // If there is no integral part, we need to find the first non zero digit. We do this by + // consecutive multiplication by 10, saving the first non zero digit followed by a dot. + // + // In either case, we continue filling up with the fractional part until we have enough + // digits. If still necessary, we decide the rounding type, mainly by looking at the + // next digit. + + if (exp >= T.mant_dig) + { + // large number without fractional digits + // + // As this number does not fit in a ulong, we use an array of ulongs. We only use 60 of the 64 bits, + // because this makes it much more easy to implement the division by 10. + int count = exp / 60 + 1; + + // saved in big endian format + ulong[] mybig = new ulong[count]; + + // only the first or the first two ulongs contain the mantiassa. The rest are zeros. + int lower = 60 - (exp - T.mant_dig + 1) % 60; + if (lower < T.mant_dig) + { + mybig[0] = mnt >> lower; + mybig[1] = (mnt & ((1L << lower) - 1)) << 60 - lower; + } + else + mybig[0] = (mnt & ((1L << lower) - 1)) << 60 - lower; + + // Generation of digits by consecutive division with reminder by 10. + int msu = 0; // Most significant ulong; when it get's zero, we can ignore it further on + while (msu < count - 1 || mybig[$-1] != 0) + { + ulong mod = 0; + foreach (i;msu .. count) + { + mybig[i] |= mod << 60; + mod = mybig[i] % 10; + mybig[i] /= 10; + } + if (mybig[msu] == 0) + ++msu; + + buffer[--left] = cast(byte) ('0' + mod); + ++final_exp; + } + --final_exp; + + start = left + f.precision + 1; + + // we need more zeros for precision + if (right < start) + buffer[right .. start] = '0'; + + // move leftmost digit one more left and add dot between + buffer[left - 1] = buffer[left]; + buffer[left] = '.'; + --left; + + // rounding type + if (start >= right) + next = roundType.ZERO; + else if (buffer[start] != '0' && buffer[start] != '5') + next = buffer[start] > '5' ? roundType.UPPER : roundType.LOWER; + else + { + next = buffer[start]=='5' ? roundType.FIVE : roundType.ZERO; + foreach (i; start + 1 .. right) + if (buffer[i] > '0') + { + next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER; + break; + } + } + + right = start; + if (f.precision == 0 && !f.flHash) --right; + } + else if (exp + 61 < T.mant_dig) + { + // small number without integer digits + // + // Again this number does not fit in a ulong and we use an array of ulongs. And again we + // only use 60 bits, because this simplifies the multiplication by 10. + int count = (T.mant_dig - exp - 2) / 60 + 1; + + // saved in little endian format + ulong[] mybig = new ulong[count]; + + // only the last or the last two ulongs contain the mantiassa. Because of little endian + // format these are the ulongs at index 0 and 1. The rest are zeros. + int upper = 60 - (-exp - 1) % 60; + if (upper < T.mant_dig) + { + mybig[0] = (mnt & ((1L << (T.mant_dig - upper)) - 1)) << 60 - (T.mant_dig - upper); + mybig[1] = mnt >> (T.mant_dig - upper); + } + else + mybig[0] = mnt << (upper - T.mant_dig); + + int lsu = 0; // Least significant ulong; when it get's zero, we can ignore it further on + + // adding zeros, until we reach first nonzero + while (lsu < count - 1 || mybig[$ - 1]!=0) + { + ulong over = 0; + foreach (i; lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + if (mybig[lsu] == 0) + ++lsu; + --final_exp; + + if (over != 0) + { + buffer[right++] = cast(byte) ('0' + over); + buffer[right++] = '.'; + break; + } + } + + if (f.precision == 0 && !f.flHash) --right; + + // adding more digits + start = right; + while ((lsu < count - 1 || mybig[$ - 1] != 0) && right - start < f.precision) + { + ulong over = 0; + foreach (i;lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + if (mybig[lsu] == 0) + ++lsu; + + buffer[right++] = cast(byte) ('0' + over); + } + + // filling up with zeros to match precision + if (right < start + f.precision) + { + buffer[right .. start + f.precision] = '0'; + right = start + f.precision; + } + + // rounding type + if (lsu >= count - 1 && mybig[count - 1] == 0) + next = roundType.ZERO; + else if (lsu == count - 1 && mybig[lsu] == 1L << 59) + next = roundType.FIVE; + else + { + ulong over = 0; + foreach (i;lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + next = over >= 5 ? roundType.UPPER : roundType.LOWER; + } + } + else + { + // medium sized number, probably with integer and fractional digits + // this is fastest, because both parts fit into a ulong each + ulong int_part = mnt >> (T.mant_dig - 1 - exp); + ulong frac_part = mnt & ((1L << (T.mant_dig - 1 - exp)) - 1); + + start = 0; + + // could we already decide on the rounding mode in the integer part? + bool found = false; + + if (int_part > 0) + { + // integer part, if there is something to print + while (int_part >= 10) + { + buffer[--left] = '0' + (int_part % 10); + int_part /= 10; + ++final_exp; + ++start; + } + + buffer[--left] = '.'; + buffer[--left] = cast(byte) ('0' + int_part); + + if (right - left > f.precision + 2) + { + auto old_right = right; + right = left + f.precision + 2; + + if (buffer[right] == '5' || buffer[right] == '0') + { + next = buffer[right] == '5' ? roundType.FIVE : roundType.ZERO; + if (frac_part != 0) + next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER; + else + foreach (i;right + 1 .. old_right) + if (buffer[i] > '0') + { + next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER; + break; + } + } + else + next = buffer[right] > '5' ? roundType.UPPER : roundType.LOWER; + found = true; + } + } + else + { + // fractional part, skipping leading zeros + while (frac_part != 0) + { + --final_exp; + frac_part *= 10; + auto tmp = frac_part >> (T.mant_dig - 1 - exp); + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + if (tmp > 0) + { + buffer[right++] = cast(byte) ('0' + tmp); + buffer[right++] = '.'; + break; + } + } + + next = roundType.ZERO; + } + + if (f.precision == 0 && !f.flHash) right--; + + // the fractional part after the zeros + while (frac_part != 0 && start < f.precision) + { + frac_part *= 10; + buffer[right++] = cast(byte) ('0' + (frac_part >> (T.mant_dig - 1 - exp))); + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + ++start; + } + + if (start < f.precision) + { + buffer[right .. right + f.precision - start] = '0'; + right += f.precision - start; + start = f.precision; + } + + // rounding mode, if not allready known + if (frac_part != 0 && !found) + { + frac_part *= 10; + auto nextDigit = frac_part >> (T.mant_dig - 1 - exp); + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + + if (nextDigit == 5 && frac_part == 0) + next = roundType.FIVE; + else if (nextDigit >= 5) + next = roundType.UPPER; + else + next = roundType.LOWER; + } + } + + // rounding + bool roundUp = false; + if (rm == RoundingMode.up) + roundUp = next != roundType.ZERO && sgn != "-"; + else if (rm == RoundingMode.down) + roundUp = next != roundType.ZERO && sgn == "-"; + else if (rm == RoundingMode.toZero) + roundUp = false; + else + { + assert(rm == RoundingMode.toNearestTiesToEven || rm == RoundingMode.toNearestTiesAwayFromZero); + roundUp = next == roundType.UPPER; + + if (next == roundType.FIVE) + { + // IEEE754 allows for two different ways of implementing roundToNearest: + + // Round to nearest, ties away from zero + if (rm == RoundingMode.toNearestTiesAwayFromZero) + roundUp = true; + else + { + // Round to nearest, ties to even + auto last = buffer[right-1]; + if (last == '.') last = buffer[right-2]; + roundUp = last % 2 != 0; + } + } + } + + if (roundUp) + { + foreach_reverse (i;left .. right) + { + if (buffer[i] == '.') continue; + if (buffer[i] == '9') + buffer[i] = '0'; + else + { + buffer[i]++; + goto printFloat_done; + } + } + + // one more digit to the left, so we need to shift everything and increase exponent + buffer[--left] = '1'; + buffer[left + 2] = buffer[left + 1]; + if (f.flHash || f.precision != 0) + buffer[left + 1] = '.'; + right--; + final_exp++; + +printFloat_done: + } + + // printing exponent + buffer[right++] = is_upper ? 'E' : 'e'; + buffer[right++] = final_exp >= 0 ? '+' : '-'; + + if (final_exp < 0) final_exp = -final_exp; + + static if (is(T == float)) + enum max_exp_digits = 2; + else + enum max_exp_digits = 3; + + byte[max_exp_digits] exp_str; + size_t exp_pos = max_exp_digits; + + do + { + exp_str[--exp_pos] = '0' + final_exp%10; + final_exp /= 10; + } while (final_exp>0); + if (max_exp_digits - exp_pos == 1) + exp_str[--exp_pos] = '0'; + + buffer[right .. right + max_exp_digits - exp_pos] = exp_str[exp_pos .. $]; + right += max_exp_digits - exp_pos; + + // sign and padding + bool need_sgn = false; + if (sgn != "") + { + // when padding with zeros we need to postpone adding the sign + if (right - left < f.width && !f.flDash && f.flZero) + need_sgn = true; + else + buffer[--left] = sgn[0]; + } + + if (right - left < f.width) + { + if (f.flDash) + { + // padding right + buffer[right .. f.width + left] = ' '; + right = f.width + left; + } + else + { + // padding left + buffer[right - f.width .. left] = f.flZero ? '0' : ' '; + left = right - f.width; + } + } + + if (need_sgn) + buffer[left] = sgn[0]; + + // without this cast it's getting too slow + return () @trusted { return cast(string)(buffer[left .. right]); }(); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(float.nan, f) == "nan"); + assert(printFloat(-float.nan, f) == "-nan"); + assert(printFloat(float.infinity, f) == "inf"); + assert(printFloat(-float.infinity, f) == "-inf"); + assert(printFloat(0.0f, f) == "0.000000e+00"); + assert(printFloat(-0.0f, f) == "-0.000000e+00"); + // cast needed due to bug 20361 + assert(printFloat(cast(float) 1e-40, f) == "9.999946e-41"); + assert(printFloat(cast(float) -1e-40, f) == "-9.999946e-41"); + assert(printFloat(1e-30f, f) == "1.000000e-30"); + assert(printFloat(-1e-30f, f) == "-1.000000e-30"); + assert(printFloat(1e-10f, f) == "1.000000e-10"); + assert(printFloat(-1e-10f, f) == "-1.000000e-10"); + assert(printFloat(0.1f, f) == "1.000000e-01"); + assert(printFloat(-0.1f, f) == "-1.000000e-01"); + assert(printFloat(10.0f, f) == "1.000000e+01"); + assert(printFloat(-10.0f, f) == "-1.000000e+01"); + assert(printFloat(1e30f, f) == "1.000000e+30"); + assert(printFloat(-1e30f, f) == "-1.000000e+30"); + + import std.math : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "1.401298e-45"); + assert(printFloat(nextDown(-0.0f), f) == "-1.401298e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.width = 20; + f.precision = 10; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == " 0.0000000000e+00"); + assert(printFloat(-0.0f, f) == " -0.0000000000e+00"); + // cast needed due to bug 20361 + assert(printFloat(cast(float) 1e-40, f) == " 9.9999461011e-41"); + assert(printFloat(cast(float) -1e-40, f) == " -9.9999461011e-41"); + assert(printFloat(1e-30f, f) == " 1.0000000032e-30"); + assert(printFloat(-1e-30f, f) == " -1.0000000032e-30"); + assert(printFloat(1e-10f, f) == " 1.0000000134e-10"); + assert(printFloat(-1e-10f, f) == " -1.0000000134e-10"); + assert(printFloat(0.1f, f) == " 1.0000000149e-01"); + assert(printFloat(-0.1f, f) == " -1.0000000149e-01"); + assert(printFloat(10.0f, f) == " 1.0000000000e+01"); + assert(printFloat(-10.0f, f) == " -1.0000000000e+01"); + assert(printFloat(1e30f, f) == " 1.0000000150e+30"); + assert(printFloat(-1e30f, f) == " -1.0000000150e+30"); + + import std.math : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == " 1.4012984643e-45"); + assert(printFloat(nextDown(-0.0f), f) == " -1.4012984643e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.width = 20; + f.precision = 10; + f.flDash = true; + + assert(printFloat(float.nan, f) == "nan "); + assert(printFloat(-float.nan, f) == "-nan "); + assert(printFloat(float.infinity, f) == "inf "); + assert(printFloat(-float.infinity, f) == "-inf "); + assert(printFloat(0.0f, f) == "0.0000000000e+00 "); + assert(printFloat(-0.0f, f) == "-0.0000000000e+00 "); + // cast needed due to bug 20361 + assert(printFloat(cast(float) 1e-40, f) == "9.9999461011e-41 "); + assert(printFloat(cast(float) -1e-40, f) == "-9.9999461011e-41 "); + assert(printFloat(1e-30f, f) == "1.0000000032e-30 "); + assert(printFloat(-1e-30f, f) == "-1.0000000032e-30 "); + assert(printFloat(1e-10f, f) == "1.0000000134e-10 "); + assert(printFloat(-1e-10f, f) == "-1.0000000134e-10 "); + assert(printFloat(0.1f, f) == "1.0000000149e-01 "); + assert(printFloat(-0.1f, f) == "-1.0000000149e-01 "); + assert(printFloat(10.0f, f) == "1.0000000000e+01 "); + assert(printFloat(-10.0f, f) == "-1.0000000000e+01 "); + assert(printFloat(1e30f, f) == "1.0000000150e+30 "); + assert(printFloat(-1e30f, f) == "-1.0000000150e+30 "); + + import std.math : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "1.4012984643e-45 "); + assert(printFloat(nextDown(-0.0f), f) == "-1.4012984643e-45 "); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.width = 20; + f.precision = 10; + f.flZero = true; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == "00000.0000000000e+00"); + assert(printFloat(-0.0f, f) == "-0000.0000000000e+00"); + // cast needed due to bug 20361 + assert(printFloat(cast(float) 1e-40, f) == "00009.9999461011e-41"); + assert(printFloat(cast(float) -1e-40, f) == "-0009.9999461011e-41"); + assert(printFloat(1e-30f, f) == "00001.0000000032e-30"); + assert(printFloat(-1e-30f, f) == "-0001.0000000032e-30"); + assert(printFloat(1e-10f, f) == "00001.0000000134e-10"); + assert(printFloat(-1e-10f, f) == "-0001.0000000134e-10"); + assert(printFloat(0.1f, f) == "00001.0000000149e-01"); + assert(printFloat(-0.1f, f) == "-0001.0000000149e-01"); + assert(printFloat(10.0f, f) == "00001.0000000000e+01"); + assert(printFloat(-10.0f, f) == "-0001.0000000000e+01"); + assert(printFloat(1e30f, f) == "00001.0000000150e+30"); + assert(printFloat(-1e30f, f) == "-0001.0000000150e+30"); + + import std.math : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "00001.4012984643e-45"); + assert(printFloat(nextDown(-0.0f), f) == "-0001.4012984643e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.precision = 1; + + assert(printFloat(11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.2e+01"); + assert(printFloat(12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.3e+01"); + assert(printFloat(11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.2e+01"); + assert(printFloat(11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.1e+01"); + assert(printFloat(11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.1e+01"); + assert(printFloat(-11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.2e+01"); + assert(printFloat(-12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.3e+01"); + assert(printFloat(-11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.2e+01"); + assert(printFloat(-11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.1e+01"); + assert(printFloat(-11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.1e+01"); + + assert(printFloat(11.5f, f) == "1.2e+01"); + assert(printFloat(12.5f, f) == "1.2e+01"); + assert(printFloat(11.7f, f) == "1.2e+01"); + assert(printFloat(11.3f, f) == "1.1e+01"); + assert(printFloat(11.0f, f) == "1.1e+01"); + assert(printFloat(-11.5f, f) == "-1.2e+01"); + assert(printFloat(-12.5f, f) == "-1.2e+01"); + assert(printFloat(-11.7f, f) == "-1.2e+01"); + assert(printFloat(-11.3f, f) == "-1.1e+01"); + assert(printFloat(-11.0f, f) == "-1.1e+01"); + + assert(printFloat(11.5f, f, RoundingMode.toZero) == "1.1e+01"); + assert(printFloat(12.5f, f, RoundingMode.toZero) == "1.2e+01"); + assert(printFloat(11.7f, f, RoundingMode.toZero) == "1.1e+01"); + assert(printFloat(11.3f, f, RoundingMode.toZero) == "1.1e+01"); + assert(printFloat(11.0f, f, RoundingMode.toZero) == "1.1e+01"); + assert(printFloat(-11.5f, f, RoundingMode.toZero) == "-1.1e+01"); + assert(printFloat(-12.5f, f, RoundingMode.toZero) == "-1.2e+01"); + assert(printFloat(-11.7f, f, RoundingMode.toZero) == "-1.1e+01"); + assert(printFloat(-11.3f, f, RoundingMode.toZero) == "-1.1e+01"); + assert(printFloat(-11.0f, f, RoundingMode.toZero) == "-1.1e+01"); + + assert(printFloat(11.5f, f, RoundingMode.up) == "1.2e+01"); + assert(printFloat(12.5f, f, RoundingMode.up) == "1.3e+01"); + assert(printFloat(11.7f, f, RoundingMode.up) == "1.2e+01"); + assert(printFloat(11.3f, f, RoundingMode.up) == "1.2e+01"); + assert(printFloat(11.0f, f, RoundingMode.up) == "1.1e+01"); + assert(printFloat(-11.5f, f, RoundingMode.up) == "-1.1e+01"); + assert(printFloat(-12.5f, f, RoundingMode.up) == "-1.2e+01"); + assert(printFloat(-11.7f, f, RoundingMode.up) == "-1.1e+01"); + assert(printFloat(-11.3f, f, RoundingMode.up) == "-1.1e+01"); + assert(printFloat(-11.0f, f, RoundingMode.up) == "-1.1e+01"); + + assert(printFloat(11.5f, f, RoundingMode.down) == "1.1e+01"); + assert(printFloat(12.5f, f, RoundingMode.down) == "1.2e+01"); + assert(printFloat(11.7f, f, RoundingMode.down) == "1.1e+01"); + assert(printFloat(11.3f, f, RoundingMode.down) == "1.1e+01"); + assert(printFloat(11.0f, f, RoundingMode.down) == "1.1e+01"); + assert(printFloat(-11.5f, f, RoundingMode.down) == "-1.2e+01"); + assert(printFloat(-12.5f, f, RoundingMode.down) == "-1.3e+01"); + assert(printFloat(-11.7f, f, RoundingMode.down) == "-1.2e+01"); + assert(printFloat(-11.3f, f, RoundingMode.down) == "-1.2e+01"); + assert(printFloat(-11.0f, f, RoundingMode.down) == "-1.1e+01"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(double.nan, f) == "nan"); + assert(printFloat(-double.nan, f) == "-nan"); + assert(printFloat(double.infinity, f) == "inf"); + assert(printFloat(-double.infinity, f) == "-inf"); + assert(printFloat(0.0, f) == "0.000000e+00"); + assert(printFloat(-0.0, f) == "-0.000000e+00"); + // / 1000 needed due to bug 20361 + assert(printFloat(1e-307 / 1000, f) == "1.000000e-310"); + assert(printFloat(-1e-307 / 1000, f) == "-1.000000e-310"); + assert(printFloat(1e-30, f) == "1.000000e-30"); + assert(printFloat(-1e-30, f) == "-1.000000e-30"); + assert(printFloat(1e-10, f) == "1.000000e-10"); + assert(printFloat(-1e-10, f) == "-1.000000e-10"); + assert(printFloat(0.1, f) == "1.000000e-01"); + assert(printFloat(-0.1, f) == "-1.000000e-01"); + assert(printFloat(10.0, f) == "1.000000e+01"); + assert(printFloat(-10.0, f) == "-1.000000e+01"); + assert(printFloat(1e300, f) == "1.000000e+300"); + assert(printFloat(-1e300, f) == "-1.000000e+300"); + + import std.math : nextUp, nextDown; + assert(printFloat(nextUp(0.0), f) == "4.940656e-324"); + assert(printFloat(nextDown(-0.0), f) == "-4.940656e-324"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + + import std.math : nextUp; + + double eps = nextUp(0.0); + f.precision = 1000; + assert(printFloat(eps, f) == + "4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599" + ~"23797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036" + ~"88718636056998730723050006387409153564984387312473397273169615140031715385398074126238565591171026" + ~"65855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332" + ~"45247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431" + ~"93609238289345836806010601150616980975307834227731832924790498252473077637592724787465608477820373" + ~"44696995336470179726777175851256605511991315048911014510378627381672509558373897335989936648099411" + ~"64205702637090279242767544565229087538682506419718265533447265625000000000000000000000000000000000" + ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ~"000000000000000000000e-324"); + + f.precision = 50; + assert(printFloat(double.max, f) == + "1.79769313486231570814527423731704356798070567525845e+308"); + assert(printFloat(double.epsilon, f) == + "2.22044604925031308084726333618164062500000000000000e-16"); + + f.precision = 10; + assert(printFloat(1.0/3.0, f) == "3.3333333333e-01"); + assert(printFloat(1.0/7.0, f) == "1.4285714286e-01"); + assert(printFloat(1.0/9.0, f) == "1.1111111111e-01"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.precision = 15; + + import std.math : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, + LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; + + assert(printFloat(cast(double) E, f) == "2.718281828459045e+00"); + assert(printFloat(cast(double) PI, f) == "3.141592653589793e+00"); + assert(printFloat(cast(double) PI_2, f) == "1.570796326794897e+00"); + assert(printFloat(cast(double) PI_4, f) == "7.853981633974483e-01"); + assert(printFloat(cast(double) M_1_PI, f) == "3.183098861837907e-01"); + assert(printFloat(cast(double) M_2_PI, f) == "6.366197723675814e-01"); + assert(printFloat(cast(double) M_2_SQRTPI, f) == "1.128379167095513e+00"); + assert(printFloat(cast(double) LN10, f) == "2.302585092994046e+00"); + assert(printFloat(cast(double) LN2, f) == "6.931471805599453e-01"); + assert(printFloat(cast(double) LOG2, f) == "3.010299956639812e-01"); + assert(printFloat(cast(double) LOG2E, f) == "1.442695040888963e+00"); + assert(printFloat(cast(double) LOG2T, f) == "3.321928094887362e+00"); + assert(printFloat(cast(double) LOG10E, f) == "4.342944819032518e-01"); + assert(printFloat(cast(double) SQRT2, f) == "1.414213562373095e+00"); + assert(printFloat(cast(double) SQRT1_2, f) == "7.071067811865476e-01"); +} + +// for 100% coverage +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'E'; + f.precision = 80; + assert(printFloat(5.62776e+12f, f) == + "5.62775982080000000000000000000000000000000000000000000000000000000000000000000000E+12"); + + f.precision = 49; + assert(printFloat(2.5997869e-12f, f) == + "2.5997869221999758693186777236405760049819946289062E-12"); + + f.precision = 6; + assert(printFloat(-1.1418613e+07f, f) == "-1.141861E+07"); + assert(printFloat(-1.368281e+07f, f) == "-1.368281E+07"); + + f.precision = 0; + assert(printFloat(709422.0f, f, RoundingMode.up) == "8E+05"); + + f.precision = 1; + assert(printFloat(-245.666f, f) == "-2.5E+02"); +}