Introduce new rounding function to StatisticsUtil

It is far less optimal than roundToPowerOfTwo, but allows to have more precision values when needed.

(cherry picked from commit 21c001ec375b2be1df5feef4695bd7390db7b62f)

IJ-CR-12365

GitOrigin-RevId: 9b5507cb00db48e844d289d2f90e09abce58a373
This commit is contained in:
Victor Matchenko
2021-07-30 12:33:38 +00:00
committed by intellij-monorepo-bot
parent bc72ce8bab
commit 3a0d98a9d6
2 changed files with 148 additions and 1 deletions
@@ -7,6 +7,7 @@ import com.intellij.internal.statistic.eventLog.EventLogConfiguration
import com.intellij.internal.statistic.utils.StatisticsUtil
import com.intellij.internal.statistic.utils.StatisticsUtil.getCurrentHourInUTC
import com.intellij.internal.statistic.utils.StatisticsUtil.getNextPowerOfTwo
import com.intellij.internal.statistic.utils.StatisticsUtil.roundToHighestDigit
import com.intellij.internal.statistic.utils.StatisticsUtil.roundToPowerOfTwo
import com.intellij.internal.statistic.utils.StatisticsUtil.roundToUpperBound
import com.intellij.testFramework.LightPlatformTestCase
@@ -120,6 +121,94 @@ class StatisticsUtilTest : LightPlatformTestCase() {
assertEquals(expected, roundToPowerOfTwo(value), "Incorrect key for value `$value`")
}
@Test
fun `test round to highest digit int`() {
// Test from -10..10
for (i in -10..-5) {
testRoundToHighestDigit(i, -10)
}
for (i in -4..-1) {
testRoundToHighestDigit(i, -1)
}
testRoundToHighestDigit(0, 0)
for (i in 1..4) {
testRoundToHighestDigit(i, 1)
}
for (i in 5..10) {
testRoundToHighestDigit(i, 10)
}
testRoundToHighestDigit(11, 10)
testRoundToHighestDigit(16, 20)
testRoundToHighestDigit(64, 60)
testRoundToHighestDigit(65, 70)
testRoundToHighestDigit(94, 90)
testRoundToHighestDigit(95, 100)
testRoundToHighestDigit(99, 100)
for (i in 1..9) {
val expected = i * 1000
testRoundToHighestDigit(expected, expected)
}
testRoundToHighestDigit(1024, 1000)
testRoundToHighestDigit(1500, 2000)
testRoundToHighestDigit(1999, 2000)
testRoundToHighestDigit(Int.MAX_VALUE, 2000000000)
testRoundToHighestDigit(Int.MIN_VALUE + 1, -2000000000)
testRoundToHighestDigit(Int.MIN_VALUE, -2000000000)
}
private fun testRoundToHighestDigit(value: Int, expected: Int) {
assertEquals(expected, roundToHighestDigit(value), "Incorrect key for value `$value`")
}
@Test
fun `test round to highest digit long`() {
// Test from -10..10
for (i in -10L..-5L) {
testRoundToHighestDigit(i, -10L)
}
for (i in -4L..-1L) {
testRoundToHighestDigit(i, -1L)
}
testRoundToHighestDigit(0L, 0L)
for (i in 1L..4L) {
testRoundToHighestDigit(i, 1L)
}
for (i in 5L..10L) {
testRoundToHighestDigit(i, 10L)
}
testRoundToHighestDigit(11L, 10L)
testRoundToHighestDigit(16L, 20L)
testRoundToHighestDigit(64L, 60L)
testRoundToHighestDigit(65L, 70L)
testRoundToHighestDigit(94L, 90L)
testRoundToHighestDigit(95L, 100L)
testRoundToHighestDigit(99L, 100L)
for (i in 1L..9L) {
val expected = i * 1000L
testRoundToHighestDigit(expected, expected)
}
testRoundToHighestDigit(1024L, 1000L)
testRoundToHighestDigit(1500L, 2000L)
testRoundToHighestDigit(1999L, 2000L)
testRoundToHighestDigit(Long.MAX_VALUE, 9000000000000000000L)
testRoundToHighestDigit(Long.MIN_VALUE + 1L, -9000000000000000000L)
testRoundToHighestDigit(Long.MIN_VALUE, -9000000000000000000L)
}
private fun testRoundToHighestDigit(value: Long, expected: Long) {
assertEquals(expected, roundToHighestDigit(value), "Incorrect key for value `$value`")
}
@Test
fun `test round to upper bound`() {
// Test empty bounds return next power of two
@@ -150,7 +239,7 @@ class StatisticsUtilTest : LightPlatformTestCase() {
private fun testRoundToUpperBound(value: Int, bounds: IntArray, expected: Int) {
assertEquals(expected, roundToUpperBound(value, bounds), "Incorrect key for value `$value`")
}
@Test
fun `test hash sensitive data`() {
@@ -5,6 +5,8 @@ import java.text.SimpleDateFormat
import java.time.ZoneOffset
import java.util.*
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlin.math.roundToLong
object StatisticsUtil {
private const val kilo = 1000
@@ -71,6 +73,62 @@ object StatisticsUtil {
return java.lang.Long.signum(value) * nextPowerOfTwo
}
/**
* Please, use only in specific cases where precision of `roundToPowerOfTwo` is not enough
*
* Anonymizes sensitive project properties by rounding it to the power of ten multiplied with either:
* - leading digit if second digit is smaller than 4 or equal to 4
* - leading digit plus one if second digit is greater than 5 or equal to 5
*
* Special cases:
* - returns the same value if all trailing digits equal zero;
* - returns `-roundToHighestDigit(abs(value))` if the value is negative;
* - returns 0 in case of 0;
* - returns +/-1 for abs(value) in 1..4
* - returns +/-10 for abs(value) in 5..10
* - returns `roundToHighestDigit(Int.MAX_VALUE)` for Int.MIN_VALUE
*/
@JvmStatic
fun roundToHighestDigit(value: Int): Int {
if (value == 0) return 0
val abs = if (value != Int.MIN_VALUE) abs(value) else Int.MAX_VALUE // See abs(Int) documentation
if (abs <= 4) return Integer.signum(value) * 1
else if (abs <= 10) return Integer.signum(value) * 10
var firstDigit = abs.toFloat()
var tenPowX = 1
while (firstDigit > 10) {
firstDigit /= 10
tenPowX *= 10
}
// can't get overflow because second digit in max int (2147483647) is '1'
return Integer.signum(value) * (firstDigit.roundToInt() * tenPowX)
}
/**
* Please, use only in specific cases where precision of `roundToPowerOfTwo` is not enough
*
* @see com.intellij.internal.statistic.utils.StatisticsUtil.roundToHighestDigit(int)
*/
@JvmStatic
fun roundToHighestDigit(value: Long): Long {
if (value == 0L) return 0L
val abs = if (value != Long.MIN_VALUE) abs(value) else Long.MAX_VALUE // See abs(Long) documentation
if (abs <= 4L) return java.lang.Long.signum(value) * 1L
else if (abs <= 10L) return java.lang.Long.signum(value) * 10L
var firstDigit = abs.toDouble()
var tenPowX = 1L
while (firstDigit > 10L) {
firstDigit /= 10L
tenPowX *= 10L
}
// can't get overflow because second digit in max long (9223372036854775807L) is '2'
return java.lang.Long.signum(value) * (firstDigit.roundToLong() * tenPowX)
}
/**
* Anonymizes value by finding upper bound in provided bounds.
* Allows more fine tuning then `com.intellij.internal.statistic.utils.StatisticsUtil#roundToPowerOfTwo`