Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ С\С++ _ Особенности функции atof

Автор: AD 19.8.2008, 16:27

Вчера увидел одну немаловажную особенность С-шной функции atof, о которой хотел всех предупредить.
Ну во-первых, функция переводит строковое значение числа в вещественное число. Особенность функции заключается в том, что этот перевод зависит от locale, которое устанавливается функцией setlocale! Например, если в приложении установлено вот так:

setlocale(LC_ALL, ".ACP");
// тогда
double d = atof("0.4"); // d =0.0
double d1 = atof("0,4"); // d1 =0.4

Т.е. в данной системе знаковым разделителем будет являться запятая.
А если вот так:
setlocale(LC_ALL, 0);
// тогда
double d = atof("0.4"); // d =0.4
double d1 = atof("0,4"); // d1 =0.0


Помните об этой особенности, если пользуетесь подобными функциями (не Qt)! :)

Автор: void* 19.8.2008, 19:14

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

Автор: AD 19.8.2008, 20:37

Цитата(void* @ 19.8.2008, 20:14) *
AD, это вполне ясное свойство, т.к. в разных странах свои особенности записи вещественных чисел, поэтому и есть зависимость от локали :)

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

Автор: Litkevich Yuriy 20.8.2008, 5:21

Цитата(void* @ 19.8.2008, 23:14) *
AD, это вполне ясное свойство, т.к. в разных странах свои особенности записи вещественных чисел, поэтому и есть зависимость от локали

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

И от того что я живу в России, я не пишу в исходном коде число Пи как в обычном тексте (3,14), я пишу так: 3.14

Автор: Tonal 20.8.2008, 13:30

А при чём тут исходный код?
Эта функция для преобразования строки в число с плавающей точкой.
А строку можно и из файла прочитать и с терминала ввести.
Какой там будет разделитель - зависит от того кто вводит - а он вводит то, что привык.
У нас принята (в школах обучают, в документах печатают) как раз запятая.
Но в связи с повсеместным распространением кривых программ, которые не учитывают локаль, сейчас часто используют и точку и запятую.

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

Автор: Litkevich Yuriy 20.8.2008, 13:39

Tonal, для случая с переменной передаваемой в эту функцию, соглашусь. Но если ты константу пишешь, как в примере AD, то получается большой касяк.
И в школе нас на уроке информатики учили, что в компьютерах десятичный разделитель = точка.

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

Автор: Tonal 20.8.2008, 13:57

Ну так в исходнике можно и напрямую числовым литералом записать.
Для числовых литералов в С/С++ стандарт чётко оговаривает использование точки.
Зачем извращаться-то?
Да и atof откуда узнает как строку была получена?

А на уроке информатики вам лапшу навесили сказали не полную правду. :)
"В компьютерах" десятичный разделитель зависит от требований программы с какой ты работаешь.

Есть системы где десятичный разделитель жёстко специфицирован - например исходники распространённых языков программирования, разные среды, отладчики. Там где нужна совместимость и переносимость в текстовом виде.
Здесь считается что пользователь должен быть достаточно подготовлен.

А есть, наоборот, где зависит от локали - это домашние и офисные приложения, где пользователь должен как можно быстрее и проще включится в работу.

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

Автор: Litkevich Yuriy 20.8.2008, 14:37

Цитата(Tonal @ 20.8.2008, 17:57) *
А на уроке информатики вам лапшу навесили сказали не полную правду.

а на тод период, я не припомню чтоб были компы использующие запятую для этих целей. у нас были два типа Крвет (БК-0010) и Нейва (БК-0011). Да и Редкие ПиСюки и Искры тоже были таковыми, тоже работали только с точкой. Так что факт.

не знаю как сейчас на уроках информатики, может ориентир на виндовоз, и его использование.

Автор: Tonal 21.8.2008, 10:59

Цитата(Litkevich Yuriy @ 20.8.2008, 18:37) *
а на тод период, я не припомню чтоб были компы использующие запятую для этих целей. у нас были два типа Крвет (БК-0010) и Нейва (БК-0011). Да и Редкие ПиСюки и Искры тоже были таковыми, тоже работали только с точкой. Так что факт.

