import SilverSight.FixedPoint open SilverSight.FixedPoint open SilverSight.FixedPoint.Q16_16 -- C functions linked through extern_lib q16_canonical. @[extern "q16_to_float"] opaque cQ16ToFloat (q : UInt32) : Float @[extern "float_to_q16_nearbyint"] opaque cFloatToQ16 (f : Float) : UInt32 @[extern "q16_add"] opaque cQ16Add (a b : UInt32) : UInt32 @[extern "q16_sub"] opaque cQ16Sub (a b : UInt32) : UInt32 private def q16ToBits (q : Q16_16) : UInt32 := Q16_16.toBits q private def bitsToQ16 (u : UInt32) : Q16_16 := Q16_16.ofBits u private def floatEq (a b : Float) (ε : Float := 1e-9) : Bool := let d := a - b d < ε && d > -ε private def intCases : List Int := [ 0, 1, 32768, 65536, 19661, -65536, -2147483648, 2147483647, -1000000, 1000000 ] -- Values chosen so that floor and round-to-nearest-even agree at Q16.16 scale, -- keeping Lean's floor-based `ofFloat` consistent with C's `nearbyint`. private def floatCases : List Float := [ 0.0, -0.0, 0.5, 1.0, -1.0, 2.5, -2.5, 32767.5, -32768.0, 1e12, -1e12 ] private def addCases : List (Int × Int) := [ (0, 0), (1, 1), (32768, 32768), (65536, 65536), (2147483647, 1), (-2147483648, -1), (-65536, 65536), (1000000, 2000000) ] private def subCases : List (Int × Int) := [ (0, 0), (1, 0), (0, 1), (2147483647, -1), (-2147483648, 1), (65536, -65536) ] private structure TestCase where name : String passed : Bool detail : String deriving Repr private def runToFloatTests : List TestCase := intCases.map fun raw => let q := Q16_16.ofRawInt raw let cVal := cQ16ToFloat (q16ToBits q) let leanVal := q.toFloat let ok := floatEq cVal leanVal { name := s!"to_float {raw}", passed := ok, detail := s!"C={cVal} Lean={leanVal}" } private def runFromFloatTests : List TestCase := floatCases.map fun f => let leanRaw := (Q16_16.ofFloat f).toInt let cRaw := (bitsToQ16 (cFloatToQ16 f)).toInt let ok := leanRaw == cRaw { name := s!"from_float {f}", passed := ok, detail := s!"C={cRaw} Lean={leanRaw}" } private def runAddTests : List TestCase := addCases.map fun (aRaw, bRaw) => let a := Q16_16.ofRawInt aRaw let b := Q16_16.ofRawInt bRaw let leanRaw := (Q16_16.add a b).toInt let cRaw := (bitsToQ16 (cQ16Add (q16ToBits a) (q16ToBits b))).toInt let ok := leanRaw == cRaw { name := s!"add {aRaw} {bRaw}", passed := ok, detail := s!"C={cRaw} Lean={leanRaw}" } private def runSubTests : List TestCase := subCases.map fun (aRaw, bRaw) => let a := Q16_16.ofRawInt aRaw let b := Q16_16.ofRawInt bRaw let leanRaw := (Q16_16.sub a b).toInt let cRaw := (bitsToQ16 (cQ16Sub (q16ToBits a) (q16ToBits b))).toInt let ok := leanRaw == cRaw { name := s!"sub {aRaw} {bRaw}", passed := ok, detail := s!"C={cRaw} Lean={leanRaw}" } private def jBool (b : Bool) : String := if b then "true" else "false" private def jStr (s : String) : String := "\"" ++ (s.replace "\\" "\\\\" |>.replace "\"" "\\\"") ++ "\"" private def jCase (t : TestCase) : String := s!"\{\"name\":{jStr t.name},\"passed\":{jBool t.passed},\"detail\":{jStr t.detail}}" private def jCases (cs : List TestCase) : String := "[" ++ String.intercalate "," (cs.map jCase) ++ "]" def main : IO UInt32 := do let tests := runToFloatTests ++ runFromFloatTests ++ runAddTests ++ runSubTests let passed := tests.filter (·.passed) let failed := tests.filter (!·.passed) let ok := failed.isEmpty IO.println <| s!"\{\"schema\":\"q16_16_lean_c_roundtrip_v1\"," ++ s!"\"ok\":{jBool ok}," ++ s!"\"total\":{tests.length}," ++ s!"\"passed\":{passed.length}," ++ s!"\"failed\":{failed.length}," ++ s!"\"tests\":{jCases tests}}" return if ok then 0 else 1