Могу ли я ожидать, что значения переменных с плавающей запятой, которые я установил из констант литерала, останутся неизменными после присвоения другим переменным? - PullRequest
4 голосов
/ 21 сентября 2009

Если я сделаю что-то вроде этого:

float a = 1.5f;
float b = a;

void func(float arg)
{
  if (arg == 1.5f) printf("You are teh awresome!");
}

func(b);

Будет ли текст печататься каждый раз (и на каждой машине)?

EDIT

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

Ответы [ 7 ]

4 голосов
/ 21 сентября 2009

Прежде всего, 1,5 может быть точно сохранено в памяти, так что для этого конкретного значения, да, это всегда будет верно.

В более общем смысле, я думаю, что неточности появляются только тогда, когда вы выполняете вычисления, есливы просто сохраняете значение, даже если оно не имеет точного представления IEEE , оно всегда будет отображаться на одно и то же значение (то есть 0.3 == 0.3, даже если 0.3 * 3 != 0.9).

3 голосов
/ 21 сентября 2009

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

Если изменить значение на единицу без точного двоичного представления , такого как 5.1F, результат будет далек от гарантированного.

Путь, путь, путь назад в своей превосходной классической книге " Элементы стиля программирования ", Kernighan & Plauger сказал:

Один мудрый программист однажды сказал: «Числа с плавающей точкой похожи на груды песка; каждый раз, когда вы перемещаете одну, вы теряете немного песка и собираете немного грязи». И после нескольких вычислений все может стать довольно грязным.

(Это одна из двух фраз в книге, которую я выделил много лет назад 1 .)

Они также наблюдают:

  • 10,0 х 0,1 вряд ли когда-либо 1,0.
  • Не сравнивайте числа с плавающей запятой только для равенства

Эти наблюдения были сделаны в 1978 году (для второго издания), но до сих пор остаются в силе сегодня.

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


1 Другая выделенная фраза (минус маркеры):

  • вызов подпрограммы позволяет нам суммировать нарушения в списке аргументов [...]
  • [t] Сама подпрограмма суммирует закономерности кода [...]
2 голосов
/ 21 сентября 2009

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

1 голос
/ 22 сентября 2009

Я посмотрел разборку компилятора VS2005. При запуске этой простой программы я обнаружил, что после float f=.1; условие f==.1 приводит к .... FALSE.

РЕДАКТИРОВАТЬ - это произошло из-за того, что компанд был double. При использовании литерала float (т. Е. .1f) сравнение получило значение ИСТИНА. Это также верно при сравнении double переменных с double литералами.

Я добавил исходный код и разборку здесь:

  float f=.1f;
0041363E  fld         dword ptr [__real@3dcccccd (415764h)] 
00413644  fstp        dword ptr [f] 

  double d=.1;
00413647  fld         qword ptr [__real@3fb999999999999a (415748h)] 
0041364D  fstp        qword ptr [d] 

  bool ffequal = f == .1f;
00413650  fld         dword ptr [f] 
00413653  fcomp       qword ptr [__real@3fb99999a0000000 (415758h)] 
00413659  fnstsw      ax   
0041365B  test        ah,44h 
0041365E  jp          main+4Ch (41366Ch) 
00413660  mov         dword ptr [ebp-0F8h],1 
0041366A  jmp         main+56h (413676h) 
0041366C  mov         dword ptr [ebp-0F8h],0 
00413676  mov         al,byte ptr [ebp-0F8h] 
0041367C  mov         byte ptr [fequal],al 
// here, ffequal is true

  bool dfequal = d == .1f;
0041367F  fld         qword ptr [__real@3fb99999a0000000 (415758h)] 
00413685  fcomp       qword ptr [d] 
00413688  fnstsw      ax   
0041368A  test        ah,44h 
0041368D  jp          main+7Bh (41369Bh) 
0041368F  mov         dword ptr [ebp-0F8h],1 
00413699  jmp         main+85h (4136A5h) 
0041369B  mov         dword ptr [ebp-0F8h],0 
004136A5  mov         al,byte ptr [ebp-0F8h] 
004136AB  mov         byte ptr [dequal],al 
// here, dfequal is false.

  bool ddequal = d == .1;
004136AE  fld         qword ptr [__real@3fb999999999999a (415748h)] 
004136B4  fcomp       qword ptr [d] 
004136B7  fnstsw      ax   
004136B9  test        ah,44h 
004136BC  jp          main+0AAh (4136CAh) 
004136BE  mov         dword ptr [ebp-104h],1 
004136C8  jmp         main+0B4h (4136D4h) 
004136CA  mov         dword ptr [ebp-104h],0 
004136D4  mov         al,byte ptr [ebp-104h] 
004136DA  mov         byte ptr [ddequal],al 
// ddequal is true
1 голос
/ 21 сентября 2009

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

Python-код для создания файла:

print """
#include <stdio.h>

int main()
{
"""
import random
chars = "abcdefghijklmnopqrstuvwxyz"
randoms = [str(random.random()) for _ in xrange(26)]
for c, r in zip(chars, randoms):
    print "float %s = %sf;" % (c, r)

for c, r in zip(chars, randoms):
    print r'if (%s != %sf) { printf("Error!\n"); }' % (c,r)

print """
    return 0;
}
"""

Фрагмент сгенерированного файла:

#include <stdio.h>

int main()
{

float a = 0.199698325654f;
float b = 0.402517512357f;
float c = 0.700489844438f;
float d = 0.699640984356f;
if (a != 0.199698325654f) { printf("Error!\n"); }
if (b != 0.402517512357f) { printf("Error!\n"); }
if (c != 0.700489844438f) { printf("Error!\n"); }
if (d != 0.699640984356f) { printf("Error!\n"); }

    return 0;
}

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

$ ./a.out 
$

Но здесь есть одна загвоздка: если вы не поставите литерал f после числа с плавающей запятой в проверке на равенство, он каждый раз будет терпеть неудачу! Вы можете оставить литерал f вне назначения, но без проблем.

0 голосов
/ 21 сентября 2009

Не думаю, что он будет вести себя одинаково:

float f1 = .1; 
// compiled as
//// 64-bits literal into EAX:EBX


if( f1 == .1 ) 
//// load .1 into 80 bits of FPU register 1
//// copy EAX:EBX into FPU register 2 (leaving alone the last 16 bits)
//// call FPU compare instruction
////

Когда компилятор генерирует код, как упомянуто выше, условие никогда не будет истинным: 16 бит будут отличаться при сравнении 64-битной версии .1 с ее 80-битной версией.

Заключение: нет: вы не можете гарантировать равенство на каждой машине с этим кодом.

0 голосов
/ 21 сентября 2009

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

Одно предостережение: определенные «магические» числа не обязательно берутся из памяти. В x86 у нас есть следующие инструкции:

  • FLDZ - загрузить 0,0 в ST (0)
  • FLD1 - загрузить 1,0 в ST (0)
  • FLD2T - загрузить log2 (10) в ST (0)
  • FLD2E - загрузить log2 (e) в ST (0)
  • FLDPI - загрузить пи в ST (0)
  • FLDLG2 - загрузить log10 (2) в ST (0)
  • FLDLN2 - загрузить ln (2) в ST (0)

Так что, если вы сравниваете одно из этих значений, вы, возможно, не получите точно такое же значение, как литерал const, на который вы ссылались.

...