Thread overview
formatting a float or double in a string with all significant digits kept
Oct 08, 2019
dan
Oct 09, 2019
berni44
Oct 09, 2019
Jon Degenhardt
Oct 09, 2019
GreatSam4sure
Oct 09, 2019
David Briant
Oct 10, 2019
dan
Oct 10, 2019
Jon Degenhardt
Oct 10, 2019
H. S. Teoh
Oct 11, 2019
dan
October 08, 2019
I have a double precision number that i would like to print all significant digits of, but no more than what are actually present in the number.  Or more exactly, i want to print the minimum number of digits necessary to recover the original number to within 2 or 3 least significant bits in the stored, in-core, version of its bit pattern.

For example,


import std.string;
import std.stdio;
import std.math;

void main( ) {
  auto t = format("%3.30f", PI );
  writeln("Value of PI is: ", PI, " or, : ", t);
}

The default way writeln prints is 5 digits to the right of the decimal point.

I can format to print with any number of digits, such as 30 above, but that's too many.

For pi, the correct number of digits to print looks to be about 18 (and the extra 12 digits presumably are from the decimal expansion of the least significant bit?).

But i would like to be able to do this without knowing the expansion of pi, or writing too much code, especially if there's some d function like writeAllDigits or something similar.

Thanks in advance for any pointers!

dan
October 09, 2019
On Tuesday, 8 October 2019 at 20:37:03 UTC, dan wrote:
> But i would like to be able to do this without knowing the expansion of pi, or writing too much code, especially if there's some d function like writeAllDigits or something similar.

You can use the property .dig to get the number of significant digits of a number:

writeln(PI.dig); // => 18

You still need to account for the numbers before the dot. If you're happy with scientific notation you can do:

auto t = format("%.*e", PI.dig, PI);
writeln("PI = ",t);

(By the way, you can shortcut that with writefln!"PI = %.*e"(PI.dig, PI);)

If you don't want to use scientific notation, you probably need to do some calculations to find out about the number of digits before the dot (you need abs to make it work for negative numbers too):

import std.conv: to;
auto x = to!int(log10(abs(PI)));
writefln!"%*.*f"(x,PI.dig-x,PI);

October 09, 2019
On Wednesday, 9 October 2019 at 05:46:12 UTC, berni44 wrote:
> On Tuesday, 8 October 2019 at 20:37:03 UTC, dan wrote:
>> But i would like to be able to do this without knowing the expansion of pi, or writing too much code, especially if there's some d function like writeAllDigits or something similar.
>
> You can use the property .dig to get the number of significant digits of a number:
>
> writeln(PI.dig); // => 18
>
> You still need to account for the numbers before the dot. If you're happy with scientific notation you can do:
>
> auto t = format("%.*e", PI.dig, PI);
> writeln("PI = ",t);

Using the '.dig' property is a really nice idea and looks very useful for this. A clarification though - It's the significant digits in the data type, not the value. (PI is 18 because it's a real, not a double.) So:

    writeln(1.0f.dig, ", ", float.dig);  =>  6, 6
    writeln(1.0.dig, ", ", double.dig);  => 15, 15
    writeln(1.0L.dig, ", ", real.dig);   => 18, 18

Another possibility would be to combine the '.dig' property with the "%g" option, similar to the use "%e" shown. For example, these lines:

    writeln(format("%0.*g", PI.dig, PI));
    writeln(format("%0.*g", double.dig, 1.0));
    writeln(format("%0.*g", double.dig, 100.0));
    writeln(format("%0.*g", double.dig, 1.00000001));
    writeln(format("%0.*g", double.dig, 0.00000001));

produce:

    3.14159265358979324
    1
    100
    1.00000001
    1e-08

