C# Float.ToString("0.###") cuts decimal numbers / rounds number to int - c#

I have an object with a float property that I want to place into a text field. The problem is, that if I normally transform it with ".ToString()" it is written with scientific notation i. e. "1.5E+07" (And it cuts of all my decimal points).
Because of that I wanted to transform it with the optional number parameter "#" and so I wrote ".ToString("0." + new string('#', 339)" to guarantee that it only cuts the useless zeros from my float, but writes all other numbers. No matter whether I use that or just "0.###" it always cuts all my decimal numbers. I. e. "-3740295.25" (float) turns into "-3740295" (string).
I really don't understand why this is happening and didn't find anything similar on the internet and most conversion are about doubles to strings anyway(which also doesn't work for me).
EDIT:
As requested here is a code snippet, which made me realize, if I create a completely fresh float it just works, I don't know why it doesn't work with the float from the object:
float testFloat1 = 1234.5544f;
float testFloat2 = (keyValuePair.Value as JsonFloat).Property; // -3740295.25
string testString1 = testFloat1.ToString("0.###"); // 1234.554
string testString2 = testFloat2.ToString("0.###"); // -3740295
This is the object in short:
public partial class JsonFloat : JsonValue
{
public float Property { get; set; }
public JsonFloat(float property)
{
Property = property;
}
}
Kind Regards

Main issue here is that float which maps to System.Single simply does not have enough precision.
And precision does not mean the digits after the decimal point, precision means the whole number.
Here, let me give you an example:
float f = 1234567890f;
Console.WriteLine(f.ToString("################"));
Output:
1234568000
As you can see, the number got "truncated" and rounded up, only about the 7 most significant digits are kept with a float.
This is also why it worked better with double, but this also have limited precision, just a high limit.
double d = 12345678901234567890d;
Console.WriteLine(d.ToString("##############################"));
Output:
12345678901234600000
As you can see here, double kept more of the digits but then gave up at about 15 significant digits.
You can see the limits specified in the documentation of System.Single and System.Double.
Even though System.Single says:
A Single value has up to 7 decimal digits of precision, although a maximum of 9 digits is maintained internally
(my emphasis)
those 9 digits might be temporary while the number is in flight during expression evaluation. The processor registers are slightly bigger and thus slightly more precise than the data types the values are ultimately stored into.

I don't know why, but if I place the float inside a double it just works. So this works:
double testFloat2 = (keyValuePair.Value as JsonFloat).Property; // -3740295.25
string testString2 = testFloat2.ToString("0.###"); // -3740295.25
If anyone knows why, then I would appreciate an explanation

Related

How to remove the leading 0s and divide the remaining value by 100, use a dot separator between the integer and the decimal part in C#

for example let's just say I have a:
var value = "0000000000002022"
how can I get : 20.22
Mathematically speaking, it doesn't matter how many zeroes are before your number, their the same, so 0000002 = 2 is true. We can use this fact to simply parse our string to a number, and then do the division, but we have to be a little careful in which number type we use, because doing (int) 16 / (int) 5 will result in 3, which obviously isn't correct, but integer division does that. So, just to be sure we don't loose any precision, we'll use float
string value = "0000000000002022";
if (float.TryParse(value, out var number))
{
// Successfully parsed our string to a float
Console.WriteLine(number / 100);
}
else
{
// We failed to parse our string to a float :(
Console.WriteLine($"Could not parse '{value}' to a float");
}
Always use TryParse except if you're 110% sure the given string will always be a number, and even then, circumstances can (and will, this is software development after all) change.
Note: float isn't infinitely large, it has a maximum and minimum value, and anything outside that range cannot be represented by a float. Plus, floating point numbers also have a caveat: They're not 100% accurate, for example 0.1 + 0.2 == 0.3 is false, you can read up more on the topic here. If you need to be as accurate as possible, for example when working with money, then maybe use decimal instead (or, make the decision to represent the money as a whole number, representing the minor units of currency your country uses)
by using convert
Int16.Parse(value);
Convert.ToDecimal(int1)/100;

How to not drop precision when converting from float to decimal in a case where this is clearly possible

The value 0.105700679f should be convertible precisely to decimal. decimal clearly is able to hold this value precisely:
decimal d = 0.105700679m;
Console.WriteLine(d); //0.105700679
float also is able to hold the value precisely:
float f = 0.105700679f;
Console.WriteLine(f == 0.105700679f); //True
Console.WriteLine(f == 0.1057007f); //False
Console.WriteLine(f.ToString("R")); //Round-trip representation, 0.105700679
(Note, that float.ToString() seems to drop precision. I just asked about that as well.)
https://www.h-schmidt.net/FloatConverter/IEEE754.html says:
It seems the value really is stored like that. I am seeing this value right now in the debugger. I received it over the network as IEEE float. This value exists!
But when I convert from float to decimal precision is dropped:
float f = 0.105700679f;
decimal d = (decimal)f;
Console.WriteLine(d); //0.1057007
Console.WriteLine(d.ToString("F15")); //0.105700700000000
Console.WriteLine(((double)d).ToString("R")); //0.1057007
I do understand that floating point numbers are imprecise. But here I see no reason for a loss of information. This is on .NET 4.7.1. How can I convert from float to decimal and preserve precision in all cases where doing so is possible?
This is important to me because I am processing financial market data and joining data sources based on price. Data is given to me as a float over a 3rd party network protocol. I need to import that float to a decimal representation.
Try converting f to double and then converting that to decimal.
I suspect you are seeing shortcomings in .NET.
Let’s look at some of the code in your question line by line. In float f = 0.105700679f;, the decimal numeral “0.105700679” is converted to 32-bit binary floating-point. The result of this is the number 0.105700679123401641845703125.
In Console.WriteLine(f == 0.105700679f);. This compares f to the value represented by 0.105700679f. Since the f suffix denotes a float type, 0.105700679f represents the decimal numeral “0.105700679” converted to 32-bit binary floating-point. So of course it has the same value as it did before, and the test for equality returns true. You have not tested whether f is equal to 0.105700679, you have tested whether it is equal to 0.105700679f, and it is.
Then we have decimal d = (decimal)f;. Based on the results you are seeing, it appears to me this conversion produces a number with only seven decimal digits, .1057007. I presume Microsoft has decided that, because a float is only “capable” of holding seven decimal digits, that only seven should be produced when converting to decimal. (This is both a false understanding of what the value of a binary floating-point number represents and an incorrect number. A conversion from decimal to float and back is only guaranteed to preserve six decimal digits, and a conversion from float to decimal and back requires nine decimal digits to preserve the float value. So seven is just wrong.)
If there is a solution to your problem, it is to convert f to decimal by some means other than the cast (decimal) f. I do not know C#, so I cannot say what the solution should be. I suggest trying to convert to double first and then decimal. Quite likely C# will convert float to double without changing the value, and then the conversion to decimal will produce more decimal digits. Another possibility could be converting f to a string with the number of decimal digits you desire and then converting the string to a decimal number.
Also, you say the data is coming via a third-party network protocol. It appears the protocol is incapable of representing the actual values it is supposed to be communicating. That is a serious defect that you should complain to the third party about. I know that may seem futile, but it should be done. Also, it casts doubt on your need to convert the float value to 0.105700679. Most eight-digit decimal numbers in the origin of this data could not survive the conversion to float and back to decimal without change. So, even if you are able to convert float to eight decimal digits, most of the results will differ from the original pre-transport values. E.g., if the original number were 0.105700680, it is changed to 0.105700679123401641845703125 when converted to a float to be sent over the network. So the receiver receives 0.105700679123401641845703125, and it is impossible for them to know the original number was 0.105700680 rather than 0.105700679.
C# data type FLOAT have Precision is 7.
float f = 0.105700679f;
Console.WriteLine(d); //0.1057007 so that result is true!
read more: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/float

C# "Integral constant is too large" - Integer variable too large for Int32 type

I'm building an ASP.NET Core API that returns the periodic table and the planets in the solar system. Mainly to play with API calls, as well as data types in .NET. Building this in Visual Studio 2017.
I'm having problems with a Mass property for planets, in Kilograms. Obviously a large integer, so I've tried declaring it as a long, Ulong, Int64, and even UInt64. However, when I try to enter in a new model of a planet, and put in the mass, I get the following error:
struct System.Int32
Represents a 32 bit signed integer.
Integral constant is too large.
Here is my PlanetModel.cs where I'm describing the property:
...
public UInt64 Volume { get; set; } //In Kilometers
public Int64 Mass { get; set; } //In Kilograms
public float Gravity { get; set; } //In m/s^2
...
Yet when I move over to my PlanetDataStore.cs, here is where I'm trying to build the object with data.
new PlanetModel()
{
Position = 1,
Name = "Mercury",
Distance = 57909050, //In Kilometers
Orbit = 0.240846F, //In years
SolarDay = 0.5F, //In Days
Radius = 2439, //In Kilometers
Volume = 60830000000, // In Kilometers
Mass = 330110000000000000000000, //In Kilograms
Gravity = 3.7F, //In m/s^2
},
I get a red error squiggly over the first '3' in my Mass, with the error above. Yet when I mouse over Mass, it reads: "long PlanetMode.Mass {get; set;}
When I mouse over the equals, it reads: "struct System.Int64"
Where is the miscommunication? Why is my property declared as a 64-bit integer, but the value stuck in 32-bit?
Obviously a large integer
That is by no means obvious. The mass of the earth is not an integral number of kilograms. You shouldn't be using any integral type for this application. Use integers for things that are genuinely integers, like the number of elements in a sequence.
Use double for physical quantities that are accurately measured to ten-ish decimal places. Mass, volume, length, force, and so on, should always be doubles.
This will also let you get rid of those hard-to-read numbers. Doubles let you use scientific notation because doubles are for science:
Mass = 3.3011E23,
Also, while we're looking at your solution, I note that for spherical planets, the volume can be computed from the radius. You might not want to store both; instead, just store one and calculate the other when you need it.
I note also that you have a single "distance" which seems to be the semi-major axis in the case of Mercury. Why are you storing only the semi-major axis, and why not call it what it is?
I note also that you have mixed up your units all over the place -- meters and kilometers, days and years, and so on. Why not keep everything in standard units? Meters for length, seconds for time, kilograms for mass. You'll find that you make fewer silly arithmetic mistakes when you use standard units consistently.
The value you have is too long for an Int64, long is also 64 bits. The largest number you can store in Int64 is (2^64 - 1)/2 = 9,223,372,036,854,775,807
You can store this number as floating point. If you want to store the number as a floating point you have to tell the compiler it's a floating point number.
Look up how floating point numbers are stored to get a better understanding. If you want to store it in a float you can do this 330110000000000000000000.0f
Long literals must be suffixed with L in C#
Mass = 123456789012345L
Though that clearly won't enable you to store a number larger than 64 bits in a long :)
Why not use a floating point type?
As others pointed out, your number is too large to fit in the UInt64 structure.
So you'll need that BigInteger, or maybe another, wider, mutable numeric type, such as double. So your model declare the Mass property like this:
public double Mass { get; set; } //In Kilograms
Next, you must suffix your numeric literal with the D, like so:
Mass = 330110000000000000000000D, //In Kilograms
You format the number for display as usual:
mercury.Mass.ToString("N0");
You have a slight problem here, the max value of an Int64/UInt64 are way less than the number you are trying to assign.
To put it in perspective:
330,110,000,000,000,000,000,000 //Your number
9,223,372,036,854,775,807 //Int64 Max Value
18,446,744,073,709,551,615 //UInt64 Max Value
Your number is simply too large for that data type.
Although I have zero experience with it, you might be able to use BigInteger if you are using .NET 4.0 or above.
I'm not sure where you're getting the structs and type info, but even at Int64 that value is simply too large. Int64.MaxValue is:
9223372036854775807
And you have:
330110000000000000000000
Even unsigned, that's orders of magnitude too large. You might use something like BigInteger instead.

Rounding the SIGNIFICANT digits in a double, not to decimal places [duplicate]

This question already has answers here:
Round a double to x significant figures
(17 answers)
Closed 7 years ago.
I need to round significant digits of doubles. Example
Round(1.2E-20, 0) should become 1.0E-20
I cannot use Math.Round(1.2E-20, 0), which returns 0, because Math.Round() doesn't round significant digits in a float, but to decimal digits, i.e. doubles where E is 0.
Of course, I could do something like this:
double d = 1.29E-20;
d *= 1E+20;
d = Math.Round(d, 1);
d /= 1E+20;
Which actually works. But this doesn't:
d = 1.29E-10;
d *= 1E+10;
d = Math.Round(d, 1);
d /= 1E+10;
In this case, d is 0.00000000013000000000000002. The problem is that double stores internally fractions of 2, which cannot match exactly fractions of 10. In the first case, it seems C# is dealing just with the exponent for the * and /, but in the second case it makes an actual * or / operation, which then leads to problems.
Of course I need a formula which always gives the proper result, not only sometimes.
Meaning I should not use any double operation after the rounding, because double arithmetic cannot deal exactly with decimal fractions.
Another problem with the calculation above is that there is no double function returning the exponent of a double. Of course one could use the Math library to calculate it, but it might be difficult to guarantee that this has always precisely the same result as the double internal code.
In my desperation, I considered to convert a double to a string, find the significant digits, do the rounding and convert the rounded number back into a string and then finally convert that one to a double. Ugly, right ? Might also not work properly in all case :-(
Is there any library or any suggestion how to round the significant digits of a double properly ?
PS: Before declaring that this is a duplicate question, please make sure that you understand the difference between SIGNIFICANT digits and decimal places
The problem is that double stores internally fractions of 2, which cannot match exactly fractions of 10
That is a problem, yes. If it matters in your scenario, you need to use a numeric type that stores numbers as decimal, not binary. In .NET, that numeric type is decimal.
Note that for many computational tasks (but not currency, for example), the double type is fine. The fact that you don't get exactly the value you are looking for is no more of a problem than any of the other rounding error that exists when using double.
Note also that if the only purpose is for displaying the number, you don't even need to do the rounding yourself. You can use a custom numeric format to accomplish the same. For example:
double value = 1.29e-10d;
Console.WriteLine(value.ToString("0.0E+0"));
That will display the string 1.3E-10;
Another problem with the calculation above is that there is no double function returning the exponent of a double
I'm not sure what you mean here. The Math.Log10() method does exactly that. Of course, it returns the exact exponent of a given number, base 10. For your needs, you'd actually prefer Math.Floor(Math.Log10(value)), which gives you the exponent value that would be displayed in scientific notation.
it might be difficult to guarantee that this has always precisely the same result as the double internal code
Since the internal storage of a double uses an IEEE binary format, where the exponent and mantissa are both stored as binary numbers, the displayed exponent base 10 is never "precisely the same as the double internal code" anyway. Granted, the exponent, being an integer, can be expressed exactly. But it's not like a decimal value is being stored in the first place.
In any case, Math.Log10() will always return a useful value.
Is there any library or any suggestion how to round the significant digits of a double properly ?
If you only need to round for the purpose of display, don't do any math at all. Just use a custom numeric format string (as I described above) to format the value the way you want.
If you actually need to do the rounding yourself, then I think the following method should work given your description:
static double RoundSignificant(double value, int digits)
{
int log10 = (int)Math.Floor(Math.Log10(value));
double exp = Math.Pow(10, log10);
value /= exp;
value = Math.Round(value, digits);
value *= exp;
return value;
}

Casting float to decimal loses precision in C#

In C# 4.0, the following cast behaves very unexpectedly:
(decimal)1056964.63f
1056965
Casting to double works just fine:
(double)1056964.63f
1056964.625
(decimal)(double)1056964.63f
1056964.625
Is this by design?
The problem is with your initial value - float is only accurate to 7 significant decimal digits anyway:
float f = 1056964.63f;
Console.WriteLine(f); // Prints 1056965
So really the second example is the unexpected one in some ways.
Now the exact value in f is 1056965.625, but that's the value given for all values from about 1056964.563 to 1056964.687 - so even the ".6" part isn't always correct. That's why the docs for System.Single state:
By default, a Single value contains only 7 decimal digits of precision, although a maximum of 9 digits is maintained internally.
The extra information is still preserved when you convert to double, because that's can preserve it without "interpreting" it at all - where converting it to a decimal form (either to print or for the decimal type) goes through code which knows it can't "trust" those last two digits.
It is by design. Float can hold your number [edit]quite accurate[/edit], but for conversion purposes to it rounds it up to nearest integer, because there are only few representable float values between your number and integer (1056964.75 and 1056964.88). See COMNumber::FormatSingle and COMDecimal::InitSingle from SSCLI.

Categories

Resources