Почему (double) 0.6f> (double) (6/10f)?

Это то, что происходит на моем компьютере:

(double)(float)0.6
= 0.60000002384185791

(double)0.6f
= 0.60000002384185791

(double)(6/10f)
= 0.6

(double)(float)(6/10f)
= 0.6

6/10f также является плавающей точкой, как получилось, что это может быть ровно 0,6?
На мой взгляд (двойной) (6/10f) также должно быть 0,60000002384185791. Может кто-нибудь помочь объяснить это? спасибо!

Ответ 1

Во-первых, важно помнить, что 0.6 нельзя точно представить как float, однако его можно точно представить как double (неточности арифметики с плавающей запятой хорошо документированы, если ее непонятно почему 0.6 нельзя точно представить как float, попробуйте эта ссылка)

Причина, по которой вы видите описанное выше поведение, сводится к компилятору - если вы посмотрите на скомпилированную сборку в рефлекторе, то что происходит здесь, это немного яснее:

( ОБНОВЛЕНИЕ. Я изменил код, чтобы он не использовал Console.WriteLine, так как я понял, что компилятор выбрал для вас перегрузку, что смутило ситуацию)

// As written in source
var j = (double)(float)0.6;
var k = (double)0.6f;
var l = (double)(6/10f);
var m = (double)(float)(6/10f);

// Code as seen by Reflector
double j = 0.60000002384185791;
double k = 0.60000002384185791;
double l = 0.6;
double m = 0.6;

Почему компилятор решает скомпилировать этот особый путь вне меня (fyi, все это с отключенными оптимизациями)

Некоторые интересные другие случаи:

// Code
var a = 0.6;
var b = (double)0.6;
var c = 0.6f;
var d = (float)0.6;

var e = 6 / 10;
var f = 6 / (10f);
var g = (float)(6 / 10);
var h = 6 / 10f;
var i = (double)6 / 10;
// Prints out 0.60000002384185791

double n = (float)0.6;
double o = f;

// As seen by Reflector
double a = 0.6;
double b = 0.6;
float c = 0.6f;
float d = 0.6f;

int e = 0;
float f = 0.6f;
float g = 0f;
float h = 0.6f;
double i = 0.6;

double n = 0.60000002384185791;
double o = f;

Компилятор, похоже, делает вышеупомянутый трюк в нескольких особых случаях, почему он делает это только тогда, когда кастинг на двойник полностью выходит за рамки меня!

Остальное время, похоже, делает некоторые обманы для создания арифметики с плавающей запятой, похоже, работает там, где на самом деле это обычно не будет.

Ответ 2

Кажется, округляет результат. Вы показываете результат с необходимыми цифрами точности? Вы можете использовать этот класс С# от Jon Skeet, чтобы получить точное числовое представление распечатанного результата.

Обратите внимание, что ToString() не всегда будет печатать все цифры, а также отладчик Visual Studio.

Ответ 3

Если бы я был игроком ставок, я бы сказал, что разница в том, где происходит принуждение. В последних двух примерах (с 6/10f) имеются два литерала, которые являются целыми числами (целое число 6 и поплавок 10.00000000...). Похоже, что деление происходит после принуждения, по крайней мере, в используемом компиляторе. В первых двух примерах у вас есть дробный плавающий литерал (0.6), который не может быть адекватно выражен как двоичное значение в мантиссе поплавка. Принуждение этого значения к двойнику не может восстановить повреждение, которое уже было сделано.

В средах, которые производят полностью согласованные результаты, деление происходит до того, как принуждение удвоится (6 будет принудительно применено к поплавке для деления, чтобы соответствовать 10, деление выполняется в пространстве с плавающей точкой, затем результат принуждается к удвоению).