2013-08-06 5 views
5

Это очень простой вопрос, но важный, поскольку он сильно влияет на весь мой проект.Усечение двойного поплавка в C

Предположим, у меня есть следующий код: snipet

unsigned int x = 0xffffffff; 
float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 

Я бы ожидать, что f быть что-то вроде 0.99999, но вместо этого, она округляет до 1, так как это ближе float приближение. Это не хорошо, так как мне нужны значения float на интервале [0,1], а не [0,1]. Я уверен, что это что-то простое, но я буду благодарен за помощь.

ответ

0

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

так 2.328306436538696e-010 было изменено на 2.3283063

3

значение, выше которого double патронов к 1 или больше при преобразовании в float в режиме округления по умолчанию IEEE 754 0x1.ffffffp-1 (в шестнадцатеричной системе счисления C99, так как ваш вопрос помечен «С»).

варианты:

  1. включить режим округления FPU для круглых вниз до преобразования или
  2. умножить на (0x1.ffffffp-1/0xffffffffp0) (давать или принимать один ULP), чтобы в полной мере использовать с одинарной точностью диапазон [ 0, 1) без получения значения 1.0f.

Метод 2 leads to use the constant0x1.ffffff01fffffp-33:

double factor = nextafter(0x1.ffffffp-1/0xffffffffp0, 0.0); 
unsigned int x = 0xffffffff; 
float f = (float)((double)x * factor); 
printf("factor:%a\nunrounded:%a\nresult:%a\n", factor, (double)x * factor, f); 

Печать:

factor:0x1.ffffff01fffffp-33 
unrounded:0x1.fffffefffffffp-1 
result:0x1.fffffep-1 
1

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

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

if (f >= 1.0f) 
    f = 0.99999994f; 

0,999999940395355224609375 является ближайшим значением, что IEEE-754 с плавающей точкой может принимать без равного 1,0.

+1

Это не является полезным ответом. Как показали другие ответы (и они показали, как), есть вещи, которые вы можете сделать. –

+0

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

+0

Заявление «Не так много, что вы можете сделать» вводит в заблуждение и бесполезно обескураживает. Утверждение о битах в 'int' и' float' не имеет значения; ОП не ожидает точной карты. Они не просят избегать округления, просто чтобы контролировать это. –

8

В C (с С99), вы можете изменить направление закругления с fesetround из libm

#include <stdio.h> 
#include <fenv.h> 
int main() 
{ 
    #pragma STDC FENV_ACCESS ON 
    fesetround(FE_DOWNWARD); 
    // volatile -- uncomment for GNU gcc and whoever else doesn't support FENV 
    unsigned long x = 0xffffffff; 
    float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 
    printf("%.50f\n", f); 
} 

Испытано с IBM XL, Sun Studio, лязгом, GNU GCC. Это дает мне 0.99999994039535522460937500000000000000000000000000 во всех случаях

+0

Является ли это функцией C++ 11? –

+0

Функция @MarkB C99, включенная в C++ 11 – Cubbi

+0

@EricPostpischil спасибо за указание, переписано в C – Cubbi

1

Вы можете просто усечь значение до максимальной точности (сохраняя 24 высоких бита) и разделить на 2^24, чтобы получить самое близкое значение, которое может представлять float, без округления до 1;

unsigned int i = 0xffffffff; 
float value = (float)(i>>8)/(1<<24); 

printf("%.20f\n", value); 
printf("%a\n", value); 

>>> 0.99999994039535522461 
>>> 0x1.fffffep-1 
+0

Это может быть хороший подход, если округление каждого значения до нуля (а не только около 1) подходит для OP. Взлом для иллюстрации не нужен; мы можем использовать спецификатор формата '% a' для отображения чисел с плавающей запятой способом, который иллюстрирует их состав. –

+0

@EricPostpischil Спасибо за формат '% a', не знали об этом. –

Смежные вопросы