Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Проблема с вещественным числами

6.1K
14 марта 2006 года
Flex25
50 / / 14.03.2006
Недавно перешел на C++. До этого очень активно программировал на Perl и Delphi.

Столкнулся с проблемой C++ приведения вещественных чисел к целому типу. Этой проблемы в др. языках нет. Вот примеры:

 
Код:
int n=(0.1+0.7)*10;

В результате получается, что в переменной n содержится число 7, а не 8, как должно быть по логике!

 
Код:
if ((0.1+0.7)*10 == 8) {} else {}

В итоге получается, что здесь тоже выражение ложное. А должно быть истинным!

Я понимаю, что на самом деле в C++ вещественное число 8.0 записывается как 7.9999(9).

Но как же тогда программировать?!!! Как вы, профессиональные программисты, решаете эти проблемы?

Что нужно сделать, чтобы два примера, приведенные мною выше, выполнялись бы КАК НАДО?!
492
14 марта 2006 года
alibabaich
238 / / 08.07.2004
Цитата:
Originally posted by Flex25
Недавно перешел на C++. До этого очень активно программировал на Perl и Delphi.

Столкнулся с проблемой C++ приведения вещественных чисел к целому типу. Этой проблемы в др. языках нет. Вот примеры:

 
Код:
int n=(0.1+0.7)*10;

В результате получается, что в переменной n содержится число 7, а не 8, как должно быть по логике!

 
Код:
if ((0.1+0.7)*10 == 8) {} else {}

В итоге получается, что здесь тоже выражение ложное. А должно быть истинным!

Я понимаю, что на самом деле в C++ вещественное число 8.0 записывается как 7.9999(9).

Но как же тогда программировать?!!! Как вы, профессиональные программисты, решаете эти проблемы?

Что нужно сделать, чтобы два примера, приведенные мною выше, выполнялись бы КАК НАДО?!


Странно, но в С# то же самое(это о др. языках). Это все нормальная потеря при приведении. Культурние надо быть. Преобразование к int идет путем отбрасывания дробной части, а не через округление. Если точность важна округляй по мат. правилам, а не приводи типы.

6.1K
14 марта 2006 года
Flex25
50 / / 14.03.2006
Цитата:
Originally posted by alibabaich
Странно, но в С# то же самое(это о др. языках). Это все нормальная потеря при приведении. Культурние надо быть. Преобразование к int идет путем отбрасывания дробной части, а не через округление. Если точность важна округляй по мат. правилам, а не приводи типы.


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

Не понимаю... Каждый знает, что (0.7+0.1)*10=8
Это вычисление может сделать со 100% точностью любой ребенок.

Так почему же C++ эти вычисления делает так неточно???!!! Как после этого программировать?

Должно быть какое-то решение, чтобы получить из вышеперечисленных выражений нормальные результаты.

534
14 марта 2006 года
HarryAxe
448 / / 19.01.2006
Цитата:
Originally posted by Flex25
Округление - это не выход, т.к. иногда нужно просто отбросить именно дробную часть. Так же хочу заметить, что всегда программа должна выполнять вычисления со 100% точностью.

Не понимаю... Каждый знает, что (0.7+0.1)*10=8
Это вычисление может сделать со 100% точностью любой ребенок.

Так почему же C++ эти вычисления делает так неточно???!!! Как после этого программировать?

Должно быть какое-то решение, чтобы получить из вышеперечисленных выражений нормальные результаты.

Для того чтобы округлить, а не отбросить дробную часть, просто прибавь к округляемому выражению 0.5. А в твоём примере ошибка связана с числом 0.7, дробная часть которого не является степенью двойки. Заменяем на 0.8 - всё канает... В общем, без введения некоторой очень малой погрешности, на мой взгляд, здесь не обойтись. Кстати, это самые неуловимые ошибки - ошибки округления. Каждый, кто с ними сталкивался на практике (когда часов шесть сидишь и пускаешь программу под отладчиком, пытаясь найти строчку кода с ошибкой, а потом выясняется, что ошибку сделал вовсе и не ты, а проклятая машина), с этим согласится. Увы...

1.8K
14 марта 2006 года
_const_
229 / / 26.11.2003
Цитата:
Originally posted by Flex25
Округление - это не выход, т.к. иногда нужно просто отбросить именно дробную часть. Так же хочу заметить, что всегда программа должна выполнять вычисления со 100% точностью.

Не понимаю... Каждый знает, что (0.7+0.1)*10=8
Это вычисление может сделать со 100% точностью любой ребенок.

Так почему же C++ эти вычисления делает так неточно???!!! Как после этого программировать?

