www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - union default initialization values

reply confuzzled <con fuzzled.com> writes:
Given the following union

union F
{
     double x;
     struct {
         ulong lo;
         ulong hi;
     }
}

I do not understand the output below. Please clarify.

import std.stdio;
void main()
{
     F fp;
     fp.lo.writeln; // Why is this not zero? How is this value derived?
     fp.hi.writeln; // expected
     fp.x.writeln;  // expected

     fp.x = 
19716939937510315926535.148979323846264338327950288458209749445923078164062862089986280348253421170679;
     fp.lo.writeln;
     fp.hi.writeln;
     fp.x.writefln!"%20.98f"; // Also, why is precision completely lost 
after 16 digits (18 if I change the type of x to real)?
}

Sorry if this seem like noise but I genuinely do not understand. What 
changes would I need to make to retain the precision of the value 
provided in the assignment above?

Thanks,
--confuzzled
Dec 05 2023
next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Tuesday, 5 December 2023 at 19:24:51 UTC, confuzzled wrote:
 Given the following union

 union F
 {
     double x;
     struct {
         ulong lo;
         ulong hi;
     }
 }
The default value of this would be `double.init`, since the first member of the union is a `double`, which is a kind of NaN. This is non-zero. You can use `double x = 0;` to specify you want it to be zero initialized.
     fp.x.writefln!"%20.98f"; // Also, why is precision 
 completely lost after 16 digits (18 if I change the type of x 
 to real)?
idk about this.
Dec 05 2023
parent reply confuzzled <con fuzzled.com> writes:
On 12/6/23 4:28 AM, Adam D Ruppe wrote:
 On Tuesday, 5 December 2023 at 19:24:51 UTC, confuzzled wrote:
 Given the following union

 union F
 {
     double x;
     struct {
         ulong lo;
         ulong hi;
     }
 }
The default value of this would be `double.init`, since the first member of the union is a `double`, which is a kind of NaN. This is non-zero.
Correct. So I expected a NaN output for x. However, I wasn't expecting lo == 13835058055282163712 and hi == 32767 where x is of type real, or lo == 9221120237041090560 and hi = 0 where x is of type double. Based on the default initialization rules, I expected both lo and hi to have a value of zero regardless if x is of type double or real. This is what I'm trying to understand, how are these values derived? Thanks again, --confuzzled
Dec 05 2023
parent reply Nick Treleaven <nick geany.org> writes:
On Tuesday, 5 December 2023 at 19:47:38 UTC, confuzzled wrote:
 On 12/6/23 4:28 AM, Adam D Ruppe wrote:
 On Tuesday, 5 December 2023 at 19:24:51 UTC, confuzzled wrote:
 Given the following union

 union F
 {
     double x;
     struct {
         ulong lo;
         ulong hi;
     }
 }
The default value of this would be `double.init`, since the first member of the union is a `double`, which is a kind of NaN. This is non-zero.
Correct. So I expected a NaN output for x. However, I wasn't expecting lo == 13835058055282163712 and hi == 32767 where x is of type real, or lo == 9221120237041090560 and hi = 0 where x is of type double. Based on the default initialization rules, I expected both lo and hi to have a value of zero regardless if x is of type double or real. This is what I'm trying to understand, how are these values derived?
ulong.sizeof is 8, like double.sizeof. So F.init.lo should have the same bit pattern as F.init.x because they share storage exactly. F.init.hi should be 0 and it is on my system. (*"If the union is larger than the first field, the remaining bits are set to 0"*). I don't know why you don't get zero for that.
Dec 06 2023
parent Nick Treleaven <nick geany.org> writes:
On Wednesday, 6 December 2023 at 12:38:35 UTC, Nick Treleaven 
wrote:
 Correct. So I expected a NaN output for x. However, I wasn't 
 expecting lo == 13835058055282163712 and hi == 32767 where x 
 is of type real, or lo == 9221120237041090560 and hi = 0 where 
 x is of type double. Based on the default initialization 
 rules, I expected both lo and hi to have a value of zero 
 regardless if x is of type double or real. This is what I'm 
 trying to understand, how are these values derived?
ulong.sizeof is 8, like double.sizeof. So F.init.lo should have the same bit pattern as F.init.x because they share storage exactly. F.init.hi should be 0 and it is on my system. (*"If the union is larger than the first field, the remaining bits are set to 0"*). I don't know why you don't get zero for that.
Oops, you said you did get zero in that case, so all is well. When `x` is `real`, that may overlap with `F.hi` because `real` can be more than 8 bytes.
Dec 06 2023
prev sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Dec 06, 2023 at 04:24:51AM +0900, confuzzled via Digitalmars-d-learn
wrote:
[...]
 import std.stdio;
 void main()
 {
     F fp;
     fp.lo.writeln; // Why is this not zero? How is this value derived?
     fp.hi.writeln; // expected
     fp.x.writeln;  // expected
 
     fp.x = 19716939937510315926535.148979323846264338327950288458209749445923078164062862089986280348253421170679;
     fp.lo.writeln;
     fp.hi.writeln;
     fp.x.writefln!"%20.98f"; // Also, why is precision completely lost after
 16 digits (18 if I change the type of x to real)?
 }
 
 Sorry if this seem like noise but I genuinely do not understand. What
 changes would I need to make to retain the precision of the value
 provided in the assignment above?
[...] A `double` type is stored as an IEEE double-precision floating-point number, which is a 64-bit value containing 1 sign bit, 11 exponent bits, and 53 mantissa bits (52 stored, 1 implied). A mantissa of 53 bits can store up to 2^53 distinct values, which corresponds with log_10(2^53) ≈ 15.95 decimal digits. So around 15-16 decimal digits. (The exponent bits only affect the position of the decimal point, not the precision of the value, so they are not relevant here.) In D, you can use the .dig property to find out approximately how many of precision a format has (e.g., `writeln(double.dig);` or `writeln(real.dig);`). The number you have above is WAY beyond the storage capacity of the double-precision floating-point format or the 80-bit extended precision format of `real`. If you need that level of precision, you probably want to use an arbitrary-precision floating point library like libgmp instead of the built-in `double` or `real`. (Keep in mind that the performance will be significantly slower, because the hardware only works with IEEE 64-bit / 8088 80-bit extended precision numbers. Anything beyond that has to be implemented in software, and will incur memory management costs as well since the storage size of the number will not be fixed.) Also, if you don't understand how floating-point in computers work, I highly recommend reading this: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html It's a bit long, but well worth the time to read to understand why floating-point behaves the way it does. T -- It is of the new things that men tire --- of fashions and proposals and improvements and change. It is the old things that startle and intoxicate. It is the old things that are young. -- G.K. Chesterton
Dec 05 2023
parent confuzzled <con fuzzled.com> writes:
On 12/6/23 4:47 AM, H. S. Teoh wrote:
 On Wed, Dec 06, 2023 at 04:24:51AM +0900, confuzzled via Digitalmars-d-learn
wrote:
 [...]
 
 Also, if you don't understand how floating-point in computers work, I
 highly recommend reading this:
 
 	https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
 
 It's a bit long, but well worth the time to read to understand why
 floating-point behaves the way it does.
 
 
 T
 
Thank you for the explanation. Heading to the link to glean more. --confuzzled
Dec 05 2023