After some research, I manage to get what I want.
The function to get the engineering format work in a few steps :
1. Dissociate the exponent from the mantissa
It's necessary to get the exponent apart of the mantissa.
The function decodeFloat
(provided by base
) decode a floating point number and return both the mantissa and the exponent in power of 2 (mant2 * 2 ^ ex2).
2. Get the mantissa and exponent expressed in the correct base
A conversion in power of 10 is required. This is the role of this function.
decompose :: Double -> (Double,Int)
decompose val = if mant2 > 0
then (mant10,ex10)
else (-mant10,ex10)
where
(mant2,ex2) = decodeFloat val
res = logBase 10 (fromIntegral (abs mant2)::Double) + logBase 10 (2 ** (fromIntegral ex2::Double))
ex10 = floor res
mant10 = 10**(res - (fromIntegral ex10::Double))
3. Set the exponent at a multiple of 3
The function ingen
test the result of the integer division of the exponent and perform the adjustement on mantissa and exponent.
ingen :: Double -> (Double,Int)
ingen val
| mod ex 3 == 0 = (mant,ex)
| mod ex 3 == 1 = (mant*10,ex-1)
| mod ex 3 == 2 = (mant*100,ex-2)
where
(mant,ex) = decompose val
Here are some conversions :
Prelude> ingen 10e7
(99.99999999999979,6)
Prelude> ingen 10e-41
(100.0,-42)
Prelude> ingen (-72364e81)
(-72.36399999999853,84)
I performed some tests with quickCheck on a wide range and a large amount of value. The conversion seems to work well despite a very small difference of value (rounding during the calculations due to the precision?).
However, another verifications should be made.
If you find a mistake or an improvement in these functions, please share.