Должно быть какое-то решение, чтобы получить из вышеперечисленных выражений нормальные результаты.



Вам уже сказали, что дело не в C++. Будьте же грамотны! Машина - не человек, она обладает конечной разрядностью (читай - точночтью). В любой цифровой системе Вы не получите 100% точности (при любом диапазоне). НИКОГДА. Отсюда вывод - либо проводите сравнение чисел с плавающей точкой с определенной точностью, либо переходите на целочисленную арифметику (но с резким уменьшением диапазона чисел).

И еще советую почитать о форматах хранения чисел в цифровых устройствах.

1.8K
14 марта 2006 года
k3Eahn
365 / / 19.12.2005
Цитата:
Originally posted by Flex25

Так почему же C++ эти вычисления делает так неточно???!!! Как после этого программировать?


После этого на АСМ'е для FPU:!!!: :!!!: :!!!:

351
14 марта 2006 года
PitxBull
633 / / 22.12.2004
:)))) да уж... скоро и 2 + 2 != 4 будет. ужасть.

советую следующее правило : всегда.. то есть нет... никогда не перемешивай в одном выражении вычисления с плавающей точкой и целочисленные вычисления. так следующий код работает нормально.

 
Код:
float a = (0.7+0.1)*10;
int b = a;

assert( b == 8 );
6.1K
14 марта 2006 года
Flex25
50 / / 14.03.2006
Цитата:
Originally posted by _const_
Вам уже сказали, что дело не в C++. Будьте же грамотны! Машина - не человек, она обладает конечной разрядностью (читай - точночтью). В любой цифровой системе Вы не получите 100% точности (при любом диапазоне). НИКОГДА. Отсюда вывод - либо проводите сравнение чисел с плавающей точкой с определенной точностью, либо переходите на целочисленную арифметику (но с резким уменьшением диапазона чисел).

И еще советую почитать о форматах хранения чисел в цифровых устройствах.



Вы пишете, что такие погрешности в числах с плавающей точкой - это нормально. Но ведь, к примеру, в Perl и Delphi все считается 100% без погрешностей. Т.е. если я в этих языках после вычислений получаю в пременную значение 0.8, то я точно уверен, что это именно 0.8, а не 0.7999999999999999999.

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

Может быть есть какие-то альтернативные типы данных, у которых нет проблем с погрешностями?

Повторюсь, на всех остальных языках программирования таких проблем нет!

351
14 марта 2006 года
PitxBull
633 / / 22.12.2004
хотя вообщем то с точки зрения математики и правил приведения к int все правильно :

7.(9) = 8. отбрасываем от первого числа дробную часть и получаем 7.
351
14 марта 2006 года
PitxBull
633 / / 22.12.2004
Цитата:
Originally posted by Flex25
Не смешивать переменные разных типов в вычислениях нельзя, т.к. часто в результате вычислений получаются значения, которые в дальнейшем нужно сравнивать. И я не могу заранее сказать, константы каких типов будут сравниваться между собой.


бред. я ж тебе написал правило и пример кода дал для подражания. прежде чем делать столь категоричные утвержедния научись правильно организовывать код на C++.

534
14 марта 2006 года
HarryAxe
448 / / 19.01.2006
Цитата:
Originally posted by Flex25
Вы пишете, что такие погрешности в числах с плавающей точкой - это нормально. Но ведь, к примеру, в Perl и Delphi все считается 100% без погрешностей. Т.е. если я в этих языках после вычислений получаю в пременную значение 0.8, то я точно уверен, что это именно 0.8, а не 0.7999999999999999999.

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

Может быть есть какие-то альтернативные типы данных, у которых нет проблем с погрешностями?

Повторюсь, на всех остальных языках программирования таких проблем нет!

Хм... Дело-то в том, что компилятор вычисляет выражение int n = (0.1 + 0.7) * 10 на этапе компиляции, а не по ходу выполнения программы. А вот float a = (0.1 + 0.7) * 10; int b = a; - по ходу выполнения:

Код:
; 5    :    int n = (0.1 + 0.7)*10;

    mov DWORD PTR _n$[ebp], 7

; 6    :    float f = (0.1 + 0.7)*10;

    mov DWORD PTR _f$[ebp], 1090519040  ; 41000000H

; 7    :    int a = f;

    fld DWORD PTR _f$[ebp]
    call    __ftol2
    mov DWORD PTR _a$[ebp], eax

