|
object PhinarySystem: |
|
|
|
// Represents a phinary number using two lists of bits: |
|
// - integerPart: powers of phi starting from 0 (ordered from lowest to highest: phi^0, phi^1, phi^2...) |
|
// - fractionalPart: negative powers starting from -1 downwards (phi^-1, phi^-2, phi^-3...) |
|
case class Phinary(integerPart: List[Int], fractionalPart: List[Int]): |
|
override def toString: String = |
|
val intStr = integerPart.reverse.map(_.toString).mkString |
|
val fracStr = if fractionalPart.isEmpty then "" else "." + fractionalPart.map(_.toString).mkString |
|
val cleanIntStr = if intStr.isEmpty then "0" else intStr |
|
s"$cleanIntStr$fracStr" |
|
|
|
// Normalizes the phinary number by iteratively applying 011 -> 100 and handling digits >= 2 |
|
def normalize(p: Phinary): Phinary = |
|
val grid = scala.collection.mutable.Map[Int, Int]().withDefaultValue(0) |
|
|
|
p.integerPart.zipWithIndex.foreach((b, i) => grid(i) = b) |
|
p.fractionalPart.zipWithIndex.foreach((b, i) => grid(-(i + 1)) = b) |
|
|
|
var hasChanges = true |
|
while hasChanges do |
|
hasChanges = false |
|
val keys = grid.keys.toList.sorted |
|
|
|
if keys.nonEmpty then |
|
val minKey = keys.head - 2 |
|
val maxKey = keys.last + 2 |
|
|
|
for i <- maxKey downTo minKey do |
|
// Rule 1: Eliminate consecutive 1s (011 -> 100) |
|
if grid(i) >= 1 && grid(i - 1) >= 1 then |
|
val pairs = Math.min(grid(i), grid(i - 1)) |
|
grid(i) -= pairs |
|
grid(i - 1) -= pairs |
|
grid(i + 1) += pairs |
|
hasChanges = true |
|
|
|
// Rule 2: Handle digits greater than 1 using the identity: 2 = phi^1 + phi^-2 |
|
if grid(i) >= 2 then |
|
val amount = grid(i) / 2 |
|
grid(i) %= 2 |
|
grid(i + 1) += amount |
|
grid(i - 2) += amount |
|
hasChanges = true |
|
|
|
val finalKeys = grid.filter(_._2 > 0).keys.toList |
|
if finalKeys.isEmpty then return Phinary(List(0), List()) |
|
|
|
val maxInt = Math.max(0, finalKeys.max) |
|
val minFrac = Math.min(-1, finalKeys.min) |
|
|
|
val newInteger = (0 to maxInt).map(grid).toList |
|
val newFractional = (-1 downTo minFrac).map(grid).toList |
|
val cleanFractional = newFractional.reverse.dropWhile(_ == 0).reverse |
|
|
|
Phinary(newInteger, cleanFractional) |
|
|
|
// Adds 1 to the phinary representation at the phi^0 position |
|
def addOne(p: Phinary): Phinary = |
|
val newInteger = p.integerPart match |
|
case Nil => List(1) |
|
case head :: tail => (head + 1) :: tail |
|
normalize(Phinary(newInteger, p.fractionalPart)) |
|
|
|
// Converts a positive integer to its standard Phinary representation |
|
def fromInteger(n: Int): Phinary = |
|
require(n >= 0, "Standard phinary system requires non-negative integers.") |
|
if n == 0 then Phinary(List(0), List()) |
|
else |
|
var current = Phinary(List(1), List()) |
|
for _ <- 2 to n do |
|
current = addOne(current) |
|
current |
|
|
|
// Converts a Phinary string representation back to an integer rounding to nearest whole number |
|
// Since standard representation of integers in base-phi is exact, we map powers mathematically |
|
def toInteger(phinaryStr: String): Int = |
|
val parts = phinaryStr.split('.') |
|
val intStr = parts(0).reverse |
|
val fracStr = if parts.length > 1 then parts(1) else "" |
|
|
|
// Lucas numbers sequence (L_n) is ideal here because phi^n + (-phi)^-n = L_n |
|
// However, since we know the input represents a clean integer, we can compute using standard powers of phi |
|
val phi = (1.0 + Math.sqrt(5.0)) / 2.0 |
|
var total = 0.0 |
|
|
|
// Sum positive powers (including phi^0) |
|
for i <- 0 until intStr.length do |
|
if intStr(i) == '1' then total += Math.pow(phi, i) |
|
|
|
// Sum negative powers |
|
for i <- 0 until fracStr.length do |
|
if fracStr(i) == '1' then total += Math.pow(phi, -(i + 1)) |
|
|
|
// Round safely to handle any micro precision floating point noise |
|
Math.round(total).toInt |
|
|
|
@main def runTests(): Unit = |
|
println("=== Running Phinary System Tests ===") |
|
|
|
// Test 1: Integer to Phinary Conversion |
|
val numbersToTest = List(1, 2, 3, 4, 5, 10, 20) |
|
println("\n[Test 1] Converting Integers to Phinary:") |
|
val phinaryResults = numbersToTest.map(n => n -> fromInteger(n)) |
|
phinaryResults.foreach((integer, phinary) => |
|
println(s"Integer: $integer \t-> Phinary string: $phinary") |
|
) |
|
|
|
// Test 2: Phinary to Integer Conversion (Inverse function) |
|
println("\n[Test 2] Converting Phinary back to Integers:") |
|
val standardPairs = List( |
|
"1" -> 1, |
|
"10.01" -> 2, |
|
"100.01" -> 3, |
|
"101.01" -> 4, |
|
"1000.1001" -> 5, |
|
"10100.0101" -> 10 |
|
) |
|
|
|
var allTestsPassed = true |
|
standardPairs.foreach: (phinaryStr, expectedInt) => |
|
val computedInt = toInteger(phinaryStr) |
|
val status = if computedInt == expectedInt then "PASS" else "FAIL" |
|
if computedInt != expectedInt then allTestsPassed = false |
|
println(s"String: $phinaryStr \t-> Expected: $expectedInt \t-> Got: $computedInt \t[$status]") |
|
|
|
// Test 3: Round-trip Verification |
|
println("\n[Test 3] Round-trip Consistency Check (Int -> Phinary -> Int):") |
|
for i <- 1 to 50 do |
|
val phinary = fromInteger(i) |
|
val backToId = toInteger(phinary.toString) |
|
if i != backToId then |
|
println(s"❌ Symmetry failed for number $i: got $backToId") |
|
allTestsPassed = false |
|
|
|
if allTestsPassed then |
|
println("\n✅ All tests passed successfully!") |
|
else |
|
println("\n❌ Some tests failed.") |