{-
Copyright (C) 2006-2008 John Goerzen <jgoerzen@complete.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}{- |
Module : Data.Quantity
Copyright : Copyright (C) 2006-2008 John Goerzen
License : GNU GPL, version 2 or above
Maintainer : John Goerzen <jgoerzen@complete.org>
Stability : provisional
Portability: portable
Tools for rendering sizes
Written by John Goerzen, jgoerzen\@complete.org -}moduleData.Quantity(renderNum,renderNums,parseNum,parseNumInt,quantifyNum,quantifyNums,SizeOpts(..),binaryOpts,siOpts)whereimportData.ListimportText.PrintfimportData.Char{- | The options for 'quantifyNum' and 'renderNum' -}dataSizeOpts=SizeOpts{base::Int,-- ^ The base from which calculations are madepowerIncr::Int,-- ^ The increment to the power for each new suffixfirstPower::Int,-- ^ The first power for which suffixes are givensuffixes::String-- ^ The suffixes themselves}{- | Predefined definitions for byte measurement in groups of 1024, from 0 to
2**80 -}binaryOpts::SizeOptsbinaryOpts=SizeOpts{base=2,firstPower=0,suffixes=" KMGTPEZY",powerIncr=10}{- | Predefined definitions for SI measurement, from 10**-24 to 10**24. -}siOpts::SizeOptssiOpts=SizeOpts{base=10,firstPower=-24,suffixes="yzafpnum kMGTPEZY",powerIncr=3}{- | Takes a number and returns a new (quantity, suffix) combination.
The space character is used as the suffix for items around 0. -}quantifyNum::(Orda,Reala,Floatingb,Ordb)=>SizeOpts->a->(b,Char)quantifyNumoptsn=(\(x,s)->(headx,s))$quantifyNumsopts[n]{- | Like 'quantifyNum', but takes a list of numbers. The first number in
the list will be evaluated for the suffix. The same suffix and scale will
be used for the remaining items in the list. Please see 'renderNums' for
an example of how this works.
It is invalid to use this function on an empty list. -}quantifyNums::(Orda,Reala,Floatingb,Ordb)=>SizeOpts->[a]->([b],Char)quantifyNums_[]=error"Attempt to use quantifyNums on an empty list"quantifyNumsopts(headnum:xs)=(map(\n->procnumn)(headnum:xs),suffix)wherenumber=casefromRational.toRational$headnumof0->1x->xincrList=mapidx2pwr[0..length(suffixesopts)-1]incrIdxList=zipincrList[0..]idx2pwri=i*powerIncropts+firstPoweroptsfinderfunc(x,_)=(fromIntegral$baseopts)**(fromIntegralx)<=(absnumber)-- Find the largest item that does not exceed the number given.-- If the number is larger than the larger item in the list,-- that's fine; we'll just write it in terms of what we have.(usedexp,expidx)=casefindfinderfunc(reverseincrIdxList)ofJustx->xNothing->headincrIdxList-- If not found, it's smaller than the firstsuffix=(suffixesopts!!(fromIntegralexpidx))procnumn=(fromRational.toRational$n)/((fromIntegral(baseopts)**(fromIntegralusedexp)))--(posres, possuf) = quantifyNum opts (headnum * (-1)){- | Render a number into a string, based on the given quantities. This is
useful for displaying quantities in terms of bytes or in SI units. Give this
function the 'SizeOpts' for the desired output, and a precision (number of
digits to the right of the decimal point), and you get a string output.
Here are some examples:
> Data.Quantity> renderNum binaryOpts 0 1048576
> "1M"
> Data.Quantity> renderNum binaryOpts 2 10485760
> "10.00M"
> Data.Quantity> renderNum binaryOpts 3 1048576
> "1.000M"
> Data.Quantity> renderNum binaryOpts 3 1500000
> "1.431M"
> Data.Quantity> renderNum binaryOpts 2 (1500 ** 3)
> "3.14G"
> Data.Quantity> renderNum siOpts 2 1024
> "1.02k"
> Data.Quantity> renderNum siOpts 2 1048576
> "1.05M"
> Data.Quantity> renderNum siOpts 2 0.001
> "1.00m"
> Data.Quantity> renderNum siOpts 2 0.0001
> "100.00u"
If you want more control over the output, see 'quantifyNum'. -}renderNum::(Orda,Reala)=>SizeOpts->Int-- ^ Precision of the result->a-- ^ The number to examine->StringrenderNumoptsprecnumber=(printf("%."++showprec++"g")num)++[suffix]where(num,suffix)=(quantifyNumoptsnumber)::(Double,Char){- | Like 'renderNum', but operates on a list of numbers. The first number
in the list will be evaluated for the suffix. The same suffix and scale will
be used for the remaining items in the list. See 'renderNum' for more
examples.
Also, unlike 'renderNum', the %f instead of %g printf format is used so that
\"scientific\" notation is avoided in the output.
Examples:
> *Data.Quantity> renderNums binaryOpts 3 [1500000, 10240, 104857600]
> ["1.431M","0.010M","100.000M"]
> *Data.Quantity> renderNums binaryOpts 3 [1500, 10240, 104857600]
> ["1.465K","10.000K","102400.000K"]
-}renderNums::(Orda,Reala)=>SizeOpts->Int-- ^ Prevision of the result->[a]-- ^ The numbers to examine->[String]-- ^ ResultrenderNumsoptsprecnumbers=mapprintitconvnumswhereprintitnum=(printf("%."++showprec++"f")num)++[suffix](convnums,suffix)=(quantifyNumsoptsnumbers)::([Double],Char){- | Parses a String, possibly generated by 'renderNum'. Parses the suffix
and applies it to the number, which is read via the Read class.
Returns Left "error message" on error, or Right number on successful parse.
If you want an Integral result, the convenience function 'parseNumInt' is for
you.
-}parseNum::(Reada,Fractionala)=>SizeOpts-- ^ Information on how to parse this data->Bool-- ^ Whether to perform a case-insensitive match->String-- ^ The string to parse->EitherStringaparseNumoptsinsensitiveinp=casereadsinpof[]->Left"Couldn't parse numeric component of input"[(num,"")]->Rightnum-- No suffix; pass number unhindered[(num,[suffix])]->caselookup(caseTransformersuffix)suffixMapofNothing->Left$"Unrecognized suffix "++showsuffixJustpower->Right$num*multiplierpower[(_,suffix)]->Left$"Multi-character suffix "++showsuffix_->Left"Multiple parses for input"wheresuffixMap=zip(mapcaseTransformer.suffixes$opts)(iterate(+(powerIncropts))(firstPoweropts))caseTransformerx|insensitive=toLowerx|otherwise=xmultiplier::(Reada,Fractionala)=>Int->amultiplierpower=fromRational.toRational$fromIntegral(baseopts)**fromIntegralpower{- | Parse a number as with 'parseNum', but return the result as
an 'Integral'. Any type such as Integer, Int, etc. can be used for the
result type.
This function simply calls 'round' on the result of 'parseNum'. A
'Double' is used internally for the parsing of the numeric component.
By using this function, a user can still say something like 1.5M and get an
integral result. -}parseNumInt::(Reada,Integrala)=>SizeOpts-- ^ Information on how to parse this data->Bool-- ^ Whether to perform a case-insensitive match->String-- ^ The string to parse->EitherStringaparseNumIntoptsinsensitiveinp=case(parseNumoptsinsensitiveinp)::EitherStringDoubleofLeftx->LeftxRightn->Right(roundn)