Они не были ориентированы на широкого пользователя или на работу с документами. К тому же большинство программного обеспечения тогда было либо стянуто, либо наскоро адаптированно.
В той же MSDOS нормальная поддержка русского в консоле дай бог если в 5ке появилась.
Но это скорее к политике и развалу науки и промышленности относится, чем к самим машинам.

Цитата(Litkevich Yuriy @ 20.8.2008, 18:37) *
не знаю как сейчас на уроках информатики, может ориентир на виндовоз, и его использование.

Причём тут винда-то?
Нормальные настройки окружения для работы пользователя.
Читать для начала:
http://ru.wikipedia.org/wiki/%D0%9B%D0%BE%D0%BA%D0%B0%D0%BB%D1%8C
http://ru.wikipedia.org/wiki/%D0%9B%D0%BE%D0%BA%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F
http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F

Автор: AD 21.8.2008, 22:16

Цитата(Tonal @ 21.8.2008, 11:59) *
Они не были ориентированы на широкого пользователя или на работу с документами. К тому же большинство программного обеспечения тогда было либо стянуто, либо наскоро адаптированно.
В той же MSDOS нормальная поддержка русского в консоле дай бог если в 5ке появилась.
Но это скорее к политике и развалу науки и промышленности относится, чем к самим машинам.
Причём тут винда-то?
Нормальные настройки окружения для работы пользователя.

Цитата(Litkevich Yuriy @ 20.8.2008, 18:37) *
а на тод период, я не припомню чтоб были компы использующие запятую для этих целей. у нас были два типа Крвет (БК-0010) и Нейва (БК-0011). Да и Редкие ПиСюки и Искры тоже были таковыми, тоже работали только с точкой. Так что факт.

Я многое пропустил, блин. У меня сегодня целый день на форуме темы не открывались! Я не совсем понял, о чем спор-то? :)

Лично по мне, так я не считаю удобным то, что программисту приходится следить за тем, что поставлено: точка или запятая. Мне кажется, что надо бы было сделать функцию так, чтобы она понимала оба этих разделителя одновременно. ПЛЮС ко всему, могу заметить следующее: года 2 или 3 назад при делании лабораторок заметил еще одну штуку: функции типа sprintf делают строки именно с точкой, а не с запятой. не проверял эту штуку, используя setlocale, но думаю, результат будет тем же! :)

Автор: Tonal 22.8.2008, 11:45

Ещё раз - для ввода пользователя десятичный разделитель зависит от локали. Если в локали прописано, что разделитель должен быть символ + значит его и нужно корректно обрабатывать.

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

Цитата
Это не в стране разруха а в головах. (с)

Автор: niXman 23.8.2008, 14:03

AD, Не подумал бы :o: Спасибо, буду внимателен.

Цитата(Litkevich Yuriy @ 20.8.2008, 6:21) *
а по мне дак это чушь полная, т.к. в исходном коде программ десятичный разделитель всегда был точкой!

Абсолютно верно!
Цитата(Tonal @ 20.8.2008, 14:30) *
Но в связи с повсеместным распространением кривых программ, которые не учитывают локаль, сейчас часто используют и точку и запятую.

Я всегда, в независимости от локали, заменяю запятую на точку, до передачи функции.
Цитата(AD @ 21.8.2008, 23:16) *
Лично по мне, так я не считаю удобным то, что программисту приходится следить за тем, что поставлено: точка или запятая. Мне кажется, что надо бы было сделать функцию так, чтобы она понимала оба этих разделителя одновременно.

Опять верно!

Автор: Tonal 25.8.2008, 7:21

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

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

Автор: AD 26.8.2008, 11:37

Покопался в Qt-шных аналогах функции atof! Выяснилось, что функция ToDouble(bool* ok) тоже зависит от локали. Но есть отличия:
1) есть возможность увидеть ошибку быстрее, так как программисту предлагают после преобразования проверять значение ok
2) очень важное отличие - при установке такого формата setlocale(LC_ALL, 0) функция преобразует и строчку "1234,45" and "1234.45" в вещественное число 1234.45!

Так что, если используете библиотеку Qt есть такие советы:
1) Указывать в приложении напрямую setlocale()!
2) Использовать qt-шные средства преобразования в вещественные числа! Преобразование легче отследить и немного универсальнее!

P.S. Немцы молодцы! :)

 QLocale::setDefault(QLocale::C);
