1

I want to print 12345.0 as 0.1235e+05. But the below code gives 1.23450e+04. Is there a way to control the format of std::scientific?

#include <iostream>
#include <string>
#include <sstream>


int main()
{
    double a = 12345.0;

    std::stringstream s;

    s.precision(3);

    s << std::scientific;

    s << a;

    std::cout << s.str() << std::endl;

    return 0;
}

The output is

1.235e+04

I also tried the code here Print exponential notation with one leading zero with C++ but there is a problem about rounding. If I set precision to 4 it does not round and gives 0.1234E5 however it should have been 0.1235E5.

Any solution to this?

KC_
  • 89
  • 11
  • I think if you want accuracy you'd need to write your own converter, e.g. see https://stackoverflow.com/questions/7153979/algorithm-to-convert-an-ieee-754-double-to-a-string – Alan Birtles Mar 27 '21 at 17:18
  • @Alan Birtles I don't want accuracy I want correct rounding from 0.12345 to 0.1235 not 0.1234. – KC_ Mar 27 '21 at 17:53
  • I am certain you will get answers that appear correct but fail (sometimes very poorly) with select cases as they introduce rounding errors. I'd recommend to print to a string with the desired precision and then, textually, post process "1.235e+04" to "0.1235e+05". – chux - Reinstate Monica Mar 27 '21 at 18:35
  • @chux - Reinstate Monica it is number of two multiplications and one rounding. There is no millions repeating calculations to accumulate rounding errors. If the code gives rounding error accumulation for only two multiplications no one can do anything. – KC_ Mar 27 '21 at 18:47
  • "If I set precision to 4 it does not round and gives 0.1234E5 however it should have been 0.1235E5." is a incorrect conclusion. The value was 12345.0; and the output was "0.1234E5". That output is certainly rounded. It just did not round in the expected direction. With "round to nearest, ties to even", "0.1234E5" is the better answer. What rounding mode are you expecting? – chux - Reinstate Monica Mar 27 '21 at 18:48
  • @KC_ " it is number of two multiplications and one rounding" --> where is that? - there is no multiplication in your question and there is a lot more that 2 multiplications in [Print exponential notation with one leading zero with C++](https://stackoverflow.com/questions/21165989/print-exponential-notation-with-one-leading-zero-with-c). – chux - Reinstate Monica Mar 27 '21 at 18:51
  • @ chux - Reinstate Monica You can't represent 0.12345 as 0.1234. It is basic engineering rule. It is 0.1235. – KC_ Mar 27 '21 at 18:54
  • @ chux - Reinstate Monica I am talking about the lines I added. 0.12345*10000=1234.5 round 1234.5=1235 and 1235/10000=0.1235. That simple. What rounding error are you talking about to fail this technique? – KC_ Mar 27 '21 at 18:56
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230454/discussion-between-chux-reinstate-monica-and-kc). – chux - Reinstate Monica Mar 27 '21 at 19:00

3 Answers3

0

Then that isn't scientific form. Scientific form must produce a number between 1-10 and multiplied by a power of 10
Source: http://chemed.chem.purdue.edu/genchem/topicreview/bp/ch1/scinot.html

voxal
  • 49
  • 2
  • 12
  • I see 0.1235E5 format in whole engineering books and also calculators. If it is not scientific form then what is it? – KC_ Mar 27 '21 at 17:55
  • 1
    @KC_ _Scientific form_ has various definitions. Perhaps call it exponential form. IAC, "Scientific form must produce a number between 1-10 and multiplied by a power of 10" does not allow printing zero. – chux - Reinstate Monica Mar 27 '21 at 18:39
  • @chux - Reinstate Monica Exponential form is a mathematical expression and a function on software / calculator side not a printing format. – KC_ Mar 30 '21 at 06:54
0

I corrected the error in the solution given in the link Print exponential notation with one leading zero with C++ by adding two lines of code. I also repeated the process for correct formatting:

int p = stream.precision();
base = round(base * pow(10, p)) / pow(10, p);

It now makes correct rounding and gives 0.1235E5. Here is the whole code:

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <cmath>

class Double {
public:
    Double(double x) : value(x) {}
    const double value;
};

