Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

386
Views
¿Cómo puedo multiplicar de forma rápida y precisa un número entero de 64 bits por una fracción de 64 bits en C#?

Hay muchas preguntas similares sobre SO, pero todavía tengo que encontrar una que funcione y sea fácil de trasladar a C#. La mayoría involucra C++ o similar, y las respuestas (presumiblemente) funcionales se basan en el ensamblado integrado o en funciones nativas de C/C++ que no existen en C#. Varias funciones funcionan para parte del rango, pero fallan en otras partes. Encontré una respuesta funcional que pude trasladar a C#, pero era muy lenta (resulta que es decentemente rápida cuando compilo en x64 en lugar de x86, así que la publiqué como la respuesta a vencer).

Problema

Necesito una función que me permita multiplicar cualquier número entero de 64 bits por una fracción de 0 a 1 (o -1 a 1) que se deriva de dos números enteros de 64 bits. Idealmente, la respuesta funcionaría tanto para Int64 como para UInt64, pero probablemente no sea difícil hacer que uno funcione a partir del otro.

En mi caso, tengo un Int64/UInt64 aleatorio de 64 bits (usando el algoritmo xoshiro256p, aunque probablemente sea irrelevante). Quiero escalar ese número a cualquier rango arbitrario en los valores permitidos del tipo. Por ejemplo, podría querer escalar Int64 al rango [1000, 35000]. Esto es, conceptualmente, bastante fácil:

 UInt64 minVal = 1000; UInt64 maxVal = 35000; UInt64 maxInt = UInt64.MaxValue; UInt64 randInt = NextUInt64(); // Random value between 0 and maxInt. UInt64 diff = maxVal - minVal + 1; UInt64 scaledInt = randInt * diff / maxInt; // This line can overflow. return scaledInt + minVal;

Como lo señalaron muchas otras personas, y el comentario anterior, el problema es que randInt * diff puede potencialmente desbordarse.

En papel, podría simplemente almacenar ese resultado intermedio en un número entero de 128 bits y luego almacenar el resultado de la división en la salida de 64 bits. Pero las matemáticas de 128 bits no son nativas de los sistemas de 64 bits, y prefiero evitar las bibliotecas de precisión arbitraria, ya que haré muchas llamadas a esta función y la eficiencia será notable.

Podría multiplicar por un doble para obtener 53 bits de precisión, lo cual está bien para lo que estoy haciendo actualmente, pero prefiero encontrar una solución adecuada.

Podría crear una biblioteca de C++ con una de las soluciones de ASM y llamar a esa biblioteca, pero me gustaría algo que sea C# puro.

Requisitos

  • Tiene que ser C# puro.
  • Debe funcionar para cualquier conjunto de entradas, de modo que randInt * diff / maxInt esté en el rango [0, maxInt] (y cada valor en sí esté en el mismo rango).
  • No debería requerir una biblioteca externa.
  • Tiene que ser +-1 de la respuesta matemáticamente correcta.
  • Tiene que ser razonablemente rápido. Tal vez solo estoy pidiendo milagros, pero siento que si los dobles pueden hacer de 5 a 10 ms, deberíamos poder alcanzar los 20 ms con un código especialmente diseñado que obtenga otros 11 bits de precisión.
  • Lo ideal es que funcione relativamente bien en los modos de lanzamiento y depuración. Mi código tiene una proporción de 3:1, por lo que creo que podríamos depurar menos de 5 veces el tiempo de lanzamiento.

Mis pruebas

He probado las siguientes soluciones para el rendimiento relativo. Cada prueba ejecutó 1 millón de iteraciones de mi generador de números aleatorios, escalando usando varios métodos. Empecé generando números aleatorios y colocándolos en listas (una para firmada, otra para no firmada). Luego revisé cada lista y la escalé a una segunda lista.

Inicialmente tuve un montón de pruebas en modo de depuración. En general, no importó (estamos probando el rendimiento relativo ), pero a las bibliotecas Int128/UInt128 les fue mucho mejor en el modo de lanzamiento.

Los números entre paréntesis son el tiempo de depuración. Los incluyo aquí porque todavía quiero un rendimiento decente durante la depuración. La biblioteca Int128, por ejemplo, es excelente para el modo de lanzamiento, pero terrible para la depuración. Puede ser útil usar algo que tenga un mejor equilibrio hasta que esté listo para el lanzamiento final. Debido a que estoy probando un millón de muestras, el tiempo en milisegundos también es el tiempo en nanosegundos por operación (todos los millones de UInt64 se generan en 33 ms, por lo que cada uno se genera en 33 ns).