Hopefully experimenting with the different formatting options available will yield one that works for your use case.
October 09, 2019
On Wednesday, 9 October 2019 at 07:16:43 UTC, Jon Degenhardt wrote:
> On Wednesday, 9 October 2019 at 05:46:12 UTC, berni44 wrote:
>> On Tuesday, 8 October 2019 at 20:37:03 UTC, dan wrote:
>>> But i would like to be able to do this without knowing the expansion of pi, or writing too much code, especially if there's some d function like writeAllDigits or something similar.
>>
>> You can use the property .dig to get the number of significant digits of a number:
>>
>> writeln(PI.dig); // => 18
>>
>> You still need to account for the numbers before the dot. If you're happy with scientific notation you can do:
>>
>> auto t = format("%.*e", PI.dig, PI);
>> writeln("PI = ",t);
>
> Using the '.dig' property is a really nice idea and looks very useful for this. A clarification though - It's the significant digits in the data type, not the value. (PI is 18 because it's a real, not a double.) So:
>
>     writeln(1.0f.dig, ", ", float.dig);  =>  6, 6
>     writeln(1.0.dig, ", ", double.dig);  => 15, 15
>     writeln(1.0L.dig, ", ", real.dig);   => 18, 18
>
> Another possibility would be to combine the '.dig' property with the "%g" option, similar to the use "%e" shown. For example, these lines:
>
>     writeln(format("%0.*g", PI.dig, PI));
>     writeln(format("%0.*g", double.dig, 1.0));
>     writeln(format("%0.*g", double.dig, 100.0));
>     writeln(format("%0.*g", double.dig, 1.00000001));
>     writeln(format("%0.*g", double.dig, 0.00000001));
>
> produce:
>
>     3.14159265358979324
>     1
>     100
>     1.00000001
>     1e-08
>
> Hopefully experimenting with the different formatting options available will yield one that works for your use case.



Good solution

But what does it takes to leave a number the way it is without formatting automatic to 6 significant figure out how that requires such a work around as shown above. I know C language is the same but must D follow suit.

One of the compilers, i think the DMD 2.084 (not sure now) correct but now the recent one's have revert back. Every number should be left the way it is like java, C#(not sure), actionscript, javascript, etc.

I don't think it will take that much effort for trivial things like to stress developers.

This is the best solution i have ever seen on this issue on the forum.i have ask this question several times on this forum

October 09, 2019
On Tuesday, 8 October 2019 at 20:37:03 UTC, dan wrote:
> I have a double precision number that i would like to print all significant digits of, but no more than what are actually present in the number.  Or more exactly, i want to print the minimum number of digits necessary to recover the original number to within 2 or 3 least significant bits in the stored, in-core, version of its bit pattern.
>
> For example,
>
>
> import std.string;
> import std.stdio;
> import std.math;
>
> void main( ) {
>   auto t = format("%3.30f", PI );
>   writeln("Value of PI is: ", PI, " or, : ", t);
> }
>
> The default way writeln prints is 5 digits to the right of the decimal point.
>
> I can format to print with any number of digits, such as 30 above, but that's too many.
>
> For pi, the correct number of digits to print looks to be about 18 (and the extra 12 digits presumably are from the decimal expansion of the least significant bit?).
>
> But i would like to be able to do this without knowing the expansion of pi, or writing too much code, especially if there's some d function like writeAllDigits or something similar.
>
> Thanks in advance for any pointers!
>
> dan

Hi Dan,

What's your usecase here, e.g. a csv/json reader / writer? You say it's for double precision numbers (64bit format) then provide an example for reals (80bit format). So I'm not certain your goal.

If you google "what every developer should know about doubles" you'll hit a number of useful articles that explain the common issues of floating point representation in detail.

-- David
October 10, 2019
On Wednesday, 9 October 2019 at 10:54:49 UTC, David Briant wrote:
> On Tuesday, 8 October 2019 at 20:37:03 UTC, dan wrote:
>> I have a double precision number that i would like to print all significant digits of, but no more than what are actually present in the number.  Or more exactly, i want to print the minimum number of digits necessary to recover the original number to within 2 or 3 least significant bits in the stored, in-core, version of its bit pattern.
>>
>> For example,
>>
>>
>> import std.string;
>> import std.stdio;
>> import std.math;
>>
>> void main( ) {
>>   auto t = format("%3.30f", PI );
>>   writeln("Value of PI is: ", PI, " or, : ", t);
>> }
>>
>> The default way writeln prints is 5 digits to the right of the decimal point.
>>
>> I can format to print with any number of digits, such as 30 above, but that's too many.
>>
>> For pi, the correct number of digits to print looks to be about 18 (and the extra 12 digits presumably are from the decimal expansion of the least significant bit?).
>>
>> But i would like to be able to do this without knowing the expansion of pi, or writing too much code, especially if there's some d function like writeAllDigits or something similar.
>>
>> Thanks in advance for any pointers!
>>
>> dan
>
> Hi Dan,
>
> What's your usecase here, e.g. a csv/json reader / writer? You say it's for double precision numbers (64bit format) then provide an example for reals (80bit format). So I'm not certain your goal.
>
> If you google "what every developer should know about doubles" you'll hit a number of useful articles that explain the common issues of floating point representation in detail.
>
> -- David