С другой стороны, скомпилировав тот же код под Release, получим, что оба выражения оптимизатор скомпоновал сразу на этапе компиляции, однако так же получим в первом случае 7, во втором - 8. То есть:
Код:
; 5    :    int n = (0.1 + 0.7)*10;
; 6    :    float f = (0.1 + 0.7)*10;
; 7    :    int a = f;
; 8    :
; 9    :    std::cout << n << std::endl << a << std::endl;

    push    7
    push    OFFSET FLAT:?cout@std@@3V?$basic_ostream@...
    call    ??6?$basic_ostream@DU?$char_traits@D@std...
    push    8
    push    eax
    call    ?endl@std@@YAAAV?$basic_ostream@DU?$... ; std::endl
Как-то странно получается... Скорее всего, для первого выражения конверсия происходит с помощью CPU, а для второго - FPU, и поэтому происходят несовпадения. Вот только второй из приведённых мной примеров ставит меня в тупик... Почему тогда во втором выражении компилятор не допустил ту же ошибку, что и в первом?
351
14 марта 2006 года
PitxBull
633 / / 22.12.2004
Цитата:
Originally posted by HarryAxe
Хм... Дело-то в том, что компилятор вычисляет выражение int n = (0.1 + 0.7) * 10 на этапе компиляции, а не по ходу выполнения программы. А вот float a = (0.1 + 0.7) * 10; int b = a; - по ходу выполнения:
Код:
; 5    :    int n = (0.1 + 0.7)*10;

    mov DWORD PTR _n$[ebp], 7

; 6    :    float f = (0.1 + 0.7)*10;

    mov DWORD PTR _f$[ebp], 1090519040  ; 41000000H

; 7    :    int a = f;

    fld DWORD PTR _f$[ebp]
    call    __ftol2
    mov DWORD PTR _a$[ebp], eax

С другой стороны, скомпилировав тот же код под Release, получим, что оба выражения оптимизатор скомпоновал сразу на этапе компиляции, однако так же получим в первом случае 7, во втором - 8. То есть:
Код:
; 5    :    int n = (0.1 + 0.7)*10;
; 6    :    float f = (0.1 + 0.7)*10;
; 7    :    int a = f;
; 8    :
; 9    :    std::cout << n << std::endl << a << std::endl;

    push    7
    push    OFFSET FLAT:?cout@std@@3V?$basic_ostream@...
    call    ??6?$basic_ostream@DU?$char_traits@D@std...
    push    8
    push    eax
    call    ?endl@std@@YAAAV?$basic_ostream@DU?$... ; std::endl
Как-то странно получается... Скорее всего, для первого выражения конверсия происходит с помощью CPU, а для второго - FPU, и поэтому происходят несовпадения. Вот только второй из приведённых мной примеров ставит меня в тупик... Почему тогда во втором выражении компилятор не допустил ту же ошибку, что и в первом?


может это прозвучит смешно но это потому что компилятор тоже написан на C++. :))))). но это только одно из правильных объяснений.

3
14 марта 2006 года
Green
4.8K / / 20.01.2000
Цитата:
Originally posted by HarryAxe
Как-то странно получается... Скорее всего, для первого выражения конверсия происходит с помощью CPU, а для второго - FPU, и поэтому происходят несовпадения. Вот только второй из приведённых мной примеров ставит меня в тупик... Почему тогда во втором выражении компилятор не допустил ту же ошибку, что и в первом?


А ты попробуй так:
int n = (0.1f + 0.7f)*10;
или вот так:
int n = float(0.1 + 0.7)*10;

534
14 марта 2006 года
HarryAxe
448 / / 19.01.2006
Цитата:
Originally posted by Flex25
Может быть есть какие-то альтернативные типы данных, у которых нет проблем с погрешностями?

А у float таких проблем и нет. Точность одинаковая что в Perl, что в C#, что в VB. Это от процессора зависит. А насчёт 100% точности в других языках ты не прав: в Delphi ты вообще такой код не напишешь, там конверсию из real в integer можно сделать только через библиотечные функции, а perl - язык вообще интерпетируемый, а значит выражение там будет вычисляться в поэтапно в runtime. В нашем же случае ошибка связана с конверсией на этапе компиляции, а именно при вычислении выражения челочисленным процессором. Если бы в Dephi можно было бы написать n := Integer((0.1 + 0.7) * 10), то, поверь мне, возникла бы точ такая же ошибка, потому что в таком случае конверсия в целое происходила бы на уровне CPU. Просто нужно явно указать компилятору, что в данном случае нужно провести конверсию препроцессором, как более специализированным логическим устройством, и всё будет OK. Поэтому см. пример от PitxBull

534
14 марта 2006 года
HarryAxe
448 / / 19.01.2006
Цитата:
Originally posted by Green
А ты попробуй так:
int n = (0.1f + 0.7f)*10;
или вот так:
int n = float(0.1 + 0.7)*10;

