3

A colleague has written some code along these lines:

var roundedNumber = (float) Math.Round(someFloat, 2);
Console.WriteLine(roundedNumber);

I have an uncertainty about this code - is the number that gets written here even guaranteed to have 2 decimal places any more? It seems plausible to me that truncation of the double Math.Round(someFloat, 2) to float might result in a number whose string representation has more than 2 digits. Can anybody either provide an example of this (demonstrating that such a cast is unsafe) or else demonstrate somehow that it is safe to perform such a cast?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • 1
    For display purposes, `Console.WriteLine(${someFloat:F2})` is much simpler and conveys its intent much more clearly. – itsme86 Jul 12 '17 at 15:29
  • This [answer](https://stackoverflow.com/questions/3498192/c-convert-double-to-float-preserving-decimal-point-precision) is not exact to your question, but may help you. – vipersassassin Jul 12 '17 at 15:30
  • 1
    This doesn't seem to be a display issue. OP seems to want a rounded `float`, not a rounded `double` – InBetween Jul 12 '17 at 15:31
  • @itsme86 sure - I'm simplifying/lying in the code example above. In my real code, the float is being returned from a method in a service class, then passed around in some data structures, then encoded in some JSON and passed to a front-end application, and so on. But what I'm interested in is the narrow question of whether the cast to float can result in a number whose `.ToString()` representation no longer has 2 decimal places, and this simple example - which I concede is clearly bad code - is sufficient to illustrate that question. – Mark Amery Jul 12 '17 at 15:31
  • @MarkAmery sorry, but could you rephrase you question somehow? String representation of `rounded` value has more then two decimal digits after the separator: `var single= Single.MaxValue; Console.WriteLine(single); var rounded = (Single)Math.Round(single, 2); Console.WriteLine(rounded);` `rounded` as a String looks like `3,402823E+38` on my machine. – Sergey.quixoticaxis.Ivanov Jul 12 '17 at 16:24
  • Depending on the machine you're running your code at the single<->double conversion may not be 'precise' anyway. At least I don't remember any strong guarantees about it. – Sergey.quixoticaxis.Ivanov Jul 12 '17 at 16:29
  • @Sergey.quixoticaxis.Ivanov I'm afraid I have no idea what you're trying to say in your first comment. Is your point that the premise of my question is flawed because it doesn't take into account floats big enough that their string representation uses E-notation? – Mark Amery Jul 12 '17 at 16:29
  • @MarkAmery nope, no sarcasm was intended. I'm really trying to understand the question but fail at it. – Sergey.quixoticaxis.Ivanov Jul 12 '17 at 16:30
  • @MarkAmery even with small numbers, double->single conversion corrupts the value, so I suppose there does exist a value that (after rounding it and downcasting) would have only one (first) non-zero digit after the separator. – Sergey.quixoticaxis.Ivanov Jul 12 '17 at 16:38
  • https://stackoverflow.com/questions/5986980/convert-float-to-double-loses-precision-but-not-via-tostring this post has a lot of useful info on both conversions and string representation/parsing. – Sergey.quixoticaxis.Ivanov Jul 12 '17 at 16:41

1 Answers1

0

Assuming single and double precision IEEE754 representation and rules, I have checked for the first 2^24 integers i that

float(double( i/100 )) = float(i/100)

in other words, converting a decimal value with 2 decimal places twice (first to the nearest double, then to the nearest single precision float) is the same as converting the decimal directly to single precision, as long as the integer part of the decimal is not too large.

I have no guarantee for larger values.

The double approximation and the single approximation are different, but that's not really the question.

Converting twice is innocuous up to at least 167772.16, it's the same as if Math.Round would have done it directly in single precision.

Here is the testing code in Squeak/Pharo Smalltalk with ArbitraryPrecisionFloat package (sorry to not exhibit it in c# but the language does not really matter, only IEEE rules do).

(1 to: 1<<24)
    detect: [:i |
        (i/100.0 asArbitraryPrecisionFloatNumBits: 24) ~= (i/100 asArbitraryPrecisionFloatNumBits: 24) ] 
    ifNone: [nil].

EDIT

Above test was superfluous because, thanks to excellent reference provided by Mark Dickinson (Innocuous double rounding of basic arithmetic operations) , we know that doing float(double(x) / double(y)) produces a correctly-rounded value for x / y, as long as x and y are both representable as floats, which is the case for any 0 <= x <= 2^24 and for y=100.

EDIT

I have checked with numerators up to 2^30 (decimal value > 10 millions), and converting twice is still identical to converting once. Going further with an interpreted language is not good wrt global warming...

aka.nice
  • 9,100
  • 1
  • 28
  • 40
  • 1
    *“the language does not really matter, only IEEE rules do”* – But I do not believe that double precision to single precision float conversion is covered by IEE 754, is it? – poke Jul 12 '17 at 21:17
  • @poke at least in IEEE754-2008 in section 1.3 `this standard specifies conversion between different floating point formats`. If I'm not mistaken, it is covered by convertFormat in section 5.4.2 – aka.nice Jul 12 '17 at 22:05
  • 1
    @aka.nice: It's a theorem for IEEE 754 arithmetic that if you have finite values `x` and `y` exactly representable as a float, then doing `float(double(x) / double(y))` produces a correctly-rounded value for `x / y`. The same is true for `+`, `-`, `*` and `sqrt`. See [this paper](https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&ved=0ahUKEwjTsOSB4YXVAhWHI8AKHXT1Dg0QFgg2MAI&url=https%3A%2F%2Fjfr.unibo.it%2Farticle%2Fdownload%2F4359%2F4017&usg=AFQjCNEIbe-MuSfKy4n6laneqZBWQJv1Fg), for example. – Mark Dickinson Jul 13 '17 at 07:41
  • @MarkDickinson that kind of intuition drove the initial limit of 2^24, but it's better with a proof, thanks for the reference! Then probability of bad rounding is sufficiently low to let us go well further... – aka.nice Jul 13 '17 at 15:39