Best practice when working with double precision magic numbers - fortran

Do I necessarily need to specify D (e.g., 1.234D+00) at the end of all magic numbers (literal constants) if I've already declared everything double precision anyway?

Short answer: Yes, you do.
Long answer: By default, real literals are single precision unless otherwise specified. Assigning single precision literals to double precision variables incurs precision loss; that is, single precision literals are evaluated first as single precision then assigned to the higher-precision variable. I'm too lazy to retrieve the F2003 Handbook from the other room but I suspect that single-to-double assignment sets the low significance mantissa bits to zero. Either that or it's left up to the vendor.
Regardless, here's a demonstration of what happens when you mix precision between literals and variables (note that 0.1 can't be stored cleanly in binary floating point):
!> Demonstrate the effects of D and E suffixes on precision of literals
program whatkind
use iso_fortran_env, only: output_unit, REAL32, REAL64
implicit none
real (kind=REAL64) :: dtest
10 format('Literal ', A, ' is of kind ', I2)
20 format(/, A)
30 format(/, 'Value stored in ', A, ' precision generated with ', A, &
' precision literals:')
40 format('Literal is ', A)
continue
write(output_unit, 10) '1.0', kind(1.0)
write(output_unit, 10) '1.0E0', kind(1.0E0)
write(output_unit, 10) '1.0D0', kind(1.0D0)
write(output_unit, 10) '1.0_REAL32', kind(1.0_REAL32)
write(output_unit, 10) '1.0_REAL64', kind(1.0_REAL64)
write(output_unit, 20) 'Raw tenths tests:'
dtest = 0.1
write(output_unit, 30) 'double', 'single'
write(output_unit, 40) '0.1'
write(output_unit, *) dtest
dtest = 0.1D0
write(output_unit, 30) 'double', 'double'
write(output_unit, 40) '0.1D0'
write(output_unit, *) dtest
dtest = 1.0 / 10.0
write(output_unit, 30) 'double', 'single'
write(output_unit, 40) '0.1'
write(output_unit, 40) '1.0 / 10.0'
write(output_unit, *) dtest
dtest = 1.0_REAL64 / 10.0_REAL64
write(output_unit, 30) 'double', 'double'
write(output_unit, 40) '1.0_REAL64 / 10.0_REAL64'
write(output_unit, *) dtest
dtest = 1.0_REAL32 / 10.0_REAL32
write(output_unit, 30) 'double', 'single'
write(output_unit, 40) '1.0_REAL32 / 10.0_REAL32'
write(output_unit, *) dtest
dtest = 1.0_REAL64 / 10.0_REAL32
write(output_unit, 30) 'double', 'mixed'
write(output_unit, 40) '1.0_REAL64 / 10.0_REAL32'
write(output_unit, *) dtest
dtest = 1.0_REAL32 / 10.0_REAL64
write(output_unit, 30) 'double', 'mixed'
write(output_unit, 40) '1.0_REAL32 / 10.0_REAL64'
write(output_unit, *) dtest
end program whatkind
The results of this are:
Literal 1.0 is of kind 4
Literal 1.0E0 is of kind 4
Literal 1.0D0 is of kind 8
Literal 1.0_REAL32 is of kind 4
Literal 1.0_REAL64 is of kind 8
Raw tenths tests:
Value stored in double precision generated with single precision literals:
Literal is 0.1
0.10000000149011612
Value stored in double precision generated with double precision literals:
Literal is 0.1D0
0.10000000000000001
Value stored in double precision generated with single precision literals:
Literal is 0.1
Literal is 1.0 / 10.0
0.10000000149011612
Value stored in double precision generated with double precision literals:
Literal is 1.0_REAL64 / 10.0_REAL64
0.10000000000000001
Value stored in double precision generated with single precision literals:
Literal is 1.0_REAL32 / 10.0_REAL32
0.10000000149011612
Value stored in double precision generated with mixed precision literals:
Literal is 1.0_REAL64 / 10.0_REAL32
0.10000000000000001
Value stored in double precision generated with mixed precision literals:
Literal is 1.0_REAL32 / 10.0_REAL64
0.10000000000000001
You see how in cases where all the literals are single precision (including those with no explicit precision set) there is low significance 'noise' stored in the double precision variable.
I find it interesting that operations on mixed precision literals seems to promote all the literals to higher precision before the operation is performed. Someone with more language-spec-fu might be able to explain that.
My advice: When in doubt, be explicit. It's safer and I think it's worth the extra keystrokes.

Related

Error: Type mismatch in argument 't_g' at (1); passed REAL(4) to REAL(8) [duplicate]