Thanks David for your reply.

Thanks also berni44 for the information about the dig attribute,
Jon for the neat packaging into one line using the attribute on the type.
Unfortunately, the version of gdc that comes with the version of debian
that i am using does not have the dig attribute yet, but perhaps i can
upgrade, and eventually i think gdc will have it.

And thanks GreatSam4sure for your reply --- i searched the archives first,
but very poorly :(.  But it's easy to believe that i'm not the first person
in the history of the world with this issue.

Now, my use case is nothing so useful or general as a csv/json reader/writer.

I'm just doing some computations, incorrectly i think, and i want to be
able to print out the results and feed them to other software.  I'm trying
to chase down a problem and rule out as many places for error as i can, and
it just seemed strange not to be able to get all the digits out in some easy way.
But the dig attribute seems to be a big step forward, and for that i am grateful.

dan
October 10, 2019
On Thursday, 10 October 2019 at 17:12:25 UTC, dan wrote:
> Thanks also berni44 for the information about the dig attribute,
> Jon for the neat packaging into one line using the attribute on the type.
> Unfortunately, the version of gdc that comes with the version of debian
> that i am using does not have the dig attribute yet, but perhaps i can
> upgrade, and eventually i think gdc will have it.

Glad these ideas helped. The value of the 'double.dig' property is not going to change between compilers/versions/etc. It's really a property of IEEE 754 floating point for 64 bit floats. (D specified the size of double as 64).  So, if you are using double, then it's pretty safe to use 15 until the compiler you're using is further along on versions. Declare an enum or const variable to give it a name so you can track it down later.

Also, don't get thrown off by the PI is a real, not a double. D supports 80 bit floats as real, so constants like PI are defined as real. But if you convert PI to a double, it'll then have 15 significant bits of precision.

--Jon
October 10, 2019
On Thu, Oct 10, 2019 at 09:13:05PM +0000, Jon Degenhardt via Digitalmars-d-learn wrote:
> On Thursday, 10 October 2019 at 17:12:25 UTC, dan wrote:
> > Thanks also berni44 for the information about the dig attribute, Jon
> > for the neat packaging into one line using the attribute on the
> > type.
> > Unfortunately, the version of gdc that comes with the version of
> > debian that i am using does not have the dig attribute yet, but
> > perhaps i can upgrade, and eventually i think gdc will have it.

What's the output of dmd --version?  I find it extremely odd that .dig isn't supported.  AFAIK, it's been there since the early days of D. Certainly, it has been there since I started using D, which was before dmd was ever available in Debian.

What's the compiler output of:

	pragma(msg, float.dig);

?


T

-- 
Many open minds should be closed for repairs. -- K5 user
October 11, 2019
On Thursday, 10 October 2019 at 22:44:05 UTC, H. S. Teoh wrote:
> On Thu, Oct 10, 2019 at 09:13:05PM +0000, Jon Degenhardt via Digitalmars-d-learn wrote:
>> On Thursday, 10 October 2019 at 17:12:25 UTC, dan wrote:
>> > Thanks also berni44 for the information about the dig attribute, Jon
>> > for the neat packaging into one line using the attribute on the
>> > type.
>> > Unfortunately, the version of gdc that comes with the version of
>> > debian that i am using does not have the dig attribute yet, but
>> > perhaps i can upgrade, and eventually i think gdc will have it.
>
> What's the output of dmd --version?  I find it extremely odd that .dig isn't supported.  AFAIK, it's been there since the early days of D. Certainly, it has been there since I started using D, which was before dmd was ever available in Debian.
>
> What's the compiler output of:
>
> 	pragma(msg, float.dig);
>
> ?
>
>
> T

Thanks HS!

I sure thought i got a compile time error when i used .dig, but i tried
it again, and it works.

I tried the pragma, and it printed out 6, and i tried PI.dig and double.dig
and they're all working now.

Just for reference, i'm using what i think is the standard gdc on debian 9,
not dmd, and the version is 2068L.

Thanks again for your help in encouraging me to try harder, and thanks again
everybody else for all the help.

dan