3

For what I'm learning, once I convert a floating point value to a decimal one, the "significant digits" I need are a fixed number (17 for double, for example). 17 totals: before and after decimal separator.

So for example this code:

typedef std::numeric_limits<double> dbl;

int main()
{
    std::cout.precision(dbl::max_digits10);
    //std::cout << std::fixed;    

    double value1 = 1.2345678912345678912345;
    double value2 = 123.45678912345678912345;
    double value3 = 123456789123.45678912345;

    std::cout << value1 << std::endl;
    std::cout << value2 << std::endl;
    std::cout << value3 << std::endl;
}

will correctly "show me" 17 values:

1.2345678912345679
123.45678912345679
123456789123.45679

But if I increase precision for the cout (i.e. std::cout.precision(100)), I can see there are other numbers after the 17 range:

1.2345678912345678934769921397673897445201873779296875
123.456789123456786683163954876363277435302734375
123456789123.456787109375

Why should ignore them? They are stored within the variables/double as well, so they will affect the whole "math" later (division, multiplication, sum, and so on).

What does it means "significant digits"? There is other...

markzzz
  • 47,390
  • 120
  • 299
  • 507
  • 2
    https://en.wikipedia.org/wiki/Significant_figures – Gordon Linoff Nov 04 '17 at 13:33
  • It means the same thing as anywhere else. If you measure something and determine that it’s about an inch long, then convert to metric and say that it’s about 2.54 centimeters long, you’re deep into false precision. The original value had 1 significant digit; the new one has 3. The last two digits in the second value are nonsense. – Pete Becker Nov 04 '17 at 13:36
  • Of course, but processor will "elaborate" the digit after the 17°, not "truncate" them. There's accomulation after N steps... – markzzz Nov 04 '17 at 13:37

3 Answers3

3

Can you help me to understand what “significant digits” means in floating point math?

With FP numbers, like mathematical real numbers, significant digits is the leading digits of a value that do not begin with 0 and then, depending on context, to 1) the decimal point, 2) the last non-zero digit, or 3) the last printed digit.

123.         // 3 significant decimal digits
123.125      // 6 significant decimal digits
0.0078125    // 5 significant decimal digits
0x0.00123p45 // 3 significant hexadecimal digits
123000.0     // 3, 6, or 7 significant decimal digits depending on context

When concerned about decimal significant digits and FP types like double. the issue is often "How many decimal significant digits are needed or of concern?"

Nearly all C FP implementations use a binary encoding such that all finite FP are exact sums of power of 2. Each finite FP is exact. Common encoding affords most double to have 53 binary digits is it significand - so 53 significant binary digits. How this appears as a decimal is often the source of confusion.

// Example 0.1 is not an exact sum of powers of 2 so a nearby value is used.
double x = 0.1;
// x takes on the exact value of 
// 0.1000000000000000055511151231257827021181583404541015625
// aka 0x1.999999999999ap-4
// aka base2: 0.000110011001100110011001100110011001100110011001100110011010
// The preceding and subsequent doubles
// 0.09999999999999999167332731531132594682276248931884765625
// 0.10000000000000001942890293094023945741355419158935546875
//   123456789012345678901234567890123456789012345678901234567890

Looking at above, one could say x has over 50 decimal significant digits. Yet the value matches the intended 0.1 to 16 decimal significant digits. Or yet since the preceding and subsequent possible double values differ in the 17 place, one could say x has 17 decimal significant digits.

What does it means "significant digits"?

Various meanings of significant digits exist, but for C, 2 common ones are:

  1. The number of decimal significant digits that a textual value to double converts as expected for all double. This is typically 15. C specifies this as DBL_DIG and must be at least 10.

  2. The number of decimal significant digits that a textual value of double needs to be printed to distinguish from another double. This is typically 17. C specifies this as DBL_DECIMAL_DIG and must be at least 10.

Why should ignore them?

It depends of coding goals. Rarely are all digits of the exact value needed. (DBL_TRUE_MIN might have 752 od them.) For most applications, DBL_DECIMAL_DIG is enough. In select apps, DBL_DIG will do. So usually, ignoring digits past 17 does not cause problems.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Are the past 17 digits stored in memory when I assign 0.1 to a double variabile? i.e. does x contain over those 50 digits? I would say no... – markzzz Nov 05 '17 at 08:53
  • @markzzz `0.1` has a 53 binary digit significand in its encoding as the closest `double`. To express that number exactly takes 56 decimal digits. If those digits are significant depends on context as mentioned above. The next `double` only has 17 of the same decimal digits. – chux - Reinstate Monica Nov 06 '17 at 00:38
  • @markzzz "Are the past 17 digits stored in memory when I assign 0.1 to a double variabile?" Yes but not as decimal digits, but as binary digits. – chux - Reinstate Monica Nov 06 '17 at 02:22
  • What do you mean with "yes but not as decimal digits"? `0.1` IS `0.1000000000000000055511151231257827021181583404541015625`, not `0.10000000000000001`. If I evalutate two different decimal number (but having the same first 17 digits), they STILL differs: http://coliru.stacked-crooked.com/a/c7d1b34533c5ad5b . – markzzz Nov 06 '17 at 07:56
  • "The next double only has 17 of the same decimal digits." yes, as in my example, but it evalutates differently. So even if got similar 17 digits, its different. Both have same 17 digit, ok, but are different numbers :O Whats the meaning of "17 significant digits" so? – markzzz Nov 06 '17 at 08:13
  • @markzz no 0.1 is **neither** of those ! there's *no* double whose exact value is 0.1 ! 0.1 is *rounded* to that first number and it's *equivalent* to that second number according to your rounding scheme. – Massimiliano Janes Nov 06 '17 at 08:56
  • @markzz elaborating on your [example](http://coliru.stacked-crooked.com/a/cb534b0249a592d6) do you see that the max_digit10 case is *still* different ? and no, no decimal digit is "stored", only binary digits are stored ! – Massimiliano Janes Nov 06 '17 at 09:04
  • Of course binary digits are stored. But 0.1 from binary digit IS `0.1000000000000000055511151231257827021181583404541015625`. Right? – markzzz Nov 06 '17 at 13:55
  • @markzzz **no**, you'd need infinitely many binary digits to represent "0.1" – Massimiliano Janes Nov 06 '17 at 13:58
  • Lol. Of course, I'm talking about "real" value that will be stored. You write 0.1, but you don't store 0.1. Neither "infinitely". You store (as binary) what correspond to ○60.1000000000000000055511151231257827021181583404541015625` – markzzz Nov 06 '17 at 15:54
1

Keep in mind that floating-point values are not real numbers. There are gaps between the values, and all those extra digits, while meaningful for real numbers, don’t reflect any difference in the floating-point value. When you convert a floating-point value to text, having std::numeric_limits<...>::max_digits10 digits ensures that you can convert the text string back to floating-point and get the original value. The extra digits don’t affect the result.

The extra digits that you see when you ask for more digits are the result of the conversion algorithm trying to do what you asked. The algorithm typically just keeps extracting digits until it reaches the desired precision; it could be written to start outputting zeros after it’s written max_digits10 digits, but that’s an additional complication that nobody bothers with. It wouldn’t really be helpful.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • So are you saying that `double value1 = 1.2345678912345678912345;` will use only (once converted to decimal) `1.2345678912345679`? Theres no other data that will be processed in a (let say) multiplication? – markzzz Nov 04 '17 at 14:04
  • @markzzz — Try it! Or do something a bit simpler: write out the appropriate string with `digits10` so that you have a starting point. Then write a program that initializes two floating-point values with those digits followed by more different ones. Then compare the two resulting floating-point values. – Pete Becker Nov 04 '17 at 14:09
  • @markzzz it's more complicated than that. A `double` of `float` don't store base 10 digits. They store a binary value. That value most often doesn't have an exact equivalent that can be printed in base 10 with a certain amount of digits – bolov Nov 04 '17 at 14:09
  • @bolow: ok, but once converted they exceed the significant digit, no? So they are storing also those digits... – markzzz Nov 04 '17 at 14:22
  • @markzzz: here's an example: `2^-40`. It is stored easily in (binary) float format. But it's decimal value is: 9.094947017729282379150390625e-13, a lot of digits. – geza Nov 04 '17 at 14:26
  • That's what I meant: there are lots of more digits to consider other than the "significant digits” ones (whatever it means). Looks at this example: http://www.cpp.sh/8capk . The digits "after" the 15° digit (which should be considered not significant) are used once multiply by 2. So they are stored as well within the binary, and processed by math operators :O Why they are not significant? – markzzz Nov 04 '17 at 14:30
  • `100.011111111111105742566` (24 digits) multiply by `2` gives `200.022222222222211485132`. So ALL digits are considered. – markzzz Nov 04 '17 at 14:32
  • @markzzz — again, if you ask the **conversion** code to give you more digits than are needed, you’ll get them **in the output**. But what you have to look at is the **floating-point value**, not the converted output. In the output, the nonsense digits are nonsense. – Pete Becker Nov 04 '17 at 14:33
  • @markzzz: that's a special case, as multiplying with 2 is exact with binary floats. Try to multiply with, for example, 0.1. You'll see that it is not exact any more. – geza Nov 04 '17 at 14:33
  • @PeteBecker: are you saying that floating point (double) store numbers (as binary) that have (once converted back to decimal) max 15 digits? And the rest of digits will be truncated? – markzzz Nov 04 '17 at 14:44
  • @markzzz -- we're talking across each other here. I can write a conversion routine that takes a floating-point number and gives you as many digits in the converted text as you want. Only the first `max_digits10` matter if you convert the text back to a floating-point value. Again: try it. In an earlier comment I suggested a simple program that might shed light on this question. – Pete Becker Nov 04 '17 at 15:08
  • Honestly I don't know what you are sugggesting to me. What's "text"? I'm talking about `double` and store them into a variable. – markzzz Nov 04 '17 at 15:12
  • Text is when you write 1.23456789 in your source code. Text is when you do `std::cout << value1 << '\n';`. (or, if you insist, `std::cout << value1 << std::endl;` ). – Pete Becker Nov 04 '17 at 15:23
  • @PeteBecker: I tried your suggestion. I've place a starting point number (0.1), take that number with following different numbers (after the significant digit) and compared: http://coliru.stacked-crooked.com/a/c7d1b34533c5ad5b ! Of course they are different. So significant digit doesn't matter :O – markzzz Nov 06 '17 at 07:57
  • @markzzz — try numbers whose decimal representation is closer; you’re seeing the effects of rounding. 1.9 and 1.1 have different values when rounded to 1 significant digit; 1.4 and 1.3 do not. – Pete Becker Nov 06 '17 at 13:42
  • 1.9 and 1.1? Uhm? Did you check out the above/correct example? Here again the link http://coliru.stacked-crooked.com/a/c7d1b34533c5ad5b . The compared values are `0.100000000000000012345234` and `0.10000000000000001678967`, which are PRETTY closer. I kept the first 17 digits as test, and still they differs. But I'm learning that this is just a MYTH, check https://stackoverflow.com/questions/47138519/arent-6-digits-guarantee-in-single-precision – markzzz Nov 06 '17 at 15:57
  • @markzzz -- yes, I saw your example, and **I responded to it** when I said: "you're seeing the effects of rounding". Let me repeat my example, more explicitly. If you round the value 1.9 to one significant digit you get 2. If you round the value 1.1 to one significant digit you get 1. The two rounded values are different. If you round the value 1.4 to one significant digit you get 1. If you round the value 1.3 to one significant digit you get 1. The two rounded values are the same. Now apply the same lesson to your values. Try values where the 18th digits are **closer**. – Pete Becker Nov 06 '17 at 16:50
  • Here you go: http://coliru.stacked-crooked.com/a/8dd3a14c8c361308 In both, 18th digit are closer. Rouding the value get the SAME 17 digits of course (even if this is really guarantee for 16 digits; here it seems just a coincidence), but they are evalutate differently. – markzzz Nov 07 '17 at 08:34
1

just to add to Pete Becker's answer, I think you're confusing the problem of finding the exact decimal representation of a binary mantissa, with the problem of finding some decimal representation uniquely representing that binary mantissa ( given some fixed rounding scheme ).

Now, regarding the first problem, you always need a finite number of decimal digits to exactly represent a binary mantissa ( because 2 divides 10 ).

For example, you need 18 decimal digits to exactly represent the binary 1.0000000000000001, being 1.00000762939453125 in decimal.

but you need just 17 digits to represent it uniquely as 1.0000076293945312 because no other number having exact value 1.0000076293945312xyz... where 0<=x<5 can exist as a double ( more precisely, the next and prior exactly representable values being 1.0000076293945314720446049250313080847263336181640625 and 1.0000076293945310279553950749686919152736663818359375 ).

Of course, this does not mean that given some decimal number you can ignore all digits past the 17th; it just means that if you apply the same rounding scheme used to produce the decimal at the 17th position and assign it back to a double you'll get the same original double.

Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
  • So I can represent a decimal with max 17 digits? (Indifferently where I place the comma). If I have a decimal with 20 digits Ill always loss 3 digit using double? Also, the conversion can round the decimal? – markzzz Nov 04 '17 at 16:23
  • @markzzz you cannot represent *all* decimals, indipendently of the max number of digits; for example, there's no double capable of representing the decimal "0.1", that will be rounded to "0.1000000000000000055511151231257827021181583404541015625" that in turn is uniquely represented by, say, "0.10000000000000001". – Massimiliano Janes Nov 04 '17 at 16:58
  • @markzz, anyway, yes, we can say that a double has at most 17 decimal significant digits – Massimiliano Janes Nov 04 '17 at 17:01
  • but 1.0000076293945314720446049250313080847263336181640625 is a decimal. Are you saying I'll only store the first 17 digit of that decimal? The rest will be loss? – markzzz Nov 04 '17 at 18:30
  • @markzzz yes, but once converted it will result in the same exact value (given the same compiler); so you’re not loosing anything in the end... – Massimiliano Janes Nov 04 '17 at 18:38
  • Of course. And also 1.0000076293945314720446049250313080847263336181640626 Will be truncated After the 17 digit. As well lots of other numbers. that fact is that they are stored (the whole), so they Will affected by math operation. that's my concern... – markzzz Nov 04 '17 at 19:32
  • @markzzz if the source and final destination of the conversion is a double, you don’t lose anything; if you need arbitrary sig.digits then you should not use floating point numbers, and use an arbitrary precision number library instead – Massimiliano Janes Nov 04 '17 at 21:41