Do I necessarily need to specify D (e.g., 1.234D+00) at the end of all magic numbers (literal constants) if I've already declared everything double precision anyway?
Short answer: Yes, you do.
Long answer: By default, real literals are single precision unless otherwise specified. Assigning single precision literals to double precision variables incurs precision loss; that is, single precision literals are evaluated first as single precision then assigned to the higher-precision variable. I'm too lazy to retrieve the F2003 Handbook from the other room but I suspect that single-to-double assignment sets the low significance mantissa bits to zero. Either that or it's left up to the vendor.
Regardless, here's a demonstration of what happens when you mix precision between literals and variables (note that 0.1 can't be stored cleanly in binary floating point):
!> Demonstrate the effects of D and E suffixes on precision of literals
program whatkind
use iso_fortran_env, only: output_unit, REAL32, REAL64
implicit none
real (kind=REAL64) :: dtest
10 format('Literal ', A, ' is of kind ', I2)
20 format(/, A)
30 format(/, 'Value stored in ', A, ' precision generated with ', A, &
' precision literals:')
40 format('Literal is ', A)
continue
write(output_unit, 10) '1.0', kind(1.0)
write(output_unit, 10) '1.0E0', kind(1.0E0)
write(output_unit, 10) '1.0D0', kind(1.0D0)
write(output_unit, 10) '1.0_REAL32', kind(1.0_REAL32)
write(output_unit, 10) '1.0_REAL64', kind(1.0_REAL64)
write(output_unit, 20) 'Raw tenths tests:'
dtest = 0.1
write(output_unit, 30) 'double', 'single'
write(output_unit, 40) '0.1'
write(output_unit, *) dtest
dtest = 0.1D0
write(output_unit, 30) 'double', 'double'
write(output_unit, 40) '0.1D0'
write(output_unit, *) dtest
dtest = 1.0 / 10.0
write(output_unit, 30) 'double', 'single'
write(output_unit, 40) '0.1'
write(output_unit, 40) '1.0 / 10.0'
write(output_unit, *) dtest
dtest = 1.0_REAL64 / 10.0_REAL64
write(output_unit, 30) 'double', 'double'
write(output_unit, 40) '1.0_REAL64 / 10.0_REAL64'
write(output_unit, *) dtest
dtest = 1.0_REAL32 / 10.0_REAL32
write(output_unit, 30) 'double', 'single'
write(output_unit, 40) '1.0_REAL32 / 10.0_REAL32'
write(output_unit, *) dtest
dtest = 1.0_REAL64 / 10.0_REAL32
write(output_unit, 30) 'double', 'mixed'
write(output_unit, 40) '1.0_REAL64 / 10.0_REAL32'
write(output_unit, *) dtest
dtest = 1.0_REAL32 / 10.0_REAL64
write(output_unit, 30) 'double', 'mixed'
write(output_unit, 40) '1.0_REAL32 / 10.0_REAL64'
write(output_unit, *) dtest
end program whatkind
The results of this are:
Literal 1.0 is of kind 4
Literal 1.0E0 is of kind 4
Literal 1.0D0 is of kind 8
Literal 1.0_REAL32 is of kind 4
Literal 1.0_REAL64 is of kind 8
Raw tenths tests:
Value stored in double precision generated with single precision literals:
Literal is 0.1
0.10000000149011612
Value stored in double precision generated with double precision literals:
Literal is 0.1D0
0.10000000000000001
Value stored in double precision generated with single precision literals:
Literal is 0.1
Literal is 1.0 / 10.0
0.10000000149011612
Value stored in double precision generated with double precision literals:
Literal is 1.0_REAL64 / 10.0_REAL64
0.10000000000000001
Value stored in double precision generated with single precision literals:
Literal is 1.0_REAL32 / 10.0_REAL32
0.10000000149011612
Value stored in double precision generated with mixed precision literals:
Literal is 1.0_REAL64 / 10.0_REAL32
0.10000000000000001
Value stored in double precision generated with mixed precision literals:
Literal is 1.0_REAL32 / 10.0_REAL64
0.10000000000000001
You see how in cases where all the literals are single precision (including those with no explicit precision set) there is low significance 'noise' stored in the double precision variable.
I find it interesting that operations on mixed precision literals seems to promote all the literals to higher precision before the operation is performed. Someone with more language-spec-fu might be able to explain that.
My advice: When in doubt, be explicit. It's safer and I think it's worth the extra keystrokes.

Precision issues when converting a decimal number to its rational equivalent