d = QString( "1234,56" ).toDouble(&ok); // ok=false
d = QString( "1234.56" ).toDouble(&ok); // ok=true, d = 1234.56

QLocale::setDefault(QLocale::German);
d = QString( "1234,56" ).toDouble(&ok); // ok=true, d = 1234.56
d = QString( "1234.56" ).toDouble(&ok); // ok=true, d = 1234.56

Автор: Litkevich Yuriy 26.8.2008, 11:44

Цитата(AD @ 26.8.2008, 15:37) *
функция преобразует и строчку "1234,45" and "1234.45"

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

Автор: AD 26.8.2008, 12:04

Цитата
вообще я думаю так бы оно и должно бы быть, а отделять группы разрядов можно и сверху (апострофом)
Но америкосы используют для этого запятую.

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

Автор: Tonal 26.8.2008, 12:59

И что ты с америкосовской записью будешь делать где тысячи запятой отделяются а десятые точкой?
Я же говорю - разделитель от локали зависит.

Автор: AD 26.8.2008, 13:16

Цитата(Tonal @ 26.8.2008, 13:59) *
И что ты с америкосовской записью будешь делать где тысячи запятой отделяются а десятые точкой?
Я же говорю - разделитель от локали зависит.

А кому вопрос? :)

Да и... я разве спорил с этим?

Цитата
1) Указывать в приложении напрямую setlocale()!

Автор: Andrew Selivanov 27.8.2008, 12:37

А вот std::stringstream (#include <sstream>) как стандартный способ преобразования почему-то никто и не вспомнил... :rolleyes:

Автор: NordWest 5.10.2010, 12:52

Не очень понял всех докладчиков. У меня такая проблема. На debian стоят такие локали:

Цитата
LANG=ru_RU.KOI8-R
LC_CTYPE="ru_RU.KOI8-R"
LC_NUMERIC="ru_RU.KOI8-R"
LC_TIME="ru_RU.KOI8-R"
LC_COLLATE="ru_RU.KOI8-R"
LC_MONETARY="ru_RU.KOI8-R"
LC_MESSAGES="ru_RU.KOI8-R"
LC_PAPER="ru_RU.KOI8-R"
LC_NAME="ru_RU.KOI8-R"
LC_ADDRESS="ru_RU.KOI8-R"
LC_TELEPHONE="ru_RU.KOI8-R"
LC_MEASUREMENT="ru_RU.KOI8-R"
LC_IDENTIFICATION="ru_RU.KOI8-R"
LC_ALL=


В вычислениях везде точки и под виндой проблем не возникает. В линуксе же воспринимаются запятые, а у чисел точками отбрасывается дробная часть. Вот например код:
int main(int argc, char *argv[])
{
...
double d = atof(argv[3]);
...
}


Если в командной строке ввести 15.5, то d=15, а если 15,5 - 15.5. Причём qDebug() выдает точки.
Пишу вначале main команду setlocale(LC_ALL, 0); - результата тот же. С QLocale::setDefault(QLocale::C); - то же самое. Если считывать данные из файла - результат будет аналогичным. А мне нужно и локаль себе не портить системную и чтобы программа работала с точками.

Автор: kwisp 5.10.2010, 13:03

Цитата
$ locale
LANG=ru_RU.utf8
LC_CTYPE="ru_RU.utf8"
LC_NUMERIC=C
LC_TIME="ru_RU.utf8"
LC_COLLATE="ru_RU.utf8"
LC_MONETARY="ru_RU.utf8"
LC_MESSAGES="ru_RU.utf8"
LC_PAPER="ru_RU.utf8"
LC_NAME="ru_RU.utf8"
LC_ADDRESS="ru_RU.utf8"
LC_TELEPHONE="ru_RU.utf8"
LC_MEASUREMENT="ru_RU.utf8"
LC_IDENTIFICATION="ru_RU.utf8"
LC_ALL=


$ cat ./main.cpp 
# include <iostream>
# include <cstdlib>

int main(int a, char** b)
{
  double d = 0;
  while (a>0)
  {
      d = atof(b[--a]);
    std::cout<< d << '\n';
  }
  return 0;
}


Цитата
$ ./a.out 15.5 14.2
14.2
15.5
0

все как видишь работает
LC_NUMERIC=C - этим ты локаль вроде не портишь

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)