Add ceilDiv to math (#18596)

* Use assert in runnableExamples and improve boundary check
* Add more tests for ceilDiv
* Fix comment in ceilDiv
* Calling ceilDiv with int type T such like sizeof(T) > 8 is error
This commit is contained in:
Tomohiro
2021-08-19 18:35:40 +09:00
committed by GitHub
parent 394f4ac7bb
commit 373bbd9bb4
3 changed files with 94 additions and 0 deletions

View File

@@ -148,6 +148,8 @@
- Added `clamp` in `math` which allows using a `Slice` to clamp to a value.
- Added `ceilDiv` in `math` for round up integer division.
- The JSON module can now handle integer literals and floating point literals of
arbitrary length and precision.
Numbers that do not fit the underlying `BiggestInt` or `BiggestFloat` fields are

View File

@@ -941,6 +941,58 @@ func euclMod*[T: SomeNumber](x, y: T): T {.since: (1, 5, 1).} =
if result < 0:
result += abs(y)
func ceilDiv*[T: SomeInteger](x, y: T): T {.inline, since: (1, 5, 1).} =
## Ceil division is conceptually defined as `ceil(x / y)`.
##
## Assumes `x >= 0` and `y > 0` (and `x + y - 1 <= high(T)` if T is SomeUnsignedInt).
##
## This is different from the `system.div <system.html#div,int,int>`_
## operator, which works like `trunc(x / y)`.
## That is, `div` rounds towards `0` and `ceilDiv` rounds up.
##
## This function has the above input limitation, because that allows the
## compiler to generate faster code and it is rarely used with
## negative values or unsigned integers close to `high(T)/2`.
## If you need a `ceilDiv` that works with any input, see:
## https://github.com/demotomohiro/divmath.
##
## **See also:**
## * `system.div proc <system.html#div,int,int>`_ for integer division
## * `floorDiv func <#floorDiv,T,T>`_ for integer division which rounds down.
runnableExamples:
assert ceilDiv(12, 3) == 4
assert ceilDiv(13, 3) == 5
when sizeof(T) == 8:
type UT = uint64
elif sizeof(T) == 4:
type UT = uint32
elif sizeof(T) == 2:
type UT = uint16
elif sizeof(T) == 1:
type UT = uint8
else:
{.fatal: "Unsupported int type".}
assert x >= 0 and y > 0
when T is SomeUnsignedInt:
assert x + y - 1 >= x
# If the divisor is const, the backend C/C++ compiler generates code without a `div`
# instruction, as it is slow on most CPUs.
# If the divisor is a power of 2 and a const unsigned integer type, the
# compiler generates faster code.
# If the divisor is const and a signed integer, generated code becomes slower
# than the code with unsigned integers, because division with signed integers
# need to works for both positive and negative value without `idiv`/`sdiv`.
# That is why this code convert parameters to unsigned.
# This post contains a comparison of the performance of signed/unsigned integers:
# https://github.com/nim-lang/Nim/pull/18596#issuecomment-894420984.
# If signed integer arguments were not converted to unsigned integers,
# `ceilDiv` wouldn't work for any positive signed integer value, because
# `x + (y - 1)` can overflow.
((x.UT + (y.UT - 1.UT)) div y.UT).T
func frexp*[T: float32|float64](x: T): tuple[frac: T, exp: int] {.inline.} =
## Splits `x` into a normalized fraction `frac` and an integral power of 2 `exp`,
## such that `abs(frac) in 0.5..<1` and `x == frac * 2 ^ exp`, except for special

View File

@@ -106,6 +106,46 @@ template main() =
doAssert euclDiv(-9, -3) == 3
doAssert euclMod(-9, -3) == 0
block: # ceilDiv
doAssert ceilDiv(8, 3) == 3
doAssert ceilDiv(8, 4) == 2
doAssert ceilDiv(8, 5) == 2
doAssert ceilDiv(11, 3) == 4
doAssert ceilDiv(12, 3) == 4
doAssert ceilDiv(13, 3) == 5
doAssert ceilDiv(41, 7) == 6
doAssert ceilDiv(0, 1) == 0
doAssert ceilDiv(1, 1) == 1
doAssert ceilDiv(1, 2) == 1
doAssert ceilDiv(2, 1) == 2
doAssert ceilDiv(2, 2) == 1
doAssert ceilDiv(0, high(int)) == 0
doAssert ceilDiv(1, high(int)) == 1
doAssert ceilDiv(0, high(int) - 1) == 0
doAssert ceilDiv(1, high(int) - 1) == 1
doAssert ceilDiv(high(int) div 2, high(int) div 2 + 1) == 1
doAssert ceilDiv(high(int) div 2, high(int) div 2 + 2) == 1
doAssert ceilDiv(high(int) div 2 + 1, high(int) div 2) == 2
doAssert ceilDiv(high(int) div 2 + 2, high(int) div 2) == 2
doAssert ceilDiv(high(int) div 2 + 1, high(int) div 2 + 1) == 1
doAssert ceilDiv(high(int), 1) == high(int)
doAssert ceilDiv(high(int) - 1, 1) == high(int) - 1
doAssert ceilDiv(high(int) - 1, 2) == high(int) div 2
doAssert ceilDiv(high(int) - 1, high(int)) == 1
doAssert ceilDiv(high(int) - 1, high(int) - 1) == 1
doAssert ceilDiv(high(int) - 1, high(int) - 2) == 2
doAssert ceilDiv(high(int), high(int)) == 1
doAssert ceilDiv(high(int), high(int) - 1) == 2
doAssert ceilDiv(255'u8, 1'u8) == 255'u8
doAssert ceilDiv(254'u8, 2'u8) == 127'u8
when not defined(danger):
doAssertRaises(AssertionDefect): discard ceilDiv(41, 0)
doAssertRaises(AssertionDefect): discard ceilDiv(41, -1)
doAssertRaises(AssertionDefect): discard ceilDiv(-1, 1)
doAssertRaises(AssertionDefect): discard ceilDiv(-1, -1)
doAssertRaises(AssertionDefect): discard ceilDiv(254'u8, 3'u8)
doAssertRaises(AssertionDefect): discard ceilDiv(255'u8, 2'u8)
block: # splitDecimal() tests
doAssert splitDecimal(54.674).intpart == 54.0
doAssert splitDecimal(54.674).floatpart ==~ 0.674