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
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.
result = formatBiggestFloat(f, format, precision, decimalSep)
proc formatSize*(bytes: BiggestInt, decimalSep = '.'): string =
## Rounds and formats `bytes`. Examples:
proc trimZeros*(x: var string) {.noSideEffect.} =
## 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
##
## formatSize(1'i64 shl 31 + 300'i64) == "2.204GB"
## formatSize(4096) == "4KB"
## formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
## 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 =
let bs = $b
insertSep($a) & decimalSep & bs.substr(0, 2) & c
let gigabytes = bytes shr 30
let megabytes = bytes shr 20
let kilobytes = bytes shr 10
if gigabytes != 0:
result = frmt(gigabytes, megabytes, "GB")
elif megabytes != 0:
result = frmt(megabytes, kilobytes, "MB")
elif kilobytes != 0:
result = frmt(kilobytes, bytes, "KB")
const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]
const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"]
var
xb: int64 = bytes
fbytes: float
last_xb: int64 = bytes
matchedIndex: int
prefixes: array[9, string]
if prefix == bpColloquial:
prefixes = collPrefixes
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 =
var i = 0
@@ -1682,9 +1735,14 @@ when isMainModule:
["1,0e-11", "1,0e-011"]
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
when not defined(testing):
echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB"
echo formatSize(1'i64 shl 31)
block: # formatSize tests
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"] ==
"The cat eats fish."

View File

@@ -50,6 +50,11 @@ Changes affecting backwards compatibility
than changing the operation for even and odd numbers.
- 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).
- 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
@@ -63,6 +68,8 @@ Library Additions
versions are for ``char`` and ``set[char]``.
- 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).
- Added ``trimZeros`` to ```strutils.nim`` to trim trailing zeros in a
floating point number.
Compiler Additions