Умные все стали! Вам не угодишь... :) Я по всякому уже крутил:

 
Код:
int n = (.1f + .7f)*10.f;
int n = (float) ((0.1 + 0.7) *10.)
И оба твоих варианта пробовал. Один фиг - 7. И в double я уже конвертил. Компайлер просто сворачивает конверсии в одну, и всё тут...
6.1K
14 марта 2006 года
Flex25
50 / / 14.03.2006
Ребята, спасибо за пищу для размышлений и критику. Я немного разобралсмя. В итоге имеем следующее: в C++ есть проблема c точностью чисел с плавающей запятой.

Лечится это (частично) так....

Не верный вариант:
 
Код:
int n=(0.1+0.7)*10;

А вот верный вариант:
 
Код:
int n=(float) (0.1+0.7)*10;


------------------

Не верный вариант:
 
Код:
if ((0.1+0.7)*10 == 8) {} else {}

А вот верный вариант:
 
Код:
if ( (float) (0.1+0.7)*10 == 8) {} else {}


Т.е. принудительно надо привести выражение в нужный тип (в данном случае float), а уж потом отбросить дробную часть. Тип (float или double) выбираем в зависимости от нужной степени точность - у float она больше.

Если нужна супер-точность, лучше выбирать long float.
6.1K
15 марта 2006 года
Flex25
50 / / 14.03.2006
Хотя действительно еще кое-что не понятно...



В выражении ниже в переменную n присваивается число 8:
 
Код:
int n=(float) (0.1+0.7)*10;



А вот в этом выражении в переменную n присваивается уже число 7!!!
 
Код:
int n=(double) (0.1+0.7)*10;



Прошу это объяснить, пожалуйста, ведь я посмотрел: тип double предоставляет большую точность, чем тип float.
2.4K
15 марта 2006 года
dinasok51
219 / / 12.11.2005
Цитата:
Originally posted by Flex25
Хотя действительно еще кое-что не понятно...



В выражении ниже в переменную n присваивается число 8:
 
Код:
int n=(float) (0.1+0.7)*10;



А вот в этом выражении в переменную n присваивается уже число 7!!!
 
Код:
int n=(double) (0.1+0.7)*10;



Прошу это объяснить, пожалуйста, ведь я посмотрел: тип double предоставляет большую точность, чем тип float.



(float) (0.1+0.7) равно 0.8
(double) (0.1+0.7) равно 0.79999999999999993

умножается на 10 , отбрасывается дробная часть и получаешь то что получаешь.

А вот цитата из MSDN c правилами преобразования.
**********************
Conversions from Floating-Point Types
A float value converted to a double or long double, or a double converted to a long double, undergoes no change in value. A double value converted to a float value is represented exactly, if possible. Precision may be lost if the value cannot be represented exactly. If the result is out of range, the behavior is undefined. See Limits on Floating-Point Constants in Chapter 1 for the range of floating-point types.

A floating value is converted to an integral value by first converting to a long, then from the long value to the specific integral value, as described below in Table 4.4. The decimal portion of the floating value is discarded in the conversion to a long. If the result is still too large to fit into a long, the result of the conversion is undefined.

Microsoft Specific —>

When converting a double or long double floating-point number to a smaller floating-point number, the value of the floating-point variable is truncated toward zero when an underflow occurs. An overflow causes a run-time error. Note that the Microsoft C compiler maps long double to type double.

END Microsoft
/////////////////////////////////////

Так это работает
Нравится или не нравится, но учитывать придется

1.8K
15 марта 2006 года
_const_
229 / / 26.11.2003
Цитата:
Originally posted by Flex25
Ребята, спасибо за пищу для размышлений и критику. Я немного разобралсмя. В итоге имеем следующее: в C++ есть проблема c точностью чисел с плавающей запятой.

Лечится это (частично) так....

Не верный вариант:
 
Код:
int n=(0.1+0.7)*10;

А вот верный вариант:
 
Код:
int n=(float) (0.1+0.7)*10;


------------------

Не верный вариант:
 
Код:
if ((0.1+0.7)*10 == 8) {} else {}

А вот верный вариант:
 
Код:
if ( (float) (0.1+0.7)*10 == 8) {} else {}


Т.е. принудительно надо привести выражение в нужный тип (в данном случае float), а уж потом отбросить дробную часть. Тип (float или double) выбираем в зависимости от нужной степени точность - у float она больше.

Если нужна супер-точность, лучше выбирать long float.


