Възможно ли е число, точно представено като float, да НЕ може точно да бъде представено като double?

Имам въпрос, който възникна от друг въпрос относно точността на плаващите числа.

Сега знам, че плаващите точки не винаги могат да бъдат представени точно и следователно те се съхраняват като най-близкото възможно плаващо число, което може да бъде представено.

Въпросът ми всъщност е за разликата в представянето на float и double.

Откъде възниква този въпрос?

Да предположим, че правя:

System.out.println(.475d+.075d);

тогава изходът няма да бъде 0.55, а 0.549999 (на моята машина)

Въпреки това, когато го направя:

System.out.println(.475f+.075f);

Получавам правилния отговор, т.е. 0.55 (малко неочаквано за мен)

Досега бях с впечатлението, че double има по-голяма точност (double ще бъде по-точен до по-голям брой десетични знаци), отколкото float. Така че, ако едно двойно не може да бъде представено точно, тогава неговото еквивалентно float представяне също ще бъде съхранено неточно.

Въпреки това резултатите, които получих, са малко смущаващи за мен. Объркан съм, ако:

  1. Не разбирам правилно какво означава precision?
  2. float и double са представени по различен начин, освен факта, че double има повече битове?

person Ankit    schedule 08.06.2012    source източник


Отговори (3)


Прецизността просто означава повече битове. Число, което не може да бъде представено като float може да има точно представяне като double, но броят на тези случаи е безкрайно малък спрямо общия брой възможни случаи.

За прости случаи като 0.1, това не може да се представи като число с плаваща запетая с фиксирана дължина, без значение какъв е броят на наличните битове. Това е същото като да кажете, че дроб като 1/7 не може да бъде представен точно в десетична система, независимо от броя на цифрите, които имате право да използвате (стига броят на цифрите да е краен). Можете да го оцените приблизително като 0,142857142857142857... повтаряйки отново и отново, но никога няма да можете да го напишете ТОЧНО, независимо колко дълго продължавате.

Обратно, ако дадено число може да бъде представено точно като float, то също ще бъде представимо точно като double. Двойният има по-голям обхват на степента и повече битове на мантиса.

За вашия пример причината за очевидното несъответствие е, че в float разликата между 0,475 и неговото плаващо представяне беше в „правилната“ посока, така че когато настъпи съкращаване, всичко вървеше според очакванията ви. При увеличаване на наличната точност, представянето беше „по-близо“ до 0,475, но сега от противоположната страна. Като груб пример, нека кажем, че най-близката възможна плаваща стойност е 0,475006, но в двойната най-близката възможна стойност е 0,474999. Това ще ви даде резултатите, които виждате.

Редактиране: Ето резултатите от бърз експеримент:

public class Test {

    public static void main(String[] args)
    {
        float  f = 0.475f;
        double d = 0.475d;

        System.out.printf("%20.16f", f);
        System.out.printf("%20.16f", d);
    }
}

Изход:

  0.4749999940395355  0.4750000000000000

Това означава, че представянето с плаваща запетая на числото 0,475, ако имате огромен брой битове, ще бъде съвсем малко по-малко от 0,475. Това се вижда в двойното представяне. Въпреки това, първият "грешен" бит се появява толкова далеч вдясно, че когато се съкрати, за да се побере в float, просто се получава 0,475. Това е чисто инцидент.

person Jim Garrison    schedule 08.06.2012
comment
попитах точно обратното. Може ли число, представено точно като float, да не бъде правилно представено като double - person Ankit; 09.06.2012
comment
Всъщност този отговор също е правилен. Разменете двойно за плаващо. Както и да е, става дума за това кои числа могат да бъдат представени като двоични и кои не. Така че, да, число, точно като float, може да загуби точността си като double. - person Jim Barrows; 09.06.2012
comment
@JimGarrison Мислех си за същото if a number is representable exactly as a float, it will also be representable exactly as a double. Но както можете да видите от въпроса, това не е така. float е представен, но не и двойният - person Ankit; 09.06.2012
comment
Освен ако числото, представено от float, не е точно в центъра на диапазона от числа, които float може да представлява, преобразуването в double често ще доведе до стойност, която е просто грешна спрямо подразбиращата се точност. Обратно, преобразувания от double към float почти винаги ще дадат резултати, които са точни до подразбиращата се прецизност, и дори когато не са съвсем точни, те ще бъдат отклонени с малка част от допълнителната част на милион. - person supercat; 09.06.2012

Число, което може да бъде представено като float, може да бъде представено и като double.

Това, което четете, е просто форматиран изход, вие не четете действително двоично представяне.

System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(.475d + .075d)));
// 11111111100001100110011001100110011001100110011001100110011001
System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(.475f + .075f)));
// 111111000011001100110011001101

double d = .475d + .075d;
System.out.println(d);
// 0.5499999999999999
System.out.println((float)d);
// 0.55 (as expected)
System.out.println((double)(float)d);
// 0.550000011920929

System.out.println( .475f + .075f == 0.550000011920929d);
// true
person Piotr Findeisen    schedule 08.06.2012

Ако човек се счита, че типовете плаваща точка всъщност представляват диапазони от стойности, а не дискретни стойности (напр. 0.1f не представлява 1342173/134217728, а по-скоро „нещо между 13421772.5/134217728 и 13421773.5/134217728“) обикновено са точни, докато преобразуванията от float към double обикновено не. За съжаление, Java позволява обикновено неточните преобразувания да се извършват имплицитно, като същевременно се изисква преобразуване на типа в обикновено точната посока.

За всяка стойност от тип float съществува стойност от тип double, чийто диапазон е центриран около центъра на диапазона на float. Това не означава, че double е точно представяне на стойността в float. Например преобразуването на 0.1f в double дава стойност, означаваща "нещо между 13421772.9999999/134217728 и 13421773.0000001/134217728", стойност, която е отклонена от над един милион пъти внушения толеранс.

За почти всяка стойност от тип double съществува стойност от тип float, чийто диапазон напълно включва диапазона, подразбиращ се от double. Единствените изключения са стойности, чийто диапазон е центриран точно на границата между две float стойности. Преобразуването на такива стойности в float ще изисква системата да избере един или друг диапазон; ако системата закръгля нагоре, когато double действително представлява число под центъра на диапазона си, или обратното, диапазонът на float няма да обхваща напълно този на double. От практическа гледна точка обаче това не е проблем, тъй като означава, че вместо float преобразуване от double, представляващо диапазон като (13421772.5/134217728 до 13421773.5/134217728), то ще представлява диапазон като (13421772.4999999/134217728 до 13 421773.5000001 /134217728). В сравнение с ужасяващата неточност, произтичаща от замятане на float до double, тази малка неточност е нищо.

Между другото, връщайки се към конкретните числа, които използвате, когато правите изчисленията си като float, изчисленията са:

0.075f = 20132660±½ / 268435456
0.475f = 31876710±½ /  67108864
Sum    = 18454938±½ /  33554432

С други думи, сумата представлява число някъде между приблизително 0,54999999701 и 0,55000002682. Най-естественото представяне е 0,55 (тъй като действителната стойност може да бъде повече или по-малко от това, допълнителните цифри биха били безсмислени).

person supercat    schedule 08.06.2012