El código fuente para mis pruebas se puede encontrar aquí, en GitGub .

  • 86 ms (267): el generador aleatorio Int64.
  • 33 ms (80): el generador aleatorio UInt64.
  • 4 ms (5): usando doble conversión a Int64, con precisión reducida.
  • 8 ms (10): de nuevo para UInt64.
  • 76 ms (197): este código C para Int64, convertido a C# (código exacto en mi respuesta a continuación).
  • 72 ms (187): de nuevo para UInt64.
  • 54 ms (1458): esta biblioteca UInt128 , para Int64.
  • 40 ms (1476): de nuevo para UInt64.
  • 1446 ms (1455): biblioteca double128 para Int64. Requiere una licencia paga para uso comercial.
  • 1374 ms (1397): de nuevo para UInt64.

No pude hacer que estos dieran los resultados adecuados.

  • esta biblioteca MulDiv64 , vinculada a la aplicación principal con DllImport.
  • QPFloat , compilado en x64, creó una función MulDiv64 en el código C++.
  • este código Java .
  • la función MFllMulDiv de la biblioteca de Microsoft Media Foundation. Traté de probarlo, pero no pude averiguar cómo hacer que VS se vincule correctamente a mi proyecto C++.

Preguntas similares

¿La forma más precisa de hacer una operación combinada de multiplicar y dividir en 64 bits?

  • Las respuestas de phuclv, Soonts, Mysticial y 500 - Internal Server Error involucran bibliotecas externas, ensamblajes o funciones específicas de MSVC.
  • Las respuestas de timos, AnT, Alexey Frunze y Michael Burr en realidad no responden a nada.
  • Las respuestas de Serge Rogatch y Pubby no son precisas.
  • La respuesta de AProgrammer funciona, pero es muy lenta (y no tengo idea de cómo funciona); terminé usándola de todos modos y obteniendo resultados decentes en la compilación x64.

¿Cómo puedo descalcificar x por n/d, cuando x*n se desborda?

  • La única respuesta, de Abhay Aravinda, no es el código real, no estaba seguro de cómo implementar la última sección, y los comentarios sugieren que puede desbordarse de todos modos para valores grandes.

Método rápido para multiplicar enteros por fracciones propias sin flotadores ni desbordamiento

  • Las respuestas de Taron y chux - Reincorporar a Mónica son aproximaciones o específicas de MSVC.
  • Respuesta de R.. GitHub DEJAR DE AYUDAR A ICE solo usa matemáticas de 64 bits ya que esa pregunta se trata de multiplicar Int32.

(a * b) / c MulDiv y cómo lidiar con el desbordamiento de la multiplicación intermedia

  • La respuesta de Jeff Penfold no funcionó para mí (creo que me falta algo en la conversión de operadores lógicos de Java a C#) y fue muy lenta.
  • La respuesta de barba gris se ve bien, pero no estaba seguro de cómo traducirla a C#.
  • Las respuestas de tohoho y Dave se desbordan.
  • La respuesta de David Eisenstat requiere bibliotecas BigInt.

¿Cómo multiplicar un entero de 64 bits por una fracción en C++ mientras se minimiza el error?

  • Todas las respuestas se desbordan en diferentes circunstancias.
over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

Pero las matemáticas de 128 bits no son nativas de los sistemas de 64 bits

Si bien eso es mayormente cierto, hay una manera decente de obtener el producto completo de 128 bits de dos números enteros de 64 bits: Math.BigMul (para .NET 5 y posterior)

x64 tiene una división correspondiente con una entrada de 128 bits, y tal par de multiplicaciones completas seguidas de una división amplia implementaría esta operación de "escala de enteros por una fracción propia" (con la limitación de que la fracción no debe ser mayor que 1, de lo contrario podría producirse un desbordamiento). Sin embargo, C# no tiene acceso a la división amplia y, aunque lo tuviera, no sería muy eficiente en la mayoría del hardware.

Pero también puede usar BigMul directamente, porque el divisor realmente debería ser 2 64 para empezar (no 2 64 - 1), y BigMul divide automáticamente entre 2 64 .

Entonces el código se convierte en: (no probado)

 ulong ignore; ulong scaled = Math.BigMul(randInt, diff, out ignore); return scaled + minVal;

Para versiones anteriores de .NET, obtener los 64 bits altos del producto podría hacerse de esta manera:

 static ulong High64BitsOfProduct(ulong a, ulong b) { // decompose into 32bit blocks (in ulong to avoid casts later) ulong al = (uint)a; ulong ah = a >> 32; ulong bl = (uint)b; ulong bh = b >> 32; // low times low and high times high ulong l = al * bl; ulong h = ah * bh; // cross terms ulong x1 = al * bh; ulong x2 = ah * bl; // carry from low half of product into high half ulong carry = ((l >> 32) + (uint)x1 + (uint)x2) >> 32; // add up all the parts return h + (x1 >> 32) + (x2 >> 32) + carry; }

Desafortunadamente, eso no es tan bueno como Math.BigMul , pero al menos todavía no hay división.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!