uint-fortran
January 28, 2024 · View on GitHub
uint-fortran—Unsigned Integer in Fortran Interoperable with C
In Fortran, handling unsigned integers that are interoperable with C can be a bit troublesome, as Fortran natively does not contain unsigned integer types.
However, with the uint-fortran package, you can work with unsigned integer seamlessly.
Let's break down how to use this package and demonstrate its interoperability with C functions.
Build
Add below in fpm.toml of your package.
[dependencies]
uint-fortran = {git ="https://github.com/ShinobuAmasaki/uint-fortran"}
Supported compilers are below:
- GNU Fortran Compiler (
gfortran, tested on v13.2.1) - Intel oneAPI Fortran Compiler Classic (
ifort, tested onifort (IFORT) 2021.11.0 20231010) - Intel oneAPI Fortran Compiler (
ifx, tested onifx (IFX) 2024.0.0 20231017)
Currently not available for LLVM 'Classic' Flang.
Usage
use statement
To use the uint-fortran package, start by including the unsigned_m module in your Fortran program.
(The previous module name of unsigned has been changed to unsigned_m.)
use unsigned_m
Make sure not to use the only: clause with this module to ensure proper operator overrides.
Declaration
Now, you can declare unsigned integer variables of 32- and 16-bit lengths, such as uint32 and uint16.
type(uint32) :: a
Assignment and Operations
You can assign values to uint32 variables just like regular integer types:
a = 3456
However, if you attempt to assign a negative number, it will underflow:
a = -10
print *, a ! This will print '4294967286'.
If you assign a number greater than 4294967295, it will overflow:
a = 4294967296_8
print *, a ! This will print '0'.
If you want to use a uint32 value as an integer(8) type, you can do so by using the int function that is overloaded.
int(a)
Interoperability with C
Call C Function from Fortran
1. Pass uint32 as an Argument
Suppose you have a C code that takes an unsigned integer as an argument and prints it using printf.
#include <stdio.h>
void unsigned_val_print(unsigned int val )
{
printf("%u\n", val);
return;
}
To call this C function from Fortran, you need to create an interface block as follows.
use :: unsigned_m
interface
subroutine unsigned_val_print(val) bind(c)
import uint32
implicit none
type(uint32), intent(in), value :: val
end subroutine unsigned_val_print
end interface
In standard Fortran, unsigned integers don't exist, so you must use the equivalent signed c_int type when passing arguments to C functions.
The uint32 type from this package corresponding to the C unsigned int type. You can pass it to C functions as demonstrated below.
type(uint32) :: a
a = 4294967295_8 ! An example value that is maximum value of unsigned int
call unsigned_val_print(a)
Running this code will display the result, which in this case would be 4294967295
Of course, you can also use these unsigned integers Fortran itself.
print *, a ! This will display the same value, 4294967295
Here's the complete Fortran program for the example:
program main
use unsigned_m
use, intrinsic :: iso_c_binding
implicit none
interface
subroutine unsigned_val_print(val) bind(c)
import uint32
implicit none
type(uint32), intent(in), value :: val
end subroutine unsigned_val_print
end interface
block
type(uint32) :: a
a = 4294967295_8
call unsigned_val_print(a)
print *, a ! This will display the same value 4294967295 to standard output.
end block
end program main
2. Getting a uint32 as a Returned Value from C
Next, let's consider a C function that always returns the value 2333444555, which exceeds
the range of 32-bit signed integer:
unsigned int return_unsigned(void)
{
unsigned int val = 2333444555;
return val;
}
To call this C function from Fortran, you can create a Fortran interface like this:
use unsigned_m
interface
function return_unsigned () bind(c, name='return_unsigned') result(res)
import uint32
implicit none
type(uint32) :: res
end function return_unsigned
end interface
By declaring this interface, you can use the function in Fortran.
When a function returns a uint32, you simply assign it to a regular derived type variable,
just like this:
block
type(uint32) :: result
result = return_unsigned()
print *, result
end block
Running this code will correctly display the value previously defined in C on the standard output:
2333444555
Here's the complete Fortran code used this example:
program main
use unsigned_m
implicit none
interface
function return_unsigned () bind(c, name='return_unsigned') result(res)
import uint32
implicit none
type(uint32) :: res
end function return_unsigned
end interface
block
type(uint32) :: result
result = return_unsigned()
print *, result
end block
end program main
Internal Structure
The uint32 derived type is declared within the uint32_t module as follows:
type, public, bind(c) :: uint32
integer(c_int32_t) :: u32
end type uint32
Internally, it simply contains a signed integer with the same 32-bit length.
To enable proper handling of uint32 as an unsigned integer in Fortran, operator
overload for assginment, arithmetic operations, comparison operations,
and exponentiation, as well as derived type I/O feature, are utilized.
Forbid assignment of negative numbers
By setting -DHARDENED to the environment variable FPM_FFLAGS and building,
you can stop the program with error stop when a negative number is assigned.
This functionality is implemented using preprocessor macros in assignment_m.F90.
The same thing can be done by specifying --flag "-DHARDENED" in the fpm command option.
uint64
uint64 as 8-byte unsigned integer type has been implemented with partial feature support.
The currently implemented features are:
type(uint64),- Assignment operator overload,
- Comparison operator overloads,
- Arithmetic operator overloads for addition and subtraction,
writestatement (unformatted and formatted, formatted is asterisk only).
Since integer(int128), which includes the entire type(uint64), is not available,
no type conversions are yet provided.
Therefore, arithmetic operations involving type(uint64) return type(uint64).
Please note that in the GNU Fortran, basically writing a numerical literal exceeding
9223372036854775807 (that is maximum value of a 64-bit signed integer) will result in a compile time error.
If you want to ignore this, consider using the -fno-range-check compile option.