Еще раз для особо понятливых. Дело НЕ в C++ - так работает арифметика с плавающей точкой в FPU. Попробуйте изменить в Дельфи строку

 
Код:
var a: double;

a := (0.1 + 0.7) * 10.1;
if a=8.08 then
 writeln("Не может быть!");


Только что проверено в Дельфи 5. Не сработает!
Просто "умные" дельфийские ф-ции при определенной точности сами округляют число.

Кстати, верный вариант совсем другой.
1. Отбрасывание дробной части
 
Код:
int i = static_cast<int>(someDouble);

2. Округление
 
Код:
int i = (someDouble < 0.0) ?
        static_cast<int>(someDouble - 0.5) :
        static_cast<int>(someDouble + 0.5);


PS В предыдущем своем посте я ошибся. Есть одно число, представленное с абсолютной точностью - это ноль (0.0).
3
15 марта 2006 года
Green
4.8K / / 20.01.2000
Цитата:
Originally posted by HarryAxe
Умные все стали! Вам не угодишь... :) Я по всякому уже крутил:
 
Код:
int n = (.1f + .7f)*10.f;
int n = (float) ((0.1 + 0.7) *10.)
И оба твоих варианта пробовал. Один фиг - 7. И в double я уже конвертил. Компайлер просто сворачивает конверсии в одну, и всё тут...


Да я и был умный. :)

Опрерации с плавающей точкой описаны стандартом ANSI/IEEE 754-1985 (там несколько последующих стандартов, но этот базовый).
На сколько помню, стандарт говорит о том, что для float после каждой операции в 80-битном регистре должно производится приведение опять к float. Это требует доп. расходов времени, поэтому по умолчанию в компиляторах VC++ это предписание стандарта отключено. Включить его можно зайдя в свойства проекта C/C++ -> Optimization -> Floating-Point Consistency заменив значение Default на Improve.

3.5K
15 марта 2006 года
Rubins
30 / / 16.06.2003
Цитата:
Originally posted by Green
Да я и был умный. :)

Опрерации с плавающей точкой описаны стандартом ANSI/IEEE 754-1985 (там несколько последующих стандартов, но этот базовый).
На сколько помню, стандарт говорит о том, что для float после каждой операции в 80-битном регистре должно производится приведение опять к float. Это требует доп. расходов времени, поэтому по умолчанию в компиляторах VC++ это предписание стандарта отключено. Включить его можно зайдя в свойства проекта C/C++ -> Optimization -> Floating-Point Consistency заменив значение Default на Improve.


Вместо прибавления 0.5 умножай округляемое число на
1+16*DBL_EPSILON - и программа будет отбрасывать
дробную часть, и (0.7+0.1)*8 будет давать 8.

534
15 марта 2006 года
HarryAxe
448 / / 19.01.2006
Цитата:
Originally posted by Green
Да я и был умный. :)

Опрерации с плавающей точкой описаны стандартом ANSI/IEEE 754-1985 (там несколько последующих стандартов, но этот базовый).
На сколько помню, стандарт говорит о том, что для float после каждой операции в 80-битном регистре должно производится приведение опять к float. Это требует доп. расходов времени, поэтому по умолчанию в компиляторах VC++ это предписание стандарта отключено. Включить его можно зайдя в свойства проекта C/C++ -> Optimization -> Floating-Point Consistency заменив значение Default на Improve.

Ну, чё-то мы уже в дебри полезли. А если я захочу проект под более старым компилятором собрать? А если проект ещё и чужой, и я понятия не имею о включенной Improve Concistency? Что тогда? Вообще, на мой взгяд, в тех случаях, когда речь идёт о точности, целиком полагаться на настройки компилятора просто глупо

6.1K
15 марта 2006 года
Flex25
50 / / 14.03.2006
Цитата:
Originally posted by dinasok51
(float) (0.1+0.7) равно 0.8
(double) (0.1+0.7) равно 0.79999999999999993



Прошу объяснить, почему в этом примере при приведении к типу float в результате выражения получается 0.8, а при приведени к double получается 0.79999(9)?

Я все прочитал в этой теме, но не мегу получить ответ на этот вопрос.

3.5K
15 марта 2006 года
Rubins
30 / / 16.06.2003
Цитата:
Originally posted by Flex25
Прошу объяснить, почему в этом примере при приведении к типу float в результате выражения получается 0.8, а при приведени к double получается 0.79999(9)?

Я все прочитал в этой теме, но не мегу получить ответ на этот вопрос.


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

((0.7+0.1)*8)(1.+16.*DBL_EPSILON)
(можно сделать отдельную функцию, чтобы
на это не смотреть постоянно)
будет точно 8. (но опять-таки может отклоняться
только в большую сторону до 0.000000000001).

