Merge pull request #4250 from abudden/4198-formatSize

Reimplementation of formatSize to return a more meaningful value
This commit is contained in:
Andreas Rumpf 2016-06-02 09:54:11 +02:00
commit c11de219e5
2 changed files with 85 additions and 20 deletions

View file

@ -1465,28 +1465,81 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault,
## after the decimal point for Nim's ``float`` type. ## after the decimal point for Nim's ``float`` type.
result = formatBiggestFloat(f, format, precision, decimalSep) result = formatBiggestFloat(f, format, precision, decimalSep)
proc formatSize*(bytes: BiggestInt, decimalSep = '.'): string = proc trimZeros*(x: var string) {.noSideEffect.} =
## Rounds and formats `bytes`. Examples: ## Trim trailing zeros from a formatted floating point
## value (`x`). Modifies the passed value.
var spl: seq[string]
if x.contains('.') or x.contains(','):
if x.contains('e'):
spl= x.split('e')
x = spl[0]
while x[x.high] == '0':
x.setLen(x.len-1)
if x[x.high] in [',', '.']:
x.setLen(x.len-1)
if spl.len > 0:
x &= "e" & spl[1]
type
BinaryPrefixMode* = enum ## the different names for binary prefixes
bpIEC, # use the IEC/ISO standard prefixes such as kibi
bpColloquial # use the colloquial kilo, mega etc
proc formatSize*(bytes: int64,
decimalSep = '.',
prefix = bpIEC,
includeSpace = false): string =
## Rounds and formats `bytes`.
##
## By default, uses the IEC/ISO standard binary prefixes, so 1024 will be
## formatted as 1KiB. Set prefix to `bpColloquial` to use the colloquial
## names from the SI standard (e.g. k for 1000 being reused as 1024).
##
## `includeSpace` can be set to true to include the (SI preferred) space
## between the number and the unit (e.g. 1 KiB).
##
## Examples:
## ##
## .. code-block:: nim ## .. code-block:: nim
## ##
## formatSize(1'i64 shl 31 + 300'i64) == "2.204GB" ## formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
## formatSize(4096) == "4KB" ## formatSize((2.234*1024*1024).int) == "2.234MiB"
## formatSize(4096, includeSpace=true) == "4 KiB"
## formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
## formatSize(4096) == "4KiB"
## formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
## ##
template frmt(a, b, c: expr): expr = const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]
let bs = $b const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"]
insertSep($a) & decimalSep & bs.substr(0, 2) & c var
let gigabytes = bytes shr 30 xb: int64 = bytes
let megabytes = bytes shr 20 fbytes: float
let kilobytes = bytes shr 10 last_xb: int64 = bytes
if gigabytes != 0: matchedIndex: int
result = frmt(gigabytes, megabytes, "GB") prefixes: array[9, string]
elif megabytes != 0: if prefix == bpColloquial:
result = frmt(megabytes, kilobytes, "MB") prefixes = collPrefixes
elif kilobytes != 0:
result = frmt(kilobytes, bytes, "KB")
else: else:
result = insertSep($bytes) & "B" prefixes = iecPrefixes
# Iterate through prefixes seeing if value will be greater than
# 0 in each case
for index in 1..<prefixes.len:
last_xb = xb
xb = bytes div (1'i64 shl (index*10))
matchedIndex = index
if xb == 0:
xb = last_xb
matchedIndex = index - 1
break
# xb has the integer number for the latest value; index should be correct
fbytes = bytes.float / (1'i64 shl (matchedIndex*10)).float
result = formatFloat(fbytes, format=ffDecimal, precision=3, decimalSep=decimalSep)
result.trimZeros()
if includeSpace:
result &= " "
result &= prefixes[matchedIndex]
result &= "B"
proc findNormalized(x: string, inArray: openArray[string]): int = proc findNormalized(x: string, inArray: openArray[string]): int =
var i = 0 var i = 0
@ -1682,9 +1735,14 @@ when isMainModule:
["1,0e-11", "1,0e-011"] ["1,0e-11", "1,0e-011"]
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
when not defined(testing):
echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB" block: # formatSize tests
echo formatSize(1'i64 shl 31) doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
doAssert formatSize((2.234*1024*1024).int) == "2.234MiB"
doAssert formatSize(4096) == "4KiB"
doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
doAssert formatSize(4096, includeSpace=true) == "4 KiB"
doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] ==
"The cat eats fish." "The cat eats fish."

View file

@ -50,6 +50,11 @@ Changes affecting backwards compatibility
than changing the operation for even and odd numbers. than changing the operation for even and odd numbers.
- The ``round`` function now accepts a ``places`` argument to round to a - The ``round`` function now accepts a ``places`` argument to round to a
given number of places (e.g. round 4.35 to 4.4 if ``places`` is 1). given number of places (e.g. round 4.35 to 4.4 if ``places`` is 1).
- In ``strutils.nim``, ``formatSize`` now returns a number representing the
size in conventional decimal format (e.g. 2.234GB meaning 2.234 GB rather
than meaning 2.285 GB as in the previous implementation). By default it
also uses IEC prefixes (KiB, MiB) etc and optionally uses colloquial names
(kB, MB etc) and the (SI-preferred) space.
Library Additions Library Additions
@ -63,6 +68,8 @@ Library Additions
versions are for ``char`` and ``set[char]``. versions are for ``char`` and ``set[char]``.
- Added ``splitDecimal`` to ``math.nim`` to split a floating point value - Added ``splitDecimal`` to ``math.nim`` to split a floating point value
into an integer part and a floating part (in the range -1<x<1). into an integer part and a floating part (in the range -1<x<1).
- Added ``trimZeros`` to ```strutils.nim`` to trim trailing zeros in a
floating point number.
Compiler Additions Compiler Additions