[#903] config: support floating-point sizes

Rounding is done using the suffix boundary (i.e. floating point value
with `gb` suffix will return size which is an integer number of
megabytes).

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2021-11-11 14:49:27 +03:00 committed by LeL
parent e7fd980951
commit aba09bb853
4 changed files with 31 additions and 5 deletions

View file

@ -1,6 +1,7 @@
package config package config
import ( import (
"math/bits"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@ -164,12 +165,28 @@ func SizeInBytesSafe(c *Config, name string) uint64 {
// with minor corrections (allow to use both `k` and `kb` forms. // with minor corrections (allow to use both `k` and `kb` forms.
// Seems like viper allows to convert sizes but corresponding parser in `cast` package // Seems like viper allows to convert sizes but corresponding parser in `cast` package
// is missing. // is missing.
func safeMul(a, b uint64) uint64 {
c := a * b // safeMul returns size*multiplier, rounding down to the
if a > 1 && b > 1 && c/b != a { // multiplier/1024 number of bytes.
// Returns 0 if overflow is detected.
func safeMul(size float64, multiplier uint64) uint64 {
n := uint64(size)
f := uint64((size - float64(n)) * 1024)
if f != 0 && multiplier != 1 {
s := n<<10 + f
if s < n {
return 0
}
n = s
multiplier >>= 10
}
hi, lo := bits.Mul64(n, multiplier)
if hi != 0 {
return 0 return 0
} }
return c return lo
} }
// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes
@ -203,6 +220,6 @@ func parseSizeInBytes(sizeStr string) uint64 {
} }
} }
size := cast.ToUint64(sizeStr) size := cast.ToFloat64(sizeStr)
return safeMul(size, multiplier) return safeMul(size, multiplier)
} }

View file

@ -130,6 +130,9 @@ func TestSizeInBytes(t *testing.T) {
require.EqualValues(t, 12*mb, config.SizeInBytesSafe(c, "size_mb")) require.EqualValues(t, 12*mb, config.SizeInBytesSafe(c, "size_mb"))
require.EqualValues(t, 4*gb, config.SizeInBytesSafe(c, "size_gb")) require.EqualValues(t, 4*gb, config.SizeInBytesSafe(c, "size_gb"))
require.EqualValues(t, 5*tb, config.SizeInBytesSafe(c, "size_tb")) require.EqualValues(t, 5*tb, config.SizeInBytesSafe(c, "size_tb"))
require.EqualValues(t, 12, config.SizeInBytesSafe(c, "size_i_am_not_very_clever"))
require.EqualValues(t, tb/2, config.SizeInBytesSafe(c, "size_float"))
require.EqualValues(t, uint64(14*gb+(gb*123/1000/mb*mb)), config.SizeInBytesSafe(c, "size_float_big"))
require.EqualValues(t, 2048, config.SizeInBytesSafe(c, "size_bytes")) require.EqualValues(t, 2048, config.SizeInBytesSafe(c, "size_bytes"))
require.EqualValues(t, 123456, config.SizeInBytesSafe(c, "size_bytes_no_suffix")) require.EqualValues(t, 123456, config.SizeInBytesSafe(c, "size_bytes_no_suffix"))
}) })

View file

@ -54,6 +54,9 @@
"size_mb": "12m", "size_mb": "12m",
"size_gb": "4g", "size_gb": "4g",
"size_tb": "5 TB", "size_tb": "5 TB",
"size_float": ".5t",
"size_float_big": "14.123 gb",
"size_i_am_not_very_clever": "12.12345678",
"size_bytes": "2048b", "size_bytes": "2048b",
"size_bytes_no_suffix": 123456 "size_bytes_no_suffix": 123456
}, },

View file

@ -47,6 +47,9 @@ sizes:
size_mb: 12m size_mb: 12m
size_gb: 4g size_gb: 4g
size_tb: 5 TB size_tb: 5 TB
size_float: .5t
size_float_big: 14.123 gb
size_i_am_not_very_clever: 12.12345678
size_bytes: 2048b size_bytes: 2048b
size_bytes_no_suffix: 123456 size_bytes_no_suffix: 123456