Kotlin BigDecimal progression


#1

A few weeks ago I posted a question to stackoverflow.

May be it was not right decision and I should ask it here. It would be great if you let me now, why progression and ranges are not possible for BigDecimal, if they not. And if it is possible, then my proposal is to add BigDecimalProgression, to be able to write something like (a..b step c), where a, b and c are instances of BigDecimal class.

P.S.: Here is a sample code, how I’ve implemented it in my project:

/**
 * Returns a progression that goes over the same range with the given step.
 */
infix fun ClosedRange<BigDecimal>.step(step: BigDecimal): BigDecimalProgression {
    if (step <= BigDecimal.ZERO) throw IllegalArgumentException("Step must be positive, was: $step.")
    return BigDecimalProgression.fromClosedRange(start, endInclusive, step)
}

/**
 * Returns a progression that goes over the same range with the given step.
 */
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`.
 */
open class BigDecimalProgression
internal constructor(start: BigDecimal, endInclusive: BigDecimal, step: BigDecimal)
    : Iterable<BigDecimal> {
    init {
        if (step == BigDecimal.ZERO) throw IllegalArgumentException("Step must be non-zero")
    }

    /**
     * The first element in the progression.
     */
    val first: BigDecimal = start

    /**
     * The last element in the progression.
     */
    val last: BigDecimal = getProgressionLastElement(start, endInclusive, step)

    /**
     * The step of the progression.
     */
    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?) =
            other is BigDecimalProgression && (isEmpty() && other.isEmpty() ||
                    first == other.first && last == other.last && step == other.step)

    override fun hashCode() = if (isEmpty()) -1 else (31 * (31 * first.hashCode() + last.hashCode()) + step.hashCode())

    override fun toString() = if (step > BigDecimal.ZERO) "$first..$last step $step" else "$first downTo $last step ${-step}"

    private 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 IllegalArgumentException("Step is zero.")
        }
    }

    companion object {
        /**
         * Creates IntProgression 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.
         */
        fun fromClosedRange(rangeStart: BigDecimal, rangeEnd: BigDecimal, step: BigDecimal): BigDecimalProgression = BigDecimalProgression(rangeStart, rangeEnd, step)
    }
}

/** An iterator over a sequence of values of type `BigDecimal`. */
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.
 */
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 NoSuchElementException()
            hasNext = false
        }
        else {
            next += step
        }
        return value
    }
}

#2

As your implementation shows it is totally possible to have a progression of BigDecimal’s (though I suppose there can be some pitfalls related to decimal rounding mode).

However we do not include something in the standard library just because it’s possible to implement, there should be some strong use cases to justify the inclusion of the feature.


#3

That’s pretty reasonable. Thank you.