Эмулируйте поведение преобразования строк с плавающей точкой Linux в Windows

Я встретился с раздражающей проблемой в выводе числа с плавающей точкой. Когда я формат 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.

7
задан EMP 10 February 2010 в 07:32
поделиться

6 ответов

Короткий ответ, для простоты и простоты использования, вы действительно не можете пойти не так с PostSharp.

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

Если вы хотите, чтобы аспекты, которые должны изменяться на основе контекста, учитывайте Spring.NET (или любую aop-инфраструктуру, которая вводит код во время выполнения на основе конфигурации). Это позволяет настраивать поведение объектов в зависимости от выполняемых действий. Например, с помощью вашей конфигурации вы можете использовать один тип регистрации в консольном приложении, а другой в веб-приложении. Обратите внимание, что Spring также является контейнером DI (и некоторые другие вещи) - он выходит за рамки AOP, и, безусловно, стоит научиться использовать.

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

Для того, что вы делаете, я рекомендую начать с PostSharp.

-121--3190185-

мне пришлось изменить код для передачи в идентификаторе пользователя и pwd.

Set objIADS = GetObject("WinNT:").OpenDSObject("WinNT://" & strDomain, strUsername, strPassword, ADS_SECURE_AUTHENTICATION)
Set objIADSUser = objIADS.GetObject("user", strUsername)

For each Member in objIADSUser.Groups
    If Member.Class = "Group" then
        If Member.Name = "TEST_AD_GROUP" then
            x = "true"
            EXIT FOR
        End If
    End If
Next
-121--4407412-

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

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

2
ответ дан 7 December 2019 в 12:19
поделиться

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

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

1
ответ дан 7 December 2019 в 12:19
поделиться

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

print "%.2f"%(11.545-1e-12)
0
ответ дан 7 December 2019 в 12:19
поделиться

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

Например, если вы округляете %. 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
ответ дан 7 December 2019 в 12:19
поделиться

Думаю, в разных ситуациях лучше другой метод. Например, если необходимо обработать возвращаемое значение перед возвращением, необходимо иметь одну точку выхода. Но в других ситуациях удобнее использовать несколько возвратов.

Одна записка. Если возвращаемое значение должно обрабатываться до возврата в нескольких ситуациях, но не во всех, наилучшие решения (IMHO) для определения метода, такого как ProcessVal, и вызова его перед возвращением:

var retVal = new RetVal();

if(!someCondition)
    return ProcessVal(retVal);

if(!anotherCondition)
   return retVal;
-121--1768986-

Одним из вариантов было бы

>>> var test = "Mar 16, 2010 00:00 AM";
>>> test.replace(test.substring(13,15),"12")
-121--2764416-

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

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

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

2
ответ дан 7 December 2019 в 12:19
поделиться

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

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

f1 == f2

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

fabs(f1 - f2) < eps

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

0
ответ дан 7 December 2019 в 12:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: