Esta noticia fue aprobado por nuestros especialistas para asegurar la veracidad de nuestro post.
Solución:
Creo que la función SimpleRoundTo de Delphi RTL hace esencialmente esto, al menos si el modo de redondeo de FPU es “correcto”. Lea atentamente su documentación e implementación, y luego decida si es lo suficientemente bueno para sus propósitos.
Pero cuidado con eso ajuste el modo de redondeo para una sola operación de redondeo como esta utiliza un cambio global para resolver un problema local. Esto podría causar problemas (subprocesos múltiples, bibliotecas, etc.).
Charla adicional: si la pregunta hubiera sido sobre el redondeo “regular” (a un número entero), creo que habría intentado un enfoque como
function RoundMidpAway(const X: Real): Integer;
begin
Result := Trunc(X);
if Abs(Frac(X)) >= 0.5 then
Inc(Result, Sign(X));
end;
en cambio.
Por supuesto, es posible escribir una función similar incluso para el caso general de norte dígitos fraccionarios. (Pero tenga cuidado de manejar correctamente los casos extremos, los desbordamientos, los problemas de punto flotante, etc.).
Actualizar: Creo que lo siguiente funciona (y es rápido):
function RoundMidpAway(const X: Real): Integer; overload;
begin
Result := Trunc(X);
if Abs(Frac(X)) >= 0.5 then
Inc(Result, Sign(X));
end;
function RoundMidpAway(const X: Real; ADigit: integer): Real; overload;
const
PowersOfTen: array[-10..10] of Real =
(
0.0000000001,
0.000000001,
0.00000001,
0.0000001,
0.000001,
0.00001,
0.0001,
0.001,
0.01,
0.1,
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000
);
var
MagnifiedValue: Real;
begin
if not InRange(ADigit, Low(PowersOfTen), High(PowersOfTen)) then
raise EInvalidArgument.Create('Invalid digit index.');
MagnifiedValue := X * PowersOfTen[-ADigit];
Result := RoundMidpAway(MagnifiedValue) * PowersOfTen[ADigit];
end;
Por supuesto, si usara esta función en el código de producción, también agregaría al menos 50 casos de prueba de unidad que prueban su corrección (para ejecutarse diariamente).
Actualizar: yo creer la siguiente versión es más estable:
function RoundMidpAway(const X: Real; ADigit: integer): Real; overload;
const
FuzzFactor = 1000;
DoubleResolution = 1E-15 * FuzzFactor;
PowersOfTen: array[-10..10] of Real =
(
0.0000000001,
0.000000001,
0.00000001,
0.0000001,
0.000001,
0.00001,
0.0001,
0.001,
0.01,
0.1,
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000
);
var
MagnifiedValue: Real;
TruncatedValue: Real;
begin
if not InRange(ADigit, Low(PowersOfTen), High(PowersOfTen)) then
raise EInvalidArgument.Create('Invalid digit index.');
MagnifiedValue := X * PowersOfTen[-ADigit];
TruncatedValue := Int(MagnifiedValue);
if CompareValue(Abs(Frac(MagnifiedValue)), 0.5, DoubleResolution * PowersOfTen[-ADigit]) >= EqualsValue then
TruncatedValue := TruncatedValue + Sign(MagnifiedValue);
Result := TruncatedValue * PowersOfTen[ADigit];
end;
pero no lo he probado completamente. (Actualmente pasa más de 900 casos de prueba de unidad, pero todavía no considero que el conjunto de pruebas sea suficiente).
Lo que está buscando es la función SimpleRoundTo en combinación con SetRoundMode. Como dice la documentación:
SimpleRoundTo devuelve el valor más cercano que tiene la potencia de diez especificada. En caso
AValue
está exactamente en el medio de los dos valores más cercanos que tienen la potencia de diez especificada (arriba y abajo), esta función devuelve:
El valor hacia más infinito si
AValue
es positivo.El valor hacia menos infinito si
AValue
es negativo y el modo de redondeo de FPU no está establecido en rmUp
Tenga en cuenta que el segundo parámetro de la función es TRoundToRange
que se refiere al exponente (potencia de 10) en lugar del número de dígitos fraccionarios en el método Math.Round de .NET. Por lo tanto, para redondear a 2 lugares decimales, usa -2 como rango de redondeo.
uses Math, RTTI;
var
LRoundingMode: TRoundingMode;
begin
for LRoundingMode := Low(TRoundingMode) to High(TRoundingMode) do
begin
SetRoundMode(LRoundingMode);
Writeln(TRttiEnumerationType.GetName(LRoundingMode));
Writeln(SimpleRoundTo(2.125, -2).ToString);
Writeln(SimpleRoundTo(-2.125, -2).ToString);
end;
end;
rmMás cercano
2,13
-2,13
rmDown
2,13
-2,13
rmUp
2,13
-2,12
rmTruncar
2,13
-2,13
Te invitamos a proteger nuestra labor dejando un comentario y dejando una valoración te lo agradecemos.