I have problem of converting a double (say N) to p/q form (rational form), for this I have the following strategy :
Multiply double N by a large number say $k = 10^{10}$
then p = y*k and q = k
Take gcd(p,q) and find p = p/gcd(p,q) and q = p/gcd(p,q)
when N = 8.2 , Answer is correct if we solve using pen and paper, but as 8.2 is represented as 8.19999999 in N (double), it causes problem in its rational form conversion.
I tried it doing other way as : (I used a large no. 10^k instead of 100)
if(abs(y*100 - round(y*100)) < 0.000001) y = round(y*100)/100
But this approach also doesn't give right representation all the time.
Is there any way I could carry out the equivalent conversion from double to p/q ?
Floating point arithmetic is very difficult. As has been mentioned in the comments, part of the difficulty is that you need to represent your numbers in binary.
For example, the number 0.125 can be represented exactly in binary:
0.125 = 2^-3 = 0b0.001
But the number 0.12 cannot.
To 11 significant figures:
0.12 = 0b0.00011110101
If this is converted back to a decimal then the error becomes obvious:
0b0.00011110101 = 0.11962890625
So if you write:
double a = 0.2;
What the machine actually does is find the closest binary representation of 0.2 that it can hold within a double data type. This is an approximation since as we saw above, 0.2 cannot be exactly represented in binary.
One possible approach is to define an 'epsilon' which determines how close your number can be to the nearest representable binary floating point.
Here is a good article on floating points:
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
have problem of converting a double (say N) to p/q form
... when N = 8.2
A typical double cannot encode 8.2 exactly. Instead the closest representable double is about
8.19999999999999928945726423989981412887573...
8.20000000000000106581410364015027880668640... // next closest
When code does
double N = 8.2;
It will be the 8.19999999999999928945726423989981412887573... that is converted into rational form.
Converting a double to p/q form:
Multiply double N by a large number say $k = 10^{10}$
This may overflow the double. First step should be to determine if the double is large, it which case, it is a whole number.
Do not multiple by some power of 10 as double certainly uses a binary encoding. Multiplication by 10, 100, etc. may introduce round-off error.
C implementations of double overwhelmingly use a binary encoding, so that FLT_RADIX == 2.
Then every finite double x has a significand that is a fraction of some integer over some power of 2: a binary fraction of DBL_MANT_DIG digits #Richard Critten. This is often 53 binary digits.
Determine the exponent of the double. If large enough or x == 0.0, the double is a whole number.
Otherwise, scale a numerator and denominator by DBL_MANT_DIG. While the numerator is even, halve both the numerator and denominator. As the denominator is a power-of-2, no other prime values are needed for simplification consideration.
#include <float.h>
#include <math.h>
#include <stdio.h>
void form_ratio(double x) {
double numerator = x;
double denominator = 1.0;
if (isfinite(numerator) && x != 0.0) {
int expo;
frexp(numerator, &expo);
if (expo < DBL_MANT_DIG) {
expo = DBL_MANT_DIG - expo;
numerator = ldexp(numerator, expo);
denominator = ldexp(1.0, expo);
while (fmod(numerator, 2.0) == 0.0 && denominator > 1.0) {
numerator /= 2.0;
denominator /= 2.0;
}
}
}
int pre = DBL_DECIMAL_DIG;
printf("%.*g --> %.*g/%.*g\n", pre, x, pre, numerator, pre, denominator);
}
int main(void) {
form_ratio(123456789012.0);
form_ratio(42.0);
form_ratio(1.0 / 7);
form_ratio(867.5309);
}
Output
123456789012 --> 123456789012/1
42 --> 42/1
0.14285714285714285 --> 2573485501354569/18014398509481984
867.53089999999997 --> 3815441248019913/4398046511104

What happens to the value of a floating point number when it's assigned to a long double?

