Recently I faced with a problem iterating through decimal numbers with a decimal step and I was wondered, why Kotlin has Progressions only for Int
, Long
and Char
.
I understand, that there could be some caveats with decimal numbers. But still. We just want to have a start BigDecimal
number, end BigDecimal
number and then iterate through them with BigDecimal
step.
Q: So, why there is no any progressions for not integer numbers? Thank you.
P.S.: Here is a sample code of possible implementation (I took sources for Int and adapted to BigDecimal
):
/**
* Returns a progression that goes over the same range with the given step.
*/
public infix fun BigDecimalProgression.step(step: BigDecimal): BigDecimalProgression {
if (step <= java.math.BigDecimal.ZERO) throw IllegalArgumentException("Step must be positive, was: $step.")
return BigDecimalProgression.fromClosedRange(first, last, if (this.step > java.math.BigDecimal.ZERO) step else -step)
}
/**
* A progression of values of type `BigDecimal`.
*/
public open class BigDecimalProgression
internal constructor
(
start: BigDecimal,
endInclusive: BigDecimal,
step: BigDecimal
) : Iterable<BigDecimal> {
init {
if (step == BigDecimal.ZERO) throw kotlin.IllegalArgumentException("Step must be non-zero")
}
/**
* The first element in the progression.
*/
public val first: BigDecimal = start
/**
* The last element in the progression.
*/
public val last: BigDecimal = getProgressionLastElement(start, endInclusive, step)
/**
* The step of the progression.
*/
public val step: BigDecimal = step
override fun iterator(): BigDecimalIterator = BigDecimalProgressionIterator(first, last, step)
/** Checks if the progression is empty. */
public open fun isEmpty(): Boolean = if (step > BigDecimal.ZERO) first > last else first < last
override fun equals(other: Any?): Boolean =
other is BigDecimalProgression && (isEmpty() && other.isEmpty() ||
first == other.first && last == other.last && step == other.step)
override fun hashCode(): Int =
if (isEmpty()) -1 else (31 * (31 * first.hashCode() + last.hashCode()) + step.hashCode())
override fun toString(): String = if (step > BigDecimal.ZERO) "$first..$last step $step" else "$first downTo $last step ${-step}"
companion object {
/**
* Creates BigDecimalProgression within the specified bounds of a closed range.
* The progression starts with the [rangeStart] value and goes toward the [rangeEnd] value not excluding it, with the specified [step].
* In order to go backwards the [step] must be negative.
*/
public fun fromClosedRange(rangeStart: BigDecimal, rangeEnd: BigDecimal, step: BigDecimal): BigDecimalProgression = BigDecimalProgression(rangeStart, rangeEnd, step)
}
}
fun getProgressionLastElement(start: BigDecimal, end: BigDecimal, step: BigDecimal): BigDecimal {
if (step > BigDecimal.ZERO) {
return start + BigDecimal(((end - start) / step).toInt()) * step
} else if (step < BigDecimal.ZERO) {
return start - BigDecimal(((start - end) / -step).toInt()) * -step
} else {
throw kotlin.IllegalArgumentException("Step is zero.")
}
}
/** An iterator over a sequence of values of type `BigDecimal`. */
public abstract class BigDecimalIterator : Iterator<BigDecimal> {
override final fun next() = nextBigDecimal()
/** Returns the next value in the sequence without boxing. */
public abstract fun nextBigDecimal(): BigDecimal
}
/**
* An iterator over a progression of values of type `BigDecimal`.
* @property step the number by which the value is incremented on each step.
*/
internal class BigDecimalProgressionIterator(first: BigDecimal, last: BigDecimal, val step: BigDecimal) : BigDecimalIterator() {
private val finalElement = last
private var hasNext: Boolean = if (step > BigDecimal.ZERO) first <= last else first >= last
private var next = if (hasNext) first else finalElement
override fun hasNext(): Boolean = hasNext
override fun nextBigDecimal(): BigDecimal {
val value = next
if (value >= finalElement) {
if (!hasNext) throw kotlin.NoSuchElementException()
hasNext = false
}
else {
next += step
}
return value
}
}
As it said in the documentation for ranges:
Floating point numbers (Double, Float) do not define their rangeTo operator, and the one provided by the standard library for generic Comparable types is used instead:
public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
The range returned by this function cannot be used for iteration. You will have to use some other kind of loop since you can't use ranges.
They simply do not define.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With