- Вопрос или проблема
- Отображение и обмен
- Внутреннее хранение
- Ответ или решение
- Как округлить числа до одного знака после запятой в Fortran
- 1. Как округлить числа до одного знака после запятой?
- 2. Как хранить округленные числа в Fortran?
- 3. Как записывать такие числа в NetCDF?
- 4. Как записывать числа в CSV или текстовый файл?
- Заключение
Вопрос или проблема
В Fortran мне нужно округлить широту и долготу до одной цифры после запятой.
Я использую компилятор gfortran и функцию nint, но следующее не работает:
print *, nint( 1.40 * 10. ) / 10. ! выводит 1.39999998
print *, nint( 1.49 * 10. ) / 10. ! выводит 1.50000000
Ищу как общие, так и специфические решения здесь. Например:
-
Как мы можем отображать числа, округленные до одного знака после запятой?
-
Как мы можем хранить такие округленные числа в Fortran? Это невозможно в переменной типа float, но есть ли другие способы?
-
Как мы можем записать такие числа в NetCDF?
-
Как мы можем записать такие числа в файл CSV или текстовый файл?
Как уже упоминали другие, проблема заключается в использовании представления чисел с плавающей запятой в файле NetCDF. С помощью утилит nco
вы можете изменить широту/долготу на короткие целые числа с factor_scale и add_offset. Вот так:
ncap2 -s 'latitude=pack(latitude, 0.1, 0); longitude=pack(longitude, 0.1, 0);' old.nc new.nc
Нет способа сделать то, что вы спрашиваете. Основная проблема заключается в том, что округленные значения, которые вы хотите получить, не могут быть правильно представлены с использованием чисел с плавающей запятой.
Например, если у вас есть значение 10.58, оно точно представлено как 1.3225000 x 2^3 = 10.580000 в формате IEEE754 float32.
Когда вы округляете это значение до одного знака после запятой (как бы вы ни решали это сделать), результат будет 10.6, однако 10.6 не имеет точного представления. Ближайшее представление – это 1.3249999 x 2^3 = 10.599999 в формате float32. Таким образом, независимо от того, как вы будете работать с округлением, нет способа точно сохранить 10.6 в значении float32 и нет способа записать его как значение с плавающей запятой в файл netCDF.
ДА, ЭТО МОЖНО СДЕЛАТЬ! “Принятый” ответ выше верен в своем ограниченном диапазоне, но ошибочен в том, что вы действительно можете достичь в Fortran (или различных других HGL).
Единственный вопрос, какую цену вы готовы заплатить, если что-то вроде записи с F(6.1) не сработает?
С одной стороны, ваша проблема является особенно тривиальным вариантом темы “вычисления с произвольной точностью”. Как вы представляете себе, как обрабатывается криптография, когда вам нужно хранить, манипулировать и выполнять “математику” с, скажем, 1024-битными числами с точностью?
Простая стратегия в этом случае состояла бы в том, чтобы разделить каждое число на его составные “LHSofD” (левая сторона десятичной точки) и “RHSofD” (правая сторона десятичной точки). Например, у вас может быть RLon(i,j) = 105.591, и вы хотите вывести 105.6 (или любым образом округлить) в ваш netCDF (или любой обычный) файл. Разделите это на RLonLHS(i,j) = 105 и RLonRHS(i,j) = 591.
… на этом этапе у вас есть выбор, который увеличивает общность, но за некоторую цену. Чтобы сэкономить “деньги”, RHS может быть сохранен как 0.591 (но потеряет общность, если вам нужно делать сложные вещи).
Для простоты предположим “дешевую и веселую” вторую стратегию.
LHS легко (Int()).
Теперь для RHS умножьте на 10 (если вы хотите округлить до 1 DEC), например, чтобы получить RLonRHS(i,j) = 5.91, а затем примените Fortran “округлить до ближайшего Int” NInt() встроенную функцию … оставляя вас с RLonRHS(i,j) = 6.0.
… и Боб ваш дядя:
Теперь вы печатаете LHS и RHS в ваш netCDF, используя подходящее оператор записи, объединяя “двойники”, и создадите ТОЧНОЕ представление в соответствии с необходимыми задачами в OP.
… конечно, позже чтение этих значений возвращается к тем же проблемам, как указано выше, если только ввод не учитывает ArbPrec.
… мы написали свою собственную библиотеку ArbPrec, но таких много, также в VBA и других HGL … но будьте осторожны, полное оборудование ArbPrec – это не тривиальная задача … повезло, ваша проблема так проста.
Существует несколько аспектов, которые можно рассмотреть в отношении “округления до одной цифры после запятой”. Они касаются: внутреннего хранения и манипуляции; отображения и обмена.
Отображение и обмен
Самые простые аспекты касаются того, как мы сообщаем о хранимом значении, независимо от используемого внутреннего представления. Как подробно описано в других ответах и в других источниках, можно использовать числовой описатель редактирования с одной дробной цифрой:
print '(F0.1,2X,F0.1)', 10.3, 10.17
end
Как вывод округляется, это изменяемый режим:
print '(RU,F0.1,2X,RD,F0.1)', 10.17, 10.17
end
В этом примере мы выбрали округление вверх, а затем вниз, но мы также могли бы округлять до нуля или округлять до ближайшего (или позволить компилятору выбирать за нас).
Для любого отформатированного вывода, будь то на экран или в файл, такие описатели редактирования доступны. Описатель G
, который можно использовать для записи CSV-файлов, также будет выполнять это округление.
Для неформатированного вывода эта концепция округления не применима, так как ссылаются на внутреннее представление. Точно так же для формата обмена, такого как NetCDF и HDF5, мы не имеем этого округления.
Для NetCDF ваша атрибутная конвенция может указывать что-то вроде FORTRAN_format
, что дает подходящий формат для окончательного отображения (по умолчанию) вещественной, не округленной, переменной.
Внутреннее хранение
Другие ответы и сам вопрос упоминают невозможность точно представлять (и работать с) десятичными цифрами. Тем не менее, ничто в языке Fortran не требует, чтобы это было невозможно:
integer, parameter :: rk = SELECTED_REAL_KIND(radix=10)
real(rk) x
x = 0.1_rk
print *, x
end
является программой Fortran, которая имеет переменную и литерал с основанием 10. См. также IEEE_SELECTED_REAL_KIND(radix=10)
.
Теперь вы, скорее всего, увидите, что selected_real_kind(radix=10)
дает вам значение -5
, но если вы хотите что-то положительное, что можно использовать в качестве параметра типа, вам просто нужно найти кого-то, предлагающего вам такую систему.
Если вы не можете найти такую вещь, то вам придется делать работу с учетом ошибок. Здесь следует учитывать две части.
Внутренние вещественные числовые типы в Fortran являются числами с плавающей запятой. Чтобы использовать фиксированный числовой тип или систему, такую как двоично-кодовая десятичная система, вам придется прибегнуть к не стандартным типам. Эта тема выходит за рамки данного ответа, но указатели направлены в эту сторону доктором Оли.
Эти усилия не будут экономически выгодными и потребуют времени программиста. Вам также нужно будет позаботиться об управлении этими типами в вашем выводе и обмене.
В зависимости от требований вашей работы, вы можете обнаружить, что просто умножение на (степени) десять и работа с целыми числами подходят. В таких случаях вам также захочется найти соответствующий атрибут NetCDF в вашей конвенции, например scale_factor
.
Что касается наших вопросов внутреннего представления, у нас есть аналогичные проблемы с округлением для вывода. Например, если мои входные данные имеют долготу 10.17...
, но я хочу округлить ее в своем внутреннем представлении до (ближайшего представимого значения) одной десятичной цифры (скажем, 10.2
/10.1999998
), а затем работать с этим, как мне это организовать?
Мы видели, как nint(10.17*10)/10.
дает нам это, но также узнали, что числовые описатели редактирования делают это удобно для вывода, включая контроль режима округления:
character(10) :: intermediate
real :: rounded
write(intermediate, '(RN,F0.1)') 10.17
read(intermediate, *) rounded
print *, rounded ! Это может не выглядеть "точно"
end
Если это желаемо, мы можем отслеживать накопление ошибок.
Оператор `round_x = nint(x*10d0)/10d0` округляет x (для abs(x) < 2**31/10, для больших чисел используйте dnint()) и присваивает округленное значение переменной round_x для дальнейших вычислений.
Как упоминалось в ответах выше, не все числа с одним значимым знаком после запятой имеют точное представление, например, 0.3 не имеет.
print *, 0.3d0
Вывод:
0.29999999999999999
Чтобы вывести округленное значение в файл, на экран или преобразовать его в строку с одной значимой цифрой после запятой, используйте описатель редактирования ‘Fw.1’ (w – ширина, w символов, 0 – переменная ширина). Например:
print '(5(1x, f0.1))', 1.30, 1.31, 1.35, 1.39, 345.46
Вывод:
1.3 1.3 1.4 1.4 345.5
@JohnE, использование ‘G10.2’ неверно, оно округляет результат до двух значимых цифр, а не до одной цифры после запятой. Например:
print '(g10.2)', 345.46
Вывод:
0.35E+03
П.С.
Для NetCDF, округление должно обрабатываться просмотрщиком NetCDF, однако вы можете выводить переменные как тип NC_STRING:
write(NetCDF_out_string, '(F0.1)') 1.49
Или, альтернативно, получить “красивые” числа NC_FLOAT/NC_DOUBLE:
beautiful_float_x = nint(x*10.)/10. + epsilon(1.)*nint(x*10.)/10./2.
beautiful_double_x = dnint(x*10d0)/10d0 + epsilon(1d0)*dnint(x*10d0)/10d0/2d0
П.П.С. @JohnE
-
Предпочтительное решение – не округлять промежуточные результаты в памяти или в файлах. Округление выполняется только при выдаче окончательного вывода человеко-читаемых данных;
-
Используйте print с описателем редактирования ‘Fw.1’, см. выше;
-
Нет простых и надежных способов точно сохранить округленные числа (числа с десятичной фиксированной точкой):
2.1. Теоретически, некоторые реализации Fortran могут поддерживать десятичную арифметику, но мне неизвестны реализации, в которых ‘selected_real_kind(4, 4, 10)’ возвращает значение, отличное от -5;
2.2. Возможность хранения округленных чисел как строк;
2.3. Можно использовать биндинг Fortran библиотеки GIMP. Функции с префиксом mpq_ предназначены для работы с рациональными числами;
- Нет простых и надежных способов записать округленные числа в файл netCDF с сохранением их свойств для читателя этого файла:
3.1. netCDF поддерживает ‘Упакованные данные’ (Packed Data Values), т.е. вы можете установить целочисленный тип с атрибутами ‘scale_factor’ и ‘add_offset’ и сохранить массивы целых чисел. Но в файле ‘scale_factor’ будет храниться как число с плавающей запятой одинарной или двойной точности, т.е. значение будет отличаться от 0.1. Соответственно, при чтении, при выполнении расчета с помощью библиотеки netCDF unpacked_data_value = packed_data_value*scale_factor + add_offset
возникнет ошибка округления. (Можно установить scale_factor=0.1*(1.+epsilon(1.))
или scale_factor=0.1d0*(1d0+epsilon(1d0))
, чтобы исключить большое количество цифр ‘9’.);
3.2. Есть атрибуты C_format и FORTRAN_format. Но довольно трудно предсказать, какой читатель будет использовать какой атрибут и будут ли они вообще их использовать;
3.3. Можно сохранить округленные числа как строки или пользовательские типы;
- Используйте write() с описателем редактирования ‘Fw.1’, смотрите выше.
Ответ или решение
Как округлить числа до одного знака после запятой в Fortran
Вопрос округления чисел до одного знака после запятой в Fortran достаточно распространен, особенно в контексте работы с координатами, такими как широта и долгота. Для решения этой задачи, важно понимать не только особенности встроенных функций, но и нюансы, связанные с представлением чисел с плавающей точкой.
1. Как округлить числа до одного знака после запятой?
Использование функции nint
может создавать некоторые проблемы, так как числа с плавающей точкой не всегда хранятся точно. Например:
print *, nint(1.40 * 10.0) / 10.0 ! может напечатать 1.39999998
print *, nint(1.49 * 10.0) / 10.0 ! может напечатать 1.50000000
Чтобы корректно округлить число до одного знака после запятой, можно использовать следующую формулу:
rounded_value = nint(value * 10.0) / 10.0
Это даст вам значение, округленное до одного знака после запятой, но стоит помнить о возможных погрешностях при хранении.
2. Как хранить округленные числа в Fortran?
Наиболее простым вариантом будет хранение округленных значений как вещественных чисел, используя классическую реализацию типа real
. Однако, как было замечено, вещественные числа не могут точно представлять десятичные дроби. Вы можете рассмотреть возможность:
- Сохранения как строк для представления округленных чисел.
- Использования целых чисел, сохраняя их в виде множителей десяти (например, хранить 10.4 как 104, а затем при выводе делить на 10).
Пример:
integer :: x
x = nint(value * 10.0)
3. Как записывать такие числа в NetCDF?
Для записи в формате NetCDF вы можете использовать атрибуты scale_factor
и add_offset
. Пример для сохранения с округлением:
! Сохранить значения с округлением
ncap2 -s 'latitude=pack(latitude, 0.1, 0); longitude=pack(longitude, 0.1, 0);' old.nc new.nc
Однако стоит отметить, что, когда вы работаете с NetCDF, важно учитывать, что точные значения могут быть изменены из-за особенностей представления с плавающей точкой.
4. Как записывать числа в CSV или текстовый файл?
Сохранение округленных значений в текстовый файл или CSV можно сделать, используя форматированные операции записи:
open(unit=10, file='output.csv', status='replace')
write(10, '(F6.1)') rounded_value ! Запись с одним знаком после запятой
close(10)
Здесь используется редактор F6.1
, который указывает формат с 6 символами, из которых 1 – после запятой.
Заключение
Рounding numbers to one decimal place in Fortran is a task that, when executed carefully, can yield the expected results. Memory representations, especially regarding floating-point precision, pose challenges that must be managed throughout the computation. Whether you output to the console, write to a file, or save to NetCDF, ensure you account for the limitations inherent to floating-point arithmetic, and leverage integer manipulations or string representations as necessary.
Поиск точного и надежного метода хранения и вывода чисел с плавающей точкой является важной частью разработки. Воспользуйтесь рекомендациями, представленными выше, чтобы справиться с задачами округления и записи данных в различных форматах, поддерживая высокие стандарты точности ваших вычислений.