1.8K
15 марта 2006 года
_const_
229 / / 26.11.2003
Цитата:
Originally posted by Flex25
Прошу объяснить, почему в этом примере при приведении к типу float в результате выражения получается 0.8, а при приведени к double получается 0.79999(9)?

Я все прочитал в этой теме, но не мегу получить ответ на этот вопрос.



Да с чего вы взяли, что будет 0.8?
Простенький эксперимент:

 
Код:
float a = (float)(0.1 + 0.7);
printf("a=%.10f\n", a);
double b = 0.1 + 0.7;
printf("b=%.10f\n", b);

В результате:
a=0.8000000119
b=0.8000000000

При этом отмечу, что на самом деле b != 0.8, это иллюзия :).
Другой пример:
 
Код:
float a = (float)(0.1 + 0.7);
ASSERT(a == (float)0.8 );
ASSERT(a == 0.8 );

Вопрос - какой ASSERT не пропустит?
Ответ - второй, т.к. там а приводится к double, т.е. типу с повышенной точностью. Почему пропустил первый? Точность ниже - при преобразовании к float компилятор обрубил все лишнее. Почему не срабатывает double == double? Обрубать нечего - все честно.
1.8K
15 марта 2006 года
_const_
229 / / 26.11.2003
Еще про преобразование к float. В конструкции вида
 
Код:
float a = (float)0.7;
if (a == (float)0.7)

сравнение вообще будет произведено в CPU (float - то 32 разрядный), поэтому условие сработает. Если же жесткого приведения к float не будет, то сравниваться будет в FPU, где все будет приведено к 80 разрядам (а double - 64 разрядный). Вот и неточность.
351
15 марта 2006 года
PitxBull
633 / / 22.12.2004
кх-кх.

следующий код ( приведенный мной ) работает при любых настройках компилятора:
 
Код:
float a = ( 0.7 + 0.1 ) * 10;
int b = a;

assert( b == 8 );


следующий код ( приведенный Green-ом ) работает только если включена соответсвующая опция компилятора ( Improve Consistency /Op ).
 
Код:
int a = ( 0.7f + 0.1f ) * 10;


этот код не работает правильно никогда :
 
Код:
double a = ( 0.7f + 0.1f ); // a = 0.79999999999999993; a = 0.8000000119 if /Op


вот процедура реализующая в моем компиляторе ( VC++ 7 ) приведение к int.
Код:
_ftol2:
004300AC  push        ebp  
004300AD  mov         ebp,esp
004300AF  sub         esp,20h
004300B2  and         esp,0FFFFFFF0h
004300B5  fld         st(0)
004300B7  fst         dword ptr [esp+18h]
004300BB  fistp       qword ptr [esp+10h]
004300BF  fild        qword ptr [esp+10h]
004300C3  mov         edx,dword ptr [esp+18h]
004300C7  mov         eax,dword ptr [esp+10h]
004300CB  test        eax,eax
004300CD  je          integer_QnaN_or_zero (43010Bh)
arg_is_not_integer_QnaN:
004300CF  fsubp       st(1),st     // отнимаем от исходного числа то же число но без дробной части.(1)
004300D1  test        edx,edx
004300D3  jns         positive (4300F3h)
004300D5  fstp        dword ptr [esp]
004300D8  mov         ecx,dword ptr [esp]
004300DB  xor         ecx,80000000h
004300E1  add         ecx,7FFFFFFFh
004300E7  adc         eax,0
004300EA  mov         edx,dword ptr [esp+14h]
004300EE  adc         edx,0
004300F1  jmp         localexit (43011Fh)
positive:
004300F3  fstp        dword ptr [esp]
004300F6  mov         ecx,dword ptr [esp]  // в этих строках по всей видимости остаток от операции (1) проверяется
004300F9  add         ecx,7FFFFFFFh        // на то больше он чем 0.5 или нет
004300FF  sbb         eax,0                // именно после этой комманды 8 превращается в 7 ( регистр EAX).
00430102  mov         edx,dword ptr [esp+14h]
00430106  sbb         edx,0
00430109  jmp         localexit (43011Fh)
integer_QnaN_or_zero:
0043010B  mov         edx,dword ptr [esp+14h]
0043010F  test        edx,7FFFFFFFh
00430115  jne         arg_is_not_integer_QnaN (4300CFh)
00430117  fstp        dword ptr [esp+18h]
0043011B  fstp        dword ptr [esp+18h]
localexit:
0043011F  leave            
00430120  ret
6.1K
16 марта 2006 года
Flex25
50 / / 14.03.2006
Цитата:
Originally posted by Flex25
Прошу объяснить, почему в этом примере при приведении к типу float в результате выражения получается 0.8, а при приведени к double получается 0.79999(9)?


