Получение дробной части двойного значения в целом без потери точности

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

#include<stdio.h>
int main()
{
    double number;
    double fractional_part;
    int output;
    number = 1.1234;
    fractional_part = number-(int)number;
    fractional_part = fractional_part*10000.0;
    printf("%lf\n",fractional_part);
    output = (int)fractional_part;
    printf("%d\n",output);
    return 0;
}

Я ожидаю, что результат будет 1234, но он дает 1233. Пожалуйста, предложите способ, чтобы я мог получить желаемый результат. Я хочу решение на языке C.


person Atul Kumar Verma    schedule 23.06.2013    source источник
comment
Вы только что использовали слова double или float и точное значение в одном предложении. Плохой моджо. Ваш метод в порядке, но добавьте 0,5 перед преобразованием в int, потому что это, вероятно, усечение, а не округление. Еще лучше использовать round() явно. Это даст вам самое близкое значение. Точного значения не существует.   -  person Lee Daniel Crocker    schedule 23.06.2013


Ответы (4)


Предполагая, что вы хотите вернуть положительную дробь даже для отрицательных значений, я бы пошел с

(int)round(fabs(value - trunc(value)) * 1e4)

который должен дать вам ожидаемый результат 1234.

Если не округлять, а просто обрезать значение

(int)(fabs(value - trunc(value)) * 1e4)

(который по сути такой же, как ваш исходный код), вы получите неожиданный результат 1233 как 1.1234 - 1.0 = 0.12339999999999995 с двойной точностью.

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

(int)(fabs(value * 1e4 - trunc(value) * 1e4))

Если интегральная часть value достаточно велика, неточности с плавающей запятой, конечно, снова сработают.

Вы также можете использовать modf() вместо trunc(), как предлагает Дэвид, что, вероятно, является лучшим подходом с точки зрения точности с плавающей запятой:

double dummy;
(int)round(fabs(modf(value, &dummy)) * 1e4)
person Christoph    schedule 23.06.2013
comment
@DavidRF: первая версия вернет ожидаемое значение 1234; второй возвращает 1233 из-за семантики с плавающей запятой; Я обновлю ответ... - person Christoph; 23.06.2013
comment
Они не всегда дают правильный ответ. Усечение не выполняется, поскольку пример в вопросе показывает, что для 1.1234 требуется 1234, но усечение дает 1233. Для округления рассмотрим значение 0,10264999999999999912514425659537664614617824554443359375. Тогда первые четыре десятичных цифры дроби равны 1026. Но метод округления дает 1027, когда используется double арифметика (64-битный двоичный код IEEE 754). - person Eric Postpischil; 24.06.2013
comment
@EricPostpischil: 0,1026499999999999912514425659537664614617824554443359375 и 0,10265 - это одно и то же число с точки зрения двойной точности, поскольку они различаются только 56-й двоичной цифрой (несмотря на ошибки с точностью до единицы); с 53-битным пределом двойников нужно просто признать, что некоторые вещи невозможны... - person Christoph; 24.06.2013
comment
@EricPostpischil: я понимаю вашу точку зрения, что использование round() не обязательно будет правильным, если Атул хочет усечения (которое он не указал в своем ответе, поэтому утверждать, что мой ответ неверен, немного преждевременно); если он действительно хочет усечения, я полагаю, вам придется возиться с nextafter() и подбрасывать монету в неоднозначных случаях... - person Christoph; 24.06.2013
comment
@Christoph: (a) я не утверждал, что этот ответ неверен. Я заявил, что код в нем не всегда дает правильный ответ. Это неоспоримый факт, так как я это продемонстрировал. (b) 0,1026499999999999912514425659537664614617824554443359375 и 0,10265 не совпадают. IEEE 754 довольно специфичен в отношении того, какие значения представляются, а 64-битный двоичный код представляет первое и не представляет второе. (c) Не нужно соглашаться с тем, что это невозможно. Когда предыдущее значение печатается с помощью %.4f с помощью Mac OS X 10.6.8 и ее инструментов, результатом будет «0,1026». - person Eric Postpischil; 24.06.2013
comment
@EricPostpischil: проблема в том, что хотя 0.1026499... != 0.10265 у нас тем не менее есть 0.1026499... * 1e4 == 0.10265 * 1e4; отказ от избыточной точности с помощью (trunc(frac * 16384.0) * 1e4) / 16384.0) работает для значений Atul и ваших выборок, но нет гарантий, что это не испортит другие крайние случаи... - person Christoph; 24.06.2013
comment
@Christoph: Да, есть проблема. Нет, это не неразрешимо. Об этом есть широко известная статья: и «Десятично-двоичные преобразования» Дэвида М. Гэя. В этом ответе представлен код, который дает неверные результаты. Методы получения правильных результатов известны и опубликованы. Если вам нужно быстрое и грязное решение и у вас есть хорошая реализация C, вы можете использовать sprintf для преобразования числа с плавающей запятой в десятичное и scanf для преобразования в целое число. - person Eric Postpischil; 24.06.2013

число = 1,1234, целое = 1, дробь = 1234

int main()
{
 double number;
 int whole, fraction;
 number = 1.1234;
 whole= (int)number;
 fraction =(int)(number*10000);
 fraction = fraction-(whole *10000);
 printf("%d\n",fraction);
 printf("%d\n",whole);
 return 0;
}
person OnePunchMan    schedule 01.09.2013
comment
Я согласен с вашим примером. Но если я не знаю числа и не знаю, насколько велика дробная часть? - person Romulus; 27.11.2013
comment
@RemusAvram проверьте этот код, затем stackoverflow.com/a/18517555/2508414 хорошо для нас, стартер, этот код должен работать нормально, если мы пытаемся написать код для очень большого числа (насколько я знаю) нет ответа.. те люди, которые занимаются исследованиями и высоким уровнем программирования, могут это сделать. но до сих пор я не нашел лучшей логики, чем та, которую я реализовал. - person OnePunchMan; 28.11.2013

Решение для любого числа может быть:

#include <cmath>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])

{ 
float number = 123.46244;
float number_final;
float temp = number;  // keep the number in a temporary variable
int temp2 = 1;        // keep the length of the fractional part

while (fmod(temp, 10) !=0)   // find the length of the fractional part
{
    temp = temp*10;
    temp2 *= 10;
}

temp /= 10;       // in tins step our number is lile this xxxx0
temp2 /= 10;
number_final = fmod(temp, temp2);

cout<<number_final;

getch();
return 0;
}
person Romulus    schedule 27.11.2013

Используйте modf и ceil

#include <stdio.h>
#include <math.h>

int main(void)
{
    double param, fractpart, intpart;
    int output;

    param = 1.1234;
    fractpart = modf(param , &intpart);
    output = (int)(ceil(fractpart * 10000));
    printf("%d\n", output);

    return 0;
}
person David Ranieri    schedule 23.06.2013
comment
Он хочет инт. Его метод умножения-и-округления хорош, ему просто нужно округлить ближе, а не усекать. - person Lee Daniel Crocker; 23.06.2013
comment
@ Дэвид РФ, я ожидал 500 в качестве вывода, когда я ввожу 1.0500, но он показывает 501, я думаю, что ceil не будет работать во всех случаях. - person Atul Kumar Verma; 23.06.2013
comment
Атул, правда, тогда дай еще одну цифру для точности и подели на 10 output = (int)(ceil(fractpart * 100000)) / 10; - person David Ranieri; 23.06.2013