Edit: I've realised that I'm working with the type long doubleand not just double which does make a difference. I've also added an example from my program below that reproduces the error in question.
Note: I'm currently working in C++11 and using GCC to compile.
I'm dealing with a situation where the result varies between the below two calculations:
value1 = x * 6.0;
double six = 6.0;
value2 = x * six;
value1 != value2
Where all variables above are of type long double.
Essentially, I wrote a line of code that gives me an incorrect answer when I use 6.0 in the actual calculation. Whereas, if I assign 6.0 to a variable of type long double first then use that variable in the calculation I receive the correct result.
I understand the basics of floating point arithmetic, and I guess it's obvious that something is happening to the bits of 6.0 when it is assigned to the long double type.
Sample from my actual program (I left the calculation as is to ensure the error is reproducible):
#include <iomanip>
#include <math.h>
long double six = 6.0;
long double value1;
long double value2;
value1 = (0.7854 * (pow(10, 5)) * six * (pow(0.033, 2)) * 1.01325 * (1.27 * 11.652375 / 1.01325 - 1.0));
value2 = (0.7854 * (pow(10, 5)) * 6.0 * (pow(0.033, 2)) * 1.01325 * (1.27 * 11.652375 / 1.01325 - 1.0));
std::cout << std::setprecision(25) << value1 << std::endl;
std::cout << std::setprecision(25) << value2 << std::endl;
Where the output is:
7074.327896870849993415931
7074.327896870850054256152
Also, I understand how floating point calculations only hold precision up to a certain number of bits (so setting such high precision shouldn't effect results, e.g. after 15-17 digits it should really matter if numbers vary but unfortunately this does affect my calculation).
Question: Why are the above two code segments producing (slightly) different results?
Note: I'm not simply comparing the two numbers with == and receiving false. I've just been printing them out using setprecision and checking each digit.
The problem here I believe is one of promotion.
long double six = 6.0;
long double value1;
long double value2;
value1 = (0.7854 * (pow(10, 5)) * six * (pow(0.033, 2)) * 1.01325 * (1.27 * 11.652375 / 1.01325 - 1.0));
value2 = (0.7854 * (pow(10, 5)) * 6.0 * (pow(0.033, 2)) * 1.01325 * (1.27 * 11.652375 / 1.01325 - 1.0));
Looking at the second calculation we notice that every term in the expression is type double. This means the whole expression will be evaluated to double precision.
However the first calculation contains the variable six that is of type long double. This will cause the entire expression to be calculated at the higher precision of a long double.
So this difference in the calculation's precision is likely the cause of the discrepancy. The whole of the first expression is promoted to long double precision but the second calculation is calculated only to double precision.
In fact a simple change to the code can prove this. If we change the type of the term 6.0 from double to long double by writing 6.0L we will get identical results because both expressions are now calculated to the same precision:
value1 = (0.7854 * (pow(10, 5)) * six * (pow(0.033, 2)) * 1.01325 * (1.27 * 11.652375 / 1.01325 - 1.0));
value2 = (0.7854 * (pow(10, 5)) * 6.0L * (pow(0.033, 2)) * 1.01325 * (1.27 * 11.652375 / 1.01325 - 1.0));

Formatting floats so they all have the same total digits (ex 1.234, 456.7) [duplicate]

I need to round a float to be displayed in a UI. e.g, to one significant figure:
1234 -> 1000
0.12 -> 0.1
0.012 -> 0.01
0.062 -> 0.06
6253 -> 6000
1999 -> 2000
Is there a nice way to do this using the Python library, or do I have to write it myself?
You can use negative numbers to round integers:
>>> round(1234, -3)
1000.0
Thus if you need only most significant digit:
>>> from math import log10, floor
>>> def round_to_1(x):
... return round(x, -int(floor(log10(abs(x)))))
...
>>> round_to_1(0.0232)
0.02
>>> round_to_1(1234243)
1000000.0
>>> round_to_1(13)
10.0
>>> round_to_1(4)
4.0
>>> round_to_1(19)
20.0
You'll probably have to take care of turning float to integer if it's bigger than 1.
%g in string formatting will format a float rounded to some number of significant figures. It will sometimes use 'e' scientific notation, so convert the rounded string back to a float then through %s string formatting.
>>> '%s' % float('%.1g' % 1234)
'1000'
>>> '%s' % float('%.1g' % 0.12)
'0.1'
>>> '%s' % float('%.1g' % 0.012)
'0.01'
>>> '%s' % float('%.1g' % 0.062)
'0.06'
>>> '%s' % float('%.1g' % 6253)
'6000.0'
>>> '%s' % float('%.1g' % 1999)
'2000.0'
If you want to have other than 1 significant decimal (otherwise the same as Evgeny):
>>> from math import log10, floor
>>> def round_sig(x, sig=2):
... return round(x, sig-int(floor(log10(abs(x))))-1)
...
>>> round_sig(0.0232)
0.023
>>> round_sig(0.0232, 1)
0.02
>>> round_sig(1234243, 3)
1230000.0
f'{float(f"{i:.1g}"):g}'
# Or with Python <3.6,
'{:g}'.format(float('{:.1g}'.format(i)))
This solution is different from all of the others because:
it exactly solves the OP question
it does not need any extra package
it does not need any user-defined auxiliary function or mathematical operation
For an arbitrary number n of significant figures, you can use:
print('{:g}'.format(float('{:.{p}g}'.format(i, p=n))))
Test:
a = [1234, 0.12, 0.012, 0.062, 6253, 1999, -3.14, 0., -48.01, 0.75]
b = ['{:g}'.format(float('{:.1g}'.format(i))) for i in a]
# b == ['1000', '0.1', '0.01', '0.06', '6000', '2000', '-3', '0', '-50', '0.8']
Note: with this solution, it is not possible to adapt the number of significant figures dynamically from the input because there is no standard way to distinguish numbers with different numbers of trailing zeros (3.14 == 3.1400). If you need to do so, then non-standard functions like the ones provided in the to-precision package are needed.
To directly answer the question, here's my version using naming from the R function:
import math
def signif(x, digits=6):
if x == 0 or not math.isfinite(x):
return x
digits -= math.ceil(math.log10(abs(x)))
return round(x, digits)
My main reason for posting this answer are the comments complaining that "0.075" rounds to 0.07 rather than 0.08. This is due, as pointed out by "Novice C", to a combination of floating point arithmetic having both finite precision and a base-2 representation. The nearest number to 0.075 that can actually be represented is slightly smaller, hence rounding comes out differently than you might naively expect.
Also note that this applies to any use of non-decimal floating point arithmetic, e.g. C and Java both have the same issue.
To show in more detail, we ask Python to format the number in "hex" format:
0.075.hex()
which gives us: 0x1.3333333333333p-4. The reason for doing this is that the normal decimal representation often involves rounding and hence is not how the computer actually "sees" the number. If you're not used to this format, a couple of useful references are the Python docs and the C standard.
To show how these numbers work a bit, we can get back to our starting point by doing:
0x13333333333333 / 16**13 * 2**-4
which should should print out 0.075. 16**13 is because there are 13 hexadecimal digits after the decimal point, and 2**-4 is because hex exponents are base-2.
Now we have some idea of how floats are represented we can use the decimal module to give us some more precision, showing us what's going on:
from decimal import Decimal
Decimal(0x13333333333333) / 16**13 / 2**4
giving: 0.07499999999999999722444243844 and hopefully explaining why round(0.075, 2) evaluates to 0.07
I have created the package to-precision that does what you want. It allows you to give your numbers more or less significant figures.
It also outputs standard, scientific, and engineering notation with a specified number of significant figures.
In the accepted answer there is the line
>>> round_to_1(1234243)
1000000.0
That actually specifies 8 sig figs. For the number 1234243 my library only displays one significant figure:
>>> from to_precision import to_precision
>>> to_precision(1234243, 1, 'std')
'1000000'
>>> to_precision(1234243, 1, 'sci')
'1e6'
>>> to_precision(1234243, 1, 'eng')
'1e6'
It will also round the last significant figure and can automatically choose what notation to use if a notation isn't specified:
>>> to_precision(599, 2)
'600'
>>> to_precision(1164, 2)
'1.2e3'
To round an integer to 1 significant figure the basic idea is to convert it to a floating point with 1 digit before the point and round that, then convert it back to its original integer size.
To do this we need to know the largest power of 10 less than the integer. We can use floor of the log 10 function for this.
from math import log10, floor
def round_int(i,places):
if i == 0:
return 0
isign = i/abs(i)
i = abs(i)
if i < 1:
return 0
max10exp = floor(log10(i))
if max10exp+1 < places:
return i
sig10pow = 10**(max10exp-places+1)
floated = i*1.0/sig10pow
defloated = round(floated)*sig10pow
return int(defloated*isign)
def round_to_n(x, n):
if not x: return 0
power = -int(math.floor(math.log10(abs(x)))) + (n - 1)
factor = (10 ** power)
return round(x * factor) / factor
round_to_n(0.075, 1) # 0.08
round_to_n(0, 1) # 0
round_to_n(-1e15 - 1, 16) # 1000000000000001.0
Hopefully taking the best of all the answers above (minus being able to put it as a one line lambda ;) ). Haven't explored yet, feel free to edit this answer:
round_to_n(1e15 + 1, 11) # 999999999999999.9
I modified indgar's solution to handle negative numbers and small numbers (including zero).
from math import log10, floor
def round_sig(x, sig=6, small_value=1.0e-9):
return round(x, sig - int(floor(log10(max(abs(x), abs(small_value))))) - 1)
The posted answer was the best available when given, but it has a number of limitations and does not produce technically correct significant figures.
numpy.format_float_positional supports the desired behaviour directly. The following fragment returns the float x formatted to 4 significant figures, with scientific notation suppressed.
import numpy as np
x=12345.6
np.format_float_positional(x, precision=4, unique=False, fractional=False, trim='k')
> 12340.
If you want to round without involving strings, the link I found buried in the comments above:
http://code.activestate.com/lists/python-tutor/70739/
strikes me as best. Then when you print with any string formatting descriptors, you get a reasonable output, and you can use the numeric representation for other calculation purposes.
The code at the link is a three liner: def, doc, and return. It has a bug: you need to check for exploding logarithms. That is easy. Compare the input to sys.float_info.min. The complete solution is:
import sys,math
def tidy(x, n):
"""Return 'x' rounded to 'n' significant digits."""
y=abs(x)
if y <= sys.float_info.min: return 0.0
return round( x, int( n-math.ceil(math.log10(y)) ) )
It works for any scalar numeric value, and n can be a float if you need to shift the response for some reason. You can actually push the limit to:
sys.float_info.min*sys.float_info.epsilon
without provoking an error, if for some reason you are working with miniscule values.
The sigfig package/library covers this. After installing you can do the following:
>>> from sigfig import round
>>> round(1234, 1)
1000
>>> round(0.12, 1)
0.1
>>> round(0.012, 1)
0.01
>>> round(0.062, 1)
0.06
>>> round(6253, 1)
6000
>>> round(1999, 1)
2000
I can't think of anything that would be able to handle this out of the box. But it's fairly well handled for floating point numbers.
>>> round(1.2322, 2)
1.23
Integers are trickier. They're not stored as base 10 in memory, so significant places isn't a natural thing to do. It's fairly trivial to implement once they're a string though.
Or for integers:
def intround(n, sigfigs):
n = str(n)
return n[:sigfigs] + ('0' * (len(n)-sigfigs))
>>> intround(1234, 1)
'1000'
>>> intround(1234, 2)
'1200'
If you would like to create a function that handles any number, my preference would be to convert them both to strings and look for a decimal place to decide what to do:
def roundall1(n, sigfigs):
n = str(n)
try:
sigfigs = n.index('.')
except ValueError:
pass
return intround(n, sigfigs)
Another option is to check for type. This will be far less flexible, and will probably not play nicely with other numbers such as Decimal objects:
def roundall2(n, sigfigs):
if type(n) is int:
return intround(n, sigfigs)
else:
return round(n, sigfigs)
Using python 2.6+ new-style formatting (as %-style is deprecated):
>>> "{0}".format(float("{0:.1g}".format(1216)))
'1000.0'
>>> "{0}".format(float("{0:.1g}".format(0.00356)))
'0.004'
In python 2.7+ you can omit the leading 0s.
I adapted one of the answers. I like this:
def sigfiground(number:float, ndigits=3)->float:
return float(f"{number:.{ndigits}g}")
I use it when I still want a float (I do formatting elsewhere).
I ran into this as well but I needed control over the rounding type. Thus, I wrote a quick function (see code below) that can take value, rounding type, and desired significant digits into account.
import decimal
from math import log10, floor
def myrounding(value , roundstyle='ROUND_HALF_UP',sig = 3):
roundstyles = [ 'ROUND_05UP','ROUND_DOWN','ROUND_HALF_DOWN','ROUND_HALF_UP','ROUND_CEILING','ROUND_FLOOR','ROUND_HALF_EVEN','ROUND_UP']
power = -1 * floor(log10(abs(value)))
value = '{0:f}'.format(value) #format value to string to prevent float conversion issues
divided = Decimal(value) * (Decimal('10.0')**power)
roundto = Decimal('10.0')**(-sig+1)
if roundstyle not in roundstyles:
print('roundstyle must be in list:', roundstyles) ## Could thrown an exception here if you want.
return_val = decimal.Decimal(divided).quantize(roundto,rounding=roundstyle)*(decimal.Decimal(10.0)**-power)
nozero = ('{0:f}'.format(return_val)).rstrip('0').rstrip('.') # strips out trailing 0 and .
return decimal.Decimal(nozero)
for x in list(map(float, '-1.234 1.2345 0.03 -90.25 90.34543 9123.3 111'.split())):
print (x, 'rounded UP: ',myrounding(x,'ROUND_UP',3))
print (x, 'rounded normal: ',myrounding(x,sig=3))
This function does a normal round if the number is bigger than 10**(-decimal_positions), otherwise adds more decimal until the number of meaningful decimal positions is reached:
def smart_round(x, decimal_positions):
dp = - int(math.log10(abs(x))) if x != 0.0 else int(0)
return round(float(x), decimal_positions + dp if dp > 0 else decimal_positions)
Hope it helps.
https://stackoverflow.com/users/1391441/gabriel, does the following address your concern about rnd(.075, 1)?
Caveat: returns value as a float
def round_to_n(x, n):
fmt = '{:1.' + str(n) + 'e}' # gives 1.n figures
p = fmt.format(x).split('e') # get mantissa and exponent
# round "extra" figure off mantissa
p[0] = str(round(float(p[0]) * 10**(n-1)) / 10**(n-1))
return float(p[0] + 'e' + p[1]) # convert str to float
>>> round_to_n(750, 2)
750.0
>>> round_to_n(750, 1)
800.0
>>> round_to_n(.0750, 2)
0.075
>>> round_to_n(.0750, 1)
0.08
>>> math.pi
3.141592653589793
>>> round_to_n(math.pi, 7)
3.141593
This returns a string, so that results without fractional parts, and small values which would otherwise appear in E notation are shown correctly:
def sigfig(x, num_sigfig):
num_decplace = num_sigfig - int(math.floor(math.log10(abs(x)))) - 1
return '%.*f' % (num_decplace, round(x, num_decplace))
Given a question so thoroughly answered why not add another
This suits my aesthetic a little better, though many of the above are comparable
import numpy as np
number=-456.789
significantFigures=4
roundingFactor=significantFigures - int(np.floor(np.log10(np.abs(number)))) - 1
rounded=np.round(number, roundingFactor)
string=rounded.astype(str)
print(string)
This works for individual numbers and numpy arrays, and should function fine for negative numbers.
There's one additional step we might add - np.round() returns a decimal number even if rounded is an integer (i.e. for significantFigures=2 we might expect to get back -460 but instead we get -460.0). We can add this step to correct for that:
if roundingFactor<=0:
rounded=rounded.astype(int)
Unfortunately, this final step won't work for an array of numbers - I'll leave that to you dear reader to figure out if you need.
import math
def sig_dig(x, n_sig_dig):
num_of_digits = len(str(x).replace(".", ""))
if n_sig_dig >= num_of_digits:
return x
n = math.floor(math.log10(x) + 1 - n_sig_dig)
result = round(10 ** -n * x) * 10 ** n
return float(str(result)[: n_sig_dig + 1])
>>> sig_dig(1234243, 3)
>>> sig_dig(243.3576, 5)
1230.0
243.36
Most of these answers involve the math, decimal and/or numpy imports or output values as strings. Here is a simple solution in base python that handles both large and small numbers and outputs a float:
def sig_fig_round(number, digits=3):
power = "{:e}".format(number).split('e')[1]
return round(number, -(int(power) - digits))
A simple variant using the standard decimal library
from decimal import Decimal
def to_significant_figures(v: float, n_figures: int) -> str:
d = Decimal(v)
d = d.quantize(Decimal((0, (), d.adjusted() - n_figures + 1)))
return str(d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize())
Testing it
>>> to_significant_figures(1.234567, 3)
'1.23'
>>> to_significant_figures(1234567, 3)
'1230000'
>>> to_significant_figures(1.23, 7)
'1.23'
>>> to_significant_figures(123, 7)
'123'
This function takes both positive and negative numbers and does the proper significant digit rounding.
from math import floor
def significant_arithmetic_rounding(n, d):
'''
This function takes a floating point number and the no. of significant digit d, perform significant digits
arithmetic rounding and returns the floating point number after rounding
'''
if n == 0:
return 0
else:
# Checking whether the no. is negative or positive. If it is negative we will take the absolute value of it and proceed
neg_flag = 0
if n < 0:
neg_flag = 1
n = abs(n)
n1 = n
# Counting the no. of digits to the left of the decimal point in the no.
ld = 0
while(n1 >= 1):
n1 /= 10
ld += 1
n1 = n
# Counting the no. of zeros to the right of the decimal point and before the first significant digit in the no.
z = 0
if ld == 0:
while(n1 <= 0.1):
n1 *= 10
z += 1
n1 = n
# No. of digits to be considered after decimal for rounding
rd = (d - ld) + z
n1 *= 10**rd
# Increase by 0.5 and take the floor value for rounding
n1 = floor(n1+0.5)
# Placing the decimal point at proper position
n1 /= 10 ** rd
# If the original number is negative then make it negative
if neg_flag == 1:
n1 = 0 - n1
return n1
Testing:
>>> significant_arithmetic_rounding(1234, 3)
1230.0
>>> significant_arithmetic_rounding(123.4, 3)
123.0
>>> significant_arithmetic_rounding(0.0012345, 3)
0.00123
>>> significant_arithmetic_rounding(-0.12345, 3)
-0.123
>>> significant_arithmetic_rounding(-30.15345, 3)
-30.2
Easier to know an answer works for your needs when it includes examples. The following is built on previous solutions, but offers a more general function which can round to 1, 2, 3, 4, or any number of significant digits.
import math
# Given x as float or decimal, returns as string a number rounded to "sig" significant digts
# Return as string in order to control significant digits, could be a float or decimal
def round_sig(x, sig=2):
r = round(x, sig-int(math.floor(math.log10(abs(x))))-1)
floatsig = "%." + str(sig) + "g"
return "%d"%r if abs(r) >= 10**(sig-1) else '%s'%float(floatsig % r)
>>> a = [1234, 123.4, 12.34, 1.234, 0.1234, 0.01234, 0.25, 1999, -3.14, -48.01, 0.75]
>>> [print(i, "->", round_sig(i,1), round_sig(i), round_sig(i,3), round_sig(i,4)) for i in a]
1234 -> 1000 1200 1230 1234
123.4 -> 100 120 123 123.4
12.34 -> 10 12 12.3 12.34
1.234 -> 1 1.2 1.23 1.234
0.1234 -> 0.1 0.12 0.123 0.1234
0.01234 -> 0.01 0.012 0.0123 0.01234
0.25 -> 0.2 0.25 0.25 0.25
1999 -> 2000 2000 2000 1999
-3.14 -> -3 -3.1 -3.14 -3.14
-48.01 -> -50 -48 -48.0 -48.01
0.75 -> 0.8 0.75 0.75 0.75
in very cases, the number of significant is depend on to the evaluated process, e.g. error. I wrote the some codes which returns a number according to it's error (or with some desired digits) and also in string form (which doesn't eliminate right side significant zeros)
import numpy as np
def Sig_Digit(x, *N,):
if abs(x) < 1.0e-15:
return(1)
N = 1 if N ==() else N[0]
k = int(round(abs(N)-1))-int(np.floor(np.log10(abs(x))))
return(k);
def Sig_Format(x, *Error,):
if abs(x) < 1.0e-15:
return('{}')
Error = 1 if Error ==() else abs(Error[0])
k = int(np.floor(np.log10(abs(x))))
z = x/10**k
k = -Sig_Digit(Error, 1)
m = 10**k
y = round(x*m)/m
if k < 0:
k = abs(k)
if z >= 9.5:
FMT = '{:'+'{}'.format(1+k)+'.'+'{}'.format(k-1)+'f}'
else:
FMT = '{:'+'{}'.format(2+k)+'.'+'{}'.format(k)+'f}'
elif k == 0:
if z >= 9.5:
FMT = '{:'+'{}'.format(1+k)+'.0e}'
else:
FMT = '{:'+'{}'.format(2+k)+'.0f}'
else:
FMT = '{:'+'{}'.format(2+k)+'.'+'{}'.format(k)+'e}'
return(FMT)
def Sci_Format(x, *N):
if abs(x) < 1.0e-15:
return('{}')
N = 1 if N ==() else N[0]
N = int(round(abs(N)-1))
y = abs(x)
k = int(np.floor(np.log10(y)))
z = x/10**k
k = k-N
m = 10**k
y = round(x/m)*m
if k < 0:
k = abs(k)
if z >= 9.5:
FMT = '{:'+'{}'.format(1+k)+'.'+'{}'.format(k-1)+'f}'
else:
FMT = '{:'+'{}'.format(2+k)+'.'+'{}'.format(k)+'f}'
elif k == 0:
if z >= 9.5:
FMT = '{:'+'{}'.format(1+k)+'.0e}'
else:
FMT = '{:'+'{}'.format(2+k)+'.0f}'
else:
FMT = '{:'+'{}'.format(2+N)+'.'+'{}'.format(N)+'e}'
return(FMT)
def Significant(x, *Error):
N = 0 if Error ==() else Sig_Digit(abs(Error[0]), 1)
m = 10**N
y = round(x*m)/m
return(y)
def Scientific(x, *N):
m = 10**Sig_Digit(x, *N)
y = round(x*m)/m
return(y)
def Scientific_Str(x, *N,):
FMT = Sci_Format(x, *N)
return(FMT.format(x))
def Significant_Str(x, *Error,):
FMT = Sig_Format(x, *Error)
return(FMT.format(x))
test code:
X = [19.03345607, 12.075, 360.108321344, 4325.007605343]
Error = [1.245, 0.1245, 0.0563, 0.01245, 0.001563, 0.0004603]
for x in X:
for error in Error:
print(x,'+/-',error, end=' \t==> ')
print(' (',Significant_Str(x, error), '+/-', Scientific_Str(error),')')
print out:
19.03345607 +/- 1.245 ==> ( 19 +/- 1 )
19.03345607 +/- 0.1245 ==> ( 19.0 +/- 0.1 )
19.03345607 +/- 0.0563 ==> ( 19.03 +/- 0.06 )
19.03345607 +/- 0.01245 ==> ( 19.03 +/- 0.01 )
19.03345607 +/- 0.001563 ==> ( 19.033 +/- 0.002 )
19.03345607 +/- 0.0004603 ==> ( 19.0335 +/- 0.0005 )
12.075 +/- 1.245 ==> ( 12 +/- 1 )
12.075 +/- 0.1245 ==> ( 12.1 +/- 0.1 )
12.075 +/- 0.0563 ==> ( 12.07 +/- 0.06 )
12.075 +/- 0.01245 ==> ( 12.07 +/- 0.01 )
12.075 +/- 0.001563 ==> ( 12.075 +/- 0.002 )
12.075 +/- 0.0004603 ==> ( 12.0750 +/- 0.0005 )
360.108321344 +/- 1.245 ==> ( 360 +/- 1 )
360.108321344 +/- 0.1245 ==> ( 360.1 +/- 0.1 )
360.108321344 +/- 0.0563 ==> ( 360.11 +/- 0.06 )
360.108321344 +/- 0.01245 ==> ( 360.11 +/- 0.01 )
360.108321344 +/- 0.001563 ==> ( 360.108 +/- 0.002 )
360.108321344 +/- 0.0004603 ==> ( 360.1083 +/- 0.0005 )
4325.007605343 +/- 1.245 ==> ( 4325 +/- 1 )
4325.007605343 +/- 0.1245 ==> ( 4325.0 +/- 0.1 )
4325.007605343 +/- 0.0563 ==> ( 4325.01 +/- 0.06 )
4325.007605343 +/- 0.01245 ==> ( 4325.01 +/- 0.01 )
4325.007605343 +/- 0.001563 ==> ( 4325.008 +/- 0.002 )
4325.007605343 +/- 0.0004603 ==> ( 4325.0076 +/- 0.0005 )

Precision to drand48

How can I add precision to drand48() in C++?
I am using it in a function like:
double x = drand48()%1000+1;
to generate numbers below 1000.
But then I get this error:
error: invalid operands of types ‘double’ and ‘int’ to binary ‘operator%’
This does not happen when I use:
double x = rand()%1000+1;
Why and what is the difference between rand() and drand48()?
drand48 returns a number from the interval [0.0, 1.0). Are you looking for a number between 1 and 1000? In this case, you need to multiply by 999 and add 1.
Actually, what are you expecting?
drand48() returns a double, whereas rand() returns int.
Furthermore, drand48() returns a value that's distributed between [0.0, 1.0), so your formula needs to change:
double x = drand48() * 1000.0 + 1; // floating-point values from [1, 1001)
or
double x = (int)(drand48() * 1000.0) + 1; // integer values from [1, 1000]
You could either scale the result of drand48() as above, or use lrand48() with your existing formula.
drand48 returns a double in the range of 0.0 to 1.0. You want to multiply that by the range you're looking to generate. double x = drand48() * 1000.0