std::ostream& operator<< (std::ostream& stream, const Double& x) {
    // So that the log does not scream
    if (x.value == 0.) {
        stream << 0.0;
        return stream;
    }


    int exponent = floor(log10(std::abs(x.value)));
    double base = x.value / pow(10, exponent);
    // Transform here
    base /= 10.0;
    int p = stream.precision();  // This line is added to correct the code
    base = round(base * pow(10, p)) / pow(10, p); // This line is added to correct the code
    exponent += 1;

    double x2 = base * pow(10, exponent);

    exponent = floor(log10(std::abs(x2)));
    base = x2 / pow(10, exponent);
    // Transform here
    base /= 10.0;
    base = round(base * pow(10, p)) / pow(10, p); // This line is added to correct the code
    exponent += 1;

    stream << base << "+e" << exponent;


    return stream;
}


int main()
{
    //double a = 12345.0;
    double a = 99995.01;

    std::stringstream s;

    s.precision(4);

    s << Double(a);

    std::cout << s.str() << std::endl;

    return 0;
}
KC_
  • 89
  • 11
  • `base * pow(10, p)` and others introduce rounding errors. The error may be minor for most cases, but catastrophic for values near a power-of-10. – chux - Reinstate Monica Mar 27 '21 at 18:29
  • @chux - Reinstate Monica we are talking about TWO MULTIPLICATIONS. I multiplied and divided 0.12345 million times by pow(10,10) and obtained the same result. You can try it yourself. – KC_ Mar 27 '21 at 18:50
  • `log10(x)` is prone to returning a _rounded_ whole number answer for `x` values just less than a power of 10.0. The problem is amplified by then calling `floor()`. – chux - Reinstate Monica Mar 27 '21 at 18:57
  • @chux - Reinstate Monica We are not talking about millions repeating lines or 10E15 something. It is simple calculator expression. 10$ calculator can do it correctly. – KC_ Mar 27 '21 at 19:00
0

A problem with re-inventing double to string conversion is the many corner cases.

Consider printing values near 99995.0 to 4 places

void foo(double value, int precision) {
  if (value == 0.) {
    printf("%g\n", 0.0);
  }

  int exponent = (int) floor(log10(fabs(value)));
  double base = value / pow(10, exponent);

  base /= 10.0;
  int p = precision;
  base = round(base * pow(10, p)) / pow(10, p);
  exponent++;

  printf("%.*fE%d\n", p, base, exponent);
  bar(value, precision);
}

foo(99994.99, 4);
foo(99995.01, 4);

Prints

0.9999E5
1.0000E5

Rather than the hoped for

0.9999E5
0.1000E6

Rather than try to out-compute the library conversion of floating point to a string, simply post process the string to the desired format.

Below is C, yet OP is looking for C++, so take this as a guide.

void bar(double value, int precision) {
  precision--;
  char buf[400];
  snprintf(buf, sizeof buf, "%.*e", precision, value);

  if (isfinite(value)) {
    char *e = strchr(buf, 'e');
    char *first_digit = e - precision - 2;
    int expo = atoi(e + 1) + 1;
    printf("%.*s0.%c%.*sE%d\n", !isdigit((unsigned char )buf[0]), buf,
        *first_digit, precision, first_digit + 2, expo);
  } else {
    printf("%s\n", buf);
  }
}

bar(99994.99, 4);
bar(99995.01, 4);

Prints

0.9999E5
0.1000E6

"If I set precision to 4 it does not round and gives 0.1234E5 however it should have been 0.1235E5."

This is a result of the default rounding mode is "round to nearest, ties to even" and OP is hoping for "round to nearest, ties to away".

Code such as base = round(base * pow(10, p)) / pow(10, p); may achieve OP's goal in select cases as the multiplication and division here can incur rounding due to imprecision, sometimes in the desired direction. This is not reliable across all double to achieve "round to nearest, ties to away".

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I am not trying to round by myself by setting the precision and fixing. I am letting the function round() do its job. If you think base = round(base * pow(10, p)) / pow(10, p); will fail you are assuming that function round() will fail for some cases. I don't think so. – KC_ Mar 30 '21 at 06:04
  • Regarding the 1.0000E5, there is no rounding error you mentioned before. The result is correct. However, I agree that there is a problem for correct formatting. The solution to this is simple: applying the process twice. I copy and pasted the relevant lines and updated the solution. Now it gives 0.1000E6. – KC_ Mar 30 '21 at 06:47