Вчера покапался в книгах и нашел ответ на данный вопрос...

В книге написано что по стандартам C/C++ разрешается приведение типа float к int. При этом точность не теряется, за исключением отбрасывания дробной части.

Но приведение типа double к int не разрешается, т.к. происходит потеря точность, что мы и наблюдаем в данной теме.

Вывод таков:
1. Можно проиводить переменные типа float к int без потери точности
2. Переменные типа double надо сначало привести к типу float, а уже потом приводить к типу int.

Т.е. вот так НЕ правильно (происходит потеря точности):
double d=(0.7+0.1)*10;
int a=d;

А вот так правильно (потери точности нет):
double d=(0.7+0.1)*10;
int a=(float) d;

Вот в чем заключается вся эта проблема. И то, что данный топик так сильно раздулся из-за обсуждений, означает, что большинство пограммистов C++ (в том числе и я) имеют не достаточно знаний в этой области. Надеюсь, теперь мы не будем совершать такие ошибки.

1.8K
16 марта 2006 года
_const_
229 / / 26.11.2003
Цитата:
Originally posted by Flex25
Вчера покапался в книгах и нашел ответ на данный вопрос...

В книге написано что по стандартам C/C++ разрешается приведение типа float к int. При этом точность не теряется, за исключением отбрасывания дробной части.

Но приведение типа double к int не разрешается, т.к. происходит потеря точность, что мы и наблюдаем в данной теме.


Выбросьте эту книгу. Подумайте сами, как может не быть потери точности при преобразовании 0.555 (не важно float или double) к int?
Вообще нет преобразования float к int. При преобразовании неявно вызывается ф-ция __ftol2, которая в качестве параметра принимает double, а возвращает long, отбрасывая дробную часть, а не округляя по правилам математики. Так вот, при преобразовании float оно сначала конвертируется в double, затем передается в __ftol2.

Цитата:
Вывод таков:
1. Можно проиводить переменные типа float к int без потери точности


Приведите 0.5 к int без потери точности.

Цитата:
2. Переменные типа double надо сначало привести к типу float, а уже потом приводить к типу int.


Ага, а затем компилятор все сделает по-своему.

Цитата:
Т.е. вот так НЕ правильно (происходит потеря точности):
double d=(0.7+0.1)*10;
int a=d;

А вот так правильно (потери точности нет):
double d=(0.7+0.1)*10;
int a=(float) d;


Про потерю точности см. выше.

Цитата:
Вот в чем заключается вся эта проблема. И то, что данный топик так сильно раздулся из-за обсуждений, означает, что большинство пограммистов C++ (в том числе и я) имеют не достаточно знаний в этой области. Надеюсь, теперь мы не будем совершать такие ошибки.


Топик разросся из-за того, что вы не хотите слышать того, о чем вам говорят.
Еще раз, почему в нижеследующем случае вы получаете свою 8

 
Код:
double d=(0.7+0.1)*10;
int a=(float) d;

double d - немного меньше, чем 8.0. Таково представление числа в формате double, ближайшее к 8 число, которое можно представить в этом формате меньше 8.
При преобразовании к float получается число, немного большее 8, опять же потому, что по счстливой случайности ближайшее к целому число, которое можно представить в формате float отклонено в "плюс".
Далее происходит преобразование к int. Вызывается ф-ция __ftol2. Ее аргумент - double, т.е. происходит преобразование от float к double. Но у double точность выше, поэтому число остается больше 8. Отбрасываем дробную чать - получаем (int)8.
Теперь уж совсем на пальцах (для примера - псевдокод):
 
Код:
double d = 8.0;  //d=7.9999999999999991
float temp=(float)d;  //temp=8.0000001
double arg=temp;  //arg=8.000000100001
int i = __flol2(arg);  //i=8 - отбросили дробную часть

И еще о потере точности. Неужели так сложно провести эксперимент?
 
Код:
float f = 1.0f;
double d = 1.0;
int i;

i = f;
i = d;

В ответ на оба преобразования вам даже компилятор скажет
warning C4244: 'initializing' : conversion from 'float' to 'int', possible loss of data
warning C4244: 'initializing' : conversion from 'double' to 'int', possible loss of data
6.1K
16 марта 2006 года
Flex25
50 / / 14.03.2006
Когда я говорил про то, что приведение float к int идет без потери точности, я имел ввиду, что при этом из числа float ТОЧНО выделяется его целая часть. Об округлениях числа я вообще ничего не говорил - это мне и не нужно.

