Эмулировать поведение преобразования строк с плавающей точкой в ​​Linux в Windows - PullRequest
7 голосов
/ 10 февраля 2010

Я столкнулся с досадной проблемой при выводе числа с плавающей запятой. Когда я форматирую 11.545 с точностью до 2 десятичных знаков в Windows, он выдает «11.55», как я и ожидал. Однако, когда я делаю то же самое в Linux, выдается «11.54»!

Изначально я столкнулся с проблемой в Python, но дальнейшие исследования показали, что разница заключается в базовой библиотеке времени выполнения C. (Архитектура x86-x64 в обоих случаях.) Запуск следующей строки C приводит к разным результатам в Windows и Linux, так же, как в Python.

printf("%.2f", 11.545);

Чтобы пролить свет на это, я напечатал число до 20 десятичных знаков ("%.20f"):

Windows: 11.54500000000000000000
Linux:   11.54499999999999992895

Я знаю, что 11.545 нельзя хранить точно как двоичное число. Так что, похоже, происходит то, что Linux выводит число, которое оно на самом деле хранит, с максимально возможной точностью, в то время как Windows выводит самое простое десятичное представление, т.е. пытается угадать, что, скорее всего, имел в виду пользователь.

У меня такой вопрос: Есть ли (разумный) способ эмулировать поведение Linux в Windows?

(Хотя поведение Windows, безусловно, интуитивно понятно, в моем случае мне действительно нужно сравнить вывод программы Windows с выводом программы Linux, а Windows - единственный, который я могу изменить. Кстати, Я попытался взглянуть на исходный код Windows printf, но фактическая функция, которая выполняет преобразование с плавающей запятой -> _cfltcvt_l, а ее источник, по-видимому, недоступен.)

РЕДАКТИРОВАТЬ: сюжет утолщается! Теория о том, что это вызвано неточным представлением, может быть неверной, потому что 0.125 имеет точное двоичное представление, и оно все равно отличается при выводе с '%.2f' % 0.125:

Windows: 0.13
Linux:   0.12

Однако round(0.125, 2) возвращает 0,13 как в Windows, так и в Linux.

Ответы [ 6 ]

2 голосов
/ 10 февраля 2010

Я не думаю, что Windows делает что-то особенно умное (например, пытается переосмыслить число с плавающей точкой в ​​базе 10): я предполагаю, что это просто точное вычисление первых 17 значащих цифр (что даст '11 .4545000000000000') и затем добавляя нули в конец, чтобы составить требуемое количество мест после точки.

Как уже говорили, разные результаты для 0.125 получены для Windows, использующей округление до половины, и Linux, использующей округление от половины до четности.

Обратите внимание, что для Python 3.1 (и Python 2.7, когда он появится), результат форматирования float будет независимым от платформы (за исключением, возможно, необычных платформ).

2 голосов
/ 10 февраля 2010

Прежде всего это звучит так, как будто в Windows это неправильно правильно в данном случае (не то, чтобы это действительно имело значение). Стандарт C требует, чтобы значение, выводимое на %.2f, было , округленным до соответствующего количества цифр . Наиболее известный алгоритм для этого - dtoa , реализованный David M. Gay . Вероятно, вы можете перенести это на Windows или найти собственную реализацию.

Если вы еще не прочитали «Как печатать числа с плавающей точкой точно» Стила и Уайта, найдите копию и прочитайте ее. Это определенно информативное чтение. Обязательно найдите оригинал конца 70-х годов. Я думаю, что в какой-то момент я приобрел свой у ACM или IEEE.

1 голос
/ 10 февраля 2010

Десятичный модуль дает вам доступ к нескольким режимам округления:

import decimal

fs = ['11.544','11.545','11.546']

def convert(f,nd):
    # we want 'nd' beyond the dec point
    nd = f.find('.') + nd
    c1 = decimal.getcontext().copy()
    c1.rounding = decimal.ROUND_HALF_UP
    c1.prec = nd
    d1 = c1.create_decimal(f)
    c2 = decimal.getcontext().copy()
    c2.rounding = decimal.ROUND_HALF_DOWN
    c2.prec = nd   
    d2 = c2.create_decimal(f)
    print d1, d2

for f in fs:
    convert(f,2)

Вы можете создать десятичное число из целого числа или строки. В вашем случае введите строку с большим количеством цифр, чем вы хотите, и обрежьте, установив context.prec.

Вот ссылка на пост pymotw с подробным обзором десятичного модуля:

http://broadcast.oreilly.com/2009/08/pymotw-decimal---fixed-and-flo.html

0 голосов
/ 10 февраля 2010

Вы можете вычесть небольшое значение из значения, чтобы форсировать округление вниз

print "%.2f"%(11.545-1e-12)
0 голосов
/ 10 февраля 2010

Вы можете попытаться вычесть (или добавить для отрицательного числа) небольшую дельту, которая не окажет влияния на округление для чисел, достаточно далеко от точности.

Например, если вы округляете с %.2f, попробуйте эту версию для Windows:

printf("%.2f", 11.545 - 0.001);

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


Пример программы:

#include <stdio.h>
int main (void) {
    printf("%.20f\n", 11.545);
    printf("%.2f\n", 11.545);
    printf("%.2f\n", 11.545 + 0.001);
    return 0;
}

выводит это в моей среде Cygwin:

11.54499999999999992895
11.54
11.55

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


Обновление:

Евгений, на основании вашего комментария:

Это работает для этого конкретного случая, но не как общее решение. Например, если число, которое я хочу отформатировать, равно 0,545 вместо 11,545, то «% .2f»% (0,545 - 0,001) возвращает «0,54», в то время как «% .2f»% 0,545 в Linux правильно возвращает «0,55».

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

Если вам нужна десятичная точность, это то, что вам нужно сделать. Но вы, возможно, захотите рассмотреть случаи в этом диапазоне, где Linux тоже идет другим путем (согласно вашему комментарию) - может быть ситуация, когда Linux и Windows расходятся во мнениях в направлении, противоположном тому, что вы нашли - десятичный тип, вероятно, выиграл не решить это.

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

0 голосов
/ 10 февраля 2010

Рассмотрите возможность сравнения чисел с плавающей запятой с некоторым допуском / эпсилоном. Это гораздо надежнее, чем пытаться точно соответствовать.

Я имею в виду, за исключением того, что говорю, что два числа равны, когда:

f1 == f2

Скажите, что они равны, когда:

fabs(f1 - f2) < eps

Для некоторых маленьких eps. Более подробную информацию по этому вопросу можно найти здесь .

...