Я ставил перед собой цель из числа с плавающей точкой выделить его целую часть БЕЗ потери точности.

Как мы видим, float -> int выделяет целую часть нормально, а вот при конверсии douиle -> int целая часть числа бывает меняется, что недопустимо для любой серьезной программы.
2.4K
16 марта 2006 года
dinasok51
219 / / 12.11.2005
Цитата:
Originally posted by Flex25
Как мы видим, float -> int выделяет целую часть нормально, а вот при конверсии douиle -> int целая часть числа бывает меняется, что недопустимо для любой серьезной программы.


Оба преобразования выполняются абсолютно одинаково: ОТБРАСЫВАНИЕМ ДРОБНОЙ ЧАСТИ

У тебя были разные значения float и double, что естественно, и поэтому получил разные значения int
Что тоже естественно. Работает все так, как написано в MSDN. А искусственные преобразования
double во float и т.п. есть подгонка результата под желаемый, "что недопустимо для любой серьезной программы".

6.1K
16 марта 2006 года
Flex25
50 / / 14.03.2006
Ладно, давайте не будем сориться. Я ОЧЕНЬ благодарен всем, кто откликнулся на мое сообщение. Спасибо!

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

Но разве это нормально, если ваша программа при вычислении целой части от (0.7+0.1)*10 выведет пользователю в результате 7, а не 8?! Ведь любой школьник засмеет такую "правильную программу"?!

Я пытался найти решение этой дурацкой проблемы и нашел его в книге Герберта Шилдта "Полный справочник по C++" на стр. 58 (если кто не верит). Там черным по белому написано, что преобразование float -> int идет нормально, а вот double -> int часто приводит к потере точности в целой части (как и произошло в примере выше). Так что надо делать приведение double так: double -> float -> int. И тогда проблем таких не будет.

Если вы думаете, что автор заблуждается, то приведите хоть один пример, когда бы преобразование float -> int или double -> float -> int привело бы к неправельному выделению целой части из вещественного числа.
1.8K
16 марта 2006 года
_const_
229 / / 26.11.2003
Цитата:
Originally posted by Flex25
Как мы видим, float -> int выделяет целую часть нормально, а вот при конверсии douиle -> int целая часть числа бывает меняется, что недопустимо для любой серьезной программы.


Это называется "коэффициент подгона". Который имеет свойство в самый неподходящий момент сработать не так как надо.
А те, кто использует double, видимо пишут несерьезные программы.

534
16 марта 2006 года
HarryAxe
448 / / 19.01.2006
Цитата:
Originally posted by Flex25
Ладно, давайте не будем сориться. Я ОЧЕНЬ благодарен всем, кто откликнулся на мое сообщение. Спасибо!

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

Как бы там ни было, молодец, подметил. Респект. Я бы, наверное, тоже на этой ерунде когда-нибудь подзавис. Теперь буду знать...

1.8K
16 марта 2006 года
_const_
229 / / 26.11.2003
Цитата:
Originally posted by Flex25
Ладно, давайте не будем сориться. Я ОЧЕНЬ благодарен всем, кто откликнулся на мое сообщение. Спасибо!

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

Но разве это нормально, если ваша программа при вычислении целой части от (0.7+0.1)*10 выведет пользователю в результате 7, а не 8?! Ведь любой школьник засмеет такую "правильную программу"?!


Да никто и не ссорится.
Чтобы программа правильно выводила рез-т, надо принимать соотв. меры. А именно, либо округлять по правилам математики (EPS = 0.5), либо учитывать точность представления. Например, если интересует точность до целых, используйте, например, EPS = 0.01. Тогда

 
Код:
i = (d < 0.0) ? static_cast<int>(d - 2.0 * EPS):
        static_cast<int>(d + 2.0 * EPS);
324
17 марта 2006 года
AndreySar
532 / / 01.08.2004
Цитата:
Originally posted by _const_
Да никто и не ссорится.
Чтобы программа правильно выводила рез-т, надо принимать соотв. меры. А именно, либо округлять по правилам математики (EPS = 0.5), либо учитывать точность представления. Например, если интересует точность до целых, используйте, например, EPS = 0.01. Тогда
 
Код:
i = (d < 0.0) ? static_cast<int>(d - 2.0 * EPS):
        static_cast<int>(d + 2.0 * EPS);



Это правильно, при работе с не целыми числами, надо устанавливать определенную точность вычисления, необхадимую для получения достаточно точного результата.

284
22 марта 2006 года
michael_is_98
587 / / 25.02.2005
Стандарт floating-point
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог